Skip to content

Instantly share code, notes, and snippets.

@krisleech
Last active February 1, 2018 15:32
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 krisleech/2c861b6806a3d3598a4da14492f65ac2 to your computer and use it in GitHub Desktop.
Save krisleech/2c861b6806a3d3598a4da14492f65ac2 to your computer and use it in GitHub Desktop.
Clojure multimethod on type

I wanted a function to convert given keys in a vector and maps from true/false to "yes"/"no".

[{ :is_it true } { :is_it false }] ;; => [{ :is_it "Yes" } { :is_it "No" }]

Note I use boolean? which was added to Clojure 1.9, it can be replaced with (instance? Boolean bool) in 1.8.

;; converts boolean (true/false) to text (yes/no)
(defn- boolean-to-text [bool]
(when (boolean? bool)
(if bool "Yes" "No")))
;; returns true if coll contains given value
(defn- seq-contains? [coll target] (some #(= target %) coll))
;; converts key values from true/false to yes/no in map
(defn- coerce-boolean-values-for-map [bool-keys record]
(into {} (for [[k v] record] [k (if (seq-contains? bool-keys k) (boolean-to-text v) v)])))
;; converts key values from boolean to text for collection of maps
(defn coerce-boolean-values-for-coll [bool-keys coll]
{:pre [(every? map? coll)]}
(map (partial coerce-boolean-values-for-map bool-keys) coll))
(defn run-test []
(if (= (coerce-boolean-values-for-coll [:is_it] [{:is_it true :name "Kris"}]) [{:is_it "Yes" :name "Kris"}])
(println "PASSED")
(println "FAILED")))
;; converts boolean (true/false) to text (yes/no)
(defn- boolean-to-text [bool]
(when (boolean? bool)
(if bool "Yes" "No")))
;; returns true if coll contains given value
(defn- seq-contains? [coll target] (some #(= target %) coll))
(defmulti coerce-boolean-keys (fn [_ x] (map? x))) ;; FIXME: should fails for non-map or non-collection
;; FIXME: true/false is not very informative
(defmethod coerce-boolean-keys true [keys m]
(into {} (for [[k v] m] [k (if (seq-contains? keys k) (boolean-to-text v) v)])))
;; I could have passed a `fn` to `map`, but choose to use `partial`, no idea which is idiomatic.
(defmethod coerce-boolean-keys false [keys c] (map (partial coerce-boolean-keys keys) c))
(defn run-test []
(if (= (coerce-boolean-keys [:is_it] [{:is_it true :name "Kris"}]) [{:is_it "Yes" :name "Kris"}])
;; There are a few ways I can think of to fix the problem with the `defmulti` distach function being binary and matching on true/false.
;; 1. add a pre clause to make sure the `x` is a `map?` or `seq?`
;; 2. match on `class`, however it seems lists have a different class when empty.
(println "PASSED")
(println "FAILED")))
(defmulti coerce-boolean-keys (fn [_ x] (class x)))
(defmethod coerce-boolean-keys clojure.lang.PersistentArrayMap [keys m]
(into {} (for [[k v] m] [k (if (seq-contains? keys k) (boolean-to-text v) v)])))
(defmethod coerce-boolean-keys clojure.lang.PersistentVector [keys v] (map (partial coerce-boolean-keys keys) v))
(defn run-test2 []
(if (= (coerce-boolean-keys [:is_it] [{:is_it true :name "Kris"}]) [{:is_it "Yes" :name "Kris"}])
(println "PASSED")
(println "FAILED")))
;; this works but fails if a list instead of a vector is passed, this may or may not be a problem, but we could add a `defmethod` for a list:
(defmethod coerce-boolean-keys clojure.lang.PersistentList [keys l] (coerce-boolean-keys keys (vec l)))
;; since an empty list has a different class, we also need to add that:
(defmethod coerce-boolean-keys clojure.lang.PersistentList$EmptyList [_ l] l) ;; we can just return the given empty list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment