Closed timothypratley closed 4 years ago
I can see the confusion with &
not having parity with Clojure being a fair point of contention. But I disagree with your claim that it should result in a seq in every instance because it "is to be analogous to the Clojure concept". Meander's pattern matcher is in no way meant to have overlapping semantics with Clojure destructuring and was born in part by dissatisfaction with it. I think the biggest mistake here is that I didn't namespace &
. If it were namespaced, we probably wouldn't be having this conversation. The Meander &
will remain type preserving on epsilon
for a couple reasons.
epsilon
. I won't knowingly allow a breaking change on any version of the project because I think that is unethical behavior.{& (,,,)}
is not equivalent {& {,,,}}
. The same goes for sets. Maps and sets are what are known as non-free data types which is a fancy way of saying they have no canonical representation. For example #{:a :b}
can also be represented as #{:b :a}
. This distinction is important to pay attention to when pattern matching against these data structures because failing to do so would result in searches that do not explore the entire search space. In other words, trying to say that {& (,,,)}
are equivalent {& {,,,}}
is wrong because (,,,)
is free and {,,,}
is not and therefore do not and cannot have same implications for pattern matching.I do think your suggestions to have &seq
etc. are interesting, however, at the same we have seqable
and app
which could be combined with a namespaced &
and achieve the same result (but I do like the brevity).
Yes, that all makes sense, and is well-founded. In rejecting seq semantics meander treats data correctly, and the differences are clear with examples.
Answer: no, & implies the same type of the collection it occurs in.
Type details to consider:
I claim that the current behavior here is "correct" for sequence matching but "incorrect" for vector matching. I think there is value in preserving the notion that
& ?rest
is strictly a sequence and does not take on the type of its container. This is especially important for maps, but also important for vectors because it determines syntax:[& (xs! ...)]
vs[& [xs! ...]]
I prefer the former to the latter because it fits my mental model of the meaning of the thing after&
. An argument for the latter is that the concept of "left behind" is useful.For maps:
{& ([!ks !vs] ...)}
vs{& {:a :b}}
I prefer the former as it solves problems for me related to the limitations of the Clojure reader, and is consistent with my notion that & implies seq. One could argue that the notion of "left behind" part is nice preserving type, but to this I say: well that could be its own syntax too.FWIW I think that the whole point of having
&
is to be analogous to the Clojure concept. But it is always a tradeoff, for instance allowing additional behavior such as not always in the tail position has value. There is a minor difference here that when deciding the type, the behavior is not additive, you have to choose one or the other.To explore why it isn't additive: Consider allowing both
{& ([!ks !vs] ...)}
and{& {:a :b}}
to match; that works, but then what does this mean?{& ?rest-map}
the type of ?rest-map is now ambiguous. We start to leak away from meanders preference to be specific about types (which I like very much). And I think ultimately we arrive at things that cannot be expressed in one by the other...{& ([:a :b] [:c :d])}
vs{& {:a :b :c :d}}
are actually not the same things for large maps because of key-pair ordering.Pump the breaks, if I care so much about types, why do I want the ultimate non-type seq for
&
? Well, it is still explicit about the types just in a different direction. I'm not advocating for treating things after&
asseqable
, that will not work, I'm advocating that they must be matched as sequences. Substitution of vectors and maps still fits this model:{& {:a :b :c :d}}
does not break the rules for substitution, and the inverse of "must produce a sequence" is "must be inserted as a sequence" (definitely NOT "must come from a sequence" that is different, wrong and undesirable). Furthermore, this explains why(m/seqable)
is an insufficient way to combine both behaviors.Clearly this would be a subtle but fundamental and breaking departure from the current behavior of
&
so it really boils down to both concepts have value "left behind of same type" vs "sequence" that are competing for the same symbol and "left behind" was selected.The obvious choice is to give my desired syntax a different name like
m/&seq
and from a practical perspective, I understand that... I wanted to make the case for whym/&seq
is needed... maybe the "left behind" version of&
turns out to be way more powerful in practice and frankly I don't have enough experience or use cases to motivate making a breaking change here. I'll experiment further with an&seq
defsyntax.