If the problem were purely one of being confused about syntax, we could avoid the confusion by eliminating all implicit associativity and precedence in the infix operators (except perhaps for addition, subtraction, multiplication, division and perhaps exponents, if we have them).
Thus A == B or C would be a syntax error, requiring parentheses: either A == (B or C) or (A == B) or C.
However, even with the mandatory parentheses, students will still get tripped up and believe that
A == (B or C) means
(A == B) or (A == C).
Parentheses do not preclude distributive laws, whether real or imaginary. After all
A * (B + C) is
A * B + A * C!
Experienced programmers in C have a momentary lapse of reason and write something like
if (state == (STATE_INITIAL || STATE_REQUEST_RECEIVED)) ...!!! This is a thing that happens; I've seen it in a code review more than once.
That particular expression pattern could be targeted by a code walker in your language, and subject to a diagnostic:
foo.lang:42:warning: X == (Y or Z) doesn't mean (X == Y) or (X == Z).
This will irks someone who knows what they are doing and wants that expression. That programmer wants to compare
B, except when
B is missing, in which case
C is taken instead and compared with
One possibility would be to have a class of warnings that are targeted at neophytes. All learning materials would steer students toward operating the language in a way that the newbie warnings are turned on.
Ultimately, the way to reduce the semantic confusion would be to make a "Blub" languages. Don't have a clever
or operator that returns one operand or the other. Have a pure Boolean
or that yields true or false.
Then make it a type error to compare some
X which is not Boolean to a Boolean value!
X == (Y or Z) # error: comparing integer X with Boolean expression (Y or Z)
The idea of
A or B or C yielding the leftmost one of A, B and C that is not falsy, and not evaluate the rest, comes from Lisp:
(or A B C ...) ; yield A if that is true, else B if that is true, else ..
However, while it is that way in Common Lisp and related dialects, it was not so in Lisp 1, according to the 1960 manual.* While Lisp 1 had N-ary
OR operators, which were short-circuiting, they yielded a strictly Boolean value. Doing anything like
(EQL EXPR (OR A B)) in Lisp-1 would have meant that
EXPR has a Boolean value,
F, and that is being compared to the truth value of the
EXPR contained, say, a number, the comparison would be false even if both
B contained that number.
So before Python could borrow the Lisp
or; first the original Lisp one had to be upgraded to the more clever semantics.
Under the new, clever
or, you will sometimes get the expected behavior. If
expr evaluates to 42 and so does
(eql expr (or a b)) will in that case behave consistently with the belief that
expr is being separately compared with
a and with
b. The student will run the code and not notice the problem. The failing test case is something like
(eql 42 (or 73 42)). Cases like
(eql 42 (or nil 42)) and
(eql 42 (or 42 nil)) do not reproduce the issue, and can confirm the wrong belief.
See page 48.