From 2db56f59aca3bc85cc35944480dcbdf1a5607283 Mon Sep 17 00:00:00 2001 From: Matus Goljer Date: Sun, 3 May 2015 18:10:07 +0200 Subject: [PATCH] [-let] Add support for &as bindings (#115) --- README.md | 40 +++++++++++++++++++++++++++++++++ dash.el | 59 +++++++++++++++++++++++++++++++++++++++++++++++-- dash.texi | 40 +++++++++++++++++++++++++++++++++ dev/examples.el | 23 ++++++++++++++++++- 4 files changed, 159 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 164b7c1f..584fcf75 100644 --- a/README.md +++ b/README.md @@ -1915,6 +1915,46 @@ plist-like key-value pairs, similarly to &keys keyword of This binds `n` values from the list to a1 ... aN, then interprets the cdr as a plist (see key/value matching above). +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. + ```el (-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)) diff --git a/dash.el b/dash.el index d2c2b4d5..2c96cf9c 100644 --- a/dash.el +++ b/dash.el @@ -1452,13 +1452,28 @@ Key-value stores are disambiguated by placing a token &plist, (dash--match-symbol match-form source)) ((consp match-form) (cond + ;; Handle the "x &as" bindings first. + ((and (consp (cdr match-form)) + (symbolp (car match-form)) + (eq '&as (cadr match-form))) + (let ((s (car match-form))) + (cons (list s source) + (dash--match (cddr match-form) s)))) ((memq (car match-form) '(&plist &alist &hash)) (dash--match-kv match-form source)) ((eq (car match-form) '&keys) (dash--match-kv (cons '&plist (cdr match-form)) source)) (t (dash--match-cons match-form source)))) ((vectorp match-form) - (dash--match-vector match-form source)))) + ;; We support the &as binding in vectors too + (cond + ((and (> (length match-form) 2) + (symbolp (aref match-form 0)) + (eq '&as (aref match-form 1))) + (let ((s (aref match-form 0))) + (cons (list s source) + (dash--match (dash--vector-tail match-form 2) s)))) + (t (dash--match-vector match-form source)))))) (defmacro -let* (varlist &rest body) "Bind variables according to VARLIST then eval BODY. @@ -1565,7 +1580,47 @@ plist-like key-value pairs, similarly to &keys keyword of (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)." +the cdr as a plist (see key/value matching above). + +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." (declare (debug ([&or (&rest (sexp form)) (vector [&rest [sexp form]])] body)) diff --git a/dash.texi b/dash.texi index a6f5a1dd..67bfee1d 100644 --- a/dash.texi +++ b/dash.texi @@ -2931,6 +2931,46 @@ plist-like key-value pairs, similarly to &keys keyword of This binds @var{n} values from the list to a1 ... aN, then interprets the cdr as a plist (see key/value matching above). +You can name the source using the syntax @var{symbol} &as @var{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 @var{a} to 1, @var{b} to 2, @var{c} to 3 and @var{list} to (1 2 3). + +Similarly: + + (bounds &as beg . end) (cons 1 2) + +binds @var{beg} to 1, @var{end} to 2 and @var{bounds} to (1 . 2). + + (items &as first . rest) (list 1 2 3) + +binds @var{first} to 1, @var{rest} to (2 3) and @var{items} to (1 2 3) + + [vect &as _ b c] [1 2 3] + +binds @var{b} to 2, @var{c} to 3 and @var{vect} to [1 2 3] (_ avoids binding as usual). + + (plist &as &plist :b b) (list :a 1 :b 2 :c 3) + +binds @var{b} to 2 and @var{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. + @example @group (-let (([a (b c) d] [1 (2 3) 4])) (list a b c d)) diff --git a/dev/examples.el b/dev/examples.el index b32d5c5a..21e8ba59 100644 --- a/dev/examples.el +++ b/dev/examples.el @@ -791,10 +791,12 @@ new list." (-let [[a b c] "abcdef"] (list a b c)) => '(?a ?b ?c) (-let [[a (b [c]) d] [1 (2 [3 4]) 5 6]] (list a b c d)) => '(1 2 3 5) (-let [(a b c d) (list 1 2 3 4 5 6)] (list a b c d)) => '(1 2 3 4) + (-let [([a b]) (list (vector 1 2 3))] (list a b)) => '(1 2) ;; d is bound to nil. I don't think we want to error in such a case. ;; After all (car nil) => nil (-let [(a b c d) (list 1 2 3)] (list a b c d)) => '(1 2 3 nil) (-let [[a b c] [1 2 3 4]] (list a b c)) => '(1 2 3) + (-let [[a] [1 2 3 4]] a) => 1 (-let [[a b &rest c] "abcdef"] (list a b c)) => '(?a ?b "cdef") (-let [[a b &rest c] [1 2 3 4 5 6]] (list a b c)) => '(1 2 [3 4 5 6]) (-let [[a b &rest [c d]] [1 2 3 4 5 6]] (list a b c d)) => '(1 2 3 4) @@ -872,7 +874,26 @@ new list." (-let [[(a _ b)] (vector (list 1 2 3 4))] (list a b)) => '(1 3) (-let [(&plist 'a a) (list 'a 1 'b 2)] a) => 1 (-let [(&plist 'a [a b]) (list 'a [1 2] 'b 3)] (list a b)) => '(1 2) - (-let [(&plist 'a [a b] 'c c) (list 'a [1 2] 'c 3)] (list a b c)) => '(1 2 3)) + (-let [(&plist 'a [a b] 'c c) (list 'a [1 2] 'c 3)] (list a b c)) => '(1 2 3) + ;; test the &as form + (-let (((items &as first . rest) (list 1 2 3))) (list first rest items)) => '(1 (2 3) (1 2 3)) + (-let [(all &as [vect &as a b] bar) (list [1 2] 3)] (list a b bar vect all)) => '(1 2 3 [1 2] ([1 2] 3)) + (-let [(all &as (list &as a b) bar) (list (list 1 2) 3)] (list a b bar list all)) => '(1 2 3 (1 2) ((1 2) 3)) + (-let [(x &as [a b]) (list (vector 1 2 3))] (list a b x)) => '(1 2 ([1 2 3])) + (-let [(result &as [_ a] [_ b]) (list [1 2] [3 4])] (list a b result)) => '(2 4 ([1 2] [3 4])) + (-let [(result &as [fst &as _ a] [snd &as _ b]) (list [1 2] [3 4])] (list a b fst snd result)) => '(2 4 [1 2] [3 4] ([1 2] [3 4])) + (-let [[x &as a b &rest r] (vector 1 2 3)] (list a b r x)) => '(1 2 [3] [1 2 3]) + (-let [[x &as a] (vector 1 2 3)] (list a x)) => '(1 [1 2 3]) + (-let [[x &as _ _ a] (vector 1 2 3)] (list a x)) => '(3 [1 2 3]) + (-let [[x &as _ _ a] (vector 1 2 (list 3 4))] (list a x)) => '((3 4) [1 2 (3 4)]) + (-let [[x &as _ _ (a b)] (vector 1 2 (list 3 4))] (list a b x)) => '(3 4 [1 2 (3 4)]) + (-let [(b &as beg . end) (cons 1 2)] (list beg end b)) => '(1 2 (1 . 2)) + (-let [(plist &as &plist :a a :b b) (list :a 1 :b 2)] (list a b plist)) => '(1 2 (:a 1 :b 2)) + (-let [(alist &as &alist :a a :b b) (list (cons :a 1) (cons :b 2))] (list a b alist)) => '(1 2 ((:a . 1) (:b . 2))) + (-let [(list &as _ _ _ a _ _ _ b _ _ _ c) (list 1 2 3 4 5 6 7 8 9 10 11 12)] (list a b c list)) => '(4 8 12 (1 2 3 4 5 6 7 8 9 10 11 12)) + (-let (((x &as a b) (list 1 2)) + ((y &as c d) (list 3 4))) + (list a b c d x y)) => '(1 2 3 4 (1 2) (3 4))) (defexamples -let* (-let* (((a . b) (cons 1 2))