I18n - defining format() and compare() as getters to simplify usage
I can imagine doing this for Collator.prototype.compare because Array.prototype.sort is such a common use case for it, but why for the format() methods? We don't want to impose the overhead of creating a bound function on each call to format() unless there's a good reason...
Of course, doing it for one and not the other is somewhat inconsistent. And since ES 5 has Function.prototype.bind (implemented in the leading browsers except Safari), it's not hard to bind compare() without library support: myArray.sort(collator.compare.bind(collator));
Norbert
- јануар 2012. 15.50, Norbert Lindenberg <ecmascript at norbertlindenberg.com
је написао/ла:
I can imagine doing this for Collator.prototype.compare because Array.prototype.sort is such a common use case for it, but why for the format() methods?
I heard a couple of reasons for format methods to be bound:
- Can be passed as functions, thus hiding the object details
- Makes it symmetrical to compare (in case we follow Allen's advice)
- No binding gotchas for users
We don't want to impose the overhead of creating a bound function on each call to format() unless there's a good reason...
Would caching first one resolve the overhead, like so (not sure about syntax):
NumberFormat.prototype = { get format(date) { var that = this; if (that.__bound === undefined) that.__bound = function(a) { uses that; return a < b }; return that.__bound; } }
Of course, doing it for one and not the other is somewhat inconsistent. And
since ES 5 has Function.prototype.bind (implemented in the leading browsers except Safari), it's not hard to bind compare() without library support: myArray.sort(collator.compare.bind(collator));
Yes, we should probably pick one or the other - go jQuery route and force developers to .bind manually, or any other library where they either bind for the users or provide two versions of the same method (one bind the other one not).
It seems that both ways of solving binding issues are present in the real world, but we should resolve the issue so we can finalize the spec (and implementations).
Norbert
On Jan 31, 2012, at 4:23 PM, Nebojša Ćirić wrote:
- јануар 2012. 15.50, Norbert Lindenberg <ecmascript at norbertlindenberg.com> је написао/ла: I can imagine doing this for Collator.prototype.compare because Array.prototype.sort is such a common use case for it, but why for the format() methods?
I think there is a straightforward design rule to apply in deciding whether such a property should be a unbound method or a bound function.
If the value of the property is a function that is dependent upon the state of its access object and the function is likely to be routinely invoke as a standalone function then use a bound (or otherwise closed over the object) function as the property value. More simply, if the property is routinely accessed with an expression like: obj.prop then it should be a bound function. If it is routinely accessed with an expression like: obj.prop() then it can be a normal method.
We have a good use case for Collator.compare that strongly suggests it should be bound: array.sort(col.compare)
Do we have comparable use cases for Formatter.format?
I heard a couple of reasons for format methods to be bound:
- Can be passed as functions, thus hiding the object details
would this be routinely done? If so it is a strong use case for a bound function displayDate(dt, mdyFmtr.format)
- Makes it symmetrical to compare (in case we follow Allen's advice) symmetry is only important if the use cases of the two functions are similar. I assume that booth Collator and Formatter have plenty of properties that are normally invoked as regular methods.
- No binding gotchas for users
There are always gotchas if you try to dissociate a function that has (explicit or implicit,if built-in) this reference dependencies. That is the reason for the above design rule. Retrieving a bound function is a different logical operation than extracting a method implementation as an reflective operation.
We don't want to impose the overhead of creating a bound function on each call to format() unless there's a good reason...
Would caching first one resolve the overhead, like so (not sure about syntax):
Yes, you would definitely want to memoize the function on first access or perhaps even upon object creation.
NumberFormat.prototype = { get format(date) { var that = this; if (that.__bound === undefined) that.__bound = function(a) { uses that; return a < b }; return that.__bound; } }
Yes, logically. However a built-in implementation would use a private internal state variable [[CompareFunction]] instead of a regular property. An ES6 implementation in ECMAScript would use a private name property.
Alternatively, you could simply create the bound function when you instantiate the Format instancet object and make it the value of the format (in this example) own property of the new instance. However, that technique would create issues if anybody ever wants to do prototypal inheritance from such an instance. For that reason, I would stick with the accessor on the prototype pattern.
Of course, doing it for one and not the other is somewhat inconsistent. And since ES 5 has Function.prototype.bind (implemented in the leading browsers except Safari), it's not hard to bind compare() without library support: myArray.sort(collator.compare.bind(collator));
Do you really want a naive HTML coder to have to remember this pattern?
Yes, we should probably pick one or the other - go jQuery route and force developers to .bind manually, or any other library where they either bind for the users or provide two versions of the same method (one bind the other one not).
For these cases, I don't see why you would need the unbound method. Such a unbound method is really only useful it you are going to move it to another object and this isn't any particular reason why that should be expected to work in this scenario. Even though the function is bound you can still say:
col.compare(a,b)
if you want to invoke it using a method invocation style
I have updated the specification such that Collator.prototype.compare is now a getter that returns a bound function, which is cached by the first call to the getter:
- If the [[boundCompare]] internal property of this Collator is undefined, then: a. Let that be this. b. Let bc be a function that takes the arguments x and y and performs the following steps: i. Return the result of calling the [[Compare]] internal method of that with arguments x and y. c. Set the [[boundCompare]] internal property of this Collator to bc.
- Return the value of the [[boundCompare]] internal property of this Collator.
[[Compare]] is what used to be Collator.prototype.compare in previous specification drafts.
The [[Set]] attribute is undefined.
I have no evidence that the format() methods are "likely to be routinely invoked as a standalone function", so I haven't changed those functions.
Norbert
We (i18n group) mentioned slight problem with Collator.compare() method to Allen at the last i18n meeting.
The problem is that you can't do:
var col = new Intl.Collator(...); array.sort(col.compare);
because of the binding loss.
Allen proposed something like this as a possible solution (typing from memory):
Collator.prototype = { get compare(a, b) { var that = this; return function(a, b) { uses that; return a < b }; } }
It seems that all major JS libraries*, except jQuery, try to help user not to stumble on this problem. Should we include this into spec?
I think that if we do it for compare() method, we should then do it for format() methods too, since that would allow user to pass functions around instead of objects.
This should not apply to supportedLocalesOf functions.
What do you think?