Implementation considerations in the ECMAScript standard (was {Weak|}{Map|Set})

# David Bruant (14 years ago)

Changing the subject to something more relavant.

Le 16/09/2011 15:36, Andreas Rossberg a écrit :

On 16 September 2011 15:17, David Bruant<david.bruant at labri.fr> wrote:

Well yes, but that's part of programming. In practice, all resources are finite. And the difference between finite and infinite space usage is a correctness criterium.

Consider writing a server. If I cannot rely on tail call optimization then writing its message loop as a recursive function (e.g. actors style) would be incorrect. If I cannot rely on GC, then allocating an object for each received message would be incorrect. If I cannot rely on weak maps, then, say, mapping every message object to a return IP would be incorrect. You are making a connection between program correctness and implementation consideration of what you use to write these programs. It doesn't sound right.

"If I cannot rely on tail call optimization then writing its message loop as a recursive function (e.g. actors style) would be incorrect." => This is not true. If your server ever receives only one message, you should be fine. Obviously, I was talking about a server that is expected to get an unbounded number of messages during its uptime.

The problem is in implementation limitation, not correctness of your program. It turns out your programming style with nowadays reasonable use cases (size of input, number of messages...) makes current implementations fail. I agree that it is annoying, but it doesn't make your program incorrect. If we start considering implementation limitations as sources of program incorrectness, then some ECMAScript programs will always be incorrect. The difference is, this limitation would be hit for the normal use case of the server. If my program cannot deal with its expected use case, then it is incorrect.

What is the definition of normal use? Size of input? When machines will be powerful enough to handle your current normal case without tail call optimization, will the definition of "normal use" change? Once again, program correctness [1] (tell me if you use "incorrect" differently and please define it if so) has nothing to do with implementation considerations of the plaform that run your program. There is no contradiction in having a correct program which fails when implemented because of implementations issues (of the platform, not the program).

I do not think the ECMAScript standard is the the place where implementation considerations should be addressed. For that matters, people have been writing JavaScript programs for years and the spec doesn't say a word on implementations.

Also, why should tail call optimization should be standardized? There is a use case, but aren't there other implementation optimizations that could be considered? Should they all been standardized? Why this one in particular? Really, once again, an appendix named "hints for implementors" or a different document or a page on the wiki would be better than a normative section. Saying that ECMAScript implementations aren't standard because they do not support one programming style sounds like a lot.

Regarding tail call optimization, as far as I'm concerned, an agreement between implementors sounds like a more reasonable approach. There is no way in the language to test this feature. In this video [1], David Herman explains that a test can be written (at 40:40), but the test relies on implementation limitations rather than the language by itself (unlike all tests that can currently be found on test262). That is true, but whether some property can be observed from within the language itself, or only by its environment, is not relevant.

It is not for people who write programs, it is for implementors because it directly impacts their work. They are not implementing a language anymore (which they still were as of ES5.1), but a language and some implementation constraints. Let imagine for a minute that tomorrow, implementors find another implementation trick which allows to run without crash the use cases that motivated the proper tail calls proposal, why would it be problematic? Why does it have to be this optimization in particular?

There is no test that you can reliably write for a 'print' function. Still you want to be able to rely on it printing what you gave it.

I (if i was an implementor) would want to test my print function. I can decide to do it from within the language or not since i have more power. You can also test a function from outside a language (which is a more relevant choice for a print function).

Or consider a sleep(secs) function. How would you test it?

Time cannot be formalized. The best we do is a human-acceptable approximation of it. For what it's worth, I recommand reading the ES5.1 spec for Date.now(). I think that test262 does a pretty good job of test coverage for it.

"If I cannot rely on GC, then allocating an object for each received message would be incorrect." => Can you refine this point? I don't understand the connection between garbage collection and correctness of your program. I allocate objects on a daily basis and have never /relied/ on garbage collection. I think you implicitly do, all the time. Just try turning off GC and see whether your programs still work reliably.

With or without GC, there are 2 possible behaviors for a program (assuming conformant platform):

  • it works reliably
  • it crashes (not reliably most of the time) GC isn't of any help regarding reliability. It is helpful to make my program running reliably longer, that's all it does. If you disagree, please provide an example proving me wrong.

What I implicitely do is not relying on the GC, it's praying that the implementation (and GC in particular) is good enough to make my program run without crashing long enough. That's what everyone does, I think. Does this kind of prayers has a place in the spec?

Same thing for tail calls. We write a program in some style praying that the implementation will run it without crashing. Turns out, recursive tail calls make implementations often crash. Is it a good reason enough to impose something on the spec for that?

Tell me if i'm wrong, but until today, all implementation decisions have been made based on market considerations. JS is fast because of that. New classes of program can be run now while they couldn't before (in the sense that performance did not allow these programs to be efficient). See the video at [2]. I do not think it's the role of a language standard to have a word on implementation considerations. If there is a need for tail call optimization from the programmer side (to run some programs which use to fail without), well, implementations will adapt. If there is no push with that regard, I do not see why the spec should tell implementors what to do. Also, there is a long history in JavaScript of implementations doing what they want regardless of spec. There may not be a need of another such case.

Spec driving features and market driving implementation choices sounds like a good trade-off.

David

[1] en.wikipedia.org/wiki/Correctness_(computer_science) [2] blogs.intel.com/research/2011/09/pjs.php

# Andreas Rossberg (14 years ago)

I think we are digressing. There are three separate questions:

  1. Intent
  2. Specification
  3. Testing

Sometimes the intent is hard or impossible to specify formally, sometimes a specification is hard or impossible to test for. That doesn't necessarily invalidate such an intent or such a specification. Something you cannot test you might still be able to prove, for example.

The whole point of having weak maps are space complexity consideration. So the name should be chosen accordingly IMO, regardless of whether we have a good solution to (2) and (3), which are really separate problems.

But I'll leave it at that, nobody wants another longish bike-shedding discussion.