Hacker News new | past | comments | ask | show | jobs | submit login

Rather than a number of languages getting associativity wrong, a simpler explanation is that the < operator is not associative.

Mathematically, studying associativity makes sense for mappings of type A x A -> A, where A is any set; < is not such a mapping.




That is quite correct. If you look up the mathematical definition of associativity, the behaviour of the "<" does not fit it. In fact associativity is only a property that can be possessed by functions or operations that operate on a particular set. Thus, both the inputs and the result of the function or operation have to belong to the set. For the "<" operation, the inputs are numbers and the result is boolean. A boolean value cannot be an input for the "<" operation (if you take the classical definition of "<"). Therefore, "<" does not operate on a particular set. Therefore "<" cannot be associative.

The behaviour of "<" the author refers to is not associativity. Therefore, the languages he criticises do not really get associativity wrong, he just needs to brush up on his math.

Now you can say "well, ok this behaviour is not associativity but it is a well know behaviour of "<" and ">". Why can't the languages get it right?" I think the answer is that this (a<b<c<d) is one of those notations that look ok in algebra class but are really problematic if you actually try to define and use a logically consistent function notation, so it is not a problem for me, if some languages do not perform (a<b<c<d) as expected in order to keep their semantics clean.


Even in a mathematics, "a < b < c" isn't an expression but merely shorthand for "a < b" and "b < c".


But... isn't that what the author says?

Haskell is right. Sugaring in the conjunction like Python violates non-associativity, which exists for predicate relations in human languages. And it's not a type error; it's a syntax error.

Note that in mathematics one sees things that look like "1 < i < n", which seems similar to what Python allows. But, if you ask a mathematician to read this aloud, they will say "some i between one and n", or in certain contexts "all i between one and n". This is an entirely different thing; it is not a predicate at all.

[...]

So with <, it's non-associative and bad to sugar up the error you get if you try to use it associatively.


Hmmm... but by the same argument, Ruby is also doing it right, by correctly crashing at runtime (there is no way for a language without a type-system and with redefinable operators to discover the error at compile time).

Somehow the author isn't clear on whether he wants "1 < 2 < 3" to return true or to be rejected; if it's the latter, then most languages get it right (except Python and Matlab, as far as I know).


I think he's actually quite clear on rejecting it as a syntax error. Ruby isn't doing it right in that it throws a type error: 1 < 2 < 3 becomes lt(lt(1, 2), 3), then lt(true, 3) which doesn't check.

I know nothing about Ruby so I don't know how its operator overloading works, but I think that could be a problem if < was redefined with an a -> a -> a signature, or a -> a -> b with b comparable to a, since in this case you wouldn't get the type error. The proper solution, according to the author, would probably be to simply refuse any expression with more than one < (or any other non-associative operator).


It doesn't seem possible to consistently make "1 < 2 < 3" a syntax error in a language powerful enough to redefine <. (Including Haskell, where you could easily hide the Prelude definition and define your own, also changing the fixity declaration.)

So I guess a fair compromise would be if the default implementation rejects "1 < 2 < 3" at either compile-time or run-time, whichever is possible.


But... isn't that what the author says?

Not really. As pointed out above, the author doesn't appear to understand that the associative/non-associative distinction is literally undefined unless we're talking about a binary operator from a set to itself. Saying that < is a non-associative operator is wrong, something like saying that a vector is a non-odd number; as long as we're complaining about parenthesization, I'd suggest the author rephrase his statement and say that < is a non-(associative operator), which at least means something.

An expression like a < b < c should be a type error - no possible parenthesization could make it a valid statement.

It's easy enough to come up with a way to make < work like this, though, by changing the signature of < so that it takes and returns a helper class instead of a numerical value, with automatic type coercions to that helper class from numerical values on both ends and booleans on the right edge. This should be easy in a language like Scala, probably a 10 minute exercise if you know what you're doing.

If we make this helper class hold both the left edges of the chain and the right edges, and use the appropriate edge for each comparison, then the thing is associative.

((a > b) > c) > d is the same as a > (b > (c > d)), since in either case, each element will at some point be checked with > against both of its neighbors, and only those neighbors, and the overall expression will be coerced to true if and only if the strict ordering is preserved at each check. Process left to right, and it works out. Even if we get messier and allow >, >=, <, <=, and == into the mix, we can mix and match everything, and the whole thing will only be true if every neighboring check is true. The author's worry in the blog comments about '1 < 2 > 1' evaluating to true strike me as odd: that should evaluate to true under any reasonable definition of those operators.

So even if we do make < into an operator such that it makes sense to talk about its associativity, it turns out to be associative. Which is why mathematicians don't bother parenthesizing their chained < expressions, there's just no ambiguity.

The article is plain wrong, IMO.


What about functions of type A x B -> A ?

An example would be C++ stream << operator which takes a stream on the left, a string on the right, and returns a stream (after writing the string into the stream). This is so that you can do chaining:

    cout << "Hello" << "World";
I don't think that there are any mathematical operators that work this way though.


It seems like, to have associativity in the sense of "(a+b)+c = a+(b+c)", you'd have to extend << to also become the string concatenation operator in C++. There are quite a few rules that need to hold to make it work...


One of the comments on the post (by Mathnerd314) says that Icon implements its comparison operators that way (if the comparison succeeds, it returns the 2nd argument).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: