Intercepting sets on array-like objects

# Tab Atkins Jr. (7 years ago)

Heya! As part of designing the CSS Typed OM drafts.css-houdini.org/css-typed-om, we've ended up with at

three (so far) places where we want an interface that represents a list of values:

representing alternating unparsed strings and variable references in a property

representing the n-ary arguments of sum/product/min/max operations

representing a list of transform functions

The most natural way to represent these is as an Array, so authors can get all the Array methods, use val[i] syntax, etc. However, doing so means we lose the ability to type-check sets to the values.

In general, type-checking is an important part of APIs defined in WebIDL. Any set to a property on an object is automatically type-checked, any arguments to methods are automatically type-checked, etc. By building this into WebIDL, it removes the need for a lot of annoying boilerplate on the part of spec authors, and more importantly, removes the possibility that spec authors will forget to typecheck at all, or will typecheck in incorrect or weird bespoke ways. Every API responds in exactly the same way when you pass the wrong type of object, and that's a Good Thing for both users and implementors.

And so, it would be great to have the same ability to typecheck these Array-likes in the Typed OM.

Naively, this requires a Proxy, so we can intercept uses of the [] syntax. However, we don't need all the rest of the Proxy functionality, just this one intercept - a setter function, just for obj[foo] rather than obj.foo. Further, Typed Arrays already have precisely the functionality I'd like to use - they intercept setting using [], and convert it into the appropriate type of number. AWB also proposed adding exactly this hook in the past (I think it was called "Array reformation" or something?).

Thoughts?

Note that if we don't get some variant of this functionality, these APIs will instead do one of:

  • just using Proxies (already defined in WebIDL)
  • using .get()/.set() functions with integer arguments to badly emulate arrays
  • just relying on iterator/constructor, so users have to convert the object to an Array, fiddle with it, then construct a brand new object

And whichever I end up with, I'll be advocating that as the Standard WebIDL Way to do array-likes, so we can finally have some consistency.

# Mark S. Miller (7 years ago)

Interesting. At tvcutsem/es-lab#21 Tom and I have been discussing a way to make Proxies cheaper, and Proxies that only override a few traps and let the rest default, much cheaper.

# Allen Wirfs-Brock (7 years ago)

On Jun 8, 2017, at 11:32 AM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

Naively, this requires a Proxy, so we can intercept uses of the [] syntax. However, we don't need all the rest of the Proxy functionality, just this one intercept - a setter function, just for obj[foo] rather than obj.foo. Further, Typed Arrays already have precisely the functionality I'd like to use - they intercept setting using [], and convert it into the appropriate type of number. AWB also proposed adding exactly this hook in the past (I think it was called "Array reformation" or something?).

that proposal page can be access here: web.archive.org/web/20160425220917/http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation, web.archive.org/web/20160425220917/http://wiki.ecmascript.org/doku.php?id=strawman:object_model_reformation

the basic idea was that distinct internal methods (Proxy traps) are used for . and [] property accesses and that the default behavior for [ ] accesses is to look for specific symbol-keyed methods and if found defer the access logic to the method. If not found the behavior is the same as for . accesses.

# Adam Klein (7 years ago)

On Thu, Jun 8, 2017 at 11:32 AM, Tab Atkins Jr. <jackalmage at gmail.com>

wrote:

Heya! As part of designing the CSS Typed OM drafts.css-houdini.org/css-typed-om, we've ended up with at three (so far) places where we want an interface that represents a list of values:

The most natural way to represent these is as an Array, so authors can get all the Array methods, use val[i] syntax, etc. However, doing so means we lose the ability to type-check sets to the values.

In general, type-checking is an important part of APIs defined in WebIDL. Any set to a property on an object is automatically type-checked, any arguments to methods are automatically type-checked, etc. By building this into WebIDL, it removes the need for a lot of annoying boilerplate on the part of spec authors, and more importantly, removes the possibility that spec authors will forget to typecheck at all, or will typecheck in incorrect or weird bespoke ways. Every API responds in exactly the same way when you pass the wrong type of object, and that's a Good Thing for both users and implementors.

And so, it would be great to have the same ability to typecheck these Array-likes in the Typed OM.

Naively, this requires a Proxy, so we can intercept uses of the [] syntax. However, we don't need all the rest of the Proxy functionality, just this one intercept - a setter function, just for obj[foo] rather than obj.foo. Further, Typed Arrays already have precisely the functionality I'd like to use - they intercept setting using [], and convert it into the appropriate type of number. AWB also proposed adding exactly this hook in the past (I think it was called "Array reformation" or something?).

Thoughts?

Note that if we don't get some variant of this functionality, these APIs will instead do one of:

  • just using Proxies (already defined in WebIDL)

When you say "Proxies" here, I believe you're referring to the "indexed properties" feature of WebIDL ( heycam.github.io/webidl/#idl-indexed-properties). This seems like the right mechanism to use, from a WebIDL spec, to get the behavior you desire. In Chromium/V8, this doesn't actually use Proxies under the hood (we have something called "indexed property handlers", see the API at cs.chromium.org/chromium/src/v8/include/v8.h?rcl=ff98ddca4a1770c2868d44f1cdfe1d4656363f30&l=5781), but it's definitely implementable using Proxies.

# Tab Atkins Jr. (7 years ago)

On Fri, Jun 9, 2017 at 1:57 PM, Adam Klein <adamk at chromium.org> wrote:

On Thu, Jun 8, 2017 at 11:32 AM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

Note that if we don't get some variant of this functionality, these APIs will instead do one of:

  • just using Proxies (already defined in WebIDL)

When you say "Proxies" here, I believe you're referring to the "indexed properties" feature of WebIDL (heycam.github.io/webidl/#idl-indexed-properties). This seems like the right mechanism to use, from a WebIDL spec, to get the behavior you desire. In Chromium/V8, this doesn't actually use Proxies under the hood (we have something called "indexed property handlers", see the API at cs.chromium.org/chromium/src/v8/include/v8.h?rcl=ff98ddca4a1770c2868d44f1cdfe1d4656363f30&l=5781), but it's definitely implementable using Proxies.

Correct.

WebIDL's indexed getters/setters would fulfill my use-case exactly. If that's okay to use, per TC39 consensus, then awesome!

However, in heycam/webidl#345

Anne says:

[When] we discussed those kind of approaches with TC39, they told us not to and to just use Array. It wasn't just about [], it was also about FileList, NodeList, etc. And the problem with those was not that they did not have enough methods like Array, it was that they required a proxy.

Thus my confusion/consternation.

If this is incorrect, and indexed getters/setters are indeed fine to use in new APIs, then we can close this thread "no change" and I can go away happy. ^_^

# Domenic Denicola (7 years ago)

I'm not really sure how you're expecting to get an answer "according to TC39." One member had replied and given his suggestion. I don't think it's a good one for new APIs; I think new APIs should use arrays and not type check

# Tab Atkins Jr. (7 years ago)

On Fri, Jun 9, 2017 at 2:07 PM, Domenic Denicola <d at domenic.me> wrote:

I'm not really sure how you're expecting to get an answer "according to TC39."

I was told by Anne in heycam/webidl#345

to "go back to TC39 I suppose and say you really want to do typed arrays again". (And in messages preceding/following that, Anne implies that his pushback on the idea is motivated by historical TC39 dislike for this approach.) I'm doing precisely that.

One member had replied and given his suggestion. I don't think it's a good one for new APIs; I think new APIs should use arrays and not type check on sets but at processing time. I'm sure there will be many other opinions from other TC39 members.

"Don't type-check things that look like arrays" means carving out a special one-off shape in web APIs - every single property set or method call in WebIDL typechecks the values. What makes "things that look like arrays" special in this regard? I can't avoid typechecking

  • why should specs have to do bespoke typechecking in their functions that accept these object, versus every other type of object in the platform that is automatically type-happy?

Drilling in deeper: fixed length "things that look like arrays" can have type-checking in WebIDL no problem, you just have to define the names in prose, due to a legacy limitation of the WebIDL syntax. This allows users to do obj[1] = val; and get a TypeError thrown when it mismatches, exactly like they'd get with obj.foo = val. What makes fixed-length different from dynamic-length "things that look like an array"? There's no semantic/usability difference between these cases; it's purely an accidental artifact of the particular way JS handles the [] syntax.

This is not good API design. Platform objects can mimic every other JS object; there's no user-understandable reason why they should be different in this one particular case.


Digging back a bit to try and find what TC39 opinion Anne was referring to, I instead found this 2009 thread lists.w3.org/Archives/Public/public-webapps/2009JulSep/1346.html

which might be what he is remembering (it mentions FileList and DOMTokenList), where TC39 members were pretty uniformly supportive of an "integer property catch-all" (explicitly suggested by Maciej in lists.w3.org/Archives/Public/public-webapps/2009JulSep/1354.html)

so that platform APIs could do non-live Array-likes.

Did something change in the intervening 8 years? If so, what, and why? (This was the second result in my mail archives for "indexed getter", so I don't think I'm cherrypicking this thread either.)