Closed Bug 304828 Opened 16 years ago Closed 16 years ago

Array extras mislocated -- move to Function.prototype? Want Generics!

Categories

(Core :: JavaScript Engine, defect, P1)

x86
Windows Server 2003
defect

Tracking

()

VERIFIED FIXED
mozilla1.8beta4

People

(Reporter: felipe, Assigned: brendan)

References

Details

(Keywords: js1.5, verified1.8, Whiteboard: [ETA 8/26])

Attachments

(4 files)

User-Agent:       Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6
Build Identifier: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6

I wanted to file this before FF 1.5 was released. Array.prototype.map, while a
good idea, breaks standard OOP linguistic semantics and should be renamed before
much code is built on it.

Consider the following:
"Reverse this array."  -> someArray.reverse()
"Split this array using ','." -> someArray.split(/,/)
"Concatenate this array with another." -> someArray.concat(otherArray)
"Capitalize this string." -> someString.toUpperCase()

Notice that in each of these examples, the direct object of the sentence is the
object whose method is called, while any indirect object (or object of
preposition) is a parameter to the method.

So, following the same conventions, what should happen is:
"Map this function onto this array." -> someFunction.map(someArray);

someArray.map(someFunc), according to standard OOP linguistic semantics, says:
"Map this array onto this function."

Reproducible: Always

Steps to Reproduce:




I propose renaming Array.prototype.map to "process" or something similar. Then
it is:

"Process this array with this function." -> someArray.process(someFunc);

This is time-critical; the sooner it is resolved, the less code will be written
with the counter-intuitive naming. Certainly since there hasn't even been a
beta, it should be fine to rename this method.
->Core/js engine
Assignee: nobody → general
URL: none
Component: General → JavaScript Engine
Product: Firefox → Core
QA Contact: general → general
Version: unspecified → Trunk
I believe that the name "map" was taken from lisp roots, where one would call
(map someFun someArray). So in that sense, the name makes sense.
The argument is that "standard OOP linguistic semantics" require verb-named
methods to take the |this| parameter (self, the receiver, etc.) as the object of
the verb phrase, but map's object is the function.

In Lisp, Scheme, etc., map is a higher order function (function taking function
parameter) and so does not have a |this| parameter.  Indeed Lisp-hy map takes
the function to apply as its first parameter, and the list over which to map as
its second.  So Lisp ain't "standard OOP" -- good point.

I'm sympathetic to the bug, but not to the proposed new name.  "process" is way
too broad and vague.  For those in the know, "map" connotes the Lisp-ish meaning
we want, if you overlook the English-linguistic objection raised here.

Ideas for better names?  What do Python, Ruby, et al. call their higher order
map-like functions, if they are expressed as OOP-ish methods?

In the end, I think we'll be frustrated by making map be an Array method.  Would
we not be better off with a generic map global function that can operate on any
array-like object?  The problem then is global namespace pollution, but perhaps
we can solve that otherwise.

/be
Status: UNCONFIRMED → NEW
Ever confirmed: true
How about moving map from Array.prototype to Function.prototype?

If you do this, though, I'd suggest that the thisObj argument would have to be
first to be parallel with call() and apply().

Similar arguments apply for the other Array Extras except indexOf and lastIndexOf.  
Shouldn't forEach(), filter(), some() and every() really be methods of Function?

And while I'm making crazy suggetions, how about an Array.prototype.addAll so that
a.addAll(b) works like Array.prototype.push.apply(a,b)
(In reply to comment #4)

> Similar arguments apply for the other Array Extras except indexOf and
lastIndexOf.  
> Shouldn't forEach(), filter(), some() and every() really be methods of Function?

YES!

/be
Assignee: general → shaver
This is an API change we need to get right and document, now.  Since I failed to
catch this when reviewing, I'll take the bug.

David Flanagan: I'll defer addAll, noting that your use of apply is sufficient,
and wondering whether concat wouldn't be wanted more often (to preserve the two
arrays "in" parameters).

