Last active
May 19, 2020 00:12
-
-
Save telekid/f2e588718dbdfe48306d64e5388bdc15 to your computer and use it in GitHub Desktop.
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
(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