Last active
March 16, 2017 18:25
-
-
Save johndenverscar/8cd0bde8af9519e6303490382b024bd6 to your computer and use it in GitHub Desktop.
null created by admay - https://repl.it/GYwt/13
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Abstractions with multimethods, protocols, records | |
;; multimethods | |
;; useful for overloading based on dispatching | |
;; vnice | |
(ns ware-creatures) | |
(defmulti full-moon-behavior (fn [were-creature] (:were-type were-creature))) | |
;; Typical multimethods look like so... | |
(defmethod full-moon-behavior :wolf ;; dispatching value is a keyword | |
[were-creature] | |
(str (:name were-creature) " will howl and murder")) | |
(defmethod full-moon-behavior :simmons | |
[were-creature] | |
(str (:name were-creature) " will jazzercise")) | |
;; You can use a default dispatcher | |
(defmethod full-moon-behavior :default | |
[were-creature] | |
(str (:name were-creature) " will stay up and eat chips")) | |
;; You can also use nil, numbers, strings, etc... | |
(defmethod full-moon-behavior nil | |
[were-creature] | |
(str (:name were-creature) " might not even be a were creature")) | |
(defmethod full-moon-behavior 11 | |
[were-creature] | |
(str (:name were-creature) " is number 11")) | |
(defmethod full-moon-behavior "apples" | |
[were-creature] | |
(str (:name were-creature) " is going to kill or be apples")) | |
;; Once defined, you can now create new methods based off of the original | |
;; multimethod by requiring this ns. | |
;; (ns other.ns | |
;; (:require [were-creatures])) | |
;; | |
;; (defmethod were-creatures/full-moon-behavior ... ) | |
;; | |
;; (full-moon-behavior { ... }) | |
;; In use, it looks like this. | |
;; Check out the REPL for some output/confirmation | |
(full-moon-behavior {:were-type :wolf | |
:name "Greg"}) | |
(full-moon-behavior {:were-type :simmons | |
:name "Rudolph"}) | |
(full-moon-behavior {:were-type :hoopla | |
:name "Jimmy John"}) | |
(full-moon-behavior {:name "Rick"}) | |
(full-moon-behavior {:were-type 11 | |
:name "Ricky"}) | |
(full-moon-behavior {:were-type "apples" | |
:name "Repunzel"}) | |
;; multimethods allow you to dispatch multiple arguments | |
;; hence the name, _multi_methods | |
(defmulti types (fn [x y] [(class x) (class y)])) | |
(defmethod types [java.lang.String java.lang.String] | |
[x y] | |
"Two strings!") | |
(types "String 1" "Thing 2") | |
;; multimethods are alright for dispatching based on argument type | |
;; protocols however, are optimized for such operations | |
(defprotocol Psychodynamics | |
"Plumb the inner depths of your data types" | |
(thoughts [x] "The datatypes inner-most thoughts") ;; method signature | |
(feelings-about [x] [x y] "How a data type feels about... things.")) ;; method signature | |
;; protocols can have multiple arguments but not 'rest' arguments | |
;; i.e. (feelings [x] [x & args] ... ) | |
;; The above is the definition of an abstraction | |
;; this does not define the implementation though | |
;; implementations are defined by extending types | |
(extend-type java.lang.String | |
Psychodynamics ;; extend the type java.lang.String with Psychodynamics | |
(thoughts [x] (str x " is longing for a simpler way of life")) | |
(feelings-about ([x] (str x " is getting impatient with complexity")) | |
([x y] (str x " is jealous of the simplicity in " y)))) | |
;; Now you can just call these methods on strings | |
(thoughts "hello") | |
(feelings-about "hello") | |
(feelings-about "hello" "world") | |
;; But nothing else... | |
;; (thoughts 1) | |
;; => | |
;; IllegalArgumentException No implementation of method: :thoughts of protocol: #'were | |
;; -creatures/Psychodynamics found for class: java.lang.Long clojure.core/-cache-protocol-fn | |
;; (core_deftype.clj:568) | |
;; You can, however, do this | |
;; :O | |
(feelings-about "string" 1234) | |
;; You can extend the java.lang.Obejct for a truly 'default' implementation | |
(extend-type java.lang.Object | |
Psychodynamics | |
(thoughts [x] (str x " is truly obtuse...")) | |
(feelings-about ([x] (str x " is touchy about everything...")) | |
([x y] (str x " can't get over when " y " ate their fries")))) | |
;; the String extension still works | |
(thoughts "hello") | |
;; But not it also works with everything else | |
(thoughts 1) | |
(feelings-about 1) | |
(feelings-about 'becky :flo-rida) | |
(feelings-about :flo-rida 'RonaldMcDonaldMC) | |
(feelings-about 1 [1 2 3]) | |
(feelings-about 1 (fn [] 1)) ;; even functions! | |
;; You can concisely define these extensions using extend-protocol | |
;; It's up to you though, there is no rule about this one | |
;(extend-protocol Psychodynamics | |
; java.lang.String | |
; (thoughts [x] (str x " is longing for a simpler way of life")) | |
; (feelings-about ([x] (str x " is getting impatient with complexity")) | |
; ([x y] (str x " is jealous of the simplicity in " y))) | |
; | |
; java.lang.Object | |
; (thoughts [x] (str x " is truly obtuse...")) | |
; (feelings-about ([x] (str x " is touchy about everything...")) | |
; ([x y] (str x " can't get over when " y " ate their fries")))) | |
;; records are like maps in the same way that ogres are like onions | |
;; they are similar but you're really grasping at straws with this one | |
(defrecord Warewolf [name title]) | |
;; defrecord will reveal the following to you | |
(Warewolf. "David" "London Tourist") | |
(->Warewolf "Jacob" "Lord of Discarding Shirts") | |
(map->Warewolf {:name "Lucian" :title "CEO of Melodrama"}) | |
; => #were_creatures.Warewolf{:name "David", :title "London Tourist"} | |
; => #were_creatures.Warewolf{:name "Jacob", :title "Lord of Discarding Shirts"} | |
; => #were_creatures.Warewolf{:name "Lucian", :title "CEO of Melodrama"} | |
;; So we now have a Warewolf class that has some fields | |
;; You can access those fields a couple of ways | |
(def jordan (->Warewolf "Jordan Belfort" "The Wolf of Wall Street")) | |
(get jordan :name) ; => "Jordan Belfort" | |
(:name jordan) ; => "Jordan Belfort" | |
(.name jordan) ; => "Jordan Belfort" | |
;; When testing for equality Clojure will test the field values and the type | |
(= jordan (->Warewolf "Jordan Belfort" "The Wolf of Wall Street")) ; => true | |
(= jordan (->Warewolf "Lucian" "CEO of Melodrama")) ; => false | |
(= jordan {:name "Jordan Belfort" :title "The Wolf of Wall Street"}) ; => false | |
;; Any function you can use on a map, can be used on a record | |
(assoc jordan :title "Ex-Convict") | |
;; Except for dissoc which returns a clojure map | |
(dissoc jordan :title) | |
;; Extending protocols while defining records | |
(defprotocol WareCreature | |
(full-moon-behavior [x])) | |
(defrecord WareWolf [name title] | |
WareCreature | |
(full-moon-behavior [this] (str name " says, Awoooo!"))) ;; You can access fields | |
(def thor (->WareWolf "Hafthor" "Worlds Second Strongest Man-Beast")) | |
(full-moon-behavior thor) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment