meander

borkdude 2025-04-23T10:03:15.882669Z

I'm working on some improvements in SCI/babashka in the area of type hints. Currently I noticed that meander uses this:

{:data #{{:memory-variable/symbol ?symbol
               :iterator/symbol (r.match.syntax/pred symbol? ?iterator-symbol)
               :as ?memory-variable}
              ^& ?rest-data}}
What is ^&? The SCI interpreter (in a branch) stumbles over a let expression where ^& seems to be used as a type hint which is generated in meander.substitute.epsilon

borkdude 2025-04-24T08:03:00.069069Z

"it's a bit hacky", I guess you could have used ^:& as well, which is much safer, since ^& is a type hint. when the generated code would have had any interop on the local with that type hint, it would have crashed, but so far it went unnoticed (until now) :)

noprompt 2025-04-25T03:44:13.877509Z

True. I think this is mostly safe. I might actually dissociate that meta from the symbol too but I can't remember. This is why I eventually went the s-expr route. The lower level version I was working was like this.

(m/cons p1 p2)
(m/concat p1 ...)
(m/conj p1 p2)
(m/assoc ?m ?k ?v)
(m/merge ?m1 ?m2)
(m/add ?set ?element)
(m/union ?set1 ?set2)
(m/str ?s1 ... ?sN)

noprompt 2025-04-25T03:52:21.225329Z

I even added stuff for arithmetic which is actually really cool because you can model decrement with matching (m/+ 1 ?x).

noprompt 2025-04-25T03:59:51.657949Z

Ultimately, what I realized is that I wanted two operators: get and set. get is for substitution, set is for matching; and I wanted to take logic out of patterns and put it on the same level as these operators. I also wanted and operator for controlling scope (call it new or whatever). The main idea is that new clears the bindings but can modify the context object, like invoking an anonymous function but instead of passing arguments you read the context object with set.

statement :=
(get )
(set )
(or  ...)
(and  ...)
(new  ...)
The top level is a disjunction. You have an environment which, at a minimum, holds the bindings, and the current object (whatever is being searched or generated). There's also a notion of def but you're defining a variable which is basically a definition for how to initialize, get and set a variable. It allows you define things like !x or *x in the language. So you can make variables that behave like fifos, counters, grouping, etc. I built a prototype of it in JS with a non-s-expr syntax (feels a bit like an assembly language) and I started working on one in zig, but since I can't really bring this stuff to work and no one cares, motivation is kinda low to hack on it.

borkdude 2025-04-23T10:03:38.605049Z

cc @noprompt

Jimmy Miller 2025-04-23T18:51:27.199539Z

I can’t remember the details. But it is basically like & for arrays. But in set literals I think you couldn’t capture that kind of ordering. So adding the metadata made it able to be found. I could be wrong on the why. But I know it is just like & for vectors.

borkdude 2025-04-23T19:00:20.037469Z

I found out what the problem was

borkdude 2025-04-23T19:01:34.007809Z

meander generates something like this:

(let [^& x :whatever] ...)
In SCI I'm working on actually using type hints and it was trying to resolve & as soon as it encounters one. But in Clojure these type hints are only resolved when you do interop. So as long the local isn't used in interop, it will fly. But this will crash for example: (let [^& x] (.length x))

noprompt 2025-04-24T02:10:35.473369Z

I believe it is & for sets. Its a bit hacky but it was the best I could come up with. I also believe you can stack them i.e. #{^& ?rest1 ^& ?rest2} (which would give you all the possible ways to split the matched set into parts ?rest1 and ?rest2. The way I wanted to do it in subsequent versions was with (m/union ?rest1 ?rest2) which, I think, makes the most sense.