Skip to content

Commit 4fb2870

Browse files
committed
Add otus-10
1 parent 60c3dc2 commit 4fb2870

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed

otus-10/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## Clojure Developer. Lesson 10
2+
3+
Polimorphism in Clojure. Part 2
4+
5+
> All of Clojure’s dispatch mechanisms – interfaces, protocols, and multimethods – are implemented using tables. All of them are open, but to different degrees. In general, their efficiency is inversely proportional to their openness.
6+
7+
*— Elements of Clojure*
8+
9+
* [Datatypes: deftype, defrecord and reify](https://clojure.org/reference/datatypes)
10+
* [Protocols](https://clojure.org/reference/protocols)
11+
* [Clojure from the ground up: polymorphism](https://aphyr.com/posts/352-clojure-from-the-ground-up-polymorphism)
12+
13+
> When performance matters, we turn to interfaces and protocols
14+
15+
* [definterface](https://clojuredocs.org/clojure.core/definterface)
16+
* [reify](https://clojuredocs.org/clojure.core/reify)
17+
* [proxy](https://clojuredocs.org/clojure.core/reify)
18+
19+
> The lesson learned from proxy is that unless you are forced to extend a class from a Java framework in order to use it, you should probably look into reify instead of proxy for the creation of quick throw-away instances. If instead your goal is polymorphism in Clojure, there are better options with protocols and multimethods.
20+
21+
### Protocols
22+
23+
There are several motivations for protocols:
24+
25+
* Provide a high-performance, dynamic polymorphism construct as an alternative to interfaces
26+
* Support the best parts of interfaces
27+
* specification only, no implementation
28+
* a single type can implement multiple protocols
29+
* While avoiding some of the drawbacks
30+
* Which interfaces are implemented is a design-time choice of the type author, cannot be extended later (although interface injection might eventually address this)
31+
* implementing an interface creates an isa/instanceof type relationship and hierarchy
32+
* Avoid the 'expression problem' by allowing independent extension of the set of types, protocols, and implementations of protocols on types, by different parties
33+
* do so without wrappers/adapters
34+
* Support the 90% case of multimethods (single dispatch on type) while providing higher-level abstraction/organization
35+
36+
[Expression Problem](https://wiki.c2.com/?ExpressionProblem)
37+
38+
* [defprotocol](https://clojuredocs.org/clojure.core/defprotocol)
39+
* [extend-protocol](https://clojuredocs.org/clojure.core/extend-protocol)
40+
* [deftype](https://clojuredocs.org/clojure.core/deftype)
41+
* [defrecord](https://clojuredocs.org/clojure.core/defrecord)
42+
43+
### deftype vs defrecord
44+
45+
* `deftype` provides no functionality not specified by the user, other than a constructor
46+
* `defrecord` provides a complete implementation of a persistent map, including:
47+
* value-based equality and hashCode
48+
* metadata support
49+
* associative support
50+
* keyword accessors for fields
51+
* extensible fields (you can assoc keys not supplied with the defrecord definition)
52+
* etc
53+
* `deftype` supports mutable fields, defrecord does not
54+
* `defrecord` supports an additional reader form of `#my.record{:a 1, :b 2}`
55+
* when a `defrecord` `Bar` is defined a corresponding function `map->Bar` is defined that takes a map and initializes a new record instance with its contents

otus-10/project.clj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
(defproject otus-10 "0.1.0-SNAPSHOT"
2+
:description "https://github.com/Clojure-Developer/Clojure-Developer-2023-10"
3+
:dependencies [[org.clojure/clojure "1.11.1"]]
4+
:repl-options {:init-ns otus-10.core}
5+
:main ^:skip-aot otus-10.homework
6+
:target-path "target/%s"
7+
:profiles {:dev {}
8+
:uberjar {:aot :all
9+
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
10+

otus-10/src/otus_10/core.clj

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
(ns otus-10.core)
2+
3+
4+
(definterface IAppend
5+
(append [x]))
6+
7+
;; reify: a fancy philosophical word that means
8+
;; "make a concrete thing out of an abstract concept"
9+
10+
(defn grocery-list
11+
"Creates an appendable grocery list. Takes a vector of groceries to buy."
12+
[to-buy]
13+
(reify IAppend
14+
(append [this x]
15+
(grocery-list (conj to-buy x)))))
16+
17+
(supers (type (grocery-list [:eggs])))
18+
(meta (grocery-list [:eggs]))
19+
20+
(.append (grocery-list [:eggs]) :tofu)
21+
22+
;;(map .append [(grocery-list [:eggs])] [:tofu])
23+
24+
#_:clj-kondo/ignore
25+
(defn grocery-list
26+
"Creates an appendable grocery list. Takes a vector of groceries to buy."
27+
[to-buy]
28+
(reify
29+
IAppend
30+
(append [this x]
31+
(grocery-list (conj to-buy x)))
32+
33+
Object
34+
(toString [this]
35+
(str "To buy: " to-buy))))
36+
37+
(str (.append (grocery-list [:eggs]) :tomatoes))
38+
39+
(.append [1 2] 3)
40+
41+
;; proxy
42+
(let [p (proxy [java.io.InputStream] []
43+
(read
44+
([] 1)
45+
([^bytes bytes] 2)
46+
([^bytes bytes off len] 3))
47+
(toString
48+
([] (str (.hashCode this)))))]
49+
(println (.read p))
50+
(println (.read p (byte-array 3)))
51+
(println (.read p (byte-array 3) 0 3))
52+
(println p))
53+
54+
;; "Expression problem"
55+
;; Existing (regular) functions can’t be extended to new types, and existing
56+
;; types can’t be extended to new interfaces.
57+
58+
;; Protocols
59+
60+
;; Protocol is like an interface which can be extended to existing types.
61+
;; It defines a named type, with functions whose 1st argument is
62+
;; an instance of that type.
63+
64+
(defprotocol Append
65+
"This protocol lets us add things to the end of a collection."
66+
(append [coll x]
67+
"Appends x to the end of collection coll."))
68+
69+
;; (doc Append)
70+
;; (doc append)
71+
72+
(append (grocery-list [:eggs]) :tomatoes)
73+
74+
#_:clj-kondo/ignore
75+
(defn grocery-list
76+
"Creates an appendable grocery list. Takes a vector of groceries to buy."
77+
[to-buy]
78+
(reify
79+
Append
80+
(append [this x]
81+
(grocery-list (conj to-buy x)))
82+
83+
Object
84+
(toString [this]
85+
(str "To buy: " to-buy))))
86+
87+
(str (append (grocery-list [:eggs]) :tomatoes))
88+
89+
(append [1 2] 3)
90+
91+
92+
(extend-protocol Append
93+
clojure.lang.IPersistentVector
94+
(append [v x]
95+
(conj v x)))
96+
97+
(append '() 2)
98+
99+
(extend-protocol Append
100+
clojure.lang.IPersistentVector
101+
(append [v x]
102+
(conj v x))
103+
104+
clojure.lang.Sequential
105+
(append [v x]
106+
(concat v (list x))))
107+
108+
(append nil 2)
109+
110+
(extend-protocol Append
111+
nil
112+
(append [_ x]
113+
[x]))
114+
115+
;; deftype
116+
117+
;; reify creates anonymous type
118+
119+
(deftype GroceryList [to-buy]
120+
Append
121+
(append [this x]
122+
(GroceryList. (conj to-buy x)))
123+
124+
Object
125+
(toString [this]
126+
(str "To buy: " to-buy)))
127+
128+
(GroceryList. [:eggs])
129+
130+
(append (GroceryList. [:eggs]) :spinach)
131+
132+
(supers GroceryList)
133+
134+
(.to-buy (GroceryList. [:eggs]))
135+
136+
(->GroceryList [:strawberries])
137+
138+
;; (map GroceryList. [[:twix] [:kale :bananas]])
139+
(map ->GroceryList [[:twix] [:kale :bananas]])
140+
141+
;; deftype is bare-bones
142+
(= (GroceryList. [:cheese]) (GroceryList. [:cheese]))
143+
(let [gl (GroceryList. [:fish])] (= gl gl))
144+
145+
#_:clj-kondo/ignore
146+
(deftype GroceryList [to-buy]
147+
Append
148+
(append [this x]
149+
(GroceryList. (conj to-buy x)))
150+
151+
Object
152+
(toString [this]
153+
(str "To buy: " to-buy))
154+
155+
(equals [this other]
156+
(and (= (type this) (type other))
157+
(= to-buy (.to-buy other)))))
158+
159+
(= (GroceryList. [:cheese]) (GroceryList. [:cheese]))
160+
161+
162+
;; defrecord
163+
164+
;; It’d be nice if we could create a type, but have it still work like a map
165+
166+
#_:clj-kondo/ignore
167+
(defrecord GroceryList [to-buy]
168+
Append
169+
(append [this x]
170+
(GroceryList. (conj to-buy x))))
171+
172+
(supers GroceryList)
173+
174+
(GroceryList. [:beans])
175+
176+
(= (GroceryList. [:beans]) (GroceryList. [:beans]))
177+
(= (GroceryList. [:beans]) {:to-buy [:beans]})
178+
179+
(.to-buy (GroceryList. [:bread]))
180+
181+
(get (GroceryList. [:bread]) :to-buy)
182+
(:to-buy (GroceryList. [:bread]))
183+
184+
(-> (GroceryList. [:chicken])
185+
(assoc :to-buy [:onion])
186+
(update :to-buy conj :beets))
187+
188+
;; records always carry around an extra map
189+
(assoc (GroceryList. [:tomatoes]) :note "Cherries if possible!")
190+
191+
;; deftype & defrecord produce named types:
192+
193+
(defprotocol Printable
194+
(print-out [x] "Print out the given object, nicely formatted."))
195+
196+
(extend-protocol Printable
197+
GroceryList
198+
(print-out [gl]
199+
(println "GROCERIES")
200+
(println "---------")
201+
(doseq [item (:to-buy gl)]
202+
(print "[ ] ")
203+
(print-out item)
204+
(println)))
205+
206+
Object
207+
(print-out [x]
208+
(print x)))
209+
210+
(print-out (GroceryList. [:cilantro :carrots :pork :baguette]))
211+
212+
(defrecord CountedItem [thing quantity]
213+
Printable
214+
(print-out [this]
215+
(print-out thing)
216+
(print (str " (" quantity "x)"))))
217+
218+
(print-out (GroceryList. [:cilantro (CountedItem. :carrots 2) :pork :baguette]))
219+
220+
;; When to use
221+
;; Take a step back, and ask: do I **need** polymorphism here?
222+
223+
;; NOTE: don't
224+
(defrecord Person [name last-name age])
225+
226+
;; NOTE: do
227+
{:name "Gordon"
228+
:last-name "Freeman"
229+
:age 42}
230+
231+
;; you’ll want to use defrecord and deftype when maps aren’t sufficient:
232+
;; * when multimethod performance is too slow
233+
;; * when you need polymorphism
234+
;; * when you need to participate in existing protocols or interfaces,
235+
236+
237+
;; extend-via-metadata
238+
239+
(defprotocol Component
240+
:extend-via-metadata true
241+
(start [component]))
242+
243+
(def component
244+
(with-meta
245+
{:name "db"}
246+
{`start (constantly "started")}))
247+
248+
(start component)

otus-10/src/otus_10/homework.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(ns otus-10.homework)
2+
3+
4+
(defn -main
5+
"I don't do a whole lot ... yet."
6+
[& args])

0 commit comments

Comments
 (0)