How and/or handle non-Boolean operands
Last updated at 6:01 pm UTC on 13 January 2004
Author: Bill Spight Date Posted: 13 March 2003

Note:
true | 10 ==> true
true & 10 ==> 10
false | 10 ==> 10
false & 10 => false

The Class Reference says:
& aBoolean Evaluating conjunction (AND). Evaluate the argument. Then answer true if both the receiver and the argument are true.
| aBoolean Evaluating disjunction (OR). Evaluate the argument. Then answer true if either the receiver or the argument is true.

There is, if not a requirement that the argument be a Boolean, at least the strong suggestion. Nowhere does it say to return a non-Boolean. As long as we're evaluating the argument, we might as well check whether it is a Boolean or not, no?

Author: Richard A. O'Keefe Date Posted: 14 March 2003
This is one of the traces of Smalltalk's link with Lisp. Traditionally Lisp has regarded everything except the one "false" value (spelled NIL) as "true". In Lisp,
(or t 10 ==> t
(or nil 10) ==> 10
(and t 10) ==> 10
(and nil 10) ==> nil

Scheme broke with this tradition. Smalltalk has broken with it in the control structures, but not in & | and: or:. Note that
true or: [10] ==> true
false or: [10] ==> 10
true and: [10] ==> 10
false and: [10] ==> false

Given the way that the compiler open-codes #or: and #and:, it could be quite costly to "fix" #and: and #or:. As things stand:
(1) &, | are consistent with #and:, #or:
(2) if the result of an expression involving &, |, #and:, #or: is used in a control structure (whileFalse[:], whileTrue[:], ifTrue:[ifFalse:] ifFalse:[ifTrue:]) it will be checked THEN that the result is Boolean, also for #not.
(3) The only cases where there is a net difference is where the result is not so used.

It's only fair to point out that the ANSI standard DOES have such a requirement: "operand unspecified" in 5.3.3.1, for example. What's more, ANSI says (5.3.3.3) that (e and: [f]) is "undefined if the result of sending #value to" the argument "is not a ". Nowhere does it say to return a non-Boolean. I believe Squeak's behaviour is an allowed extension of ANSI behaviour here.

[With respect to] “As long as we're evaluating the argument, we might as well check whether it is a Boolean or not, no?”

Benefits:
(1) slightly earlier error reporting.
(2) stricter conformance to ANSI.
Costs:
(3) extra time in &, |, and:, #or:.
(4) extra space in every use of #and:, #or:.
(5) risk of breaking existing code (probably slight)

It really isn't clear that the potential benefits are enough to outweigh the costs

Author: Bill Spight Date Posted: 14 March 2003
Since #and: and #or: do not promise to evaluate their arguments, the fact that they do not determine whether they are Booleans or not does not seem like a bug. If you do not evaluate them, how do you know? That is why I confined my remarks to & and |.

Author: Richard A. O'Keefe Date Posted: 14 March 2003
In fact, #and: and #or: DO evaluate their argument (sometimes) and #& and #| NEVER evaluate their argument. On the contary, when control arrives at #& or #| the argument has already been evaluated.

Strictly speaking, #and: and #or: don't evaluate their argument either. They send it the #value message (sometimes), and it is just plain confusing to call that "evaluation".

My point is that the ANSI standard says the SAME thing about #and: and #or: (returns a ) as it does about #& and #|. If it is a bug for one of them, it is a bug for all of them. If you DO send #value to the block argument, you DO know.

To make this stuff check for Boolean-ness, you'd have to do something like
False>> & aBoolean aBoolean not not. ^self
False>> | aBoolean ^aBoolean not not
True>> & aBoolean ^aBoolean not not
True>> | aBoolean aBoolean not not. ^self

which is still vulnerable to redefinition of #not. Best we could do would be a new reserved method #NOTNOT understood by the compiler and generating a 'check if Boolean' bytecode, so
False>> & aBoolean aBoolean NOTNOT. ^self
False>> | aBoolean ^aBoolean NOTNOT
True>> & aBoolean ^aBoolean NOTNOT
True>> | aBoolean aBoolean NOTNOT. ^self

and then
e1 and: [e2]
would compile to something like
e1. jump-if-false L.
e2. NOTNOT.
L:
to get the Boolean check there.

To "fix" this "bug",
- the documentation must be changed
- the VM needs to implement a new NOTNOT bytecode
- the compiler needs to be changed to emit it for #and: and #or: (unless #and: and #or: are to be inconsistent with #& and #| which is surely highly undesirable)

This is a lot more work than just
+ fixing the documentation.

To justify it, there needs to be sufficient benefit.

I haven't seen an argument that the benefit is noticeable, let alone worthwhile. How many people have bugs in their code that would be found a lot earlier with this change? How many of those people are happy for e1 and: [e2] to run slower (when e1 is false)?

Author: Daniel Vainsencher Date Posted: 14 March 2003
Another reason people don't have bugs with this is that most conditional control structures, and all that use #|/#& eventually call ifTrue:ifFalse variants, which are strict about the who receives them, so the bug doesn't remain hidden for long.