Is it possible to match on metadata?
user=> (require '[meander.epsilon :as m])
nil
user=> (m/match ^{:foo "bar"} [] ^{:foo ?bar} [] [?bar])
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: ?bar in this context
I'm guessing I might need some custom extension?Maybe I should just do [(meta x) x]
as my match :)
Something I need to do is maintain the form meta while changing the contents. I don't suppose there's a trick for that?
besides using with-meta
?
Yeah. Essentially I'm wondering about transformation of the input rather than creating a new thing. I'm looking at it specifically from the mindset of knowing I'm using meander in a certain way,so that question doesn't seem generally applicable to different use cases.
@dominicm I’ve considered adding “meta patterns” like that for a while because I’ve wanted it in many situations.
The reason I’ve been reluctant is because I’m concerned, maybe irrationally, that could cause pattern matches to fail due to meta added automatically but I might simply be paranoid.
This works though
(m/match ^{:foo "bar"} []
(m/and [] (m/app meta {:foo ?bar}))
?bar)
;; => "bar"
Alternatively you could extend the syntax so its not so ugly and you could do something like
(m/defsyntax with-meta
([meta]
(if (m/match-syntax? &env)
`(m/app clojure.core/meta ~meta)
&form))
([object meta]
(if (m/match-syntax? &env)
`(m/and ~object (m/app clojure.core/meta ~meta))
&form)))
(m/match ^{:foo "bar"} []
(with-meta [] {:foo ?bar})
?bar)
;; => "bar"
I could also have metapatterns be something you turn on
^::m/metapatterns (m/match ^{:foo "bar"} [] ^{:foo ?bar} [] [?bar]) ;; => "bar"
That first syntax is pretty great actually
Sounds good. I have considered adding the with-meta
thing to the epsilon
namespace for this bit.
The custom syntax is interesting, I might play with that generally though.
As in, for building a custom DSL for my use case
https://github.com/noprompt/meander/issues/86 any quick hints to where this should go?
Thanks for opening that @dominicm 👍
I think we’re just missing a page.
Oh man, I was excited to read that article 😀
Ha! I can throw one together here in a bit.
I think I might have a bit of one in the works.
I kinda went off a cliff after 5 months straight of working on the project. :rolling_on_the_floor_laughing:
fwiw, I was hoping to find out what's going on here:
(m/defsyntax number
([] `(number _#))
([pattern]
(if (m/match-syntax? &env)
`(m/pred number? ~pattern)
&form)))
What's the if all about? What is the else case? Why do we just return the code in that case, won't that blow up?The else case, the &form
, ensures that if the syntax expansion is not in the match syntax context, we return the original form without touching it.
Basically, its like a macro.
(defmacro foo [x]
(if (= x 1)
:one
&form))
(macroexpand '(foo x))
;; => (foo x)
(macroexpand '(foo 1))
;; => :one
The difference is that syntax expansions can have different expansions depending on whether or not they are happening in a match or substitution context.
m/symbol
, for example, works in both match and substitution contexts.
(m/match 'foo/bar
(m/symbol ?ns ?name)
[?ns ?name])
;; => ["foo" "bar"]
(m/subst (m/symbol "foo" "bar"))
;; => 'foo/bar
The match expansion checks if the thing being matched is a symbol?
and binds then applies pattern matching to the namespace
and name
parts of it against ?ns
and ?name
. The substitution expansion applies substitution to its arguments and then applies symbol
to it.
Why would you return the original form for substitution in the example case though?
What defsyntax
does is hook into some plumbing that the parser utilizes to expand forms like m/symbol
etc. When the parser runs it binds &form
and &env
so that the function defined by defsyntax
has access to it.
Because it doesn’t have a definition on that side.
I mean, I suppose, you could throw an exception.
Which, actually, might be a fine design, however, I wanted to give it some more thought.
So, yeah, for many of those extensions I just have them return &form
when I don’t have an answer.
ah, okay. I'm unsure about what will actually happen if you ran it in the substitution case, also an exception?
I think what I might like to have is something like (m/syntax-error "message")
or something which returns a value the parser can use to produce a good error message.
You may have seen a :syntax-trace
on exception info thrown by Meander. This is essentially the rough start of saying “this extension caused this problem and these are the steps in the expansion that resulted in it”.
Here’s what happens:
(m/subst (m/number 1))
;; => (m/number 1)
This is because subst
is symbolic e.g. if there’s no expansion for m/number
then its (list 'm/number 1)
.
Which you can see in
(macroexpand '(m/subst (m/number 1)))
;; => (clojure.core/list 'm/number 1)
Changing subject briefly, why doesn't this modify the input?
(def remove-comments
(m*/rewrite
[:whitespace (m*/pred string?)] [:whitespace ""]))
((m*/attempt remove-comments)
[:whitespace " "])
I'm getting the same input backBecause I think you mean (m/pred string?)
🙂
derp, rubber ducking. m/pred
works. m*/pred
is a different thing.
Snap, thanks! :)
Is there a "soft list" matching? e.g. don't care if it's a vector, list, cons, whatever?
You can use m/seqable
for that (and thank @jimmy for it).
(m/match [1 2 3]
(m/seqable ?x ?y ?z)
{:x ?x :y ?y :z ?z})
;; => {:x 1, :y 2, :z 3}
That will work on anything seqable?
too so maps, sets, etc.
That's great. Is there a technique for debugging failing matches? I'm looking at something that's a bit complicated now, and I'm not sure where it's failing to match.
There isn’t at the moment, I’m sorry. 😕
We’ve talked about it a bunch though.
That's okay. I can just build up piece by piece
The issue “expose an interpreter” is what would do it.
Essentially, exposing an interpreter makes it possible to use function composition by wrapping the interpreter thus giving you the ability to wrap calls to the interpreter with whatever you want.
zeta
will have a much better story for this and issues that are related.
What I’m trying to do with zeta
is pull the staged compilation trick where the compiler and the interpreter are virtually the same in terms of code structure with the primary difference being the compiler reduces the code to just whats needed by the runtime.
(first (mapcat f xs))
;; =>
(f (first xs))
This technique is generally applicable to any Clojure code but, obviously, taking on that general case is much more work.
I really wish that :inline
and :inline-arities
were encouraged.
Whats really cool about those is they pretty much give you functions that can operate both as functions and macros.
Like zero?
is a good example of a function that can optimize something like
(if (zero? x) ,,,)
while at the same time still making (filter zero? xs)
perfectly fine.The (zero? x)
case gets rewritten as (. clojure.lang.Numbers (clojure.core/isZero x))
Okay, turning rambling off. 😆