Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does & imply seq? #101

Closed
timothypratley opened this issue Jan 4, 2020 · 3 comments
Closed

Does & imply seq? #101

timothypratley opened this issue Jan 4, 2020 · 3 comments

Comments

@timothypratley
Copy link
Collaborator

Type details to consider:

(m/match
  '(1 2 3 4)
  (1 2 & ?rest)
  (type ?rest))
#_#_=> clojure.lang.LazySeq

 (m/match
  '(1 2 3 4)
  (1 2 . !rest ...)
  (type !rest))
#_#_=> clojure.lang.PersistentVector

 (m/match
  [1 2 3 4]
  [1 2 & ?rest]
  (type ?rest))
#_#_=> clojure.lang.APersistentVector$SubVector

 (m/match
  [1 2 3 4]
  [1 2 . !rest ...]
  (type !rest))
#_#_=> clojure.lang.APersistentVector$SubVector

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 & as seqable, 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 why m/&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.

@noprompt
Copy link
Owner

noprompt commented Jan 4, 2020

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.

  1. Changing it to be a non-type preserving operation would be a breaking change on epsilon. I won't knowingly allow a breaking change on any version of the project because I think that is unethical behavior.
  2. For maps {& (,,,)} 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).

@timothypratley
Copy link
Collaborator Author

timothypratley commented Jan 4, 2020

Yes, that all makes sense, and is well-founded. In rejecting seq semantics meander treats data correctly, and the differences are clear with examples.

@timothypratley
Copy link
Collaborator Author

Answer: no, & implies the same type of the collection it occurs in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants