Created
September 16, 2017 01:31
-
-
Save denisidoro/e9296abb4bbc78bc96b72ff79b6f1037 to your computer and use it in GitHub Desktop.
Inline, schema-like macro for clojure.spec
This file contains hidden or 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
(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