Skip to content

Instantly share code, notes, and snippets.

@mrBliss
Created September 24, 2010 16:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrBliss/595634 to your computer and use it in GitHub Desktop.
Save mrBliss/595634 to your computer and use it in GitHub Desktop.
(defn replace-symbols
"Given a map of replacement pairs and a form, returns a (nested)
form with any symbol = a key in smap replaced with the corresponding
val in smap."
[smap form]
(if (sequential? form)
(map (partial replace-symbols smap) form)
(get smap form form)))
(defmacro lazy-let
"A lazy version of let. It doesn't evaluate any bindings until they
are needed. No more nested lets and it-lets when you have many
conditional bindings."
[bindings & body]
(let [locals (take-nth 2 bindings)
local-forms (take-nth 2 (rest bindings))
smap (zipmap locals (map (fn [local] `(first ~local)) locals))
bindings (->> (map (fn [lf]
`(lazy-seq (list ~(replace-symbols smap lf))))
local-forms) (interleave locals) vec)
body (replace-symbols smap body)]
(conj body bindings 'let)))
;; Returns yes, because x and y can be evaluated
(let [s [[1 2] 3]
x (nth s 0)
y (inc (second x))]
(if (= 2 (count s))
(if (vector? x)
(if (number? y)
"yes"
"fail3")
"fail2")
"fail1"))
;; Throws an exception because we cannot increment nil
(let [s nil
x (nth s 0)
y (inc (second x))]
(if (= 2 (count s))
(if (vector? x)
(if (number? y)
"yes"
"fail3")
"fail2")
"fail1"))
;; If we use lazy-let instead of let, the locals aren't evaluated
;; until they are needed. The first if fails, since the count of s (=
;; nil) is 0, so there is no need to evaluate x and y.
(lazy-let [s nil
x (nth s 0)
y (inc (second x))]
(if (= 2 (count s))
(if (vector? x)
(if (number? y)
"yes"
"fail3")
"fail2")
"fail1"))
;; This is how the form above will be macroexpanded
;; (without the clojure.core/ qualifiers)
(let [s (lazy-seq (list nil))
x (lazy-seq (list (nth s 0)))
y (lazy-seq (list (inc (second x))))]
(if (= 2 (count (first s)))
(if (vector? (first x))
(if (number? (first y))
"yes"
"fail3")
"fail2")
"fail1"))
Copy link

ghost commented Feb 1, 2019

Here is a version based on delays that supports any kind of bound expr (not just sequences).
It works by wrapping bound exprs into (delay ...) then overriding the corresponding binding syms with symbol-macrolet so that they expand to (deref ...).

(require '[clojure.tools.macro :refer [symbol-macrolet]])

(defmacro lay [[sym expr & more-bindings] & body]
  (let [delay-sym (gensym (str "laid-" sym "-"))]
    `(let [~delay-sym (delay ~expr)]
       (symbol-macrolet [~sym (deref ~delay-sym)]
         ~@(if (empty? more-bindings)
             body
             `[(lay ~more-bindings ~@body)])))))

More: https://gist.github.com/TristeFigure/09c1c6309b70ccdb5ae164606e3876e9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment