problem-solving icon indicating copy to clipboard operation
problem-solving copied to clipboard

Junctions with comparisons - or why is != meta

Open 2colours opened this issue 3 years ago • 11 comments

!= appears to behave like a meta-operator construction with junctions which results into confusing semantics ("all elements aren't equal to $n" turns into "not all elements are equal to $n" !); while other comparisons like > have their non-meta counterparts (like <=), I don't see anything similar for == (or eq, for that matter).

There seems to be some discrepancy with the semantics of junction comparisons and the implementation adds to it.
so all(1,3) > 2 #False so all(1,3) <= 2 #False In these cases, it seems like the semantics is "for all elements listed, the substitution results into a True value".

However
so all(1,3) == 3 #False so all(1,3) != 3 #True! The last line seems to contradict this semantics - indeed, it doesn't hold for all values that they aren't equal to 3. What does hold, though, is that not all of them is equal to 3... Actually, we don't have to play this guessing game: all(1,3) != 3 #True It collapsed the junction straight away.

It seems to me != is implemented using the ! meta-operator so eventually all(1,3) != 3 is the same as !(all(1,3) == 3). This behavior can be replicated by using !> instead of <=.

I think it's arguable whether this behavior of the ! meta-operator is reasonable at all - the bigger problem is that != is THE negation of == and we don't really have much choice or control over this meta-behavior, unlike in the case of > where we can use an equally "real" operator as the negation of it, without collapsing the junction.

2colours avatar Mar 14 '22 23:03 2colours

Bonus issue(?) all(1,4) &infix:<!=> 4 might look like something that should do the same as all(1,4) != 4 - what it actually does is more than surprising: it parses as (all(1,4))&(infix:<!=>(4)), hence accidentally creating a new junction rather than using & as a sigil. Moreover, infix:<!=> 4 is True - actually, I failed to pass anything to it that wouldn't have given True, including 0 and Nil.

This also may be worth taking a look at. Why is the precedence like this? Why does infix:<!=> 4 not only work with one argument but return True, pretty much no matter what?

2colours avatar Mar 14 '22 23:03 2colours

I know this is not the point of the issue, but it's probably better to use so none(1,3) == 3.

all(1,4) &infix:<!=> 4 might look like something that should do the same as all(1,4) != 4 - what it actually does is more than surprising: it parses as (all(1,4))&(infix:<!=>(4)), hence accidentally creating a new junction rather than using & as a sigil.

That behavior is correct. If you want to call a function, you shouldn't use the & sigil, unless you use parenthesis: &infix:<!=>(all(1,4), 4) or infix:<!=> all(1,4), 4. If you want to use it as an infix, you should wrap in brackets: all(1,3) [&infix:<!=>] 4

CIAvash avatar Mar 15 '22 06:03 CIAvash

I know this is not the point of the issue, but it's probably better to use so none(1,3) == 3.

Yes, I realized it somewhere on the way and it definitely impacts the severity of the whole phenomenon that there is a nice way out. But yes... as we all know, "sometimes consistency is not a bad thing either". "all" is logically a more transparent concept than "none" and "all of the values are different from 3" is a perfectly valid equivalent of "none of the values are equal to 3".

That behavior is correct. (...)

Thank you for the explanation. What I would still be curious about is why infix:<!=> 4 works at all and why it returns True.

2colours avatar Mar 15 '22 11:03 2colours

What I would still be curious about is why infix:<!=> 4 works at all and why it returns True.

The answer to the first part is here. As for the second part, I think I saw a discussion about it or at least something similar, the reason may be to allow infix operators work with reduce and map, ..., but I'm not sure. It's better to leave it to a more knowledgeable person answer that.

CIAvash avatar Mar 15 '22 12:03 CIAvash

With regard to the expected behavior, ! is a meta. Basically, it is a shortcut for !(<expression>). In this particular case, to !($a == $b). So, two rules:

  • one cannot expect negation of an expression to act as a singular operator
  • != must not behave differently from what it is a shortcut for

vrurg avatar Mar 15 '22 14:03 vrurg

Hi @vrurg ,

