[BUG?] Missing class checks on send| and send& ?

Bijan Parsia bparsia at email.unc.edu
Tue Mar 20 04:49:58 UTC 2001


On Tue, 20 Mar 2001, Richard A. O'Keefe wrote:

> Bijan Parsia <bparsia at email.unc.edu> writes:
[snip]
> 	Thus the contrast is supposed to be between a short circuting and non
> 	short circuting set of operators.
> 	
> That *is* precisely the contrast between & and and:.

Not if by "evaluate the argument" you mean (as makes sense in
Smalltalk) "sends #value to it". It also depends on what you mean by
"argument".

	true & self msg1 msg2 msg3

Is the argument to #& above the message chain "self msg1 msg2 msg3"? (I
called this the syntactic argument.) Or is it the *result* of sending msg1
to self, msg2 to the result of that, and msg3 to the result of
that? Ordinarily, the argument to #& would be the latter. That is, after
all, what gets sent to true and is what's bound to the formal parameter of
the method.

It's true that that message chain gets evaluated, so, in contrast to:

	true & [self msg1 msg2 msg3]

the syntactic argument isn't short circuted (in the first example). Note
that this is *not* a property of the #&. And *even if* the code block
*would* return true if sent value, "&" will return *the code
block*. Unevaluated. The unevaluated code block. Thus, #& does not
evaluate its argument (for a reasonable sense of evaluate and argument).

Is this a problem? Dunno. I would avoid sending blocks to & (indeed,
non-booleans) as a matter of personal preference and style. I would also
make it clear that & doesn't evaluate the object passed as its argument
(*even if* it's passed a boolean, since, for the some senses of evalute,
it's not).

> Note that the method comment for & says nothing about what happens if
> the receiver is not true

As it should not. If the receiver isn't true, we have a
#doesNotUnderstand: unless your object or one of its superclasses
implement #& (e.g., is a new subclass of True).

> or the argument is not true.

Yes. And closer reading of the ANSI spec convinces me that the 
non-boolean argument case is undefined. So whatever! (But we do want to be
sensible. Since every smalltalk I've investigated does things this way,
I'm not agitating for change unless it's global, ANSI based change.)

But it *does* say that #& evalutates its argument. Boolean>>and: says:

	"Nonevaluating conjunction. If the receiver is true, answer the	
	value of 
	the argument, alternativeBlock; otherwise answer false without  
	evaluating the argument."

Thus, "evaluating" an argument, in context, is gettiing the value, that is
sending #value (which is what True>>and: *does*.

Neither #& nor #| evaluate their argumetn.

> In fact True>>& alternativeObject 's comment says
> 	answer alternativeObject since receiver is true.

Fine. This is all reasonable behavior, but it's not evaluating that
alternativeObject.

Contrast with True>>and:'s comment:
	"Nonevaluating conjunction -- answer the value of
	alternativeBlock since the receiver is true."
	^ alternativeBlock value

Now, the *nonevaluating* conjunction evaluates its argumetn while the
*evaluating* conjection *doesn't*!! ("Nonevaluating" surely means "short
circuting", which surely means (for conjunction) *evaluate* the second
conjunct if and only if the first conjunct is true.

> We find
> 
> True>>
>     & alternativeObject
>         ^alternativeObject
>     and: alternativeBlock
> 	^alternativeBlock value

There is the inconsistency. In #and:, "evaluate the argument" means "send
#value to it", where is in #& it means "return it". 

>     | aBoolean
> 	^self
>     or: alternativeBlock
> 	^self

And here, the second argument isn't evaluated at all, and isn't expected
to be (hence the "aBoolean"). *Mutatis mutandis* for False.

[snip]
> There is complete consistency here *except* that the argument of
> #| should be called alternativeObject, not aBoolean.

Do you still think this? If so, we should go to private mail and I need an
argument. Note that I'm *not* arguing that this bad semantics or
changeworthy semantics, just ambiguously commented semantics.

> If (expr) is free of side effects, then

Aren't the interesting cases the side effected ones? Oh well.

> 	x & (expr)
> 	x and: [(expr)]
> will have the same result, even if (expr)'s result is not a Boolean,

But the relevant contrast cases are:
	x & [(expr)]
	x and: [(expr)]

Your case compares the evaluation of the *syntactic* argument. Fine, but
no where else to we consider the *syntactic* argument to be the argument
of a message. I recognize that we're doing a bit of operator/binary
message punning here, but I don't think it's all that helpful.

[snip]
> For example,
> 	true & 3	=> 3
> 	true and: [3]	=> 3

	true & [3]      =>  [] in UndefinedObject>>DoIt "I.e., the block"

Now, #and: is supposed to *evaluate* its argument iff the receiver is
true. In your example, the receiver is true and #and: returns the *value*
of the argument, to wit, 3.

#& (if "evaluating"/non-short circuting) is supposed to evaluate its
argument (no matter what) and return true if both receiver and argument
are true. I'll by pretending that a Boolean evaluates to itself, but if
you passed a block with & to true to you *don't* get the value of that
argument, you get the argument itself! That argument *never never* gets
evaluated (in the sense of evaluation established by #and:).

Clearly, in practice, this hasn't been a big deal. But it's *surely* an
inconsistency.

[snip]
> To stick a test in #& or #| without sticking a result test in #and:
> or #or: would be to *introduce* an inconsistency.

1) I didn't advocate tests, or indeed, any change. 2) I would agree that
that's an additional inconsistency to be avoided. Right now, you *can't
pass a boolean* to #and:! That seems inconsistent with the "true *is*, for
our purpuses, self-evaluating". Which seems to argue that #& is supposed
to evaluate its *syntactic* argument. But #and: can do that too:

	true and: self returnABlockAnsweringTrue

Now, this #and: evaluates both its syntactic argument, to get its
blockAnsweringTrue, and its argument (to get true). For fun, try:

	true and: [[true]] value

Oops, damn. #and: can't do either. That's because the *parser* catches
it! Weird. It seems that it'll only allow a block or a not messaged
object as its syntactic argument. Which means:

	|temp|
	temp := [true].
	true and: temp.

*will* work. Hmm.

Ok, I might be willing to concede that "evaluate" means something like "if
whatever it was was bundled up in a block, what that block would return,
but with non-evaluating #and:, we have to put the block in explicitly, but
heck, the parser will help you keep that straight". Which, upon
reflection, is how I used to understand it. Oops! :)

