You right, the error is occurring because operations on Missing (such as comparisons in this case) results in missing, and using a type other than Bool with boolean operators like && results in an error. So essentially, the expression: 3 < missing < 5 is expanded to (3 < missing) && (missing < 5), which expands to missing && missing, and that’s what results in the error.
The reason that (3 < missing) & (missing < 5) works its because & is the bitwise and operator, which is not equal to the logical and operator (&&). So essentially, using operations like && and || with objects that are not Boolean results in a error, and performing any operation (other than boolean operations) on missing will, generally, result in missing.
Chained comparisons use the && operator for scalar comparisons, and the & operator for elementwise comparisons, which allows them to work on arrays. For example, 0 .< A .< 1 gives a boolean array whose entries are true where the corresponding elements of A are between 0 and 1.
What is the advantage of && for this? Why not just use & in both cases? To handle a missing I can’t use chaining, because it fails with &&, but it would work fine with &.
As numbers are containers of themselves this works.
The advantage is being what is expected, I believe. At least, I expected 1 < x < 5 to be expanded to (1 < x) && (x < 5). & is for bitwise comparison so it would be unexpected to see it there. What is a little strange to me is that even with && being short-circuiting the double constraint is not.
julia> 1 < 2 < error("a")
ERROR: a
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope at REPL[4]:1
I think this is a footgun. It’s not obvious that 1 < missing < 2 lowers to (1 < missing) && (missing < 2). I get the appeal of short circuiting in this instance, but it’s still unexpected for new users.
It’s frustrating that we have &&, which errors with missing and & which has unexpected behavior with integers etc, but nothing which is just for booleans and handles missing.