URLs / subclassing JavaScript
Le 17/12/2012 15:19, Anne van Kesteren a écrit :
If down the road we want to allow for the theoretical possibility of having all platform APIs implemented in JavaScript, we might want a sync Object.observe.
Which part of the platform needs a sync Object.observe? I feel all platform APIs can be implemented with ES Proxies (WindowProxy being one exception soon solved, I hope). If the objects people wanted to observe were only objects they created, a library on top of proxies would be enough for that. It's the necessity to observe objects that the author doesn't create that makes Object.observe a necessary API (and I'll be honest, it's also convenient to have regardless)
If we have types down the road as well (this might be a bit presumptuous), URLQuery could just be a MultiMap and whenever the MultiMap was mutated you'd update the associated URL synchronously (and potentially do other things, such as navigating to a URL). The latter would require synchronous change delivery. Or I suppose some kind of subclass that changes all the manipulation methods to also perform some kind of notification.
I don't understand what you mean by "types". And I also don't understand what you can't implement in pure ES6 in what you've described.
I feel there are 2 different goals:
- Being able at all to implement browser APIs in ECMAScript (which is close to being possible with Proxies. document.all being an exception).
- Being able to conveniently implement browser APIs in ECMAScript (which may require the addition of a MultiMap) which is up for debate.
I guess the problems here are that a) there's no MultiMap b) there's no types c) we want to reduce the burdon on developers for doing URL manipulation probably without waiting for a/b, but I rather not pull a Typed Array. Similarly, there's an idea to expose the path of a URL as an array of segments. Ideally this would be a type-constrained Array that when manipulated updates the associated URL.
I think you can create your own abstractions using proxies. For instance, you can create proxies which accept only one type as property values. I encourage to experiment and build what you need. If you face things that are impossible to build with proxies, come share your experience here. Likewise if you feel the current APIs make your abstractions inefficient by design.
On Mon, Dec 17, 2012 at 3:39 PM, David Bruant <bruant.d at gmail.com> wrote:
Which part of the platform needs a sync Object.observe?
(Thanks for the reply.) I think nothing does per se, but it might make manner more convenient.
I don't understand what you mean by "types". And I also don't understand what you can't implement in pure ES6 in what you've described.
By types I mean e.g. constraining set() to just accept strings.
There's not a problem implementing this in ES6, I'm just wondering what is considered better here. I'm getting kinda tired of the "IDL is terrible and outside TC39 people design terrible APIs" sentiment so I figured I'd just go and ask since this is a new API and can be amended.
I feel there are 2 different goals:
- Being able at all to implement browser APIs in ECMAScript (which is close to being possible with Proxies. document.all being an exception).
- Being able to conveniently implement browser APIs in ECMAScript (which may require the addition of a MultiMap) which is up for debate.
I feel there's another one too, "make it JavaScript-y", though this is never described much in detail, although from what I got so far using Proxies is not considered good practice. That's why the URLQuery API has no getter/setter but instead has methods like get() and set().
From: es-discuss-bounces at mozilla.org [es-discuss-bounces at mozilla.org] on behalf of Anne van Kesteren [annevk at annevk.nl] Sent: Monday, December 17, 2012 09:56
By types I mean e.g. constraining set() to just accept strings.
I think the "JavaScript-y" way of doing this, as exemplified in the ES5 spec's built-in functions, is to do a ToString() abstract operation on the input.
Le 17/12/2012 15:56, Anne van Kesteren a écrit :
On Mon, Dec 17, 2012 at 3:39 PM, David Bruant <bruant.d at gmail.com> wrote:
Which part of the platform needs a sync Object.observe? (Thanks for the reply.)
Thanks for your post.
I think nothing does per se, but it might make manner more convenient.
I understand. It however doesn't necessarily make it a good candidate for ECMAScript (just as a reminder, I'm just someone sending emails; I don't have any decision power among TC39, so that's just my opinion). I feel that browser APIs need some sort of cross-API tools. Sync Object.observe, MultiMap, Promises, maybe even Streams as examples. I don't know if these cross-API things are necessarily good candidates for ECMAScript, but I agree it's worth discussing.
I don't understand what you mean by "types". And I also don't understand what you can't implement in pure ES6 in what you've described. By types I mean e.g. constraining set() to just accept strings.
There's not a problem implementing this in ES6, I'm just wondering what is considered better here. I'm getting kinda tired of the "IDL is terrible and outside TC39 people design terrible APIs" sentiment :-) I guess this deserves a long answer. I think IDL is a necessary and important tool. At a TestTheWebForward event, I've seen a test suite based on a WebIDL parser and I thought it was one of the most brilliant idea regarding testing the ECMAScript representation of browser APIs. If that was the only reason for WebIDL to exist, that'd be enough to make WebIDL a good idea. Now, one problem that Alex Russel noted elsewhere is that some people who're used to C++ interfaces use WebIDL as a guideline to define new APIs. The bad use of the WebIDL tool is a problem. Not the tool in itself.
I don't necessarily think that TC39 does everything perfectly when it comes to APIs (second argument of Object.create someone?); I have myself provided lots of feedback on APIs for ES6. TC39 has proven to be receptive to feedback, for sure. I have much less experience with other standard bodies, so I couldn't compare.
so I figured I'd just go and ask since this is a new API and can be amended.
That's an initiative I encourage, because es-discuss (which contains many more people than TC39) is a cross-road of a lot of people using JavaScript, a good share caring a lot about clean, composable APIs.
If looking for advice on API design, I would also recommend asking the Node.js folks. Some read es-discuss. I don't know how to contact them for advice on JS API design, but I find most of their work on JavaScript APIs admirable. (I look forward to some interoperability between Node and web browser APIs, but I'm a dreamer)
I feel there are 2 different goals:
- Being able at all to implement browser APIs in ECMAScript (which is close to being possible with Proxies. document.all being an exception).
- Being able to conveniently implement browser APIs in ECMAScript (which may require the addition of a MultiMap) which is up for debate. I feel there's another one too, "make it JavaScript-y", though this is never described much in detail, although from what I got so far using Proxies is not considered good practice. That's why the URLQuery API has no getter/setter but instead has methods like get() and set().
I agree that proxies don't seem to be a good fit to emulate MultiMaps. Proxies are more like maps [1]. It would be awkward to distinguish append and set with proxies. I think I also agree to reduce the use of proxies magics as much as possible. I was citing proxies, because they're a major tool in closing the gap that separates ECMAScript and the platform APIs magic.
David
On Mon, Dec 17, 2012 at 9:19 AM, Anne van Kesteren <annevk at annevk.nl> wrote:
If down the road we want to allow for the theoretical possibility of having all platform APIs implemented in JavaScript, we might want a sync Object.observe. If we have types down the road as well (this might be a bit presumptuous), URLQuery could just be a MultiMap and whenever the MultiMap was mutated you'd update the associated URL synchronously (and potentially do other things, such as navigating to a URL). The latter would require synchronous change delivery. Or I suppose some kind of subclass that changes all the manipulation methods to also perform some kind of notification.
I guess the problems here are that a) there's no MultiMap b) there's no types c) we want to reduce the burdon on developers for doing URL manipulation probably without waiting for a/b, but I rather not pull a Typed Array.
First, I don't really see what types are doing in this discussion. Are you thinking about classes?
Second, by far the easiest way to accomplish all of this would be to
just make URLQuery
implement the same interface as a MultiMap (and a
Map). Then there's no need for any observation, or proxies, or any of
that, since Maps are carefully designed to be implementable entirely
in JS. As far as I can see, some deeper tying of URLQuery to a
platform-builtin MultiMap would accomplish potentially the following:
- making
instanceof
work in a few cases. - allowing use of MultiMap.prototype.get.call on a URLQuery.
- anything else?
I think instanceof
is irrelevant, and that functions that expect
MultiMaps or URLQuerys should just use the m.get()
of the value they
have, and not expect nominal typing. [1]
Oh, and by the way, I read infrequently.org/2012/12/reforming-the-w3c-tag/#comment-240289 the other day. 1) In so far there would be an organisation responsible for the URL work, it would be the WHATWG, not the W3C. 2) But really as far as the API of URLQuery goes, that's mostly arv, TabAtkins, and I. 3) If you have feedback on it, better to email it :-)
The point I was making in that comment is that one thing we on TC39 might want to do is provide more collection APIs so that you don't have to design them on the DOM side. I'm glad that you're talking to TC39 about the design you're doing.
[1] Item 57 in Effective JS. :)
hey Anne, Sam! Comments inline:
On Monday, December 17, 2012, Sam Tobin-Hochstadt wrote:
On Mon, Dec 17, 2012 at 9:19 AM, Anne van Kesteren <annevk at annevk.nl<javascript:;>> wrote:
If down the road we want to allow for the theoretical possibility of having all platform APIs implemented in JavaScript, we might want a sync Object.observe.
I don't think that's wise. Rafael (cc'd) can explain why in much more detail, but the gist of it is:
Object.observe() is a notification, not interception, mechanism. Where we need to stratify an intercept, ES 6 Proxies are the mechanism we should lean on, but in the main, we should ALWAYS seek to avoid using them. That is to say, if we must do magic (use proxies), we must do magic; however we should only arrive there after exhausting all other routes; both on the JS and DOM sides.
If we have types down the road as well (this
might be a bit presumptuous), URLQuery could just be a MultiMap and whenever the MultiMap was mutated you'd update the associated URL synchronously (and potentially do other things, such as navigating to a URL). The latter would require synchronous change delivery. Or I suppose some kind of subclass that changes all the manipulation methods to also perform some kind of notification.
In the shorter term, your constructor can vend a Proxy (using the mechanism Allen et. al. have devised on es-discuss and here recently).
I guess the problems here are that a) there's no MultiMap b) there's no types
The lack of typing strikes me as something we can use de-sugaring in WebIDL-generated JS to enforce. I.e., generate the JS that you'd need for type checking:
Imagine we have IDL for a function:
// IDL void takesOnlyStrings(DOMString arg, ...);
// JS de-sugaring function takesOnlyStrings(...args) { if(!args.every(function(i) { return typeof i == "string"; })) { throw new TypeError("..."); } // implementation goes here }
But if we consider a JS-only world, the typing discussion becomes a bit less obvious. The semantic I'd instead expect in most libraries is for them to call .toString() on the argument in question, not to reject with an error.
c) we want to reduce the burdon on developers for doing URL
manipulation probably without waiting for a/b, but I rather not pull a Typed Array.
First, I don't really see what types are doing in this discussion. Are you thinking about classes?
Second, by far the easiest way to accomplish all of this would be to just make
URLQuery
implement the same interface as a MultiMap (and a Map). Then there's no need for any observation, or proxies, or any of that, since Maps are carefully designed to be implementable entirely in JS. As far as I can see, some deeper tying of URLQuery to a platform-builtin MultiMap would accomplish potentially the following:
- making
instanceof
work in a few cases.- allowing use of MultiMap.prototype.get.call on a URLQuery.
- anything else?
I think
instanceof
is irrelevant, and that functions that expect MultiMaps or URLQuerys should just use them.get()
of the value they have, and not expect nominal typing. [1]Oh, and by the way, I read infrequently.org/2012/12/reforming-the-w3c-tag/#comment-240289 the other day. 1) In so far there would be an organisation responsible for the URL work, it would be the WHATWG, not the W3C. 2) But really as far as the API of URLQuery goes, that's mostly arv, TabAtkins, and I. 3) If you have feedback on it, better to email it :-)
The point I was making in that comment is that one thing we on TC39 might want to do is provide more collection APIs so that you don't have to design them on the DOM side. I'm glad that you're talking to TC39 about the design you're doing.
Agreed on both counts.
On Tue, Dec 18, 2012 at 4:32 PM, Alex Russell <slightlyoff at google.com> wrote:
Object.observe() is a notification, not interception, mechanism. Where we need to stratify an intercept, ES 6 Proxies are the mechanism we should lean on, but in the main, we should ALWAYS seek to avoid using them. That is to say, if we must do magic (use proxies), we must do magic; however we should only arrive there after exhausting all other routes; both on the JS and DOM sides.
It seems you either need to use a Proxy, some kind of wrapper method, or a custom implementation in most cases. Typically when objects akin to Map or Array are exposed in a platform API, mutating them has observable (synchronous) side effects.
Imagine we have IDL for a function:
// IDL void takesOnlyStrings(DOMString arg, ...);
// JS de-sugaring function takesOnlyStrings(...args) { if(!args.every(function(i) { return typeof i == "string"; })) { throw new TypeError("..."); } // implementation goes here }
But if we consider a JS-only world, the typing discussion becomes a bit less obvious. The semantic I'd instead expect in most libraries is for them to call .toString() on the argument in question, not to reject with an error.
That is what IDL specifies, fwiw.
On Dec 18, 2012, at 8:08 AM, Anne van Kesteren wrote:
On Tue, Dec 18, 2012 at 4:32 PM, Alex Russell <slightlyoff at google.com> wrote:
Object.observe() is a notification, not interception, mechanism. Where we need to stratify an intercept, ES 6 Proxies are the mechanism we should lean on, but in the main, we should ALWAYS seek to avoid using them. That is to say, if we must do magic (use proxies), we must do magic; however we should only arrive there after exhausting all other routes; both on the JS and DOM sides.
It seems you either need to use a Proxy, some kind of wrapper method, or a custom implementation in most cases. Typically when objects akin to Map or Array are exposed in a platform API, mutating them has observable (synchronous) side effects.
strawman:object_model_reformation proposes a mechanism that would support defining such object behaviors without requiring the full magic of Proxy.
On Tue, Dec 18, 2012 at 6:01 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
It seems you either need to use a Proxy, some kind of wrapper method, or a custom implementation in most cases. Typically when objects akin to Map or Array are exposed in a platform API, mutating them has observable (synchronous) side effects.
strawman:object_model_reformation proposes a mechanism that would support defining such object behaviors without requiring the full magic of Proxy.
I think we're talking past each other or I might be misunderstanding. Lets say that hypothetically Map is sufficient for URL query parameters and we do not need a MultiMap for its semantics. We have a URL object and you can get to its query parameters using URL.prototype.query. That property cannot point directly to a Map because when I perform an operation on that Map, say query.delete("x"), not only should "x" be deleted from the Map, it should also be removed from URL's string synchronously. The latter does not seem possible using Map directly, but such a pattern is found all over.
(That delete()'s and friends argument needs to be stringified is another reason of course that makes it hard to reuse native types directly.)
On Dec 18, 2012, at 1:36 PM, Anne van Kesteren wrote:
On Tue, Dec 18, 2012 at 6:01 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
It seems you either need to use a Proxy, some kind of wrapper method, or a custom implementation in most cases. Typically when objects akin to Map or Array are exposed in a platform API, mutating them has observable (synchronous) side effects.
strawman:object_model_reformation proposes a mechanism that would support defining such object behaviors without requiring the full magic of Proxy.
I think we're talking past each other or I might be misunderstanding. Lets say that hypothetically Map is sufficient for URL query parameters and we do not need a MultiMap for its semantics. We have a URL object and you can get to its query parameters using URL.prototype.query. That property cannot point directly to a Map because when I perform an operation on that Map, say query.delete("x"), not only should "x" be deleted from the Map, it should also be removed from URL's string synchronously. The latter does not seem possible using Map directly, but such a pattern is found all over.
(That delete()'s and friends argument needs to be stringified is another reason of course that makes it hard to reuse native types directly.)
Several, observations
-
yes, we probably are talking past each other, sorry...
-
To me, we are also talking about OO design esthetics and we may well be applying different esthetics.
As you now more fully describe the situations, I would say, that a "query object" certainly should not be just a Map or any other basic collection style object. The reason is that the primary role of any collection is to hold and provide structured access (in some specific manner) to a collection of data. (see www.wirfs-brock.com/PDFs/Characterizing Classes.pdf ). As soon as you starting adding domain specific semantics to a collection you are giving it a very different role. Probably that of a service provider or coordinator.
In this case, (and without really knowing your actual requirements for a URL query parameter object) I would say the value provided by URL.prototype.query should be an instance of the URLQuery class (or would URLFilter or URLInspector be a better name) whose primary responsibility is to support structured manipulation/inspection of a URL. It may present a Map-like interface for accomplish for performing these manipulations. But that collection style interface is simply a secondary (although perhaps very convenient) characteristic of the object.
When implementing a URLQuery (or whatever it's called) object in JavaScript you might well choose to encapsulate a regular Map object as part of the URLQuery internal state. In that case, you would probably implement the Map interface on URLQuery by some sort of wrapper method delegating to the encapsulated Map instance. But the wrapper methods would also do other things, like updating the URL string.
-
When initially setting a query on an URL, it may be convenient to pass the parameters for initialize the query object as a regular Map or other generic collection. That's fine, as long as it is understood that what is being passed is only initialization parameters and that the actual query object will be a new object (probably of a different domain specific kind) rather than the Map that was originally passed.
-
I agree, this pattern is found all over and in all kinds of applications. Arguably it is at the essence of what object-oriented design is all about. When do you reuse a canned general purpose object and when do you need to introduce a new application domain specific special-purpose kind of object.
Many years ago I coined a phrase for a style guideline for Smalltalk programmers. It was "never use an Array when an Object will do". The basic idea is still the same, and it really applies to all generic collection objects and all languages. The basic message is don't place application domain specific behavior into general purpose collection objects and conversely don't use a general purpose collection where you need to have domain specific behavior. Use a domain object (eg, URLQuery) in situations like this.
If down the road we want to allow for the theoretical possibility of having all platform APIs implemented in JavaScript, we might want a sync Object.observe. If we have types down the road as well (this might be a bit presumptuous), URLQuery could just be a MultiMap and whenever the MultiMap was mutated you'd update the associated URL synchronously (and potentially do other things, such as navigating to a URL). The latter would require synchronous change delivery. Or I suppose some kind of subclass that changes all the manipulation methods to also perform some kind of notification.
I guess the problems here are that a) there's no MultiMap b) there's no types c) we want to reduce the burdon on developers for doing URL manipulation probably without waiting for a/b, but I rather not pull a Typed Array.
Similarly, there's an idea to expose the path of a URL as an array of segments. Ideally this would be a type-constrained Array that when manipulated updates the associated URL.
Oh, and by the way, I read infrequently.org/2012/12/reforming-the-w3c-tag/#comment-240289 the other day. 1) In so far there would be an organisation responsible for the URL work, it would be the WHATWG, not the W3C. 2) But really as far as the API of URLQuery goes, that's mostly arv, TabAtkins, and I. 3) If you have feedback on it, better to email it :-)
URLQuery: url.spec.whatwg.org/#urlquery