Skip to content

Instantly share code, notes, and snippets.

@noprompt
Last active July 27, 2017 21:21
Show Gist options
  • Save noprompt/52c1e1940f16b9f221e2112d4656dc22 to your computer and use it in GitHub Desktop.
Save noprompt/52c1e1940f16b9f221e2112d4656dc22 to your computer and use it in GitHub Desktop.
(ns conform-case
(:require [clojure.spec :as spec]))
(defn build-clause-form
{:private true}
[expression-form clause else-form]
(let [[spec binding-form & body] clause]
`(let [conform-value# (spec/conform ~spec ~expression-form)]
(if (= conform-value# ::spec/invalid)
~else-form
(let [~binding-form conform-value#]
(do ~@body))))))
(defmacro conform-case
{:arglists '([expression & clauses])
:style/indent [1 :form [:defn]]}
[& arguments]
(let [[expression & clauses] arguments
[inner-most-clause & outer-clauses] (reverse clauses)
expression-symbol (gensym "expression__")]
`(let [~expression-symbol ~expression]
~(reduce
(fn [inner-clause-form outer-clause]
(build-clause-form expression-symbol outer-clause inner-clause-form))
(build-clause-form expression-symbol inner-most-clause nil)
outer-clauses))))
(spec/def ::conform-case-clause
(spec/and list?
(spec/cat :spec any?
:binding :clojure.core.specs/binding-form
:body (spec/* any?))))
(spec/def ::conform-case-arguments
(spec/cat :expression any?
:clauses (spec/* ::conform-cond-clause)))
(spec/fdef ::conform-case
:args ::conform-case-arguments
:ret any?)
;; I like the "old school" style of `case` so kill me.
(let [value '(some-spec {:keys [x y z]} body)]
(conform-case value
(::conform-cond-clause {:keys [spec binding]}
[:a [spec binding]])
(:clojure.core.specs/binding-form conform-data
[:b [conform-data]])))
;; => [:a [:map {:keys [x y z]}]]
;; Local Variables:
;; eval: (put-clojure-indent 'conform-case '(1 (:defn)))
;; End:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment