This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 27008 - "Initializing objects from iterables" needs syntax
Summary: "Initializing objects from iterables" needs syntax
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: WebIDL (show other bugs)
Version: unspecified
Hardware: PC All
: P2 normal
Target Milestone: ---
Assignee: Cameron McCormack
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 26190
  Show dependency treegraph
 
Reported: 2014-10-09 11:14 UTC by Anne
Modified: 2016-10-17 15:42 UTC (History)
5 users (show)

See Also:


Attachments

Description Anne 2014-10-09 11:14:35 UTC
http://heycam.github.io/webidl/#toc

[[
Initializing objects from iterables
]]

I would like syntax support for this. It's not clear how I rewrite the constructor from Headers https://fetch.spec.whatwg.org/#headers-class to make use of this new construct.

Context: https://github.com/heycam/webidl/pull/13#issuecomment-51331208
Comment 1 Boris Zbarsky 2014-10-09 15:01:10 UTC
I'm not quite sure why you need that here.  I mean, you need a definition of OpenEndedDictionary, but the iterable bit is handled by the sequence in that union, no?
Comment 2 Anne 2014-10-09 15:05:23 UTC
I was under the impression that this new concept from IDL introduced by Domenic replaced the need for that sequence/dictionary construct.
Comment 3 Boris Zbarsky 2014-10-09 15:19:19 UTC
Oh, I see.

I don't think it does, in fact, since it assumes that you have an iterable and just want to initialize from it.  But that's not what you have.  You have either an iterable or a random non-iterable object, no?

So ignoring web idl for now, how would one implement this?  First check for a Headers instance, fine.  Then check whether your object is iterable.  If so, you have two choices.  You can do the sequence thing, which will snapshot the iterable, and then you init from that snapshot.  Or you could do the "add map elements from iterable" thing, which will redo some observable work (like getting @@iterator from the object, though we could maybe change that to pass in the iterator getter, like we do for "creating a sequence from an iterable"), right?

Lastly, if your thing is not iterable, then you treat it as an OpenEndedDictionary.

So in terms of syntax, we'd basically need to introduce some "live iterator" type which would check whether the object has a non-undefined @@iterator and then either pass through just that object or that object and the @@iterator value, right?  And then you'd replace the sequence you have here with this "live iterator" type and perform "add map elements from an iterable" in prose?

We _could_ try to jam everything that "add map elements from an iterable" wants (at least the method name, but it's not clear to me how it should guess the destination object, or whether it should be explicitly specified) in the syntax, but then it ends up being pretty weird if someone uses that type in, say, a dictionary: at that point, what is the destination object?
Comment 4 Anne 2014-10-09 15:26:06 UTC
Please forget about the current IDL syntax for Headers. It is not important.

A Headers instance is iterable so we would not have to specialcase it. This is a bit slower, but that seems fine since it's not a critical path.

I thought a normal object would also be iterable and that Domenic's proposal would do the right thing for { "H" : "V", "H2" : "V2" }.


My question I guess is whether HeadersInit should just become an alias for "any" then or if we can do something better.
Comment 5 Boris Zbarsky 2014-10-09 15:42:54 UTC
> I thought a normal object would also be iterable

It's not...

> My question I guess is whether HeadersInit should just become an alias for
> "any" 

I'd like to understand what the problem is with the current HeadersInit definition.  ;)
Comment 6 Domenic Denicola 2014-10-09 15:51:18 UTC
My intention was to use `any`, so as to avoid additional type-checks and coercions before invoking the "initializing objects from iterables" algorithm.

You could add some new type that has the same semantics as `any`, I guess, e.g. `ProcessedInProse` or something.
Comment 7 Anne 2014-10-09 15:55:59 UTC
http://krijnhoetmer.nl/irc-logs/whatwg/20140627 has some of the context from my discussion with Domenic which leaded to that pull request.

The basic gist seemed to be that what we ended up with did not look like what the Map constructor from ES6 does.
Comment 8 Anne 2014-10-09 15:56:24 UTC
Domenic, why does it not handle this case: { "H" : "V", "H2" : "V2" }?
Comment 9 Domenic Denicola 2014-10-09 16:27:20 UTC
ES6 maps take arbitrary objects as keys, so it needs to be [["H", "V"], ["H2", "V2"]] so that instead of "H" you can use `{ foo: "bar" }` or something. This seems important in the general case for headers so you can do [["H1", "V1a"], "H1", "V1b"]].

It makes sense to allow the Headers constructor to be overloaded with a (non-iterable) object as well for the less-general case. Nothing in ES does this but you could follow a pretty similar pattern. In JS it'd probably be

```js
function useHeaders(headersInit) {
  Object.keys(headersInit).forEach(key => this.set(key, headersInit[key]);
}
```

Now that I look at this more closely I think I see why you desire syntax. Here is my thought on the ideal way for it to work, without any compromises like copying everything into a sequence and then performing the algorithm:

- We would add an "initializing objects from open-ended dictionaries" too, roughly following the above JS algorithm.
- We would modify the "add map elements from an iterable" to return a success/failure signal.
- You'd use some sort of syntax (WebIDL gurus make this better, and see below for intended semantics)

  [Constructor(optional (Headers or MapInitIterable or OpenEndedDictionary) init)]

- Your constructor algorithm would contain prose to the effect of:

  - "In the MapInitIterable case, [add map elements from iterable] _init_ to **this** with adder method `"append"`"
  - "In the OpenEndedDictionary case, [add map elements from open-ended dictionary] _init_ to **this** with adder method `"append"`.

  (you could use "set" for the latter instead of "append"; it doesn't matter.)

- The intended semantics of the constructor definition are then that it tries in sequence: (1) if it's undefined, do nothing. Otherwise, (2) check for the Headers brand, and if present, use that; (3) if not, try the add map elements from iterable algorithm; (4) if that returns a failure signal, try the add map elements from open-ended dictionary algorithm; (5) if that returns a failure signal, throw a TypeError.

Note how there is no type argument to OpenEndedDictionary since calling `this.append` does the conversion at that stage.

Also note that in practice you could omit the Headers from the constructor since Headers is already iterable. It would be observably different in that if someone had instrumented Headers.prototype[Symbol.iterator] or Headers.prototype.append they would not get their instrumentation triggered in the brand-check case, I assume, since that assimilation would not happen through public APIs but instead by pulling the private state out of the other Headers instance and plopping it into `this`.
Comment 10 Boris Zbarsky 2014-10-10 04:26:59 UTC
>  [Constructor(optional (Headers or MapInitIterable or OpenEndedDictionary) init)]

The point of OpenEndedDictionary is to have something to do type coercions for you.

Here you apparently don't want them done, because you want to pass things as-is to the append method.  So you don't actually want OpenEndedDictionary.  You want something more like "object", except that's not distinguishable from sequence right now.

But given that you want all the behavior in prose anyway, apparently, sounds like you really just want to take "optional object" and then go from there.
Comment 11 Cameron McCormack 2015-11-30 10:20:45 UTC
I think as Boris says, given you want to pass the object down to either the "initialize map from an iterable" or "initialize map from an open-ended dictionary" prose algorithms, you don't gain anything from having syntax for MapInitIterable and OpenEndedDictionary.  And given you can treat a Headers object like the from-iterable case, I think using `optional object` and passing the object off to a single prose algorithm that handles both initialization types would be the most straightforward thing to do.  Call that algorithm "add map elements from an object" and have it do:

* If object has an `@@iterator`, then call "add map elements from iterable".
* Otherwise, call "add map elements from open-ended dictionary".

which is pretty much what the JS-to-union-value conversion algorithm would do when deciding whether we've got a MapInitIterable or OpenEndedDictionary.

If we need an API later where we do need to handle other IDL interface types differently (e.g. say if Headers weren't iterable), then we could make object be distinguishable from interface types.  But for now I don't think it's necessary.
Comment 12 Anne 2015-11-30 14:50:37 UTC
I guess the construct I was thinking of would be something where we give enough information to IDL so it can create these instances by iterating over the provided value.

It seems IDL would only need to know the method name for that to be able to do the rest. This is assuming we do indeed want to do it via the method and allow JavaScript to overwrite what happens.
Comment 13 Anne 2016-09-27 08:24:54 UTC
So I guess my current opinion is that we should remove the construct Domenic added and fix bug 20158 instead. We want unrestricted dictionary / OpenEndedDictionary in IDL for numerous features and it makes little sense to use something else for Headers.