/be
Assignee: shaver → brendan
Flags: blocking1.8b4+
Priority: -- → P1
Summary: Array.prototype.map is mis-named → Array extras mislocated -- move to Function.prototype
Target Milestone: --- → mozilla1.8beta4
Let me suggest that you reconsider the function names when you move them from
Array.prototype to Function.prototype.  In particular, I don't find some() and
every() very mnemonic.
some and every come from Common Lisp.  However, the current implementation does
not (because these are Array methods) deal with parallel processing of multiple
sequence arguments.  Moving these to Function.prototype and retaining the Common
Lisp names begs for that change, too.  Now we are far down the slippery slope!

/be
As this bug gets discussed, please bear in mind the usefulness of such code as:

var formVals = document.getElementsByTagName('input')
.filter(function(i) { return (i.type == 'text'); })
.map(function(i) { return i.value; })
;

If anything does change in the current JS extras, it should be done to preserve
the ability to write highly concise, useful code like this.
In response to the "some"/"every" naming question:

I thought "some" and "every" were funky names, too. What about "logicalOr" and
"logicalAnd", since that's literally what's being done? Highly intuitive and
easy to remember.
(In reply to comment #3)
> Ideas for better names?  What do Python, Ruby, et al. call their higher order
> map-like functions, if they are expressed as OOP-ish methods?

In Ruby, Array class has the following instance methods:
grep (similar to Array.prototype.filter)
each (similar to Array.prototype.forEach)
map (similar to Array.prototype.map)
(In reply to comment #9)
> As this bug gets discussed, please bear in mind the usefulness of such code as:
> 
> var formVals = document.getElementsByTagName('input')
> .filter(function(i) { return (i.type == 'text'); })
> .map(function(i) { return i.value; })
> ;
> 
> If anything does change in the current JS extras, it should be done to preserve
> the ability to write highly concise, useful code like this.

How does that work today?  I see "undefined" alerted by this on the current bug
page: javascript:var t = document.getElementsByTagName('a');alert(t.forEach) --
and alert(t instanceof Array) in a similar URL shows "false".

It's easy to transpose operands from the inside out in what you wrote:

var formVals =
  (function (i) { return i.value; }).map(
    (function (i) { return i.type == 'text'; }).filter(
      document.getElementsByTagName('input')
    )
  )
;

although this is less clear, I agree -- but it's not less concise.

Sounds like Ruby commits the same verb/object solecism as the current Array
extras do.  OOP special-casing of |this| (self, whatever) is hurting us here. 
Generic functions would avoid that, and then you could write

var formVals = map(function(i){return i.value},
                   filter(function(i){return i.type == 'text'},
                          document.getElementsByTagName('input')));

Would this not be most concise and clear of all?

/be
If you're going to make them functions rather than methods (and I agree that
this might be an attractive option) how about making them properties of the
Function constructor.

So the example becomes:

var formVals = Function.map(function(i){return i.value},
                   Function.filter(function(i){return i.type == 'text'},
                          document.getElementsByTagName('input')));

This seems to improve clarity to me.  Although Function.filter might be better
as Array.filter...

So Function.prototype.apply set a bad precedent, and F.p.call is at best an
optimization:

f.call(thisObj, arg1, ... argN) === f.apply(thisObj, [arg1, ... argN])

In bootstrapping Narcissus (JS in JS,
http://lxr.mozilla.org/mozilla/source/js/narcissus/) we have wanted apply to be
an operator that composes with new, so one can invoke a constructor given an
argument list.

The some/every perplex also brings to mind operators such as Perl 6's junctions.

JS should not be a slave to the Java-ish OOP conventions it appropriated.

/be
(In reply to comment #13)

> var formVals = Function.map(function(i){return i.value},
>                    Function.filter(function(i){return i.type == 'text'},
>                           document.getElementsByTagName('input')));
> 
> This seems to improve clarity to me.  Although Function.filter might be better
> as Array.filter...

These are JS1 poor-man's namespaces, and mostly a matter of mnemonic taste and
good sense, but they make things quite a bit more verbose.  They do build on
existing precedent (String.fromCharCode, Date.parse).

I'm still looking for a solution that satisfies all goals, though, without too
much verbosity or OOP/FP mismatch.

/be
I like the idea of moving these methods to Function.prototype. But more
importantly, I agree with comment 0: this is time critical. I would suggest that
you take your time to find the best solution but disable the methods immediately
to make it quite clear that they won't be included in their current form in
Firefox 1.5 (see the current mozillaZine front page).
(In reply to comment #12)
> (In reply to comment #9)
> > As this bug gets discussed, please bear in mind the usefulness of such code as:
> > 
> > var formVals = document.getElementsByTagName('input')
> > .filter(function(i) { return (i.type == 'text'); })
> > .map(function(i) { return i.value; })
> > ;
> > 
> > If anything does change in the current JS extras, it should be done to preserve
> > the ability to write highly concise, useful code like this.
> 
> How does that work today?  I see "undefined" alerted by this on the current bug
> page: javascript:var t = document.getElementsByTagName('a');alert(t.forEach) --
> and alert(t instanceof Array) in a similar URL shows "false".
> 

I neglected to wrap my custom-made "makeArray" function around
getElementsByTagName in my example; excuse me.


> Sounds like Ruby commits the same verb/object solecism as the current Array
> extras do.  OOP special-casing of |this| (self, whatever) is hurting us here. 
> Generic functions would avoid that, and then you could write
> 
> var formVals = map(function(i){return i.value},
>                    filter(function(i){return i.type == 'text'},
>                           document.getElementsByTagName('input')));
> 
> Would this not be most concise and clear of all?
> 
> /be

I think "map" is too general a name for a global function. I propose making
"map" a Function method and coming up with a new name for the Array method (that
would just use the same code).

The real downside to a global function, besides namespace pollution, is that it
breaks the elegant OO "feel" of javascript. Even non-programmers are very used
now to thinking "object-oriented"; modern GUIs with right-click menus work the
same way as OO code: the menu shows a list of "properties" and "methods" for
whatever "object" you right-clicked on.

Re my suggestion of "process" - I agree that it's very general, and I've been
pondering what a better name might be. "iterate" could work, except that that
doesn't imply returning a "processed" array. Perhaps "transform"? "alter"?

The concept of starting with an array (as object) and ending with another one
is, I think, clearer and more intuitive than starting with a function and ending
with an array.
We should remember to tell these guys when we change everything around:

http://www.webreference.com/programming/javascript/ncz/column4/
The extras were also reported on Slashdot....
Felipe, a some quick points:
- OOP is not always and everywhere elegant.  It's a style that can be used where
it doesn't make sense.
- I don't propose to put the name "map" into the global namespace, of course. 
See the end of comment 3 ("perhaps we can solve that otherwise").
- The fact that you had to make a DOM NodeList delegate to Array.prototype says
to me that most of the generic methods on Array (and on String) should be more
easy to use on any array-like object (one with length counting elements).

In JS2 there will be support for packages, which help isolate names of functions
from one another and put the user in charge of global (or other) name pollution.
 The current candidate syntax would require users to import from a package.
That's something we won't have implemented for Firefox 1.5, so whatever we do
will have to be kept around for backward compatibility.

I don't think we should withdraw these methods, since they provide much-wanted
functionality that's now actually used.

/be
An alternative to a package requiring an import is to put these functions in a
namespace, requiring either ::-qualified usage, or a use namespace statement. 
So the code in comment 9 would become either:

var ns = lisp_higher_order_functions;
var formVals = ns::map(function(i){return i.value},
                       ns::filter(function(i){return i.type == 'text'},
                                  document.getElementsByTagName('input')));

or

use namespace lisp_higher_order_functions;

var formVals = map(function(i){return i.value},
                   filter(function(i){return i.type == 'text'},
                          document.getElementsByTagName('input')));

I used an ugly_long_winded namespace name.  Suggestions for a better name, and
any comments at all, are welcome.

For 1.5, I'm tempted to use Function and Array as poor-man's namespaces as David
proposed.  Work on Edition 4 is about to take off and occupy much of my time,
and I'll make sure these methods get some attention, so we'll have to migrate
and keep compatibility no matter what we do.

If someone thinks we should just leave the methods in Array.prototype, please
argue against a quick cleanup patch for Firefox 1.5.

/be
All right, here is what I propose to do, in full, for Mozilla 1.8 / Firefox 1.5:

1.  Leave the Array extras where they are, but (conceptually -- actual layering
may be the other way around) reimplement them in terms of these new Array static
methods: every, filter, forEach, indexOf, lastIndexOf, map, and some.

2.  Factor generic "static methods" from the following *intentionally generic*
(search for that phrase in ECMA-262 Edition 3) methods of Array.prototype,
conceptually re-implementing the prototype methods in terms of their static
counterparts: concat, join, pop, push, reverse, shift, slice, sort, splice, and
unshift.

3.  Ditto for String.prototype generic methods: factor generic functions from
them provided as "static methods", effectively reimplementing the OOP forms in
terms of the new functions.  The generic String.prototype methods are: charAt,
charCodeAt, concat, indexOf, lastIndexOf, localeCompare, match, replace, search,
slice, split, substring, toLowerCase, toLocaleLowerCase, toUpperCase,
toLocaleUpperCase, and substr.

4.  Agonize over map being an Array method instead of Function.  Contemplate
adding redundant ways to call that favor the function argument over the
array-like argument both for OOP-ness and to use the better poor-man's namespace.

5.  Agonize over forEach vs. every -- wouldn't each go better with every (and
some; and make Ruby hackers' lives easier)?  I'm less perturbed by the Lisp-y
conciseness of every and some.

This may seem like overkill.  I argue that it's better to be consistent and make
all the *intentionally generic* prototype methods available as generic
functions, than to pick and choose.

One could argue (similar to my argument against doing addAll, see comment 6)
that Function.prototype.apply should be used to invoke generic methods on other
types of objects than the generic method's "home type", but (a) addAll has
distinctly different trade-offs, and (b) two wrongs don't make a right.

I think OOP-think led us away from generality and conciseness in making generic
methods instead of functions.  Selectively appealing to minimalism after this
non-minimal OOP-think mistake should not prevent us from fixing the mistake. 
And JS is non-minimal in its built-in methods anyway (consider slice, substring,
substr).

Comments?

/be
I fail to see why anything but "map" would be relocated anyway. "filter"
particularly is in the right place: "Filter (elements of) this array using that
function." None of the other extras is a verb-named method, which was
established in comment 3 as being the issue at hand.

This being said, I'm all for renaming "every" and "some". These are hard names
to remember, IMO.

The idea of static methods seems to me a bit overy perl-ish in its
TMTOWTDI-ness. I'm not quite sure why one would *choose* to call
Array.join(anArray, glue) rather than anArray.join(glue); the former is wordier
and less clear. And since if it can happen it probably will, ultimately this
leads to JS being a language made harder to maintain by a greater number of ways
to get something done and longer documentation.
(In reply to comment #23)
> I fail to see why anything but "map" would be relocated anyway.

No relocation, generic-ization.

> "filter"
> particularly is in the right place: "Filter (elements of) this array using that
> function."

But it's in the wrong place if you want to operate on a DOM NodeList without
having to hack its prototype chain.

Hence the idea of static methods.

> None of the other extras is a verb-named method, which was
> established in comment 3 as being the issue at hand.

I morphed your bug, sorry.  I think you have only scratched the surface in
objecting to the map misnomer.  The mandatory OOP-ness is the underlying bug.

To make up for that morphing, how about this added proposal:

6. Rename map to collect (the original impetus for this bug was the map
misnomer).  See http://www.rubycentral.com/ref/ref_m_enumerable.html#collect for
Ruby precedent.

> This being said, I'm all for renaming "every" and "some". These are hard names
> to remember, IMO.

Harder than "each"?

> The idea of static methods seems to me a bit overy perl-ish in its
> TMTOWTDI-ness. I'm not quite sure why one would *choose* to call
> Array.join(anArray, glue) rather than anArray.join(glue);

Because anArray is not an instance of Array.

Remember, per ECMA-262, these are intentionally generic methods.

> the former is wordier
> and less clear.

Than what?  The relevant comparison is not what you showed, but the NodeList
counterexample you've hacked.  Why should everyone wanting to use one of these
generics on a NodeList, or on any array-like object (even a string!), have to
reinvent those hacks?

> And since if it can happen it probably will, ultimately this
> leads to JS being a language made harder to maintain by a greater number of
> ways to get something done and longer documentation.

Redundant forms does not make maintenance harder if they layer on a common
implementation, as they should.

/be
Ruby again: Array.map comes from Array including the Enumerable module, which is
generic.

I do not want to add modules or generics as a distinct type facility to JS1.x,
but I do wish to make allowed (see ECMA-262 Edition 3 Chapter 16) extensions
exposing generics without the mandatory-OOP-ness that requires ugly prototype
hacking or .apply usage.  And I definitely want to do this here, in this bug,
rather than just rename map or move generics from one relatively narrow
prototype to another.

/be
> > "filter"
> > particularly is in the right place: "Filter (elements of) this array using that
> > function."
> 
> But it's in the wrong place if you want to operate on a DOM NodeList without
> having to hack its prototype chain.

AHHHH....ok, this just made sense. Yes, not having to do makeArray(someNodeList)
would be nice.


> 6. Rename map to collect (the original impetus for this bug was the map
> misnomer).  See http://www.rubycentral.com/ref/ref_m_enumerable.html#collect for
> Ruby precedent.

How does "collect" describe mapping a function onto an array?

> 
> > This being said, I'm all for renaming "every" and "some". These are hard names
> > to remember, IMO.
> 
> Harder than "each"?

Well, harder than "logicalOr" and "logicalAnd". :) "each" is worse yet, since
that's usually used as an iterator.
(In reply to comment #26)
> How does "collect" describe mapping a function onto an array?

Since this is arr.prototype.collect(fun) or Array.collect(arrlike, fun), the
verb and object are "collect" and "function-mapped values" (in a new array).

It's all about the object of the verb: you map a function over an array, or you
collect mapped values from an array into another array.

> > > This being said, I'm all for renaming "every" and "some". These are hard
> > > names to remember, IMO.
> > 
> > Harder than "each"?
> 
> Well, harder than "logicalOr" and "logicalAnd". :) "each" is worse yet, since
> that's usually used as an iterator.

I didn't mean to propose each as another name for every or some, only to compare
it (shaver chose forEach, but each matches every nicely).  It's true that every
and some are somewhat more terse than the Java-influenced method names in JS,
but logicalAnd and logicalOr are long without differing except in the last two
or three chars.  Still looking for better names.

/be
At the time, I considered names like

visit for forEach
allMatch for every
anyMatch for some

visit seemed too computer-science, but it has the virtue of not colliding with
E4X's new "for each" construction.  (I think it is similar to "for each" in
behaviour, though, so maybe that's a good collision.  I also considered a
tryEach variant that swallowed exceptions thrown or propagated by the provided
function, because I found myself using that construct a lot when doing things
like calling fallible observers, the failure of one of which should not prevent
others from being called.)

allMatch and anyMatch were mainly considered because there was a fair bit of
conceptual distance between "every" and "every one of the elements in this array
will product a true value when passed to this function".  But I didn't like
"match" much better than the implication, so I followed Lisp.

I actually considered making "map" a member of Function.prototype, but decided
that it was close enough to Array.prototype.sort ("sort this array according to
the provided function") or even String.prototype.split ("split this array
according to the provided regexp").  I wish I could find the IRC conversation I
had with someone (vlad?) about this, to recount here.

I disagree with the reasoning in comment 0, perhaps predictably: "map this array
according to the provided function" seems more natural an interpretation to me,
given that we don't concat this array _onto_ the provided one, or split the
string _onto_ the regexp.  We perform the operation named by the method _with_
the argument, or according to the rules specified by it (in the split case).  I
think you really do map a set (array), and that the function is a detail, but
maybe that's just because I see the data as primary in this operation, and not
the function. I would reason on the basis of starting with one set of data and
wanting another, and construct the function to perform the transformation I
need, rather than having a function and finding a set to map with it.  (Again,
analogous to sorting an array, or splitting a string: the rules for the
operation are secondary to the object on which the operation is performed, and
therefore the object gets to be the "subject", the honoured LHS of the .-operator.)

I'm out sick today, or at least until this maybe-food-poisoning clears, but I'll
try to watch this bug for action.
In response to comment #26, I want to withdraw the objection I expressed in
comment #7 to the names "every" and "some".  I have since realized that these
functions are intended for use as the condition of an if statement or while
loop, and in that context, they are clear and concise.  Much clearer, actually
than logicalAnd() and logicalOr().

Using the generic version Brendan is now proposing, I could write this:

if (Array.every(array, predicate)) 

this tests if every element in the array satisfies the predicate.

And to see if any elements in the array satisfy the predicate:

if (Array.some(array, predicate))

If anything any() might be better than some().

In any case, now that I've figured out how these functions are intended to be
used, I think they are well-named.
The patch will be easy to do, but the issue needs to be resolved to the
satisfaction of crucial JS landholders, at least within the Mozilla world if not
in ECMA TG1.  Again, we can carry extensions to ECMA forever, if they are cheap
to maintain (as these will be).

/be
Keywords: js1.5
Whiteboard: [ETA 8/26]
Summary: Array extras mislocated -- move to Function.prototype → Array extras mislocated -- move to Function.prototype? Want Generics!
(In reply to comment #29)
> Using the generic version Brendan is now proposing, I could write this:
> 
> if (Array.every(array, predicate)) 
> 
> this tests if every element in the array satisfies the predicate.

Again, just to be super-clear, I want to leave the Array.prototype OOP methods
too, so if array above satisfies (array instanceof Array), that could be better
written:

if (array.every(predicate))

Acknowledged that any is slightly better than some, but (a) Lisp precedent helps
slightly in the other direction; (b) any is a Perl 6 junction and I'd like not
to use that name.

/be
The .NET Generics collection has:

List.TrueForAll(array, predicate)

http://msdn2.microsoft.com/library/kdxe4x4w.aspx

(Array isa List)
No collect synonym, no forEach=>each renaming, just static method entry points
for the generic prototype methods that take the |this| param of the prototype
method as the first param, and shift the other params (if any) over one
position.

Footprint hit on gcc 3.4.4 (FC3): 4512 bytes.  A single native that dispatches
based on name could save more space, at the cost of runtime.  It would add some
space, so the savings would be < 4K.  Depending on how it dispatches, it might
actually lose.	Anyway, I think 4K is acceptable.  Anyone disagree?

I'm seriously considering David suggestion via email to bump our "JS version"
number to 1.6.

/be
Attachment #193868 - Flags: superreview?(shaver)
Attachment #193868 - Flags: review?(mrbkap)
Attachment #193868 - Flags: approval1.8b4+
Comment on attachment 193868 [details] [diff] [review]
Patch as promised

sr=shaver, thanks for taking this.
Attachment #193868 - Flags: superreview?(shaver) → superreview+
Comment on attachment 193868 [details] [diff] [review]
Patch as promised

>Index: jsstr.c
>+    {"fromCharCode",    str_fromCharCode,                 1,0,0},
>+
>+#if JS_HAS_TOSOURCE
>+    {"quote",               str_static_quote,             1,0,0},
>+#endif

str_fromCharCode doesn't line up correctly.

r=mrbkap with that nit picked.
Attachment #193868 - Flags: review?(mrbkap) → review+
Shaved down to a 1434-byte increase:

$ diff {old,newer}sz
2c2
<  546066	  16796     240  563102   8979e libmozjs.so
---
>  547048	  17248     240  564536   89d38 libmozjs.so
$ cat newersz
   text    data     bss     dec     hex filename
 547048   17248     240  564536   89d38 libmozjs.so

/be
Attachment #194118 - Flags: superreview?(shaver)
Attachment #194118 - Flags: review?(mrbkap)
Attachment #194118 - Flags: approval1.8b4+
Comment on attachment 194118 [details] [diff] [review]
patch for smaller footprint increase, via generic native method dispatcher

Is it safe to presume that the JSFunctionSpec will live after we return from
JS_DefineGenericFunctions?  We don't require that for JS_DefineFunctions, so
people might find it surprising.  (Though we do require that fs->extra be
appropriately long-lived.)

We should at least document that requirement in jsapi.h.  Embedders who can't
live with it can roll their own.

sr=shaver with addition to jsapi.h's comment.
Attachment #194118 - Flags: superreview?(shaver) → superreview+
Comment updated:

+/*
+ * Define generic, arity N+1 methods on the class constructor whose prototype
+ * is obj, based on their arity N native counterparts specified by fs.
+ *
+ * E.g., to expose a generic Array.prototype.join method, whose native
function+ * is array_join, as an Array.join static method, include
+ *
+ *    {"join", array_join, 2, 0, 0}
+ *
+ * in fs, and pass Array.prototype (or any instance delegating to it) as obj.
+ * This is done for all generic Array and String methods, to relieve callers
+ * who pass non-instance objects that conform to the generics' requirements
+ * from having to use Function.prototype.apply or .call.
+ */
+extern JS_PUBLIC_API(JSBool)
+JS_DefineGenericMethods(JSContext *cx, JSObject *obj, JSFunctionSpec *fs);

/be
I'm having a dumb spell: how does that comment indicate the lifetime
requirements for fs?
Oops, copied and pasted from an old diff.  Here's the latest:

+/*
+ * Define generic, arity N+1 methods on the class constructor whose prototype
+ * is obj, based on their arity N native counterparts specified by fs.
+ *
+ * E.g., to expose a generic Array.prototype.join method, whose native
function+ * is array_join, as an Array.join static method, include
+ *
+ *    {"join", array_join, 2, 0, 0}
+ *
+ * in fs, and pass Array.prototype (or any instance delegating to it) as obj.
+ * This is done for all generic Array and String methods, to relieve callers
+ * who pass non-instance objects that conform to the generics' requirements
+ * from having to use Function.prototype.apply or .call.
+ *
+ * NB: The storage at fs must live as long as these methods live; typically
+ * fs is a static, initialized array of JSFunctionSpec.
+ */
+extern JS_PUBLIC_API(JSBool)
+JS_DefineGenericMethods(JSContext *cx, JSObject *obj, JSFunctionSpec *fs);

/be
Comment on attachment 194118 [details] [diff] [review]
patch for smaller footprint increase, via generic native method dispatcher

r=mrbkap
Attachment #194118 - Flags: review?(mrbkap) → review+
(In reply to comment #36)
> Created an attachment (id=194118) [edit]
> patch for smaller footprint increase, via generic native method dispatcher

> +    /*
> +     * Copy args down over our |this| parameter, argv[-1], which is likely
> +     * the class constructor object, e.g. Array.  Then call the prototype
> +     * native method with our first argument passed as |this|.
> +     */
> +    memmove(argv - 1, argv, argc * sizeof(jsval));
> +    return fs->call(cx, JSVAL_TO_OBJECT(argv[-1]), argc - 1, argv, rval);

Integer underflow happens if argc == 0.
What should be done if generics are called with no args?
Oops.  Thanks, based on shutdown's comment I found my brain (I accidentally left
it in the diaper pail at 4:30am this morning -- colicky newborn, what can I
say!). I'll fix the generic dispatcher to deal with too-few args.

/be
This fixes not just the bug shutdown pointed out, which took some thinking cuz
we want to follow ECMA-262 Edition 3 on Function.prototype.apply/.call, but
also the bug where missing but required args (fs->nargs - argc counts these,
off the end of the argc actual args passed in argv -- the JS engine guarantees
that these slots in argv are valid) were not copied down one jsval.

Re-requesting reviews.

/be
Attachment #194230 - Flags: superreview?(shaver)
Attachment #194230 - Flags: review?(mrbkap)
Attachment #194230 - Flags: approval1.8b4+
Comment on attachment 194230 [details] [diff] [review]
interdiff against last patch to fix the generic dispatcher

>+    if (argc == 0) {
> ...
>+        argv[0] = OBJECT_TO_JSVAL(obj);

If fs->nargs is also 0, then this could overwrite the end of the argv array. We
should probably do something a bit better. Brendan says that he'll fix this
when he checks in, so r=mrbkap.
Attachment #194230 - Flags: review?(mrbkap) → review+
Attached patch best patch yet!Splinter Review
Down to a 432 byte increase.  This retasks JSFUN_LAMBDA, an internal flag used
only for scripted functions, as JSFUN_GENERIC_NATIVE when used in the flags
member of JSFunctionSpec.  This is a backward-compatible API extension, since
JSFUN_LAMBDA was meaningless on natives till now (except to the decompiler,
where it would add unwanted parenthesization).

$ cat newestsz
   text    data     bss     dec     hex filename
 546498   16796     240  563534   8994e libmozjs.so
$ diff {old,newest}sz
2c2
<  546066	  16796     240  563102   8979e libmozjs.so
---
>  546498	  16796     240  563534   8994e libmozjs.so

This bug is an exercise in proving myself wrong by taking the time to minimize.


/be
Attachment #194256 - Flags: superreview?(shaver)
Attachment #194256 - Flags: review?(mrbkap)
Attachment #194256 - Flags: approval1.8b4+
Comment on attachment 194256 [details] [diff] [review]
best patch yet!

r=mrbkap
Attachment #194256 - Flags: review?(mrbkap) → review+
(In reply to comment #46)
> Created an attachment (id=194256) [edit]
> best patch yet!

> +            /* We can't assume fs->call is well-aligned, so store fs instead. */
> +            if (!JS_SetReservedSlot(cx, fun->object, 0, PRIVATE_TO_JSVAL(fs)))
> +                return JS_FALSE;

This comment is not needed anymore since js_generic_native_method_dispatcher()
uses not only fs->call but also fs->nargs now.
(In reply to comment #48)
> 
> This comment is not needed anymore since js_generic_native_method_dispatcher()
> uses not only fs->call but also fs->nargs now.

Thanks, good point!  I'll fix.

/be
Comment on attachment 194256 [details] [diff] [review]
best patch yet!

Best yet! sr=shaver, thanks to shutdown for his catch.
Attachment #194256 - Flags: superreview?(shaver) → superreview+
Fixed on trunk and branch.

Need to document these on DevMo.  Do we file bugs for such tasks?

Do these, plus the Array extras, add up to "JS1.6"?  "JS1.5.1"?  Comments welcome.

/be
Status: NEW → RESOLVED
Closed: 16 years ago
Keywords: fixed1.8
Resolution: --- → FIXED
Comment on attachment 194230 [details] [diff] [review]
interdiff against last patch to fix the generic dispatcher

sr=shaver
Attachment #194230 - Flags: superreview?(shaver) → superreview+
Flags: testcase?
Checking in regress-304828.js;
/cvsroot/mozilla/js/tests/js1_6/Array/regress-304828.js,v  <--  regress-304828.js
initial revision: 1.1

Kind of funky behavior using the Array methods on Strings. Please check this test to make sure it is expecting the correct behavior.
Flags: testcase? → testcase+
verified fixed 1.8.x and trunk.
Status: RESOLVED → VERIFIED
Keywords: fixed1.8verified1.8
Blocks: es5
You need to log in before you can comment on or make changes to this bug.