'void' as a value
Lately people post overlong proposals that do not define the proposed primitives first. Also errors in the sketching don't help credibility.
Andrew Fedoniouk <mailto:news at terrainformatica.com> September 8, 2013 11:24 AM Consider this function:
function foo( t ) { if( t ) return undefined; // no return here, sic! }
As you see it returns 'undefined' when 't' gets true. When 't' is false it also returns the same 'undefined'.
But conceptually these are two different return cases/values: 'undefined' and 'nothing' (or 'void').
Allowing functions to return 'void' values will give us opportunity to use ordinary functions as iterators without need of separate Iterator entity.
You mean separate iteration protocol? This has been considered, but first some problems.
Consider this Range implementation:
function Range(low, high){ var index = low; return function() { if( index < high ) return index++; } }
and its use:
for(var i in Range(0,10) )
You must mean for (var i of Range(0, 10)) here, because for-in cannot be changed to iterate without breaking backward compatibility.
...
Internal implementation of for..in is calling the function in each iteration and stops when the function will return 'void'.
Assuming you mean for-of, the problems for any "new undefined-like sentinel" remain:
-
It will be too easily returned by accident, especially given how it is hard to distinguish from undefined.
-
It may end up in collections being iterated over, causing premature termination.
Any in-band value is a problem by the latter point. Thus the previous Pythonic out-of-band StopIteration exception, or the current ES6 wrapping of iteration protocol step results in {value, done} structure to avoid overloading the returned value and creating a pigeon hole problem.
To keep backward compatibility the 'void' value shall not be assignable, so after this:
function returnsNothing() { return; }
var retval = returnsNothing();
the retval will still contain 'undefined' value.
This will make very hard-to-detect bugs, by the first bullet point above.
And expression: returnsNothing() === undefined yields 'true'.
Therefore the 'void' value is mostly for internal use.
But in some cases user space code may also be interested in checking returns for 'void' values. In this case we can provide checkVoid(probe,valOrCallback) primitive:
var result = checkVoid( returnsNothing(), "nothing" );
in this case the result will get "nothing" value. If valOrCallback is a function then it will be invoked to indicate 'void' value:
var gotNothing = false; var result = checkVoid( returnsNothing(), function() { gotNothing = true } );
My pardon if something similar to this was already discussed by the group.
We have discussed in-band sentinels (including where the iterator-creator can choose the sentinel), out-of-band exceptions, and finally the chosen ES6 solution: out-of-band result wrapping with a done flag, many times.
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|.
On Sun, Sep 8, 2013 at 12:39 PM, Brendan Eich <brendan at mozilla.com> wrote:
Lately people post overlong proposals that do not define the proposed primitives first. Also errors in the sketching don't help credibility.
Andrew Fedoniouk <mailto:news at terrainformatica.com> September 8, 2013 11:24 AM
Consider this Range implementation:
function Range(low, high){ var index = low; return function() { if( index < high ) return index++; } }
and its use:
for(var i in Range(0,10) )
You must mean for (var i of Range(0, 10)) here, because for-in cannot be changed to iterate without breaking backward compatibility.
Correct, "for..of" is the right construct for that.
...
Internal implementation of for..in is calling the function in each iteration and stops when the function will return 'void'.
Assuming you mean for-of, the problems for any "new undefined-like sentinel" remain:
- It will be too easily returned by accident, especially given how it is hard to distinguish from undefined.
I do not understand the problem to be honest. Are you saying that it is hard to distinguish these two cases:
return something; // and return;
?
- It may end up in collections being iterated over, causing premature termination.
I think you've missed my point. 'void' IS 'undefined' by any means except of slightly different internal representation. It is like 'undefined' value has special flag .isVoid that is not exposed outside.
While assigning that undefined/isVoid value to any variable or field the isVoid gets cleared - in other words you cannot store void value anywhere.
So collections or variables simply cannot contain 'void' values. void is void.
Any in-band value is a problem by the latter point. Thus the previous Pythonic out-of-band StopIteration exception, or the current ES6 wrapping of iteration protocol step results in {value, done} structure to avoid overloading the returned value and creating a pigeon hole problem.
{value, done} creates another problem - heap pollution, this:
var counter = 0; for( var n of Range(0,10) ) ++counter;
should not cause heap allocations. In case of protocol you've described {value, done} object gets allocated. And I suspect it is so on each iteration.
...
"ĄBasta!"
Creo en tu palabra.
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "ĄBasta!" -- I'm not kidding :-|.
This.They cause way more (subtle and hard-to-debug) problems than they're worth, just take a look at NullPointerExceptions in Java and the like. Nope. Explicitly wrapping in a Maybe/Option type is much better, and ES6's direction captures the same idea.
Andrew Fedoniouk <mailto:news at terrainformatica.com> September 8, 2013 2:11 PM
...
Internal implementation of for..in is calling the function in each iteration and stops when the function will return 'void'. Assuming you mean for-of, the problems for any "new undefined-like sentinel" remain:
- It will be too easily returned by accident, especially given how it is hard to distinguish from undefined.
I do not understand the problem to be honest. Are you saying that it is hard to distinguish these two cases:
return something; // and return;
?
A reader can distinguish those, although return undefined; is less easy to spot at a glance. And delegation, meaning return bar(); where bar returns undefined, is in general beyond static analysis. So, yes!
The situation is worse when you consider the caller, not the callee (the iterator-consumer, not the iterator implementation). People will mix up undefined and void, Murphy was an optimist.
- It may end up in collections being iterated over, causing premature termination.
I think you've missed my point. 'void' IS 'undefined' by any means except of slightly different internal representation. It is like 'undefined' value has special flag .isVoid that is not exposed outside.
While assigning that undefined/isVoid value to any variable or field the isVoid gets cleared - in other words you cannot store void value anywhere.
This fixes only part of the problem (see delegation point above). Worse, it requires a write barrier to censor void via undefined. That is going to lose any implementors who aren't sold otherwise.
So collections or variables simply cannot contain 'void' values. void is void.
Collection in space is stream in time, the delegation problem remains. But you are right, I should have added the write barrier to the bullet-list of problems!
Any in-band value is a problem by the latter point. Thus the previous Pythonic out-of-band StopIteration exception, or the current ES6 wrapping of iteration protocol step results in {value, done} structure to avoid overloading the returned value and creating a pigeon hole problem.
{value, done} creates another problem - heap pollution, this:
var counter = 0; for( var n of Range(0,10) ) ++counter;
should not cause heap allocations.
I quite agree, and proposed StopIteration (in general, a class, per PEP380) for years. But this heap allocation is relatively straightforward to avoid in modern JITs, compared to other optimizations already done for JS. When for-of drivers the iteration, there's no need for the allocation, which is typically done in a return expression:
function Range(start, end) { let i = start; return { next: function () { if (i >= end) return {done: true}; return {value: i++, done: false}; } }; }
Generator functions make optimization even easier, since the returned object shape is guaranteed:
function Range(start, end) { for (let i = start; i < end; i++) yield i; }
In case of protocol you've described {value, done} object gets allocated. And I suspect it is so on each iteration.
Not observably when for-of creates and drives the iteration protocol and the iterator does not pass or stash a reference to the returned {value, done} object anywhere else.
This optimization possibility is a crucial part of the design. Were it not so, I suspect we would still be using an OOB exception approach.
...
"ĄBasta!"
Creo en tu palabra.
Gracias.
Generators can also return values, not only yield them: That way, a generator function can call another one via yield*
and receive a result (the value returned by the latter generator function [1]). That’s why a sentinel value is not enough, you need to be able to indicate that the iterator is done and to return a value at the same time.
As for allocating too many objects: To iterate, you already need to create an iterator object. If I’m not mistaken, the next() method could return this
and the iterator itself could have the properties value
and done
.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20130908/f316ae7b/attachment
Brendan Eich wrote:
Rather, it is the hand-coded task.js-like async libraries that will have to cope with some object allocation overhead.
Even here, the destructuring optimization mentioned long ago by Lars Hansen (see discussion:destructuring_assignment#performance):
let {value, done} = task.thread.next(result);
and the object allocation could be optimized away.
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|.
Except those in favor of nil?
Ahem Carry on!
Nathan
Le 08/09/2013 21:39, Brendan Eich a écrit :
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|.
Two values are already arguably (!) a regrettable feature DavidBruant/ECMAScript-regrets#26
Le 9 sept. 2013 à 10:35, David Bruant <bruant.d at gmail.com> a écrit :
Le 08/09/2013 21:39, Brendan Eich a écrit :
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|. Two values are already arguably (!) a regrettable feature DavidBruant/ECMAScript-regrets#26
David
For me, it is a excellent feature, if we get the correct semantic:
undefined
means "no value", or "nothing";null
means "empty value", or (well) "null".
Practical usefulness:
- ES5: JSON stringification of object:
null
meansnull
, andundefined
means "no value, don't include the corresponding key". - ES6: default values in function arguments (and maybe destructuring assignment?):
null
meansnull
, andundefined
means "no value, take the default".
Le 09/09/2013 11:41, Claude Pache a écrit :
Le 9 sept. 2013 à 10:35, David Bruant <bruant.d at gmail.com> a écrit :
Le 08/09/2013 21:39, Brendan Eich a écrit :
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|. Two values are already arguably (!) a regrettable feature DavidBruant/ECMAScript-regrets#26
David For me, it is a excellent feature, if we get the correct semantic:
The issue has a lengthy debate too ;-)
undefined
means "no value", or "nothing";null
means "empty value", or (well) "null".Practical usefulness:
- ES5: JSON stringification of object:
null
meansnull
, andundefined
means "no value, don't include the corresponding key".- ES6: default values in function arguments (and maybe destructuring assignment?):
null
meansnull
, andundefined
means "no value, take the default".
This is all after-the-fact justification. I understand the usefulness made of this feature now that we have the 2 values, but I wonder if given the choice to start over a newJS would be designed with 2 such values.
Among the nonsense of undefined-as-a-value: var wm = new WeakMap(); var o = {}; console.log(wm.has(o), wm.get(o)); // false, undefined wm.set(o, undefined); console.log(wm.has(o), wm.get(o)); // true, undefined
So we can "define" a weakmap entry with the "undefined" value. That's how objects work, so that can't be changed. I wonder what has been gained for weakmaps. For sure, some memory is wasted to keep the "has" boolean working. WeakMaps would feel simpler if wm.set(o, undefined) was an equivalent for wm.delete... to me at least...
Le 9 sept. 2013 à 11:51, David Bruant <bruant.d at gmail.com> a écrit :
Le 09/09/2013 11:41, Claude Pache a écrit :
Le 9 sept. 2013 à 10:35, David Bruant <bruant.d at gmail.com> a écrit :
Le 08/09/2013 21:39, Brendan Eich a écrit :
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|. Two values are already arguably (!) a regrettable feature DavidBruant/ECMAScript-regrets#26
David For me, it is a excellent feature, if we get the correct semantic: The issue has a lengthy debate too ;-)
undefined
means "no value", or "nothing";null
means "empty value", or (well) "null".Practical usefulness:
- ES5: JSON stringification of object:
null
meansnull
, andundefined
means "no value, don't include the corresponding key".- ES6: default values in function arguments (and maybe destructuring assignment?):
null
meansnull
, andundefined
means "no value, take the default". This is all after-the-fact justification. I understand the usefulness made of this feature now that we have the 2 values, but I wonder if given the choice to start over a newJS would be designed with 2 such values.
Even if the two particular cases I've mentioned were not forecasted when designing JavaScript, it does not diminish the value of the feature. When designing JS, some decisions had been taken, whose usefulness or badness has been understood only after experience.
Among the nonsense of undefined-as-a-value: var wm = new WeakMap(); var o = {}; console.log(wm.has(o), wm.get(o)); // false, undefined wm.set(o, undefined); console.log(wm.has(o), wm.get(o)); // true, undefined
So we can "define" a weakmap entry with the "undefined" value. That's how objects work, so that can't be changed. I wonder what has been gained for weakmaps. For sure, some memory is wasted to keep the "has" boolean working. WeakMaps would feel simpler if wm.set(o, undefined) was an equivalent for wm.delete... to me at least...
Yes, undefined
does have its quirks. Maybe, when designing JS some years ago, it would have been semantically better to make obj.foo = _i_dont_exist_
an equivalent of delete obj.foo
, so that getting obj.foo
would search the value in the prototype chain of obj
instead of returning undefined
?
David Bruant <mailto:bruant.d at gmail.com> September 9, 2013 2:51 AM Le 09/09/2013 11:41, Claude Pache a écrit :
undefined
means "no value", or "nothing";null
means "empty value", or (well) "null".Practical usefulness:
- ES5: JSON stringification of object:
null
meansnull
, andundefined
means "no value, don't include the corresponding key".- ES6: default values in function arguments (and maybe destructuring assignment?):
null
meansnull
, andundefined
means "no value, take the default". This is all after-the-fact justification. I understand the usefulness made of this feature now that we have the 2 values, but I wonder if given the choice to start over a newJS would be designed with 2 such values.
There's really no point, is there? FTR (I've written this before), I had both null and undefined because JS was under not only "make it look like Java" marching orders, it was meant to connect to Java, with objects reflecting both ways. This was LiveConnect, shipped in Netscape 3.
But from the start I had null for Java's object reference types, meaning "no object"; and undefined as the bottom of the semiattice, meaning "no value". Java didn't have a way to express primitive | reference unions, rather had boxing (has this changed?).
Among the nonsense of undefined-as-a-value: var wm = new WeakMap(); var o = {}; console.log(wm.has(o), wm.get(o)); // false, undefined wm.set(o, undefined); console.log(wm.has(o), wm.get(o)); // true, undefined
Why are you blaming undefined for this? If we didn't have it, the same design decision could have been made for null. Isn't this really a WeakMap design choice?
Brendan Eich wrote:
But from the start I had null for Java's object reference types, meaning "no object"; and undefined as the bottom of the semiattice,
"semilattice"
meaning "no value". Java didn't have a way to express primitive | reference unions, rather had boxing (has this changed?).
The alternative would have been to make everything an object and null the bottom. Would have been better but I was in a tearing hurry, and using unboxed primitives was easier, and "made it look like Java"
On Mon, Sep 9, 2013 at 5:41 AM, Claude Pache <claude.pache at gmail.com> wrote:
Le 9 sept. 2013 à 10:35, David Bruant <bruant.d at gmail.com> a écrit :
Le 08/09/2013 21:39, Brendan Eich a écrit :
In no case does anyone that I've spoken to, on TC39 or anywhere else around this planet, want yet another bottom type and singleton value a la null and undefined. No one. Those who speak Spanish and nearby languages tend to say "¡Basta!" -- I'm not kidding :-|. Two values are already arguably (!) a regrettable feature DavidBruant/ECMAScript-regrets#26
David
For me, it is a excellent feature, if we get the correct semantic:
undefined
means "no value", or "nothing";null
means "empty value", or (well) "null".
This was exactly my response, and I'll add that the only regrettable aspect is the typeof null issue.
Practical usefulness:
- ES5: JSON stringification of object:
null
meansnull
, andundefined
means "no value, don't include the corresponding key".- ES6: default values in function arguments (and maybe destructuring assignment?):
null
meansnull
, andundefined
means "no value, take the default".
Yep. I think that in the world of ES6 we'll see developers learning and respecting the difference between null and undefined.
Consider this function:
function foo( t ) { if( t ) return undefined; // no return here, sic! }
As you see it returns 'undefined' when 't' gets true. When 't' is false it also returns the same 'undefined'.
But conceptually these are two different return cases/values: 'undefined' and 'nothing' (or 'void').
Allowing functions to return 'void' values will give us opportunity to use ordinary functions as iterators without need of separate Iterator entity.
Consider this Range implementation:
function Range(low, high){ var index = low; return function() { if( index < high ) return index++; } }
and its use:
for(var i in Range(0,10) ) ...
Internal implementation of for..in is calling the function in each iteration and stops when the function will return 'void'.
To keep backward compatibility the 'void' value shall not be assignable, so after this:
function returnsNothing() { return; }
var retval = returnsNothing();
the retval will still contain 'undefined' value.
And expression: returnsNothing() === undefined yields 'true'.
Therefore the 'void' value is mostly for internal use.
But in some cases user space code may also be interested in checking returns for 'void' values. In this case we can provide checkVoid(probe,valOrCallback) primitive:
var result = checkVoid( returnsNothing(), "nothing" );
in this case the result will get "nothing" value. If valOrCallback is a function then it will be invoked to indicate 'void' value:
var gotNothing = false; var result = checkVoid( returnsNothing(), function() { gotNothing = true } );
My pardon if something similar to this was already discussed by the group.
I am using this mechanism in my TIScript and found it quite useful and effective for implementing iterator cases - at least no need for exceptions as shown here: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators