Fork me on GitHub
#meander
<
2022-03-25
>
markaddleman01:03:07

I use the following idiom:

(m/rewrite [1 2 3]
   [(m/and !a1 !a2) ...]
   {:a1 [!a1 ...] :a2 [!a2 ...]})

🙌 2
enn17:03:59

How can I bind nested values without failing if a parent is absent?

(m/match {:a 1}
  {:b {:c ?c}}
  ?c)
Execution error (ExceptionInfo) at clubhouse.resolvers.datalayer/eval641426 (REPL:203).
non exhaustive pattern match

Jimmy Miller17:03:14

You can always add an extra case like _ nil or ?x ?x

Jimmy Miller17:03:20

Or use m/or at the variable

enn17:03:59

in my real use case, I still want to bind a bunch of other things at other levels, something like

(m/match {:a 1 :c {:foo 3}}
  {:a ?a
   :b ?b
   :c {:foo ?c}
   :d {:bar ?d}}
  [?a ?b ?c ?d])

enn17:03:07

I thought I could do :d {:bar (m/or ?d (m/let [?d 1]))}, but that still gives me non-exhaustive pattern match. Is that not the right level for the m/or?

enn17:03:05

ah, if I move m/or up a level it works

Jimmy Miller17:03:04

Yeah at that level there would have to be a map at :d. The key couldn't be missing. Moving it up one level allows that.

enn20:03:19

When matching a map, how can I distinguish between a key’s absence and its having the value nil ?

noprompt22:03:53

This is one of those areas where I don't have a great answer. When I went to put together the map pattern matching semantics I based them on the observations that 1) find use is uncommon, and 2) most folks are concerned with observable equivalence and not semantic equivalence when it comes to get . The subtle difference between the following two expressions do not weight heavily on most. Its a pragmatic trade off I suppose.

(get {} :m)
(get {:m nil} :m)
Considering these observations, I chose to have the compiler use get instead of find and favor the common map access patterns. The nice thing is that it does help reduce the number of clauses you might write for map pattern matching. The drawback is exactly this case. 🙂

noprompt22:03:10

If it is cheap, you could preprocess the map to have "semantic nils" i.e. ::blank / ::undefined and then pattern match on those. So

user=> (def blank (fnil identity ::blank))
#'user/blank
user=> (blank (get {:m nil} :m ::undefined))
:user/blank
user=> (blank (get {} :m ::undefined))
:user/undefined
user=> (blank (get {:m 10} :m ::undefined))
10
(`blank` is probably a terrible name but you get the idea.)

enn23:03:43

Interesting, that makes sense. It’s good to know I’m not missing an obvious way to do this.

enn23:03:05

Perhaps regrettably, we’ve got an API contract for updates where {:foo nil} means “unset the :foo property,” while the absence of :foo means “leave the :foo property as it is.”

noprompt23:03:10

Ha! Yep. We have a situation like that with one of our APIs too. {:foo nil} is fine but if :foo is missing the API yells at you. 😐