Slide 1

Slide 1 text

SOLID Clojure Colin Jones / Software Craftsman / 8th Light @trptcolin

Slide 2

Slide 2 text

What paradigm is home for you?

Slide 3

Slide 3 text

What paradigm is home for you? Object-oriented?

Slide 4

Slide 4 text

What paradigm is home for you? Functional?

Slide 5

Slide 5 text

What paradigm is home for you? Logic?

Slide 6

Slide 6 text

What paradigm is home for you? Stack-based? Concatenative?

Slide 7

Slide 7 text

What paradigm is home for you? Which one is Clojure again?

Slide 8

Slide 8 text

#Clojure is one of the most object-oriented languages you can find today. - @takeoutweight

Slide 9

Slide 9 text

Axiom: Everybody wants the same thing

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

SOLID principles

Slide 12

Slide 12 text

Principles how do they work?

Slide 13

Slide 13 text

SOLID principles

Slide 14

Slide 14 text

SOLID DILOS principles

Slide 15

Slide 15 text

DILOS principles

Slide 16

Slide 16 text

DILOS Dependency Inversion Interface Segregation Liskov Substitution Open-Closed Single Responsibility

Slide 17

Slide 17 text

Dependency Inversion Principle

Slide 18

Slide 18 text

Problem to solve: A change in one place causes too many other changes

Slide 19

Slide 19 text

Problem to solve: Rigidity, fragility

Slide 20

Slide 20 text

A common cause Higher-level modules depend on lower-level details

Slide 21

Slide 21 text

Higher level forced to reuse lower level

Slide 22

Slide 22 text

Clients of the higher level forced to reuse lower level

Slide 23

Slide 23 text

Clients of clients of the higher level forced to reuse lower level

Slide 24

Slide 24 text

Let's make this concrete

Slide 25

Slide 25 text

Example: nREPL One approach to transport might have been: (defn recv [] (be/payload>string (be/read-bytes (.getInputStream socket)))) (defn send [msg] (let [os (.getOutputStream socket)] (.write os (be/string>payload (.getBytes msg))) (.flush os))) (defn echo [] (when-let [msg (recv)] (send msg) (recur)))

Slide 26

Slide 26 text

Depends on: bencode sockets (defn recv [] (be/payload>string (be/read-bytes (.getInputStream socket)))) (defn send [msg] (let [os (.getOutputStream socket)] (.write os (be/string>payload (.getBytes msg))) (.flush os))) (defn echo [] (when-let [msg (recv)] (send msg) (recur)))

Slide 27

Slide 27 text

Java Aspect-oriented programming Dependency injection

Slide 28

Slide 28 text

Clojure Dynamic binding

Slide 29

Slide 29 text

Clojure Dynamic binding all over the place: good idea?

Slide 30

Slide 30 text

We can do better

Slide 31

Slide 31 text

Depend on abstractions ...not concretions

Slide 32

Slide 32 text

OOP Interfaces & abstract classes

Slide 33

Slide 33 text

Clojure, the language: ISeq, IDeref, IPersistentVector, etc.

Slide 34

Slide 34 text

Clojure Interfaces & abstract classes

Slide 35

Slide 35 text

Clojure definterface, gen-class

Slide 36

Slide 36 text

Clojure That's it! NEXT PRINCIPLE.

Slide 37

Slide 37 text

Clojure (just kidding)

Slide 38

Slide 38 text

Clojure Abstraction: defprotocol Concretion: defrecord, deftype

Slide 39

Slide 39 text

Clojure Abstraction: defmulti Concretion: defmethod

Slide 40

Slide 40 text

Example: nREPL The naive version, again (defn recv [] (be/payload>string (be/read-bytes (.getInputStream socket)))) (defn send [msg] (let [os (.getOutputStream socket)] (.write os (be/string>payload (.getBytes msg))) (.flush os))) (defn echo [] (when-let [msg (recv)] (send msg) (recur)))

Slide 41

Slide 41 text

Example: nREPL The real deal (defprotocol Transport "Defines the interface for a wire protocol implementation for use with nREPL." (recv [this] [this timeout] "Reads and returns the next message received. Will block. Should return nil if the message is not available after `timeout` ms or if the underlying channel has been closed.") (send [this msg] "Sends msg. Implementations should return the transport."))

Slide 42

Slide 42 text

