Add smarter key destructuring

master
Matus Goljer 8 years ago
parent 0505f5d2b4
commit e52909f098
  1. 33
      README.md
  2. 78
      dash.el
  3. 109
      dash.info
  4. 33
      dash.texi
  5. 29
      dev/examples.el

@ -2324,14 +2324,17 @@ Key/value stores:
(&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the (&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the
`source` plist to aK. If the `source` plist to aK. If the
value is not found, aK is nil. 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 (&alist key0 a0 ... keyN aN) - bind value mapped by keyK in the
`source` alist to aK. If the `source` alist to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses `assoc` to fetch values.
(&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the (&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the
`source` hash table to aK. If the `source` hash table to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses `gethash` to fetch values.
Further, special keyword &keys supports "inline" matching of Further, special keyword &keys supports "inline" matching of
plist-like key-value pairs, similarly to &keys keyword of plist-like key-value pairs, similarly to &keys keyword of
@ -2342,6 +2345,36 @@ plist-like key-value pairs, similarly to &keys keyword of
This binds `n` values from the list to a1 ... aN, then interprets 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).
`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`. You can name the source using the syntax `symbol` &as `pattern`.
This syntax works with lists (proper or improper), vectors and This syntax works with lists (proper or improper), vectors and
all types of maps. all types of maps.

@ -1618,7 +1618,7 @@ SOURCE is a proper or improper list."
(cond (cond
((and (symbolp (car match-form)) ((and (symbolp (car match-form))
(memq (car match-form) '(&keys &plist &alist &hash))) (memq (car match-form) '(&keys &plist &alist &hash)))
(dash--match-kv match-form (dash--match-cons-get-cdr skip-cdr source))) (dash--match-kv (dash--match-kv-normalize-match-form match-form) (dash--match-cons-get-cdr skip-cdr source)))
((dash--match-ignore-place-p (car match-form)) ((dash--match-ignore-place-p (car match-form))
(dash--match-cons-1 (cdr match-form) source (dash--match-cons-1 (cdr match-form) source
(plist-put props :skip-cdr (1+ skip-cdr)))) (plist-put props :skip-cdr (1+ skip-cdr))))
@ -1702,6 +1702,47 @@ is discarded."
(setq i (1+ i)))) (setq i (1+ i))))
(-flatten-n 1 (nreverse re)))) (-flatten-n 1 (nreverse re))))
(defun dash--match-kv-normalize-match-form (pattern)
"Normalize kv PATTERN.
This method normalizes PATTERN to the format expected by
`dash--match-kv'. See `-let' for the specification."
(let ((normalized (list (car pattern)))
(skip nil)
(fill-placeholder (make-symbol "--dash-fill-placeholder--")))
(-each (apply '-zip (-pad fill-placeholder (cdr pattern) (cddr pattern)))
(lambda (pair)
(let ((current (car pair))
(next (cdr pair)))
(if skip
(setq skip nil)
(if (or (eq fill-placeholder next)
(not (or (and (symbolp next)
(not (keywordp next))
(not (eq next t))
(not (eq next nil)))
(and (consp next)
(not (eq (car next) 'quote)))
(vectorp next))))
(progn
(cond
((keywordp current)
(push current normalized)
(push (intern (substring (symbol-name current) 1)) normalized))
((stringp current)
(push current normalized)
(push (intern current) normalized))
((and (consp current)
(eq (car current) 'quote))
(push current normalized)
(push (cadr current) normalized))
(t (error "-let: found key `%s' in kv destructuring but its pattern `%s' is invalid and can not be derived from the key" current next)))
(setq skip nil))
(push current normalized)
(push next normalized)
(setq skip t))))))
(nreverse normalized)))
(defun dash--match-kv (match-form source) (defun dash--match-kv (match-form source)
"Setup a kv matching environment and call the real matcher. "Setup a kv matching environment and call the real matcher.
@ -1774,7 +1815,7 @@ Key-value stores are disambiguated by placing a token &plist,
(cons (list s source) (cons (list s source)
(dash--match (cddr match-form) s)))) (dash--match (cddr match-form) s))))
((memq (car match-form) '(&keys &plist &alist &hash)) ((memq (car match-form) '(&keys &plist &alist &hash))
(dash--match-kv match-form source)) (dash--match-kv (dash--match-kv-normalize-match-form match-form) source))
(t (dash--match-cons match-form source)))) (t (dash--match-cons match-form source))))
((vectorp match-form) ((vectorp match-form)
;; We support the &as binding in vectors too ;; We support the &as binding in vectors too
@ -1876,14 +1917,17 @@ Key/value stores:
(&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the (&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the
SOURCE plist to aK. If the SOURCE plist to aK. If the
value is not found, aK is nil. 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 (&alist key0 a0 ... keyN aN) - bind value mapped by keyK in the
SOURCE alist to aK. If the SOURCE alist to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses `assoc' to fetch values.
(&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the (&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the
SOURCE hash table to aK. If the SOURCE hash table to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses `gethash' to fetch values.
Further, special keyword &keys supports \"inline\" matching of Further, special keyword &keys supports \"inline\" matching of
plist-like key-value pairs, similarly to &keys keyword of plist-like key-value pairs, similarly to &keys keyword of
@ -1894,6 +1938,36 @@ plist-like key-value pairs, similarly to &keys keyword of
This binds N values from the list to a1 ... aN, then interprets 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).
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. You can name the source using the syntax SYMBOL &as PATTERN.
This syntax works with lists (proper or improper), vectors and This syntax works with lists (proper or improper), vectors and
all types of maps. all types of maps.

@ -2245,13 +2245,16 @@ control.
Key/value stores: Key/value stores:
(&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the (&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. 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 (&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. 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 (&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. 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 Further, special keyword &keys supports "inline" matching of
plist-like key-value pairs, similarly to &keys keyword of plist-like key-value pairs, similarly to &keys keyword of
@ -2262,6 +2265,36 @@ control.
This binds N values from the list to a1 ... aN, then interprets 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).
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. You can name the source using the syntax SYMBOL &as PATTERN.
This syntax works with lists (proper or improper), vectors and This syntax works with lists (proper or improper), vectors and
all types of maps. all types of maps.
@ -2976,13 +3009,13 @@ Index
* -juxt: Function combinators. * -juxt: Function combinators.
(line 31) (line 31)
* -keep: List to list. (line 8) * -keep: List to list. (line 8)
* -lambda: Binding. (line 220) * -lambda: Binding. (line 253)
* -last: Other list operations. * -last: Other list operations.
(line 232) (line 232)
* -last-item: Other list operations. * -last-item: Other list operations.
(line 301) (line 301)
* -let: Binding. (line 66) * -let: Binding. (line 66)
* -let*: Binding. (line 200) * -let*: Binding. (line 233)
* -list: Other list operations. * -list: Other list operations.
(line 334) (line 334)
* -map: Maps. (line 10) * -map: Maps. (line 10)
@ -3056,7 +3089,7 @@ Index
* -select-column: Sublist selection. (line 199) * -select-column: Sublist selection. (line 199)
* -select-columns: Sublist selection. (line 180) * -select-columns: Sublist selection. (line 180)
* -separate: Partitioning. (line 63) * -separate: Partitioning. (line 63)
* -setq: Binding. (line 243) * -setq: Binding. (line 276)
* -slice: Sublist selection. (line 86) * -slice: Sublist selection. (line 86)
* -snoc: Other list operations. * -snoc: Other list operations.
(line 42) (line 42)
@ -3271,39 +3304,39 @@ Ref: -when-let*71822
Ref: -if-let72350 Ref: -if-let72350
Ref: -if-let*72745 Ref: -if-let*72745
Ref: -let73362 Ref: -let73362
Ref: -let*78155 Ref: -let*79450
Ref: -lambda79096 Ref: -lambda80391
Ref: -setq79898 Ref: -setq81193
Node: Side-effects80714 Node: Side-effects82009
Ref: -each80908 Ref: -each82203
Ref: -each-while81315 Ref: -each-while82610
Ref: -each-indexed81675 Ref: -each-indexed82970
Ref: -dotimes82193 Ref: -dotimes83488
Ref: -doto82496 Ref: -doto83791
Node: Destructive operations82923 Node: Destructive operations84218
Ref: !cons83096 Ref: !cons84391
Ref: !cdr83302 Ref: !cdr84597
Node: Function combinators83497 Node: Function combinators84792
Ref: -partial83771 Ref: -partial85066
Ref: -rpartial84166 Ref: -rpartial85461
Ref: -juxt84568 Ref: -juxt85863
Ref: -compose85000 Ref: -compose86295
Ref: -applify85558 Ref: -applify86853
Ref: -on86005 Ref: -on87300
Ref: -flip86528 Ref: -flip87823
Ref: -const86840 Ref: -const88135
Ref: -cut87184 Ref: -cut88479
Ref: -not87670 Ref: -not88965
Ref: -orfn87980 Ref: -orfn89275
Ref: -andfn88414 Ref: -andfn89709
Ref: -iteratefn88909 Ref: -iteratefn90204
Ref: -fixfn89612 Ref: -fixfn90907
Ref: -prodfn91181 Ref: -prodfn92476
Node: Development92247 Node: Development93542
Node: Contribute92596 Node: Contribute93891
Node: Changes93344 Node: Changes94639
Node: Contributors96343 Node: Contributors97638
Node: Index97967 Node: Index99262
 
End Tag Table End Tag Table

@ -3564,14 +3564,17 @@ Key/value stores:
(&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the (&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the
@var{source} plist to aK. If the @var{source} plist to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses @code{plist-get} to fetch values.
(&alist key0 a0 ... keyN aN) - bind value mapped by keyK in the (&alist key0 a0 ... keyN aN) - bind value mapped by keyK in the
@var{source} alist to aK. If the @var{source} alist to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses @code{assoc} to fetch values.
(&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the (&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the
@var{source} hash table to aK. If the @var{source} hash table to aK. If the
value is not found, aK is nil. value is not found, aK is nil.
Uses @code{gethash} to fetch values.
Further, special keyword &keys supports "inline" matching of Further, special keyword &keys supports "inline" matching of
plist-like key-value pairs, similarly to &keys keyword of plist-like key-value pairs, similarly to &keys keyword of
@ -3582,6 +3585,36 @@ plist-like key-value pairs, similarly to &keys keyword of
This binds @var{n} values from the list to a1 ... aN, then interprets This binds @var{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).
@var{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 @code{foo} pattern,
- a key 'bar is converted into @code{bar} pattern,
- a key "baz" is converted into @code{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 @var{symbol} &as @var{pattern}. You can name the source using the syntax @var{symbol} &as @var{pattern}.
This syntax works with lists (proper or improper), vectors and This syntax works with lists (proper or improper), vectors and
all types of maps. all types of maps.

@ -1100,6 +1100,35 @@ new list."
(-let (((_ _ . (&alist 'a a 'c c)) (list 1 2 '(a . b) '(e . f) '(g . h) '(c . d)))) (list a c)) => '(b d) (-let (((_ _ . (&alist 'a a 'c c)) (list 1 2 '(a . b) '(e . f) '(g . h) '(c . d)))) (list a c)) => '(b d)
(-let (((x y (&alist 'a a 'c c)) (list 1 2 '((a . b) (e . f) (g . h) (c . d))))) (list x y a c)) => '(1 2 b d) (-let (((x y (&alist 'a a 'c c)) (list 1 2 '((a . b) (e . f) (g . h) (c . d))))) (list x y a c)) => '(1 2 b d)
(-let (((_ _ . ((&alist 'a a 'c c))) (list 1 2 '((a . b) (e . f) (g . h) (c . d))))) (list a c)) => '(b d) (-let (((_ _ . ((&alist 'a a 'c c))) (list 1 2 '((a . b) (e . f) (g . h) (c . d))))) (list a c)) => '(b d)
;; auto-derived match forms for kv destructuring
;;; test that we normalize all the supported kv stores
(-let (((&plist :foo :bar) (list :foo 1 :bar 2))) (list foo bar)) => '(1 2)
(-let (((&alist :foo :bar) (list (cons :foo 1) (cons :bar 2)))) (list foo bar)) => '(1 2)
(let ((hash (make-hash-table)))
(puthash :foo 1 hash)
(puthash :bar 2 hash)
(-let (((&hash :foo :bar) hash)) (list foo bar))) => '(1 2)
(-let (((_ &keys :foo :bar) (list 'ignored :foo 1 :bar 2))) (list foo bar)) => '(1 2)
;;; go over all the variations of match-form derivation
(-let (((&plist :foo foo :bar) (list :foo 1 :bar 2))) (list foo bar)) => '(1 2)
(-let (((&plist :foo foo :bar bar) (list :foo 1 :bar 2))) (list foo bar)) => '(1 2)
(-let (((&plist :foo x :bar y) (list :foo 1 :bar 2))) (list x y)) => '(1 2)
(-let (((&plist :foo (x) :bar [y]) (list :foo (list 1) :bar (vector 2)))) (list x y)) => '(1 2)
(-let (((&plist 'foo 'bar) (list 'foo 1 'bar 2))) (list foo bar)) => '(1 2)
(-let (((&plist 'foo foo 'bar) (list 'foo 1 'bar 2))) (list foo bar)) => '(1 2)
(-let (((&plist 'foo foo 'bar bar) (list 'foo 1 'bar 2))) (list foo bar)) => '(1 2)
(-let (((&plist 'foo x 'bar y) (list 'foo 1 'bar 2))) (list x y)) => '(1 2)
(-let (((&alist "foo" "bar") (list (cons "foo" 1) (cons "bar" 2)))) (list foo bar)) => '(1 2)
(-let (((&alist "foo" x "bar") (list (cons "foo" 1) (cons "bar" 2)))) (list x bar)) => '(1 2)
(-let (((&alist "foo" x "bar" y) (list (cons "foo" 1) (cons "bar" 2)))) (list x y)) => '(1 2)
(-let (((&alist :a 'b "c") (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&alist 'b :a "c") (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&alist 'b "c" :a) (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&alist "c" 'b :a) (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&alist "c" :a 'b) (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&alist :a "c" 'b) (list (cons :a 1) (cons 'b 2) (cons "c" 3)))) (list a b c)) => '(1 2 3)
(-let (((&plist 'foo 1) (list 'foo 'bar))) (list foo)) !!> error
(-let (((&plist foo :bar) (list :foo :bar))) (list foo)) !!> error
;; test the &as form ;; test the &as form
(-let (((items &as first . rest) (list 1 2 3))) (list first rest items)) => '(1 (2 3) (1 2 3)) (-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 [vect &as a b] bar) (list [1 2] 3)] (list a b bar vect all)) => '(1 2 3 [1 2] ([1 2] 3))

Loading…
Cancel
Save