However, it's not bleeding obvious from the comments!

[snip]
> It's really a speed/safety tradeoff.  Do you want every and: and or: in
> the Smalltalk universe to run slower to catch type errors that don't really
> seem to be a problem?

Course not :) *bad hoc* [sic] test:

	canc: alternativeBlock
		alternativeBlock value
		ifTrue: [^ true]

(Adds two bycodes.)

Time millisecondsToRun: [10000 timesRepeat: [true cand: [true]]] 125
Time millisecondsToRun: [10000 timesRepeat: [true and: [true]]] 25

If you drop out the ^true (which should have the same effect), you can
shave that down to one extra bytecode and 111 milliseconds. So, around
5-fold difference.

How's this for some weird kool-aid:

	Time millisecondsToRun: [10000 timesRepeat: [true & true]] 24
	Time millisecondsToRun: [10000 timesRepeat: [true & [true]]] 60

Huh!! Strange. Returning the block is mucho expensive compared with
returning its value (in the dummy case, of course).

	Time millisecondsToRun: [10000 timesRepeat: [true and: [3]]]  17
	Time millisecondsToRun: [10000 timesRepeat: [true & [3]]] 56

Wow, this holds true for a lot of simple operations (including
float/integer addition). I wonder why.

Oh well, this note has gone on long enough :)

Cheers,
Bijan Parsia.

P.S. I was looking at your book, the *Craft of Prolog* tonight! Good
stuff. I hear your "email voice" as I read it :)





More information about the Squeak-dev mailing list