Skip to content

Instantly share code, notes, and snippets.

@joinr
Created August 3, 2020 20:19
Show Gist options
  • Save joinr/2dde3d59115e910ff6c2864fae46725d to your computer and use it in GitHub Desktop.
Save joinr/2dde3d59115e910ff6c2864fae46725d to your computer and use it in GitHub Desktop.
of types and pizza
(ns pizza
(:require [clojure.spec.alpha :as s]))
(s/def :order/id int?)
(s/def :order/status #{:received :delivered :cancelled})
(s/def :pizza/kind #{:plain :pepperoni :hawaiian})
(s/def ::pizza-order (s/keys :req [:order/id :order/status :pizza/kind]))
;;pack some "type" into the data
(def my-pizza-order
{:order/id 1234
:order/type :pizza
:order/status :delivered
:pizza/kind :hawaiian})
;;define a new type...
(defrecord pizza-order [id status kind])
(def my-other-pizza-order (->pizza-order 0 :received :plain))
;;we could just use functions. this may suffice if
;;there are a small number of possibilities, or if
;;we can abstract out the predicates into something simple.
(defn pizza-record? [m]
(instance? pizza.pizza-order m ))
(defn simple-handle [m]
(cond (pizza-record? m)
(get m :kind)
(and (map? m)
(= (get m :order/type) :pizza))
(get m :pizza/kind)
:else :no-idea))
;; pizza> (simple-handle my-pizza-order)
;; :hawaiian
;; pizza> (simple-handle my-other-pizza-order)
;; :plain
;;define a multimethod to infer "type" from the input
;;define different implementations for different "types" of order
;;We look up the :order/type key in the input, otherwise
;;dispatch on the input's concrete type:
(defmulti handle-order
(fn [m]
(or (get m :order/type)
(type m))))
(defmethod handle-order :default [order]
(println "no idea!"))
(defmethod handle-order nil [order]
(println "no-idea!"))
(defmethod handle-order :pizza [order]
(println (str "Hey, it's a " (order :pizza/kind) " pizza!")))
;; pizza> (handle-order my-pizza-order)
;; Hey, it's a :hawaiian pizza!
;; nil
;; pizza> (handle-order my-other-pizza-order)
;; no-idea!
;; nil
(defmethod handle-order pizza.pizza-order [order]
(println (str "Hey, it's a " (get order :kind) " pizza!")))
;; pizza> (handle-order my-other-pizza-order)
;; Hey, it's a :plain pizza!
;; nil
;;define a protocol, which will dispatch on the actual type of
;;its argument to determine implementation
(defprotocol IOrderHandler
(-handle-order [this]))
;;define a new type that implements the protocol
(defrecord smart-pizza-order [id status kind]
IOrderHandler
(-handle-order [this]
(println (str "Hey, it's a strongly-typed " kind " pizza!"))))
(def smarter-pizza-order (->smart-pizza-order 0 :received :pepperoni))
;;extend the protocol to existing concrete types
;;implementation just delegates to the multimethod from before.
(extend-protocol IOrderHandler
clojure.lang.PersistentArrayMap
(-handle-order [this]
(handle-order this))
clojure.lang.PersistentHashMap
(-handle-order [this]
(handle-order this))
pizza.pizza-order
(-handle-order [this]
(handle-order this)))
;;pizza> (-handle-order my-other-pizza-order)
;;Hey, it's a :plain pizza!
;;nil
;;pizza> (-handle-order my-pizza-order)
;;Hey, it's a :hawaiian pizza!
;; pizza> (-handle-order smarter-pizza-order)
;; Hey, it's a strongly-typed :pepperoni pizza!
;; nil
;;records add a wrinkle to the existing spec, since their "keys" are
;;not namespace-qualified.
(s/def ::pizza-order-unqualified (s/keys :req-un [::id ::status ::kind]))
(s/def ::any-pizza-map (s/or :qualified ::pizza-order
:unqualified ::pizza-order-unqualified))
;; pizza> (s/valid? ::any-pizza-map my-pizza-order)
;; true
;; pizza> (s/valid? ::any-pizza-map smarter-pizza-order)
;; true
;; pizza> (s/valid? ::any-pizza-map my-other-pizza-order)
;; true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment