Function-ality

I’m sitting here in Derek Featherstone’s amazing a11y talk at Fronteers and I feel like I need to follow up the last post with a quick primer on the zen of function for (both of) the spec authors who read this blog.

The reason it’s offensive to the JS hacker for WebIDL to disallow new against DOM types — any of them — is that it means that it’s no longer specifying how you’d describe these types in the primitive we use over here in JS for this sort of thing: functions. This might sound nuts to folks who come from C++ or who spend their time in spec-ese, but there’s no difference between a plain-old function, a “constructor”, and a “partial” or “mixin” in JS semantics. We use functions for all of them. You can say new function(){ ... } and create an instance of an anonymous “class” in JS. You can take the same function and invoke it as a “regular function” — (function(){ ... })(); — and you can use that sort of function as a mixin or partial interface too: new function(){ (function(){ ... }).call(this); ... }. The exact same function object can even act in all of these capacities (although it’s rare). People use them as they need to, but they all boil down to functions.

What, then, does it mean for something to disallow new against some type for which you can in some otherwise get an instance in JS? The same thing when you can’t .call() it: it’s alien. It’s not a class as we know it, which means that it’s not a function, and if it’s not a function…well, it doesn’t belong. Fundamentally, it’s smuggling static semantics into a language that has perfectly good dynamic semantics for the same thing. This strikes at the very heart of what WebIDL is supposed to be for: describing JS types for things implemented somewhere else. By not allowing new and .call() WebIDL is giving JS semantics the bird, asserting that the fact that these things aren’t JS types makes them better in some way…and that is a bug, either in the perspective of the spec authors or of the specs themselves.

Luckily, the fix is easy: all WebIDL types should de-sugar to functions. All of them. All the time. No questions asked. That you will be able to use new and .call() and all the rest isn’t a bug, and it’s not something to guard against. It’s just how JavaScript rolls…and how JavaScript’s largest, most important library should roll too.

8 Comments

  1. Posted October 6, 2011 at 11:05 pm | Permalink

    Great last line. I love the state of mind thinking about the dom as a JS library.

  2. Posted October 9, 2011 at 8:34 am | Permalink

    Alex,

    I think you’re underestimating the difficulties involved in what you advocate. Yes, the DOM is the largest and most important JavaScript library. But it was designed as a Java library with abstract types, a baroque type hierarchy and awkward factory methods. It *is* alien to JavaScript. But at least it is internally consistent. Adding constructors across the board will make it some kind of hybrid, but won’t make it feel like a JavaScript library. Using Node as a constructor, for example, just doesn’t make sense. And adding a NodeList() constructor isn’t going to make nodelists feel native. For that, the entire DOM would need to change to use JS arrays.

    I think it is worth pushing on the DOM spec to add more features that feel like native JS, including constructors that make sense. And maybe it is worth advocating for a grand redesign of WebIDL so it becomes more natural for future spec authors to create APIs that feel like JS rather than C++. But I don’t think you can get natural JS APIs by just tweaking WebIDL around the edges.

    The Node.append() stuff being debated for DOM4 seems promising. Perhaps someone (you?) can create a JS library to implement that new method and at the same time include the constructors you propose so we can all try them out in practice.

  3. Posted October 9, 2011 at 2:35 pm | Permalink

    Hey David,

    There’s nothing that can’t be done here, and changing NodeLists to be Arrays is absolutely on my TODO list…it’s one of the reasons why I’m look for us to get the “.length” issue fixed in JS, and I think a recent Proxies evolution will get us there.

    More the the point, WebIDL can just *decree* that these things be Array types. Yes, that’s magical today (we don’t have a way to explain it), but it’s surely less crazy than the current behavior.

    Anyhow, we have patches for this “new-ing” stuff that we’re using in a build of Chrome, and it sure feels nice to work with. No JS library needed. The name “HTML” in all of the class names is a terrible thing to look at, but it beats passing opaque strings to a factory function.

    Regards

  4. Posted October 12, 2011 at 7:41 pm | Permalink

    Alex, you say “By not allowing new and .call() WebIDL is giving JS semantics the bird …” but that’s not true. Web IDL does allow new-able call-able interface objects. What it doesn’t do is require it.

    I agree with the point that David makes above, and which Anne and others have been making, which is that there are some interface objects where having them constructable makes no sense — or at least no sense if you want the result of the “new” to be an object that implements that interface and nothing more specific.

    It’s true that these interfaces were designed in a Java happy era, and maybe that’s why these interfaces like Node (which are more like abstract classes, really) exist. Do JS programmers ever create hierarchies that have abstract classes, not intended to be constructed? Do they somehow check for this and throw (as Web IDL requires for non-[Constructor] annotated interfaces) or do they just let a half functional object be created with the right [[Prototype]]? Is the latter actually helpful behaviour for the author?

    “The name “HTML” in all of the class names is a terrible thing to look at, but it beats passing opaque strings to a factory function.”

    I suggested `new Element(“div”)` on www-dom; seems more obvious to me than `new HTMLDivElement`, and especially for cases where the construct name is not just `”HTML” . ucfirst($tagName) . “Element”`.

  5. Posted October 12, 2011 at 7:42 pm | Permalink

    Which is not to say — and I’ve mentioned this before — that I am against making as many interface objects constructable as makes sense. We should do that.

  6. Posted October 13, 2011 at 6:14 am | Permalink

    Cameron:

    I think you’re still not understanding. You’re saying “we allow what is natural”, the also true inverse of which is “we allow what isn’t natural”.

    JS, on the other hand, doesn’t allow what isn’t natural.

    Let me repeat myself: if you have a function in JS, you can use new against it. End of story.

    The fact that WebIDL breaks this invariant is the bug. Arguing about the utility of it is really strange. WebIDL should be accepting JS as it is and building an API for the DOM in it, not projecting what’s “right” for C++ or Java into JS. Do you disagree?

  7. Posted October 17, 2011 at 4:27 pm | Permalink

    Alex,

    Let me preface this by saying that your I/O talk (“Learning to Love JavaScript”) is _the_ reason I’m working in JS right now.

    But. However.

    Crockford would argue (and I agree) that we largely do not need ‘new’ or ‘call()’. We do sometimes need ‘apply()’. I posit that while there is nothing wrong with ‘new’ and ‘call()’ –JS implements them perfectly well, their use causes us to think of JS in more classical terms. And this is potentially bad.

    JS is a functional language and begs to be used in functional ways. We need neither ‘new’ nor ‘call()’ to accomplish most of our work, so why would their omission be a problem?

  8. Posted October 18, 2011 at 7:42 am | Permalink

    Hey Christopher,

    JS is a multi-paradigm language, but the essential paradigms — no matter how you choose to write your code — are that we have objects and functions. What WebIDL is asking of us is that we accept INVISIBLE constructors. That is, classes whose instances you come face-to-face with but can’t call “new” on the way the system does. No matter how you think idiomatic JS should be written, that’s just offensive. Now, maybe you can make the case that what we instead might want are joined factory/ctors like “Array” where you can use both “new Array()” and “Array()” and get new instances. Fine. But WebIDL doesn’t give us that either. The fact reamains that these things act like functions under the covers but don’t expose themselves that way to us. It’s inconsistent and creates mental overhead.

    And anyway, who says you get to keep the system from getting better for folks who want to program in a different style to the one you do ;-)

    Regards