Example: nREPL (deftype FnTransport [recv-fn send-fn close] Transport (send [this msg] (-> msg clojure.walk/stringify-keys send-fn) this) (recv [this] (.recv this Long/MAX_VALUE)) (recv [this timeout] (clojure.walk/keywordize-keys (recv-fn timeout))) java.io.Closeable (close [this] (close)))

Slide 43

Slide 43 text

Clojure What is an abstraction?

Slide 44

Slide 44 text

Abstraction tries to reduce and factor out details so that the programmer can focus on a few concepts at a time -Wikipedia

Slide 45

Slide 45 text

Example: nREPL Where are the abstractions? (deftype FnTransport [recv-fn send-fn close] Transport (send [this msg] (-> msg clojure.walk/stringify-keys send-fn) this) (recv [this] (.recv this Long/MAX_VALUE)) (recv [this timeout] (clojure.walk/keywordize-keys (recv-fn timeout))) java.io.Closeable (close [this] (close)))

Slide 46

Slide 46 text

Functions are abstractions in Java-land: IFn

Slide 47

Slide 47 text

Functions are abstractions what details do we need to call a function?

Slide 48

Slide 48 text

Inject abstractions as arguments (deftype FnTransport [recv-fn send-fn close] Transport (send [this msg] (-> msg clojure.walk/stringify-keys send-fn) this) (recv [this] (.recv this Long/MAX_VALUE)) (recv [this timeout] (clojure.walk/keywordize-keys (recv-fn timeout))) java.io.Closeable (close [this] (close)))

Slide 49

Slide 49 text

Namespaces are abstractions

Slide 50

Slide 50 text

Interface Segregation Principle

Slide 51

Slide 51 text

Problem to solve: An abstraction is too fat

Slide 52

Slide 52 text

Concrete problem: (defrecord User [attributes] ActiveRecord (find [this conditions]) (save [this]) (valid? [this]) (before-create [this]) (after-update [this]) ; etc, etc, etc. (to-xml [this]) (to-json [this]))

Slide 53

Slide 53 text

I don't need all that crap So don't make me implement it! (defrecord User [attributes] Storable (find [this conditions]) (save [this]) Validatable (valid? [this]))

Slide 54

Slide 54 text

Clients shouldn't have to depend on abstractions they don't use.

Slide 55

Slide 55 text

Reasonably small, cohesive protocols

Slide 56

Slide 56 text

Reasonably small, cohesive namespaces

Slide 57

Slide 57 text

Easy to do Common practice in Clojure code

Slide 58

Slide 58 text

Liskov Substitution Principle

Slide 59

Slide 59 text

Problem to solve: A particular subtype is not substitutable for its base type

Slide 60

Slide 60 text

Problem to solve: A reasonable reading of the abstraction becomes wrong

Slide 61

Slide 61 text

Classic OOP Example class Rectangle attr_accessor :height, :width end

Slide 62

Slide 62 text

Classic OOP Example class Square < Rectangle def height=(new_height) @height = new_height @width = new_height end def width=(new_width) @height = new_height @width = new_width end end

Slide 63

Slide 63 text

Classic OOP Example r = procure_a_rectangle r.width = 10 r.height = 5 r.area.should == 50

Slide 64

Slide 64 text

Clojure Is this problem possible outside Java interop?

Slide 65

Slide 65 text

Clojure Concrete inheritance is not in the language

Slide 66

Slide 66 text

But of course! Don't let the mutable state fool you (defprotocol Rectangle (with-height [this h]) (with-width [this w]) (area [this])) (defrecord Square [side] Rectangle (with-height [this h] (Square. h)) (with-width [this w] (Square. w)) (area [this] (* side side))) (-> (Square. 3) (with-height 10) (with-width 5) (area)) ;=> 25

Slide 67

Slide 67 text

What is a subtype, really? If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T. - Barbara Liskov

Slide 68

Slide 68 text

Let's rephrase the problem: A particular subtype is not substitutable for its base type A particular concretion is not substitutable for its abstraction

Slide 69

Slide 69 text

Clojure concretions Protocol/interface implementors (reify, proxy, defrecord, deftype, etc.)

Slide 70

Slide 70 text

Clojure concretions Function implementations (defn, defmethod)

Slide 71

Slide 71 text

Liskov substitution: What can clients expect of an abstraction?

Slide 72

Slide 72 text

How can we help define expectations?

Slide 73

Slide 73 text

