Binding
Macros that combine let and let* with destructuring and flow control.
Macro: -when-let ((var val) &rest body)
If val evaluates to non-nil, bind it to var and execute body.
Note: binding is done according to -let (see -let).
(-when-let (match-index (string-match "d" "abcd")) (+ match-index 2))
⇒ 5(-when-let ((&plist :foo foo) (list :foo "foo")) foo)
⇒ "foo"(-when-let ((&plist :foo foo) (list :bar "bar")) foo)
⇒ nilMacro: -when-let* (vars-vals &rest body)
If all vals evaluate to true, bind them to their corresponding vars and execute body. vars-vals should be a list of (var val) pairs.
Note: binding is done according to -let* (see -let*). vals are evaluated sequentially, and evaluation stops after the first nil val is encountered.
(-when-let* ((x 5) (y 3) (z (+ y 4))) (+ x y z))
⇒ 15(-when-let* ((x 5) (y nil) (z 7)) (+ x y z))
⇒ nilMacro: -if-let ((var val) then &rest else)
If val evaluates to non-nil, bind it to var and do then, otherwise do else.
Note: binding is done according to -let (see -let).
(-if-let (match-index (string-match "d" "abc")) (+ match-index 3) 7)
⇒ 7(--if-let (even? 4) it nil)
⇒ tMacro: -if-let* (vars-vals then &rest else)
If all vals evaluate to true, bind them to their corresponding vars and do then, otherwise do else. vars-vals should be a list of (var val) pairs.
Note: binding is done according to -let* (see -let*). vals are evaluated sequentially, and evaluation stops after the first nil val is encountered.
(-if-let* ((x 5) (y 3) (z 7)) (+ x y z) "foo")
⇒ 15(-if-let* ((x 5) (y nil) (z 7)) (+ x y z) "foo")
⇒ "foo"(-if-let* (((_ _ x) '(nil nil 7))) x)
⇒ 7Macro: -let (varlist &rest body)
Bind variables according to varlist then eval body.
varlist is a list of lists of the form (pattern source). Each pattern is matched against the source "structurally". source is only evaluated once for each pattern. Each pattern is matched recursively, and can therefore contain sub-patterns which are matched against corresponding sub-expressions of source.
All the SOURCEs are evalled before any symbols are bound (i.e. "in parallel").
If varlist only contains one (pattern source) element, you can optionally specify it using a vector and discarding the outer-most parens. Thus
(-let ((pattern source)) …)
becomes
(-let [pattern source] …).
-let (see -let) uses a convention of not binding places (symbols) starting with _ whenever it’s possible. You can use this to skip over entries you don’t care about. However, this is not *always* possible (as a result of implementation) and these symbols might get bound to undefined values.
Following is the overview of supported patterns. Remember that patterns can be matched recursively, so every a, b, aK in the following can be a matching construct and not necessarily a symbol/variable.
Symbol:
a - bind the source to a. This is just like regular let.
Conses and lists:
(a) - bind car of cons/list to a
(a . b) - bind car of cons to a and cdr to b
(a b) - bind car of list to a and cadr to b
(a1 a2 a3 …) - bind 0th car of list to a1, 1st to a2, 2nd to a3...
(a1 a2 a3 … aN . rest) - as above, but bind the Nth cdr to rest.
Vectors:
[a] - bind 0th element of a non-list sequence to a (works with vectors, strings, bit arrays…)
[a1 a2 a3 …] - bind 0th element of non-list sequence to a0, 1st to a1, 2nd to a2, ... If the pattern is shorter than source, the values at places not in pattern are ignored. If the pattern is longer than source, an error is thrown.
[a1 a2 a3 … &rest rest] - as above, but bind the rest of the sequence to rest. This is conceptually the same as improper list matching (a1 a2 … aN . rest)
Key/value stores:
(&plist key0 a0 … keyN aN) - bind value mapped by keyK in the source plist to aK. If the value is not found, aK is nil. Uses plist-get to fetch values.
(&alist key0 a0 … keyN aN) - bind value mapped by keyK in the source alist to aK. If the value is not found, aK is nil. Uses assoc to fetch values.
(&hash key0 a0 … keyN aN) - bind value mapped by keyK in the source hash table to aK. If the value is not found, aK is nil. Uses gethash to fetch values.
Further, special keyword &keys supports "inline" matching of plist-like key-value pairs, similarly to &keys keyword of cl-defun.
(a1 a2 … aN &keys key1 b1 … keyN bK)
This binds n values from the list to a1 … aN, then interprets the cdr as a plist (see key/value matching above).
a shorthand notation for kv-destructuring exists which allows the patterns be optionally left out and derived from the key name in the following fashion:
- a key :foo is converted into foo pattern, - a key ’bar is converted into bar pattern, - a key "baz" is converted into baz pattern.
That is, the entire value under the key is bound to the derived variable without any further destructuring.
This is possible only when the form following the key is not a valid pattern (i.e. not a symbol, a cons cell or a vector). Otherwise the matching proceeds as usual and in case of an invalid spec fails with an error.
Thus the patterns are normalized as follows:
;; derive all the missing patterns (&plist :foo ’bar "baz") => (&plist :foo foo ’bar bar "baz" baz)
;; we can specify some but not others (&plist :foo ’bar explicit-bar) => (&plist :foo foo ’bar explicit-bar)
;; nothing happens, we store :foo in x (&plist :foo x) => (&plist :foo x)
;; nothing happens, we match recursively (&plist :foo (a b c)) => (&plist :foo (a b c))
You can name the source using the syntax symbol &as pattern. This syntax works with lists (proper or improper), vectors and all types of maps.
(list &as a b c) (list 1 2 3)
binds a to 1, b to 2, c to 3 and list to (1 2 3).
Similarly:
(bounds &as beg . end) (cons 1 2)
binds beg to 1, end to 2 and bounds to (1 . 2).
(items &as first . rest) (list 1 2 3)
binds first to 1, rest to (2 3) and items to (1 2 3)
[vect &as _ b c] [1 2 3]
binds b to 2, c to 3 and vect to [1 2 3] (_ avoids binding as usual).
(plist &as &plist :b b) (list :a 1 :b 2 :c 3)
binds b to 2 and plist to (:a 1 :b 2 :c 3). Same for &alist and &hash.
This is especially useful when we want to capture the result of a computation and destructure at the same time. Consider the form (function-returning-complex-structure) returning a list of two vectors with two items each. We want to capture this entire result and pass it to another computation, but at the same time we want to get the second item from each vector. We can achieve it with pattern
(result &as [_ a] [_ b]) (function-returning-complex-structure)
Note: Clojure programmers may know this feature as the ":as binding". The difference is that we put the &as at the front because we need to support improper list binding.
(-let (([a (b c) d] [1 (2 3) 4])) (list a b c d))
⇒ (1 2 3 4)(-let [(a b c . d) (list 1 2 3 4 5 6)] (list a b c d))
⇒ (1 2 3 (4 5 6))(-let [(&plist :foo foo :bar bar) (list :baz 3 :foo 1 :qux 4 :bar 2)] (list foo bar))
⇒ (1 2)Macro: -let* (varlist &rest body)
Bind variables according to varlist then eval body.
varlist is a list of lists of the form (pattern source). Each pattern is matched against the source structurally. source is only evaluated once for each pattern.
Each source can refer to the symbols already bound by this varlist. This is useful if you want to destructure source recursively but also want to name the intermediate structures.
See -let (see -let) for the list of all possible patterns.
(-let* (((a . b) (cons 1 2)) ((c . d) (cons 3 4))) (list a b c d))
⇒ (1 2 3 4)(-let* (((a . b) (cons 1 (cons 2 3))) ((c . d) b)) (list a b c d))
⇒ (1 (2 . 3) 2 3)(-let* (((&alist "foo" foo "bar" bar) (list (cons "foo" 1) (cons "bar" (list 'a 'b 'c)))) ((a b c) bar)) (list foo a b c bar))
⇒ (1 a b c (a b c))Macro: -lambda (match-form &rest body)
Return a lambda which destructures its input as match-form and executes body.
Note that you have to enclose the match-form in a pair of parens, such that:
(-lambda (x) body) (-lambda (x y …) body)
has the usual semantics of lambda. Furthermore, these get translated into normal lambda, so there is no performance penalty.
See -let (see -let) for a description of the destructuring mechanism.
(-map (-lambda ((x y)) (+ x y)) '((1 2) (3 4) (5 6)))
⇒ (3 7 11)(-map (-lambda ([x y]) (+ x y)) '([1 2] [3 4] [5 6]))
⇒ (3 7 11)(funcall (-lambda ((_ . a) (_ . b)) (-concat a b)) '(1 2 3) '(4 5 6))
⇒ (2 3 5 6)Macro: -setq ([match-form val] ...)
Bind each match-form to the value of its val.
match-form destructuring is done according to the rules of -let (see -let).
This macro allows you to bind multiple variables by destructuring the value, so for example:
(-setq (a b) x (&plist :c c) plist)
expands roughly speaking to the following code
(setq a (car x) b (cadr x) c (plist-get plist :c))
Care is taken to only evaluate each val once so that in case of multiple assignments it does not cause unexpected side effects.
(let (a) (-setq a 1) a)
⇒ 1(let (a b) (-setq (a b) (list 1 2)) (list a b))
⇒ (1 2)(let (c) (-setq (&plist :c c) (list :c "c")) c)
⇒ "c"