meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
dominicm 2019-12-03T10:45:05.260800Z

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?

dominicm 2019-12-03T11:55:41.260900Z

Maybe I should just do [(meta x) x] as my match :)

dominicm 2019-12-03T13:40:40.261800Z

Something I need to do is maintain the form meta while changing the contents. I don't suppose there's a trick for that?

yuhan 2019-12-03T13:46:34.262300Z

besides using with-meta?

dominicm 2019-12-03T13:49:05.264500Z

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.

noprompt 2019-12-03T17:03:27.265600Z

@dominicm I’ve considered adding “meta patterns” like that for a while because I’ve wanted it in many situations.

noprompt 2019-12-03T17:09:37.268100Z

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.

noprompt 2019-12-03T17:10:36.268300Z

This works though

(m/match ^{:foo "bar"} []
  (m/and [] (m/app meta {:foo ?bar}))
  ?bar)
;; => "bar"

❤️ 1
noprompt 2019-12-03T17:13:39.269300Z

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"

noprompt 2019-12-03T17:16:32.271400Z

I could also have metapatterns be something you turn on

^::m/metapatterns (m/match ^{:foo "bar"} [] ^{:foo ?bar} [] [?bar]) ;; => "bar"

dominicm 2019-12-03T17:22:20.272200Z

That first syntax is pretty great actually

noprompt 2019-12-03T17:24:26.273Z

Sounds good. I have considered adding the with-meta thing to the epsilon namespace for this bit.

dominicm 2019-12-03T17:29:03.273600Z

The custom syntax is interesting, I might play with that generally though.

dominicm 2019-12-03T17:29:20.274100Z

As in, for building a custom DSL for my use case

dominicm 2019-12-03T19:01:13.274500Z

https://github.com/noprompt/meander/issues/86 any quick hints to where this should go?

noprompt 2019-12-03T19:04:52.275200Z

Thanks for opening that @dominicm 👍

noprompt 2019-12-03T19:05:06.275600Z

I think we’re just missing a page.

dominicm 2019-12-03T19:05:54.275700Z

Oh man, I was excited to read that article 😀

noprompt 2019-12-03T19:07:30.276500Z

Ha! I can throw one together here in a bit.

noprompt 2019-12-03T19:07:45.277Z

I think I might have a bit of one in the works.

noprompt 2019-12-03T19:08:22.277900Z

I kinda went off a cliff after 5 months straight of working on the project. :rolling_on_the_floor_laughing:

dominicm 2019-12-03T19:09:10.278Z

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?

noprompt 2019-12-03T19:13:55.279500Z

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.

noprompt 2019-12-03T19:14:04.279800Z

Basically, its like a macro.

noprompt 2019-12-03T19:15:11.280400Z

(defmacro foo [x]
  (if (= x 1)
    :one
    &form))

(macroexpand '(foo x))
;; => (foo x)
(macroexpand '(foo 1))
;; => :one

noprompt 2019-12-03T19:15:36.281Z

The difference is that syntax expansions can have different expansions depending on whether or not they are happening in a match or substitution context.

noprompt 2019-12-03T19:16:33.282Z

m/symbol , for example, works in both match and substitution contexts.

noprompt 2019-12-03T19:17:25.282300Z

(m/match 'foo/bar
  (m/symbol ?ns ?name)
  [?ns ?name])
;; => ["foo" "bar"]

(m/subst (m/symbol "foo" "bar"))
;; => 'foo/bar

noprompt 2019-12-03T19:18:49.284Z

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.

dominicm 2019-12-03T19:21:11.285700Z

Why would you return the original form for substitution in the example case though?

noprompt 2019-12-03T19:21:32.286100Z

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.

noprompt 2019-12-03T19:21:53.286600Z

Because it doesn’t have a definition on that side.

noprompt 2019-12-03T19:22:09.287Z

I mean, I suppose, you could throw an exception.

noprompt 2019-12-03T19:23:52.288200Z

Which, actually, might be a fine design, however, I wanted to give it some more thought.

noprompt 2019-12-03T19:24:38.289Z

So, yeah, for many of those extensions I just have them return &form when I don’t have an answer.

dominicm 2019-12-03T19:24:45.289300Z

ah, okay. I'm unsure about what will actually happen if you ran it in the substitution case, also an exception?

noprompt 2019-12-03T19:25:14.289800Z

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.

noprompt 2019-12-03T19:26:18.291100Z

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”.

noprompt 2019-12-03T19:26:36.291400Z

Here’s what happens:

(m/subst (m/number 1))
;; => (m/number 1)

noprompt 2019-12-03T19:27:17.292300Z

This is because subst is symbolic e.g. if there’s no expansion for m/number then its (list 'm/number 1).

noprompt 2019-12-03T19:28:02.292700Z

Which you can see in

(macroexpand '(m/subst (m/number 1)))
;; => (clojure.core/list 'm/number 1)

dominicm 2019-12-03T19:28:43.292900Z

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 back

noprompt 2019-12-03T19:29:14.293300Z

Because I think you mean (m/pred string?) 🙂

dominicm 2019-12-03T19:29:16.293400Z

derp, rubber ducking. m/pred works. m*/pred is a different thing.

dominicm 2019-12-03T19:29:22.293600Z

Snap, thanks! :)

dominicm 2019-12-03T19:29:48.294200Z

Is there a "soft list" matching? e.g. don't care if it's a vector, list, cons, whatever?

noprompt 2019-12-03T19:30:32.295100Z

You can use m/seqable for that (and thank @jimmy for it).

🙂 2
noprompt 2019-12-03T19:31:10.295400Z

(m/match [1 2 3]
  (m/seqable ?x ?y ?z)
  {:x ?x :y ?y :z ?z})
;; => {:x 1, :y 2, :z 3}

noprompt 2019-12-03T19:33:06.296700Z

That will work on anything seqable? too so maps, sets, etc.

dominicm 2019-12-03T22:38:27.298900Z

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.

noprompt 2019-12-03T22:40:00.300100Z

There isn’t at the moment, I’m sorry. 😕

noprompt 2019-12-03T22:40:07.300400Z

We’ve talked about it a bunch though.

dominicm 2019-12-03T22:40:38.301900Z

That's okay. I can just build up piece by piece

noprompt 2019-12-03T22:40:45.302100Z

The issue “expose an interpreter” is what would do it.

noprompt 2019-12-03T22:44:11.305Z

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.

noprompt 2019-12-03T22:48:00.308100Z

zeta will have a much better story for this and issues that are related.

noprompt 2019-12-03T22:49:22.310100Z

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.

noprompt 2019-12-03T22:50:17.310800Z

(first (mapcat f xs))
;; =>
(f (first xs))

noprompt 2019-12-03T22:51:38.311600Z

This technique is generally applicable to any Clojure code but, obviously, taking on that general case is much more work.

noprompt 2019-12-03T22:52:12.312400Z

I really wish that :inline and :inline-arities were encouraged.

noprompt 2019-12-03T22:53:41.313600Z

Whats really cool about those is they pretty much give you functions that can operate both as functions and macros.

noprompt 2019-12-03T22:54:54.315Z

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.

noprompt 2019-12-03T22:55:38.315400Z

The (zero? x) case gets rewritten as (. clojure.lang.Numbers (clojure.core/isZero x))

noprompt 2019-12-03T23:00:15.316200Z

Okay, turning rambling off. 😆