Skip to content

Instantly share code, notes, and snippets.

@telekid
Last active May 19, 2020 00:12
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 telekid/f2e588718dbdfe48306d64e5388bdc15 to your computer and use it in GitHub Desktop.
Save telekid/f2e588718dbdfe48306d64e5388bdc15 to your computer and use it in GitHub Desktop.
(require '[clojure.spec.alpha :as s]
'[clojure.spec.test.alpha :as spec.test])
;; In Clojure, multimethods are designed to be open for extension. That means that
;; a library author can define a function, and then consumers of that library
;; can extend it arbitrarily by defining additional implementations of that function.
;;
;; However, there isn't a ton of documentation floating around that describes how
;; to properly spec functions in an open way. This gist provides an example of doing
;; just that; it defines a function called `magic`, and then extends it with two
;; implementations, each of which has seperate argument requirements. (The same
;; strategies can be used to offer polymorphic return types.)
;;
;; The key is to leverage Clojure's heiarchy affordances when defining your dispatch
;; system - heirarhies, like multimethods, are open for extension.
(s/def ::tools #(boolean ((descendants ::tool) %)))
;; I don't understand why this needs to be (-> args first first)
;; rather than (first args) ¯\_(ツ)_/¯
(defmulti magic-args (fn dispatch [& args] (-> args first first)))
(s/def ::magic-args (s/multi-spec magic-args (fn retag [gen-v dispatch-tag] gen-v)))
(s/fdef magic
:args ::magic-args
:ret string?)
(defmulti magic (fn [& args] (first args)))
;; possibly defined in a library somewhere
(derive ::wand ::tool)
(defmethod magic-args ::wand [_] (s/cat :wand #{::wand} :friend string?))
(defmethod magic ::wand [_ friend] (str "you made your friend " friend " disappear!"))
;; possibly defined in userland
(derive ::hat ::tool)
(defmethod magic-args ::hat [_] (s/cat :hat #{::hat} :count number? :animal string?))
(defmethod magic ::hat [_ count animal] (str "you pulled " count " " animal "(s) from your hat!"))
(spec.test/instrument `magic)
(magic ::wand "Eddie")
(magic ::hat 2 "rabbits")
(clojure.spec.test.alpha/check `magic)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment