In the relatively recent past I’ve been again caught out by the eagerness of Scala’s | and & operators. How did I get caught out you might ask as I’ve known for a long time that Scala is eager by default. I think it was because it was in the context of match guards. If and If-else constructs are lazy even in C, so I guess that put me into a lazy frame of mind. The normal catch involves a collection. If the collection is non empty AND the head element conforms to some predicate, throws an exception on an empty collection.
| and & 's eagerness is irritating. So this got me thinking, is there any reason not to make them lazy? Is there even a single piece of Scala code out there that relies on | and & 's eagerness?
Note: Scala of course does not have lazy by value parameters, but in this case a by-name parameter is functionally equivalent to a lazy by value parameter.
and they aren’t in scala? what do you mean by laziness here and how does that translate to | and & operators?
|| and && do short circuit in scala (Short-circuit evaluation - Wikipedia). | and & evaluate both sides. i belive it’s the same in java, c and other languages.
are you using | and & by mistake where you should be using || and &&?
Yes I am talking about short circuit evaluation although I wasn’t aware of the term. From the above linked Wiki
In any programming language that implements short-circuit evaluation, the expression x and y is equivalent to the conditional expressionif x then y else x, and the expression x or y is equivalent to if x then x else y. In either case, x is only evaluated once.
Because of this I disagree with Dijkstra. The fact that exclusive or can not have a lazy 2nd parameter does not really complicate things in my opinion.
scala> var aa: Array[Int] = Array()
var aa: Array[Int] = Array()
scala> aa match{
| case _ if aa.nonEmpty & aa(0) > 0 => "Hurrah"
| case _ => "Boo"
| }
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
The eagerness of the second parameter to the AND method, requires an additional match to avoid the exception.
It’s possibly a bad idea (due to this confusion) that | and & even work at all for Boolean, although it kind of makes sense. Those are the bitwise-comparison operators, not the logical ones, and it’s 100% intentional that both sides are evaluated: you’re combining all of the bits in the values in order to get the result.
That makes more sense if you think of the Boolean as a one-bit Integer – just as 32-bit Integer | needs to be eager, so is the one-bit Boolean. It would be semantically weird if the 1-bit version behaved differently from the 8-bit, 16-bit, 32-bit, etc versions. It’s explicitly documented as such.
| isn’t the same thing as ||; & isn’t the same thing as &&. They have different purposes and use cases. (And Scala isn’t particularly unusual in this, AFAIK.)
At some point, maybe a few years ago, I proposed swapping the semantics, so that ‘&’ is short-circuiting and ‘&&’ eager, but I don’t have a link, though I know I did not think it was practical. I think the context was interesting, but I do not recall what that was.
FWIW, in the red book, the authors use && and || in their explanation of ‘Strict and non-strict functions’ and ‘by-name parameters’, saying that you can also think of the two boolean operators as functions that may choose not to evaluate their arguments
I don’t know if I agree. Sure the behavior isn’t perfect, but it’s been a de-facto standard for literally 50 years now. Anyone coming to Scala from Java or Javascript - the two major platforms we run on - will likely be familiar with this. I feel that’s enough of a global standard of behavior that we don’t really need to add any Scala-specific guardrails here.
Just because one user has got this wrong doesn’t mean that the solution is to add extra checks. I suspect that RichType now understands how this works, which will help for all C like languages.
Then again, legit use cases for & and | on booleans are rare; very rare. The only good reason I can think of is if you need to exploit their branchless nature. That would only make a difference in highly performance-sensitive code, where hopefully your code is so littered with comments justifying what you’re doing that an additional @nowarn won’t make a difference. I say that as someone who has actually written branchless “algorithms” in performance-sensitive code in Scala (with measurable outcomes!).
Yes luckily for me Seppuku, is not a strongly enforced norm in the Scala community. I did try and check in the REPL whether && was short circuiting, but somehow I messed up the test, concluding it behaved the same as &. Hence why it took so long for me to recheck and get it.
Personally I’m perfectly happy with how Scala handles this.
Only because someone did something in the past in some way and it didn’t change for a long time doesn’t mean that you should continue to do it in the same way!
With that “argumentation” you can just go back to program in C. Because “it’s been a de-facto standard for literally 50 years now”, so it must be fine…
Also a warning about a very likely bug doesn’t have any downsides. But a lot of upsides!
And just imagine, there are people who don’t “come form JS or Java”…