-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathpolymorphism.clj
More file actions
533 lines (452 loc) · 13.5 KB
/
polymorphism.clj
File metadata and controls
533 lines (452 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
;; gorilla-repl.fileformat = 1
;; **
;;; # Clojure Polymorphism
;;;
;;;
;; **
;; **
;;; ## Motivation
;;;
;;; There are many situations where it is useful to provide behavior that varies based on type or value. Clojure provides two different mechanisms for conditional (polymorphic) behavior: protocols and multimethods.
;;;
;;; Also, Clojure provides two constructs that allow you to create new data "types": records (which we saw in collections earlier) and types.
;;;
;;; Clojure was created with some strong opinions in this area:
;;;
;;; * Only derive from interfaces, never from implementations
;;; * All generic methods should be defined in interfaces
;;; * Polymorphism doesn't require inheritance
;;; * Data is still immutable
;;; * No data hiding (aka encapsulation)
;;;
;;; ### What does "type" mean?
;;;
;;; "What is the type (class) of object x?"
;;; * `(class x)` or `(type x)`
;;;
;;; "Foo is a type (class)"
;;; * `(defrecord Foo ...)` or `(deftype Foo ...)`
;;; * or Java `public class Foo { ... }`
;;; * Java primitive types - `long`, `double`, arrays, etc
;;;
;;; ## Protocols
;;;
;;; Protocols define an abstract behavioral contract. Protocols create a named group of generic functions. Each function has (only) parameters and doc strings, no implementation.
;;;
;;; Protocols are polymorphic (choose the implementation) based on the type of the first argument (like methods in Java). Each protocol function must have at least one argument (the one used for dispatch) - equivalent to `this` in Java.
;;;
;;; ### defprotocol
;; **
;; @@
(defprotocol MyProtocol
"A doc string for MyProtocol abstraction"
(bar [q r] "bar docs")
(baz [q] "baz docs"))
;; @@
;; **
;;; Define a protocol named MyProtocol (in the current namespace) with a protocol docstring and functions `bar` and `baz`. These functions both dispatch based on the type of `q`.
;; **
;; **
;;; ### Protocol dispatch
;;;
;;; `defprotocol` creates generic functions. These are normal functions, just like `defn`. They are invoked just like any other Clojure function.
;; **
;; @@
(defprotocol Describe
(desc [self]))
(desc thing) ; Invoke it like this
(.desc thing) ; Not this
;; Similar to Java: thing.desc()
;; @@
;; **
;;; ### Extending Protocols to Types
;;;
;;; What if we want to add protocols to an existing type (for example, Java types like `String`)?
;;;
;;; This is sometimes called the "Expression Problem". The problem is how to enable extending either the set of concrete types or the set of generic operations while existing code continues to work.
;;;
;;; Common solutions won't work:
;;; * Inheritance - can't inherit from `String`
;;; * Multiple inheritance - complex, not allowed in Java
;;; * Wrapping - complex, breaks type and equality
;;; * Open classes - no namespacing, error-prone
;;; * Conditionals - complex, not extensible without changing code
;;;
;;; ### Protocol extension
;;;
;;; Clojure allows extending any protocol to any type, including final Java classes. The type is not modified in any way. Implementations can be extended to both nil and Object.
;;;
;;; ### `extend-type` and `extend-protocol`
;;;
;;; Extensions are specified with `extend-type` or `extend-protocol`.
;; **
;; @@
(extend-type SomeType ; 1 type, many protocols
SomeProtocol
(some-method [...] ...)
AnotherProtocol
(another-method [...] ...))
(extend-protocol SomeProtocol ; 1 protocol, many types
SomeType
(some-method [...] ...)
AnotherType
(some-method [...] ...))
;; @@
;; **
;;; ### `extend-type example
;; **
;; @@
; java.lang.String does not implement Describe
(desc "a")
; IllegalArgumentException No implementation of
; method: :desc of protocol: #'user/Describe
; found for class: java.lang.String
(satisfies? Describe "a")
;;=> false
; extend Describe to java.lang.String
(extend-type String
Describe
(desc [s] s))
(satisfies? Describe "a")
;;=> true
; try again...
(desc "a")
;;=> "a"
;; @@
;; **
;;; ### Reifying protocols
;;;
;;; `reify` builds anonymous type and instance on the fly, conceptually similar to anonymous functions or anonymous inner classes in Java. Function bodies are closures.
;; **
;; @@
(def r (let [x 42]
(reify Describe
(desc [_] (str "describe with " x)))))
;;=> #user/r
(desc r)
;;=> "describe with 42"
;; @@
;; **
;;; ## Multimethods
;;;
;;; Protocols are limited to single-argument dispatch on the type of the first argument. Multimethods support multi-argument dispatch on any criteria of all of the arguments, so it is much more flexible.
;; **
;; @@
(defmulti reaction
;; 2-argument dispatch function:
(fn [a b] [(:species a) (:species b)]))
(defmethod reaction [:hero :monster] ; match criteria
[hero monster] ; function parameters
(str hero " fights " monster)) ; function body
(defmethod reaction [:monster :hero]
[monster hero]
(str monster " eats " hero))
(defmethod reaction [:monster :monster]
[monster1 monster2]
(str monster1 " plays with " monster2))
(defmethod reaction [:hero :hero]
[hero1 hero2]
(str hero1 " taunts " hero2))
;; @@
;; **
;;; ### Custom dispatch
;;;
;;;
;; **
;; @@
(defmulti shape count)
(defmethod shape 3 [points] "triangle")
(defmethod shape 4 [points] "rectangle")
(defmethod shape 5 [points] "pentagon")
(defmethod shape 6 [points] "hexagon")
(defmethod shape :default [n] "who cares?")
(shape [[0 0], [0 5], [5 0]]) ;;=> "triangle"
(shape [[0 0], [0 5], [5 0], [5 5]]) ;;=> "rectangle"
(shape []) ;;=> "who cares?"
;; @@
;; **
;;; ### Multimethods vs Protocols
;;;
;;; | | Multimethods | Protocols |
;;; |-|--------------|-----------|
;;; | Extensible | yes | yes|
;;; | Java interop story | Vars | interfaces |
;;; | Dispatch on arguments | any number | only first |
;;; | Dispatch function | arbitrary | only type |
;;; | Method grouping | no | yes |
;;; | High performance | no | yes |
;;;
;; **
;; **
;;; ## Records
;;;
;;; As we discussed in the Collections section, records create entities with explicit types, not just generic maps. Records can implement both protocols and interfaces directly at definition time:
;; **
;; @@
(defrecord Car [make model year]) ; named type with fields
;;=> user.Car
(def car (->Car "Dodge" "Omni" 1980)) ; instantiation
;;=> #'user/car
(:year car) ; field access
;;=> 1980
;; @@
;; **
;;; ### Records are classes
;;;
;;;
;; **
;; @@
(def car (Car. "Dodge" "Omni" 1980)) ; Java constructor
;;=> #'user/car
(.-year car) ; fields are public & final
;;=> 1980
(class car) ; ordinary class
;;=> user.Car
(supers (class car)) ; lots of built-in functionality
;;=> #{clojure.lang.IObj clojure.lang.IKeywordLookup java.util.Map
;; clojure.lang.IPersistentMap clojure.lang.IMeta java.lang.Object
;; java.lang.Iterable clojure.lang.ILookup clojure.lang.Seqable
;; clojure.lang.Counted clojure.lang.IPersistentCollection
;; clojure.lang.Associative}
;; @@
;; **
;;; ### Implementing protocols on records
;;;
;;; Protocols (and Java interfaces) can be implemented directly in `defrecord`. Method implementation bodies can access record fields directly but *do not* close over the lexical environment. If a record does not implement all of the protocol methods, undefined ones will throw `AbstractMethodError`.
;; **
;; @@
; specify protocol(s) directly inline
(defrecord Car [make model year]
Describe
(desc [self] (str year " " make " " model)))
(def car (->Car "Dodge" "Omni" 1980))
(desc car)
;;=> "1980 Dodge Omni"
;; @@
;; **
;;; ### Using protocols and records
;; **
;; @@
(defprotocol Player "A rock/paper/scissors player"
(choose [p] "return :rock, :paper or :scissors")
(update-player [p me you]
"return a new player based on what you and I did"))
(defrecord Stubborn [choice]
Player ; implement Player protocol
(choose [_] choice) ; always play the choice
(update-player [this _ _] this)) ; never change
(defrecord Mean [last-win] ; last thing that won for me
Player
(choose [_]
(if last-win ; play last-win or random
last-win
(random-choice)))
(update-player [_ me you]
;; reuse last choice, or switch to random
(->Mean (when (i-won? me you) me))))
;; @@
;; **
;;; ## `deftype`
;;;
;;; `deftype` is used for (usually) advanced use cases where you want a new type with custom behavior. `deftype` looks like `defrecord` but provides *no* default behavior. Additionally, fields can be mutable (DANGER!).
;;;
;;;
;; **
;; @@
(deftype Point [x y]) ; named type with fields
;;=> user.Point
(def p (->Point 1 2)) ; constructor
;;=> #'user/p ; (but no map->Point)
(.-x p) ; ordinary field access
;;=> 1 ; (but no keyword lookup)
(class p) ; ordinary class
;;=> user.Point
(supers (class p)) ; an (almost) blank slate
;;=> #{java.lang.Object clojure.lang.IType}
;; @@
;; **
;;; # LAB: Rock, Paper, Scissors
;;;
;;; In this lab, we will write programs to play the classic game of [Rock, Paper, Scissors](http://en.wikipedia.org/wiki/Rock-paper-scissors). The rules are simple:
;;;
;;; * Rock beats Scissors
;;; * Scissors beats Paper
;;; * Paper beats Rock
;;;
;;; (For each section, the solutions are available at the bottom.)
;;;
;;; ## World domination
;;;
;;; Define a function dominates that takes a keyword argument and returns the keyword naming the thing that beats it.
;;;
;;; Hint: remember that data structures are functions.
;;;
;;; ## Choices, choices
;;;
;;; Define a vector of the possible choices, reusing the definition of dominates.
;;;
;;; Hint: the `keys` function returns a sequence of the keys in a map.
;;;
;;; ## Winners and losers
;;;
;;; Define a function winner that takes two players' choices and returns the winner, or nil for a tie.
;;;
;;; ## Draws and ties
;;;
;;; Define two predicates:
;;;
;;; * `draw?` takes two players' choices and returns true if they are a draw
;;; * `iwon?` takes two players' choices and returns true if the first player won
;;;
;;; ## The players
;;;
;;; All the players will conform to a `Player` protocol. It should have two methods:
;;;
;;; * `choose` takes a player and returns that player's choice
;;; * `update-player` takes a player, that player's last choice, and the other player's last choice, returning a new `Player` for the next round
;;;
;;; Define the `Player` protocol.
;;;
;;; ## Random player
;;;
;;; Define a `Random` player who always picks at random and never changes strategy based on what the other player is doing.
;;;
;;; Hint: Clojure's `rand-nth` function picks a random element from a collection.
;;;
;;; ## Stubborn player
;;;
;;; Define a Stubborn player who is initialized with a choice and sticks with it no matter what.
;;;
;;; ## Mean player
;;;
;;; Define a Mean player, who is slightly more subtle. The mean player sticks with what worked last time if it won, or plays at random following a loss.
;;;
;;; ## Playing a game
;;;
;;; Define a `game` function with three arguments: two players and a number of rounds. The game should return the two player's scores in a map.
;;;
;;; This can be nicely represented as a `loop` with five variables:
;;;
;;; 1. Player One
;;; 2. Player Two
;;; 3. Player One's current score
;;; 4. Player Two's current score
;;; 5. The number of rounds remaining
;;;
;;; Try some games with various combinations of players. Do the results match your intuition?
;;;
;;; Examples:
;; **
;; @@
(game (->Stubborn :rock) (->Stubborn :scissors) 100)
;;=> {:p1 100, :p2 0}
(game (->Random) (->Random) 100)
;;=> {:p1 34, :p2 25}
(game (->Stubborn :rock) (->Mean nil) 100)
;;=> {:p1 5, :p2 93}
;; @@
;; **
;;; # LAB SOLUTIONS
;;;
;;; ## World domination
;; **
;; @@
(def dominates
{:rock :paper
:scissors :rock
:paper :scissors})
;; @@
;; **
;;; ## Choices, choices
;; **
;; @@
(def choices (vec (keys dominates)))
;; @@
;; **
;;; ## Winners and losers
;; **
;; @@
(defn winner [p1-choice p2-choice]
(cond
(= p1-choice p2-choice) nil
(= (dominates p1-choice) p2-choice) p2-choice
:else p1-choice))
;; @@
;; **
;;; ## Draws and ties
;; **
;; @@
(defn draw? [me you] (= me you))
(defn iwon? [me you] (= (winner me you) me))
;; @@
;; **
;;; ## The players
;; **
;; @@
(defprotocol Player
(choose [p])
(update-player [p me you]))
;; @@
;; **
;;; ## Random player
;; **
;; @@
(defrecord Random []
Player
(choose [_] (rand-nth choices))
(update-player [this me you] this))
;; @@
;; **
;;; ## Stubborn player
;; **
;; @@
(defrecord Stubborn [choice]
Player
(choose [_] choice)
(update-player [this me you] this))
;; @@
;; **
;;; ## Mean player
;; **
;; @@
(defrecord Mean [last-winner]
Player
(choose [_]
(if last-winner last-winner (rand-nth choices)))
(update-player [_ me you]
(->Mean (when (iwon? me you) me))))
;; @@
;; **
;;; ## Playing a game
;; **
;; @@
(defn game
[p1 p2 rounds]
(loop [p1 p1
p2 p2
p1-score 0
p2-score 0
rounds rounds]
(if (pos? rounds)
(let [p1-choice (choose p1)
p2-choice (choose p2)
result (winner p1-choice p2-choice)]
(recur
(update-player p1 p1-choice p2-choice)
(update-player p2 p2-choice p1-choice)
(+ p1-score (if (= result p1-choice) 1 0))
(+ p2-score (if (= result p2-choice) 1 0))
(dec rounds)))
{:p1 p1-score :p2 p2-score})))
;; @@
;; **
;;;
;;; ## Navigation
;;;
;;; * [Up (Home)](/worksheet.html?filename=src/cljlab/start.clj)
;;; * [Previous (Sequences)](/worksheet.html?filename=src/cljlab/sequences.clj)
;;; * [Next (State and Concurrency)](/worksheet.html?filename=src/cljlab/state.clj)
;; **
;; **
;;;
;; **