Re: APIs that overload numbers and strings

On Apr 13, 2013, at 1:36 PM, Boris Zbarsky wrote:

> Right now WebIDL cannot express an API that overloads a number and a string.  For example, this:

I suspect you won't be surprised by the following comment:

I think "overload" is the wrong way to think about these particular use cases and in general is not a good way to be thinking about JS functions. "Overload" to many developers implies multiple distinct functions that share a common name but where each function has it own distinct definition and behavior.  Static analysis of argument types determines which function is actually called at each call site. That's not how JS works or how a JS programmer would think about the equivalant situation.  In JS there is one function with a single definition that may include logic that dynamic selects distinct behaviors based upon analysis of  the actual parameter values.  In the JS case, internally dispatching upon an parameter value's type is really not much different from dispatching on the numeric value of a control parameter, or based upon the properties of an option object.

I think that pushing API designer towards thinking in terms of overloads is one of the things that contribute to API designs that feel unnatural from a JS perspective.

> 
>  void foo(double arg);
>  void foo(DOMString arg);
> 
> is not valid WebIDL.
> 
> I think I've seen 3 requests in the last several months to lift this restriction.  The question is whether we should and if so what the behavior should end up being, from an idiomatic JS perspective.
> 
> First, the use cases:
> 
> 1)  I can't recall what the first one was.  :(
> 
> 2)  The second is that audio API has legacy constants that are integers and "new-style" constants that are strings and wants to have a property whose setter takes either one.  This _could_ be done with just strings, with the set of allowed values then looking something like "0", "1", "2", "foo", "bar", "baz" and an attempt to assign 0 auto-coercing to "0".  But that does work that seems unnecessary: creation of the string "0" from the number...  Ehsan, are there other issues with this approach too?

This is interesting from an legacy compatibility perspective. If numeric strings and equivalent numbers always mean the same thing then simply saying the argument is a string would seem most straight forward.  I won't worry about the unnecessary string conversions. If most of the legacy values correspond to named constant attributes you may be able to respecify those constants as string values and no conversion is needed.  (they would still get converted to numbers if used in a computation).  Even if you can't change the constants, I won't worry about the conversion.  Note that in  JS someArray[5] also implies an implicit number to string conversion.

The bigger concern I have is if such values are retrieval as the value of some attribute or return value from some API calls. If so, you would need to change that result type to DOMString and there may be client code that isn't prepared to deal with that.  However, this may be another situation where auto-coercion from string to number saves the day.

> 
> 3)  The third one is IndexedDB, which allows primitive numbers and primitive strings (but not Number or String objects, note) as key values.  It also allows Arrays and Dates, but those are a separate concern.  IndexedDB treats 42 and "42" as different keys.  Right now it does this via using "any" in IDL and then defining the whole thing in terms of introspection of the original ES value involved.  This API seems pretty weird to me, at first glance, but there is quite a bit of existing library API precedent for treating String objects and primitive strings differently, as far as I can tell...  Checking for typeof == "string" is pretty common.

Idiomatic JS code that needs to distinguish numbers and strings would just say typeof foo === "number" or typeof foo === "string".  However, the test may no be needed at all, if the value is just going to be passed on to some lower level interface that is doing its own type discrimination. 

I won't worry about doing something special for String or Number wrapper objects.  They're really just an unfortunately exposed implementation artifact that JS programs seldom have to explicitly deal with. This is especially true starting with ES5 where wrapper are never exposed by language level operations. You have to go out of your way and say new Number(n) or new String(s) to actual get a reference to one

Some APIs may go to extra work to treat such wrappers as if they were the equivalent wrapped primitive value. It's probably unnecessary.  We even did it in a couple of places in the ES5 JSON spec. and we probably shouldn't have bothered.

> 
> So even if we take IndexedDB as legacy, what should actually happen here?  People clearly _want_ to be doing this sort of overloading, but should they be if they want idiomatic APIs?

Going back to the beginning, it isn't that they want overloading, its that they may want to do distinct things for certain kinds of values.  Distinguishing numbers and strings is one possible discrimination, so is distinguishing positive and negative numbers, and objects that have a foo property from those that don't.  I don't think you can generalize very much here as the "right think" to do is highly situational.




> 
> -Boris
> 

Received on Saturday, 13 April 2013 23:40:19 UTC