Logical operators don't use valueOf()
That's the way ToBoolean has always been defined people.mozilla.org/~jorendorff/es6-draft.html#sec-7.1.2 . All objects are considered to be Boolean true values.
&
, |
,^
, ~
are actually bitwise operations on numeric values so they do ToInt32 (which may ultimately call valueOf).
!
, &&
, ||
are logical boolean operations so they call ToBoolean.
This generally makes sense, and far too much existing code depends upon this behavior to every consider changing it.
This is intentional and dates to ES1. It arises because the &&
and ||
operators are value-preserving (as in Perl and various Lisps) as well as
short-circuiting (as in C). Suppose we have
var obj = {valueOf: function(){return Math.random() < 0.5}};
var huh = (obj || foo) && bar;
If obj.valueOf()
were called during the temporary implicit conversion to
boolean, and its return value were truthy, then foo
would not be
evaluated and control would jump to the && bar
-- but with the result of
(obj || foo)
not being true
, rather being obj
. Then &&
would have to
evaluate obj
by implicitly converting it to boolean, and under the
hypothesis call valueOf()
.
The problem is that implicit conversion, which if customizable (e.g., by
valueOf
) cannot be idempotent in general, must apparently be done more
than once -- even though there is only one obj
term in the whole
chain. This is a consequence of the value-preserving on top of
short-circuiting nature of &&
and ||
.
I argued for implementations being required to memoize implicit
conversion results so no each term is implicitly converted at most once,
in order to enable conversion to boolean being customizable via valueOf
.
My TC39 TG1 peer from Microsoft argued that we should throw out the
value-preserving semantics of &&
and ||
. In order to keep those, I conceded.
In the value objects strawman I'm working on, !
and !=
are not
overloadable. Boolean test is overloadable, though, so 0L
, 0UL
, 0m
, etc.
can be falsy. Without reopening this ancient ES1 conflict, I hope to
find a solution that does not complicate the semantics with memoization
of implicit conversion results.
That's the way ToBoolean has always been defined people.mozilla.org/~jorendorff/es6-draft.html#sec-7.1.2 . All objects are considered to be Boolean true values.
So does this mean I can override toBoolean to change the way !
behaves on an object?
No, ToBoolean is a spec-internal abstract operation. I.e., it's a collection of steps to be taken whenever the operation is required by the specification of some other operation, but it's not available to JS code.
So how about implementing toBoolean
as an overridable method, which is
called whenever !
, ||
or &&
are used. This way an object has three method
for converting it into a primitive; toString
, toValue
and toBoolean
.
Never having a toBoolean hook would be quite constraining. The example from that e-mail involved:
var huh = (obj || foo) && bar;
And the problem is that a valueOf or toBoolean hook on obj
could result in it being called multiple times, and since the hook is written in JS it could be stateful and it could change its mind. The execution steps could be:
- Call
toBoolean
onobj
. Lets say this time it returnstrue
. - Skip evaluation of
foo
- The expression
(obj || foo)
now has the value ofobj
, and not the value it returned fromtoBoolean
- since we are doing value preservation. - Now we are evaluate
obj && bar
, which means that we need to again calltoBoolean
onobj
. At this point it may return false, just for fun.
We have two choices:
A) Reject the notion that obj
appearing once in the original source results in multiple calls to some hook on obj
(valueOf, toBoolean, whatever).
B) Allow multiple calls along with all that it entails.
I like the idea of B because as weird as it is, the pros outweigh the cons, in my own accounting. I hope I didn't miss any pros or cons.
Pro:
- Allows users to easily create value-like objects. I think value-like objects are a wonderful abstraction and it would be great to allow it.
- Solves the problem that Brendan alluded to: "Without reopening this ancient ES1 conflict, I hope to find a solution that does not complicate the semantics with memoization of implicit conversion results." This is a solution and it doesn't involve memoization.
- Creates consistency with other operators in the language, which do allow for value conversion hooks.
- Systems often have APIs or language features that result in a surprising and sometimes even non-deterministic number of calls to a user-provided callback, where that callback may be stateful even though for any sensible use you're supposed to make it stateless. Java's hashCode()/equals()/compare() when used with any built-in collections have this behavior. And it turns out that while yucky, it's not a big deal. JavaScript's sort is another example of this.
Con:
- You can write a program and get hilarious results.
- It reopens an ancient ES1 conflict that was thought to have been resolved.
- It is a substantial change to the semantics of a fundamental language operation. There is an elegance to saying that ToBoolean is pure. We would break that purity.
- It will be hell for JS optimizing JITs.
I think that the pros are much mightier than the cons here. Creating value-like objects would be awesome. Sure, you can get it wrong by making your toBoolean hook stateful - but I've rarely seen Java code get tripped up on statefulness of hashCode/equals/compare. I mean, it does happen sometimes - but not enough for anyone to really care. On the other hand, I can't get passionate about any of the cons. Of course you can write a program and get weird results - that's true of any Turing-complete language; the relevant question is: how likely are users to encounter the dark corners? In this case I don't see it as being very likely. I can't get passionate about an ES1 conflict because I wasn't there. I also don't think you can achieve nice value types without accepting that some currently pure language features will become impure. Finally, I don't think that the difficulty of implementation for JS language implementors should be a factor.
Note that I didn't include compatibility in the Con list - maybe I'm wrong but it feels like there ought to be a way of adding an API to register a toBoolean hook such that if you don't call the hook you're guaranteed to get the old behavior. But this does imply that we use a new hook (something like a toBoolean method) rather than saying that ToBoolean calls valueOf or toString.
So, sorry to push on this ancient decision - but it really feels like we could get a lot of win from reconsidering it.
Thanks for this reply.
Filip Pizlo <mailto:fpizlo at apple.com> September 8, 2013 1:56 PM
We have two choices:
A) Reject the notion that 'obj' appearing once in the original source results in multiple calls to some hook on 'obj' (valueOf, toBoolean, whatever). B) Allow multiple calls along with all that it entails.
I like the idea of B because as weird as it is, the pros outweigh the cons, in my own accounting. I hope I didn't miss any pros or cons.
I mentioned another in private email to you, while noting that it's too narrow: a value object class would be able to define a static "zero" value (observably immutable and with copy not reference semantics, as for all value object instances). This would be bit-compared by ToBoolean.
Problem is it doesn't support signed zero (IEEE 754r decimal has them, just like 754 binary floating point). More general (not just for numeric value objects) problem: value objects might need multiply falsy values.
A grandiose idea, inspired by JMatch. Instead of an arbitrary hook, support a general linear-algebraic projection function by which the JS VM can verify that the ToBoolean hook is idempotent (and even optimize accordingly if we want to allow that). I doubt this could be both expressive and efficient enough but I thought I'd throw it out here!
[pro vs. con analysis, with which I agree, deleted. /be]
Note that I didn't include compatibility in the Con list - maybe I'm wrong but it feels like there ought to be a way of adding an API to register a toBoolean hook such that if you don't call the hook you're guaranteed to get the old behavior. But this does imply that we use a new hook (something like a toBoolean method) rather than saying that ToBoolean calls valueOf or toString.
Agreed. My value-objects strawman reserves boolean-test as a hook available only for novel classes of objects, solving the fairly pressing numeric/scicomp/SIMD/etc. use-cases without letting other classes of objects sprout such hair.
So, sorry to push on this ancient decision - but it really feels like we could get a lot of win from reconsidering it.
No need to apologize -- thanks again for the followup!
The only thing that is unclear is the reason memoizatation isn't used. It would seem to be the way most of the js engines would implement this anyways. I'm not familiar with the inner workings of JavaScript JIT engines, but in a situation like this, would that code not be compiled into something which stores the truthyness of obj in a register, until the expression is completely evaluated?
Thanks for the detailed information you can provide on this subject btw.
Marius Gundersen <mailto:gundersen at gmail.com> September 8, 2013 3:20 PM
The only thing that is unclear is the reason memoizatation isn't used. It would seem to be the way most of the js engines would implement this anyways.
This isn't an implementation question -- first we have to agree on the semantics, then (unobservable except by stopwatch) optimizations can be done.
The semantics we discussed way back in late '96 or early '97 only briefly touched on memoization, and none of the implementors in the room liked it. We all had naive (and slow) interpreters then. We didn't do much or any analysis at compile time. We didn't have JITs.
I'm not familiar with the inner workings of JavaScript JIT engines, but in a situation like this, would that code not be compiled into something which stores the truthyness of obj in a register, until the expression is completely evaluated?
First, the more easy-to-understand semantics would not memoize -- it would require multiple calls to the hook, one for each implicit conversion. This is what value objects bring back; see Filip's post and my reply for more.
Second, we would have had a hard time specifying and requiring memoization. Your paragraph here hopes that it "falls out" of implementations, but there's no guarantee. A register-poor machine such as x86 would not necessarily even keep the last implicit conversion's boolean result around at all. If we required memoization, extra would would be done for every &&/|| chain.
Finally, if we came up with a guaranteed-idempotent extension mechanism, then we might indeed require memoization, but the spec would be quite complex. It does not seem worthwhile to go down this road.
Thanks for the detailed information you can provide on this subject btw.
No problem. I hope it's clear this is not an implementation problem, rather a specification one, or two: (a) what do we want; (b) how can we spec that.
While playing around with valueOf, I discovered that three operators (
!
,||
,&&
) don't seem to use valueOf when you use them. You can test this in your preferred browser here: lab.mariusgundersen.net/valueOfIs there a reason for this? Shouldn't these operators work the same way as the other operators?