As someone who (from what I can tell) has worked quite a lot with junctions with regards to their semantics and implementation as well, I kind of hoped for you to show up. However, I have to say this remark from you feels way too little. It's mostly stuff that "everybody knows already" and falls outside of the discussion.

I could refer to my own comment under https://github.com/rakudo/rakudo/pull/3874 to avoid writing the same thing - the "too long; didn't read" would be: don't take neither the meta behavior with junctions (i.e that the negation is applied outside of the whole junction, rather than value-by-value) nor "!= is a shortcut for !==" for granted. These are technicalities, and from what I see, technicalities that can be changed, it only takes a decision, especially for the latter.

I think it's very important to point out that the reasoning should revolve around the semantics, not the implementation. I'm yet to see but one explanation as to why all(1,4) != 4 being straight-up True would be a do what I mean design choice or why it would be desirable behavior, even if you know and accept the reasoning behind it.

And then the ne operator still remains. I think it's seriously questionable to implement a != shortcut for !== that is one character shorter and just tries to hide the meta-behavior - but then what can we say about ne that doesn't give the slightest hint of not being a "singular operator"? What good reason is there to make people learn lexical peculiarities like "ne is not a real operator, <= is a real operator, != is not a real operator ..."?

I feel that I'm giving so much evidence - maybe even too much, considering that several people have agreed with the overall sentiment over time, including @lizmat - and compared to that, the feedback is so little and feels so... inbred. Sorry for the more personal tone in advance but like... do you guys really not feel how crazy all(1,4) != 4 #True seems to anyone expecting a clean and straightforward "for all of the values, P(x) holds" semantics? How crazy it seems that something that works flawlessly for <= just won't work for != or even ne? This is what you guys should address for the sake of the users and this what drives remarks like (I took this one from https://github.com/rakudo/rakudo/issues/3748)

This may be a notabug, but it's really unexpected and annoying behavior. This makes junctions way too unpredictable to work with.

2colours avatar Mar 15 '22 18:03 2colours

@2colours I barely can add much to the discussion in rakudo/rakudo#3874. Neither I have much spare time for this kind of discussions these days. Just two notes.

How many questions would there be asking about the distinction between !== and !=? It's a rather 50/50 kind of situation. Unfortunately, DWIM is good until it results in too many special cases which are kind of apparent, but not always. Sometimes strict rules work better, even if they're kind of counterintuitive.

To add some personal touch to this. Emotionally I tend to like the semantics you're pushing for. But emotions are bad advisors. :)

vrurg avatar Mar 15 '22 19:03 vrurg

Please don't feel pressurized, any feedback is welcome and I'm still hoping for someone to show up with a thorough rationale that can be applied to the described scenario. :)

Still I would like to target this remark shortly:

How many questions would there be asking about the distinction between !== and !=? It's a rather 50/50 kind of situation. Unfortunately, DWIM is good until it results in too many special cases which are kind of apparent, but not always. Sometimes strict rules work better, even if they're kind of counterintuitive.

Honestly, I don't think there would be a lot. The ! metaoperator is kind of overrated. It's an advanced, exotic feature of Raku, completely different from people expecting a negative counterpart of == (that they actually don't really get in Raku...) I think if we think broader, !== and != being the same raises more questions for the newcomer than !== and != being different - and != isn't a regular formation in Raku either, it's not the meta of =.

2colours avatar Mar 16 '22 11:03 2colours

Thanks to deoac (IRC), I think the last nail in the coffin of != and ne has really arrived:

(1, 1).one (elem) (1..10) # one(True, True)
(1, 1).one ∉ (1..10) # one(False, False)
#as opposed to:
(1, 1).one !(elem) (1..10) # True

This proves that

  • as opposed to https://docs.raku.org/language/unicode_ascii#index-entry-%E2%88%89, ∉ is NOT equivalent to !(elem)
  • ∉ apparently "deserves" to be an operator on its own while != and ne doesn't

I think the current behavior of ∉ is really an excessive proof that there are good precedences already, and it's really hard to justify why != and ne still go against the flow.

Also, since I think it's valid behavior that ∉ and !(elem) are not equivalent, hoping that it stays this way (and ne and != will be fixed in this direction as well), I am willing to take care of the documentation change that this observation requires.

2colours avatar Oct 01 '22 10:10 2colours