Clojure Function pre- and post-conditions (built in) Trammel (https://github.com/fogus/trammel) Unit tests Generative tests Documentation

Slide 74

Slide 74 text

Open-Closed Principle

Slide 75

Slide 75 text

Problem to solve: Adding a new feature requires changes to existing code

Slide 76

Slide 76 text

When / why is this a real problem? (outside the ideal world)

Slide 77

Slide 77 text

When you don't own the existing code or you do own it, but it's hard to change

Slide 78

Slide 78 text

Things should be open for extension, closed for modification

Slide 79

Slide 79 text

Traditional OO solutions Avoid switching on type: use polymorphic dispatch instead Use wrappers for external libraries

Slide 80

Slide 80 text

The Expression Problem (tm) (r) (c) Watch chouser's talk & read stuartsierra's article extend-type / extend-protocol / extend

Slide 81

Slide 81 text

Multimethods always open?

Slide 82

Slide 82 text

Depends on the dispatch fn (defmulti print-dup (fn [x writer] (class x))) vs. (defn collection-tag [x] (cond (instance? java.util.Map$Entry x) :entry (instance? java.util.Map x) :map (sequential? x) :seq :else :atom)) (defmulti is-leaf collection-tag)

Slide 83

Slide 83 text

We can't extend the dispatch function

Slide 84

Slide 84 text

All problems in computer science can be solved by another level of indirection - David Wheeler

Slide 85

Slide 85 text

(defmulti collection-tag class) (defmethod collection-tag java.util.Map$Entry [x] :entry) (defmethod collection-tag java.util.Map [x] :map) (defmethod collection-tag clojure.lang.Sequential [x] :seq) (defmethod collection-tag :default [x] :atom) (defmulti is-leaf collection-tag)

Slide 86

Slide 86 text

Problem? (defn collection-tag [x] (cond (instance? java.util.Map$Entry x) :entry (instance? java.util.Map x) :map (sequential? x) :seq ;;; <- this :else :atom)) (defmulti collection-tag class) (defmethod collection-tag java.util.Map$Entry [x] :entry) (defmethod collection-tag java.util.Map [x] :map) (defmethod collection-tag clojure.lang.Sequential [x] :seq) (defmethod collection-tag :default [x] :atom)

Slide 87

Slide 87 text

Sci-fi-future-solution: predicate dispatch? Someone please clone David Nolen ...and watch his Conj 2011 talk / help out if you can

Slide 88

Slide 88 text

Meantime... for multimethods, think hard about dispatch functions

Slide 89

Slide 89 text

Single Responsibility Principle

Slide 90

Slide 90 text

Problem to solve: A program unit has too many reasons to change

Slide 91

Slide 91 text

Concrete problem to solve: A program unit has too many clients asking for potentially-conflicting changes

Slide 92

Slide 92 text

Concrete problem to solve: Reuse of sub-units is impeded

Slide 93

Slide 93 text

A solution Give a unit a single reason to change

Slide 94

Slide 94 text

...aka modularity

Slide 95

Slide 95 text

OOP class level

Slide 96

Slide 96 text

Clojure protocol/type/record level

Slide 97

Slide 97 text

Clojure function level

Slide 98

Slide 98 text

Project Euler #1 Find the sum of all the multiples of 3 or 5 below 1000. (defn solve-euler-1 [] (reduce + (filter #(or (zero? (mod % 3)) (zero? (mod % 5))) (range 1000))))

Slide 99

Slide 99 text

Project Euler #1 (defn old-solve-euler-1 [] (reduce + (filter #(or (zero? (mod % 3)) (zero? (mod % 5))) (range 1000)))) (defn divides? [n divisor] (zero? (mod n divisor))) (defn divides-any? [n & divisors] (some #(divides? n %) divisors)) (defn solve-euler-1 [] (reduce + (filter #(divides-any? % 3 5)) (range 1000)))

Slide 100

Slide 100 text

DILOS Dependency Inversion Interface Segregation Liskov Substitution Open-Closed Single Responsibility

Slide 101

Slide 101 text

DILOS SOLID Dependency Inversion Interface Segregation Liskov Substitution Open-Closed Single Responsibility

Slide 102

Slide 102 text

More learnings from OO

Slide 103

Slide 103 text

Don't reinvent the wheel

Slide 104

Slide 104 text

Think about why things are hard

Slide 105

Slide 105 text

Understand the underlying problems

Slide 106

Slide 106 text

What else can we learn?

Slide 107

Slide 107 text

Thank you Colin Jones / Software Craftsman / 8th Light @trptcolin https://github.com/trptcolin/reply https://github.com/functional-koans/clojure-koans