Skip to content

Instantly share code, notes, and snippets.

@admay
Last active March 16, 2017 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save admay/8cd0bde8af9519e6303490382b024bd6 to your computer and use it in GitHub Desktop.
Save admay/8cd0bde8af9519e6303490382b024bd6 to your computer and use it in GitHub Desktop.
null created by admay - https://repl.it/GYwt/13
;; 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