Skip to content

Instantly share code, notes, and snippets.

@denisidoro
Created September 16, 2017 01:31
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 denisidoro/e9296abb4bbc78bc96b72ff79b6f1037 to your computer and use it in GitHub Desktop.
Save denisidoro/e9296abb4bbc78bc96b72ff79b6f1037 to your computer and use it in GitHub Desktop.
Inline, schema-like macro for clojure.spec
(ns inline-spec-macro.clj
(:require [clojure.spec.alpha :as s]
[orchestra.spec.test :as st]))
(clojure.core/defn ^:private in?
[coll elm]
(some #(= elm %) coll))
(clojure.core/defn ^:private indices
[pred coll]
(keep-indexed #(when (pred %2) %1) coll))
(clojure.core/defn ^:private first-index
[pred coll]
(first (indices pred coll)))
(clojure.core/defn ^:private spec-for-symbol
[schema-indexes fn-args symbol-index]
(let [schema-index (+ symbol-index 2)]
(if (in? schema-indexes schema-index)
(get fn-args schema-index)
`any?)))
(clojure.core/defn ^:private filter-by-index
[coll idxs]
(keep-indexed #(when ((set idxs) %1) %2) coll))
(def ^:private colon? (partial = :-))
(clojure.core/defn ^:private extract-schema
[fn-args args-before-fn-args]
(let [colon-indexes (indices colon? fn-args)
schema-indexes (map inc colon-indexes)
non-symbol-indexes (concat colon-indexes schema-indexes)
all-indexes-set (->> fn-args count range set)
symbol-indexes (-> non-symbol-indexes set (remove all-indexes-set) vec sort)
spec-fn (partial spec-for-symbol schema-indexes fn-args)
symbol-fn (comp keyword (partial get fn-args))
fn-colon-index (first-index colon? args-before-fn-args)
return-spec (if fn-colon-index (get args-before-fn-args (inc fn-colon-index)) any?)]
{:fdef-args (reduce #(conj %1 (symbol-fn %2) (spec-fn %2)) [] symbol-indexes)
:return-spec return-spec
:symbols (filter-by-index fn-args symbol-indexes)}))
(defmacro defn
[name & args]
(let [argv (vec args)
arg-vector-index (first-index coll? argv)
fn-args (get argv arg-vector-index)
args-before-fn-args (if (pos? arg-vector-index) (subvec argv 0 arg-vector-index) [])
schema-data (-> fn-args vec (extract-schema args-before-fn-args))
fn-args-symbols (-> schema-data :symbols vec)
return-spec (:return-spec schema-data)
fdef-args (:fdef-args schema-data)
fn-body (subvec argv (inc arg-vector-index))]
`(do
(clojure.core/defn ~name ~fn-args-symbols ~@fn-body)
(s/fdef ~name
:args (s/cat ~@fdef-args)
:ret ~return-spec))))
;; todo
;; - support for multi-arity functions
;; - support for & args
;; test
(st/instrument)
(defn myfn :- number?
[x :- number?, y, z :- string?]
(inc x))
(defn myfn2 :- string?
[x :- number?, y, z :- string?]
(inc x))
(myfn 1 2 "b") ; => success
; (myfn 1 2 3) ; => error
; (myfn2 1 2 "b") ; => error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment