Wondering how best to deal w/ matching on nested map data where keys are sometimes missing. This is fine:
(require '[meander.epsilon :as meander])
(meander/match {:foo 1}
{:foo ?foo
:bar ?bar} [?foo ?bar])
;; => [1 nil]
But adding a layer breaks it
(meander/match {:foo 1}
{:foo ?foo
:bar {:baz ?baz}} [?foo ?baz])
;; => non exhaustive pattern match
I’d like the result to be [1 nil] again.
Using [meander/epsilon "0.0.602"].@noprompt Unfortunately optional doesn’t seem to nest:
(m/match {:foo {:bar "bar"
:baz nil}}
{:foo (optional {:bar ?bar
:baz (optional {:quux ?quux})})} ?quux)
;; => 1. Caused by clojure.lang.ExceptionInfo
substition not supported
{:form (optional {:quux ?quux})}
Also running into performance issue when using a lot of optional : https://github.com/noprompt/meander/issues/234
Changing the definition of optional to the following allows nesting:
(defsyntax optional [p]
(if (m/match-syntax? &env)
(let [var-syms (map :symbol (m-syntax/variables (m-syntax/parse p)))]
`(m/or ~p (m/and nil ~@var-syms)))
&form))
This seems to be the [pattern](https://github.com/noprompt/meander/blob/epsilon/doc/defsyntax.md) used by most defsyntax constructs.I guess the problem is that nil doesn’t match a map.
(meander/match nil
{:foo ?foo} ?foo)
;; => non exhaustive pattern match
Would be nice to have something like:
(meander/match {:foo 1}
{:foo ?foo
:bar (meander/optional {:baz ?baz})} [?foo ?baz])
;; => [1 nil]I see that there’s a recipe for a “Maybe pattern”: https://github.com/noprompt/meander/blob/epsilon/doc/cookbook.md#the-maybe-pattern
But in my case if there are many keys beyond just :baz, all of them have to be specified in the (m/and nil ...) clause. Which is fine…but rather tedious and inelegant.
Yes. It is not elegant. Another solution, which is also not elegant, is to use cata.
(m/match {:foo 1}
{:foo ?foo
:bar (m/cata ?x)} [?foo ?x]
{:baz (m/some ?baz)} ?baz
_ nil)
This returns [1 nil]
This also returns [1 nil]:
(m/match {:foo 1}
{:foo ?foo
:bar (m/or {:baz ?baz}
?baz)}
[?foo ?baz])
I think the last solution works if there’s only one expected key in the :bar map, but in my case there could be many…
(The (m/and nil ,,,) can be dropped because either you have a map or you don't. If you do have a map, then ?baz will bind to whatever is there. If you don't have a map then ?baz will be bound to that.
If there are many optional keys, then it will be a lot of m/or. FWIW this is actually the price of maps with optional keys.
In vanilla Clojure there's (if-some [e (find m k)] ,,,) , (get-in m [k1 k2] not-found), (or (get m k) not-found), etc.
This is one of those things where I'm open to suggestions about how it could be more elegant.
Dunno about the practicality of implementation but I like this syntax: https://clojurians.slack.com/archives/CFFTD7R6Z/p1652759046882109?thread_ts=1652758301.794729&cid=CFFTD7R6Z
The meander/optional could just be a rewrite of the cookbook’s solution, where it collects all the binding vars & sticks them in an (m/and nil …).
[?foo (m/optional [:baz ?baz]) ?baz]
What happens in a case like this?I’d think it’d be equivalent to
[?foo (m/or (m/and nil ?baz) [:baz ?baz]) ?baz](require '[meander.epsilon :as m])
(require '[meander.syntax.epsilon :as m.syntax])
(m/defsyntax optional [p]
(if (m/match-syntax? &env)
(let [var-syms (map :symbol (m.syntax/variables (m.syntax/parse p)))]
`(m/or ~p (m/and nil ~@var-syms)))
(throw (ex-info "substition not supported" {:form &form}))))
(m/match {:foo 1}
{:foo ?foo
:bar (optional {:baz ?baz})}
[?foo ?baz])
;; =>
[1 nil]This is approximately what I would do. Maybe some more thinking on it. But you can use this in your project if it helps you and irons out some of that ugliness. 🙂
It’s hardcoded to :baz ?baz though? Or should that be replaced w/ something?
Yes, it should be replaced with p. (I fixed the code above).
I'm late to the party, but this may be helpful https://github.com/noprompt/meander/pull/144/files?short_path=e88fbb5#diff-e88fbb5c0cd00327380696124537df29287c3c89a5a2395a0757548eb5b5d2f3
Just ran into the strangest bug, but only on [meander/epsilon "0.0.602"] (the version I was on).
W/ a project containing nothing but:
(ns meander-test.core
(:require [meander.epsilon :as m]))
(defn extract
[m]
(m/match m
{:letters (m/or
(m/and nil
?a
?b
?c
?d
?e
?f
?g
?h
?i
?j
?k
?l
?m
?n)
{:a ?a
:b ?b
:c ?c
:d ?d
:e ?e
:f ?f
:g ?g
:h ?h
:i ?i
:j ?j
:k ?k
:l ?l
:m ?m
:n ?n})}
{:a ?a}))
cider-jack-in hangs & causes CPU usage to go crazy. Doesn’t happen w/ 0.0.626.But, if I comment out one element in the and, it works fine again! 🤯
(ns meander-test.core
(:require [meander.epsilon :as m]))
(defn extract
[m]
(m/match m
{:letters (m/or
(m/and nil
?a
?b
?c
?d
?e
?f
?g
?h
?i
?j
?k
?l
?m
#_?n)
{:a ?a
:b ?b
:c ?c
:d ?d
:e ?e
:f ?f
:g ?g
:h ?h
:i ?i
:j ?j
:k ?k
:l ?l
:m ?m
:n ?n})}
{:a ?a}))No 💡 why it does this but boy was this one a doozy to 🔨 🐛 !