~technomancy/fennel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH] add nil-safe table lookup operator

Details
Message ID
<20210224163018.225187-1-andreyorst@gmail.com>
DKIM signature
pass
Download raw message
Patch: +62 -1
This patch adds new ?. operator. This operator can be used to do
nested table lookup in a nil-safe manner of -?> and -?>>
operators. This means that if any of subsequent keys do not exist,
this operator will short-circuit to nil value:

(local t {:a {:b [:c :d {:e {:f 42}} :g]}})

(?. t :a :b 3 :e :f) ; 42
(?. t :a :b 5 :e :f) ; nil, because there's only 4 elements in vector

Note that the implementation is not what we've discussed
before. Instead I've chosen mostly the same approach as for -?>>
macro, which does repeated recursion expansion for each key lookup
until amount of keys exhausts. This way there's no iteration, and we
will short-circuit at run-time if any key doesn't exist. It also
behaves the same way compile time wise as ordinary dot operator,
e.g. you can't pass an unpacked table to it.
---
 changelog.md          |  1 +
 reference.md          | 21 +++++++++++++++++++++
 src/fennel/macros.fnl | 15 ++++++++++++++-
 test/macro.fnl        | 26 ++++++++++++++++++++++++++
 4 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/changelog.md b/changelog.md
index 0ee580c..4a2927b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -2,6 +2,7 @@

## 0.8.2 / ???

* Add nil-safe table access operator `?.`
* Add support for guards using `where`/`or` clauses in `match`
* Allow symbols to compare as equal in macros based on name
* Fix a bug where newlines were emitted with backslashes in fennel.view
diff --git a/reference.md b/reference.md
index 295d35d..d5d989e 100644
--- a/reference.md
+++ b/reference.md
@@ -712,6 +712,27 @@ Example:
Note that if the field name is a string known at compile time, you
don't need this and can just use `mytbl.field`.

### Nil-safe `?.` table lookup

Looks up a given key in a table. Multiple arguments will perform
nested lookup. If any of subsequent keys is not present, will
short-circuit to `nil`.

Example:

```fennel
(?. mytbl myfield)
```


Example:

```fennel
(let [t {:a [2 3 4]}] (?. t :a 4 :b)) ; => nil
(let [t {:a [2 3 4 {:b 42}]}] (?. t :a 4 :b)) ; => 42
```


### `:` method call

Looks up a function in a table and calls it with the table as its
diff --git a/src/fennel/macros.fnl b/src/fennel/macros.fnl
index b78dad0..96a54f0 100644
--- a/src/fennel/macros.fnl
+++ b/src/fennel/macros.fnl
@@ -60,6 +60,19 @@ Same as ->> except will short-circuit with nil when it encounters a nil value."
               (-?>> ,el ,(unpack els))
               ,tmp)))))

(fn ?dot [tbl ...]
  "Nil-safe table look up.
Same as . (dot), except will short-circuit with nil when it encounters
a nil value in any of subsequent keys."
  (if (= 0 (select "#" ...))
      tbl
      (let [ks [...]
            k (table.remove ks 1)]
        `(let [tmp# (. ,tbl ,k)]
           (if tmp#
               (?. tmp# ,(unpack ks))
               tmp#)))))

(fn doto* [val ...]
  "Evaluates val and splices it into the first argument of subsequent forms."
  (let [name (gensym)
@@ -422,7 +435,7 @@ Syntax:
        (table.insert match-body else-branch))
    (match* val (unpack match-body))))

{:-> ->* :->> ->>* :-?> -?>* :-?>> -?>>*
{:-> ->* :->> ->>* :-?> -?>* :-?>> -?>>* :?. ?dot
 :doto doto* :when when* :with-open with-open*
 :collect collect* :icollect icollect*
 :partial partial* :lambda lambda*
diff --git a/test/macro.fnl b/test/macro.fnl
index 76e08b6..92d857f 100644
--- a/test/macro.fnl
+++ b/test/macro.fnl
@@ -15,6 +15,31 @@
    (each [code expected (pairs cases)]
      (l.assertEquals (fennel.eval code) expected code))))

(fn test-?. []
  (let [cases {"(?. {:a 1})" {:a 1}
               "(?. {:a 1} :a)" 1
               "(?. {:a 1} :b)" nil
               "(?. [-1 -2])" [-1 -2]
               "(?. [-1 -2] 1)" -1
               "(?. [-1 -2] 3)" nil
               "(?. {:a {:b {:c 3}}} :a :b :c)" 3
               "(?. {:a {:b {:c 3}}} :d :b :c)" nil
               "(?. {:a {:b {:c 3}}} :a :d :c)" nil
               "(?. {:a {:b {:c 3}}} :a :b :d)" nil
               "(?. [-1 [-2 [-3] [-4]]] 2 3 1)" -4
               "(?. [-1 [-2 [-3] [-4]]] 0 3 1)" nil
               "(?. [-1 [-2 [-3] [-4]]] 2 5 1)" nil
               "(?. [-1 [-2 [-3] [-4]]] 2 3 2)" nil
               "(?. {:a [{} {:b {:c 4}}]} :a 2 :b :c)" 4
               "(?. {:a [{} {:b {:c 4}}]} :a 1 :b :c)" nil
               "(?. {:a [{} {:b {:c 4}}]} :a 3 :b :c)" nil
               "(?. {:a [[{:b {:c 5}}]]} :a 1 :b :c)" nil
               "(?. {:a [[{:b {:c 5}}]]} :a 1 1 :b :c)" 5
               "(local t {:a [[{:b {:c 5}}]]}) (?. t :a 1 :b :c)" nil
               "(local t {:a [[{:b {:c 5}}]]}) (?. t :a 1 1 :b :c)" 5}]
    (each [code expected (pairs cases)]
      (l.assertEquals (fennel.eval code) expected code))))

(fn test-comprehensions []
  (let [cases {"(collect [k v (pairs {:apple :red :orange :orange})]
                  (values (.. :color- v) (.. :fruit- k)))"
@@ -204,6 +229,7 @@
      (l.assertEquals (fennel.eval code {:correlate true}) expected code))))

{: test-arrows
 : test-?.
 : test-comprehensions
 : test-import-macros
 : test-require-macros
-- 
2.29.2
Details
Message ID
<87czwn1suq.fsf@whirlwind>
In-Reply-To
<20210224163018.225187-1-andreyorst@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Andrey Listopadov <andreyorst@gmail.com> writes:

> This patch adds new ?. operator. This operator can be used to do
> nested table lookup in a nil-safe manner of -?> and -?>>
> operators. This means that if any of subsequent keys do not exist,
> this operator will short-circuit to nil value:

Thanks! Applied and pushed, with a little tidying up of the
implementation. This will come in handy.

-Phil
Reply to thread Export thread (mbox)