This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-05-17
Channels
- # announcements (2)
- # asami (3)
- # babashka (30)
- # beginners (23)
- # calva (28)
- # cider (3)
- # clj-kondo (16)
- # clj-on-windows (7)
- # cljs-dev (7)
- # clojure (47)
- # clojure-austin (3)
- # clojure-europe (25)
- # clojure-gamedev (3)
- # clojure-greece (1)
- # clojure-nl (1)
- # clojure-uk (3)
- # clojurescript (54)
- # community-development (24)
- # conjure (16)
- # duct (1)
- # emacs (8)
- # events (1)
- # figwheel-main (4)
- # fulcro (13)
- # gratitude (20)
- # helix (3)
- # honeysql (8)
- # hyperfiddle (12)
- # introduce-yourself (1)
- # jobs (6)
- # lambdaisland (1)
- # lsp (23)
- # malli (1)
- # meander (27)
- # minecraft (11)
- # off-topic (12)
- # pathom (1)
- # portal (11)
- # releases (1)
- # remote-jobs (1)
- # ring (11)
- # sci (1)
- # shadow-cljs (53)
- # specter (5)
- # xtdb (20)
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"]
.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 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 …)
.
(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. 🙂
I'm late to the party, but this may be helpful https://github.com/noprompt/meander/pull/144/files?short_path=e88fbb5#diff-e88fbb5c0cd00327380696124537df29287c3c89a5a2395a0757548eb5b5d2f3
@U06MDAPTP 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.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}))