Skip to content

Instantly share code, notes, and snippets.

@loganmhb
Last active August 25, 2016 19:12
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 loganmhb/bc4af0371ffb763d91356f804f3e5094 to your computer and use it in GitHub Desktop.
Save loganmhb/bc4af0371ffb763d91356f804f3e5094 to your computer and use it in GitHub Desktop.
spec-based for macro
(ns spec-test.core
(:require [clojure.spec :as s])
(:refer-clojure :exclude [for]))
(s/def ::for-bindings
(s/* (s/cat :lval any?
:rval any?
:modifiers (s/* (s/cat :type #{:when :let :while}
:modifier-body any?)))))
(defn for*
([body {:keys [lval rval modifiers]}]
`(let [iter# (fn iter# [s#]
(lazy-seq
(when-let [~lval (first s#)]
(when ~(or (:while modifiers)
true)
(if ~(or (:when modifiers)
true)
(let [~@(:let modifiers)]
(cons ~body
(iter# (rest s#))))
(iter# (rest s#)))))))]
(iter# ~rval)))
([body {:keys [lval rval modifiers]} & bindings]
`(let [iter# (fn iter# [s#]
(lazy-seq
(when-let [~lval (first s#)]
(when ~(or (:while modifiers)
true)
(if ~(or (:when modifiers) true)
(let [~@(:let modifiers)]
(concat ~(apply for* body bindings)
(iter# (rest s#))))
(iter# (rest s#)))))))]
(iter# ~rval))))
(defmacro for [bindings body]
(let [parse-tree (map (fn [binding]
(update binding
:modifiers
#(->> %
(map (juxt :type :modifier-body))
(into {}))))
(s/conform ::for-bindings bindings))]
(apply for* body parse-tree)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment