RegExps in array functions

# Felix Böhm (13 years ago)

just a first try for a proposal: Array.prototype.[filter/some/every] currently require a function as an argument. Most of the time, everything I want to do is check if they match a certain pattern. So what I end up writing is a RegExp that matches the pattern, and calling its test() method for every member of the array.

My proposal is to allow people to use a regular expression as an argument for the three methods. Current implementations throw an error when they don't get a function as an argument, so this won't break any (working) code.

An example:

["fOo","bAr"].filter(function(elem){ return /foo/i.test(elem); });

should be replaceable with

["fOo","bAr"].filter(/foo/i);

For non-string members of the array, the current behavior of the RegExp.prototype.test method (ie. converting the value to a string) applies.

Any reasons why this is a bad idea? (Or is it just my bad attitude? :D )

# Adam Shannon (13 years ago)

It seems like too much of a shortcut to do that. RegEx's in es are not natively callable, so it seems like this is an overload rather than an additional feature.

On Friday, March 23, 2012, Felix Böhm <esdiscuss at feedic.com> wrote:

Hi guys, just a first try for a proposal: Array.prototype.[filter/some/every]

currently require a function as an argument. Most of the time, everything I want to do is check if they match a certain pattern. So what I end up writing is a RegExp that matches the pattern, and calling its test() method for every member of the array.

My proposal is to allow people to use a regular expression as an argument

for the three methods. Current implementations throw an error when they don't get a function as an argument, so this won't break any (working) code.

An example: ["fOo","bAr"].filter(function(elem){ return /foo/i.test(elem); }); should be replaceable with ["fOo","bAr"].filter(/foo/i); For non-string members of the array, the current behavior of the

RegExp.prototype.test method (ie. converting the value to a string) applies.

# Domenic Denicola (13 years ago)

Does passing /foo/i.test not work? (Away from computer.) Perhaps it should, if it doesn't?

On Mar 23, 2012, at 16:28, "Felix Böhm" <esdiscuss at feedic.com<mailto:esdiscuss at feedic.com>> wrote:

Hi guys,

just a first try for a proposal: Array.prototype.[filter/some/every] currently require a function as an argument. Most of the time, everything I want to do is check if they match a certain pattern. So what I end up writing is a RegExp that matches the pattern, and calling its test() method for every member of the array.

My proposal is to allow people to use a regular expression as an argument for the three methods. Current implementations throw an error when they don't get a function as an argument, so this won't break any (working) code.

An example:

["fOo","bAr"].filter(function(elem){ return /foo/i.test(elem); });

should be replaceable with

["fOo","bAr"].filter(/foo/i);

For non-string members of the array, the current behavior of the RegExp.prototype.test method (ie. converting the value to a string) applies.

Any reasons why this is a bad idea? (Or is it just my bad attitude? :D )

# Adam Shannon (13 years ago)

I'd opt for a solution like that it's a lot clearer than overloading collection functions.

On Friday, March 23, 2012, Domenic Denicola <domenic at domenicdenicola.com>

wrote:

Does passing /foo/i.test not work? (Away from computer.) Perhaps it

should, if it doesn't?

On Mar 23, 2012, at 16:28, "Felix Böhm" <esdiscuss at feedic.com> wrote:

Hi guys, just a first try for a proposal: Array.prototype.[filter/some/every]

currently require a function as an argument. Most of the time, everything I want to do is check if they match a certain pattern. So what I end up writing is a RegExp that matches the pattern, and calling its test() method for every member of the array.

My proposal is to allow people to use a regular expression as an argument

for the three methods. Current implementations throw an error when they don't get a function as an argument, so this won't break any (working) code.

An example: ["fOo","bAr"].filter(function(elem){ return /foo/i.test(elem); }); should be replaceable with ["fOo","bAr"].filter(/foo/i); For non-string members of the array, the current behavior of the

RegExp.prototype.test method (ie. converting the value to a string) applies.

# John Tamplin (13 years ago)

On Fri, Mar 23, 2012 at 8:21 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:

Does passing /foo/i.test not work? (Away from computer.) Perhaps it should, if it doesn't?

It does, if you bind it to the regex first. Ie:

var re = /foo/i; var filtered = array.filter(re.test.bind(re));

So that doesn't look better than just passing an anonymous function wrapper (though you probably want to build the regex once).

# Russell Leggett (13 years ago)

On Mar 23, 2012, at 9:05 PM, John Tamplin <jat at google.com> wrote:

On Fri, Mar 23, 2012 at 8:21 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote: Does passing /foo/i.test not work? (Away from computer.) Perhaps it should, if it doesn't?

It does, if you bind it to the regex first. Ie:

var re = /foo/i; var filtered = array.filter(re.test.bind(re));

So that doesn't look better than just passing an anonymous function wrapper (though you probably want to build the regex once).

Yeah, gotta watch your receiver. If you find yourself doing this a lot, just make a function. You can either add a method to RegExp that returns a bound version of test, or make a function that takes a regex and returns a bound version of test like

function predicate(regex){
    return regex.test.bind(regex);
}

var filtered = array.filter(predicate(/foo/i));
# Domenic Denicola (13 years ago)

Would it be a reasonable language upgrade to pre-bind test? I imagine there would be philosophical objections, but would be interested in hearing them enunciated. (Are there other pre-bound methods in ES5's "standard library"?)

On Mar 23, 2012, at 21:48, "Russell Leggett" <russell.leggett at gmail.com<mailto:russell.leggett at gmail.com>> wrote:

On Mar 23, 2012, at 9:05 PM, John Tamplin <jat at google.com<mailto:jat at google.com>> wrote:

On Fri, Mar 23, 2012 at 8:21 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:

Does passing /foo/i.test not work? (Away from computer.) Perhaps it should, if it doesn't?

It does, if you bind it to the regex first. Ie:

var re = /foo/i; var filtered = array.filter(re.test.bind(re));

So that doesn't look better than just passing an anonymous function wrapper (though you probably want to build the regex once).

Yeah, gotta watch your receiver. If you find yourself doing this a lot, just make a function. You can either add a method to RegExp that returns a bound version of test, or make a function that takes a regex and returns a bound version of test like

function predicate(regex){
    return regex.test.bind(regex);
}

var filtered = array.filter(predicate(/foo/i));
  • Russ

-- John A. Tamplin Software Engineer (GWT), Google

# Brendan Eich (13 years ago)

Felix Böhm wrote:

Hi guys,

just a first try for a proposal: Array.prototype.[filter/some/every] currently require a function as an argument. Most of the time, everything I want to do is check if they match a certain pattern. So what I end up writing is a RegExp that matches the pattern, and calling its test() method for every member of the array.

My proposal is to allow people to use a regular expression as an argument for the three methods. Current implementations throw an error when they don't get a function as an argument, so this won't break any (working) code.

An example:

["fOo","bAr"].filter(function(elem){ return /foo/i.test(elem); });

should be replaceable with

["fOo","bAr"].filter(/foo/i);

Firefox 3 era js shell:

js> ["fOo","bAr"].filter(/foo/i);

fOo js> version()

180

However, the latest Firefox and js shells do not make RegExp instances callable:

js> ["fOo","bAr"].filter(/foo/i);

typein:5: TypeError: /foo/i is not a function js> ["fOo","bAr"].filter(re=/foo/i, re.test.bind(re));

typein:6: TypeError: re is not a function js> ["fOo","bAr"].filter(function(){return /foo/i}); ["fOo", "bAr"]

We removed callable regexps because they violate ES3, as David-Sarah Hopwood pointed out (esdiscuss/2008-November/008008 and related).

For non-string members of the array, the current behavior of the RegExp.prototype.test method (ie. converting the value to a string) applies.

Any reasons why this is a bad idea? (Or is it just my bad attitude? :D )

typeof nativeObject == "function" <=> nativeObject is callable

is violated by making regexps callable.

# Brandon Benvie (13 years ago)

I've been struggling for a way to describe this idea, but it's almost like we're lacking what amounts to a "valueOf" where the expected result is a callable. The regex-as-filter is a good example of that use case. You don't want the base object to be a function or callable necessarily, but you do want to indicate that there's a clear callable representative of the object.

# Brendan Eich (13 years ago)

Brendan Eich wrote:

js> ["fOo","bAr"].filter(function(){return /foo/i}); ["fOo", "bAr"]

LOL, I returned a truthy value. Fixing:

js> ["fOo","bAr"].filter(function(s){return /foo/i.test(s)}); ["fOo"]

Even longer :-/.

So yes, callable regexps were useful -- that's why I made 'em callable way back when (Netscape 4 impl, based on Perl 4[!], fed into ES3). But they wound up being outlawed by ES3's text.

# Lasse Reichstein (13 years ago)

On Sat, Mar 24, 2012 at 5:54 AM, Brandon Benvie <brandon at brandonbenvie.com> wrote:

I've been struggling for a way to describe this idea, but it's almost like we're lacking what amounts to a "valueOf" where the expected result is a callable. The regex-as-filter is a good example of that use case. You don't want the base object to be a function or callable necessarily, but you do want to indicate that there's a clear callable representative of the object.

The problem is that there are so many ways you might want to use an object as a function, not just one. In this case, we want it as a predicate. In other cases you might want it to return the match as a string, or as a match. Or you want your generic objects to match against different properties. The use-case here is that you have an object and want it to act as a function in a specific case, and it's driven by the way the object is used, which is unbounded, not the way the object is defined. You can't be expected to predict all the ways an object is going to be used, and create one function representation suiting the all.

For this, you need a function per usage, so the "predicate" function suggested above is exactly the right level of abstraction.

# Brendan Eich (13 years ago)

Lasse Reichstein wrote:

The problem is that there are so many ways you might want to use an object as a function, not just one. In this case, we want it as a predicate. In other cases you might want it to return the match as a string, or as a match. Or you want your generic objects to match against different properties. The use-case here is that you have an object and want it to act as a function in a specific case, and it's driven by the way the object is used, which is unbounded, not the way the object is defined. You can't be expected to predict all the ways an object is going to be used, and create one function representation suiting the all.

For this, you need a function per usage, so the "predicate" function suggested above is exactly the right level of abstraction.

Well argued. I agree in general.

In this specific case, just a couple of thoughts (not disagreeing):

When I made regexps callable way back when, I chose calling a regexp as short for exec'ing, which creates a match array result. Then I optimized the exec/call built-in to peek into its continuation to see if the result was used only for its boolishness. If so, the built-in would pass a flag parameter to the common internal API for regexp execution to avoid creating the result array, substituting false/true for null/the-array.

One way for generic callback-invoking map, forEach, filter, etc. functions to go: call the .call method of the callback, assuming the callback is a function but enabling non-function objects to delegate to a call method that knows how to invoke them. This is shown at Steve Levithan's XRegExp github readme:

slevithan/XRegExp

// XRegExp regexes get call and apply methods // To demonstrate, let's first create the function we'll be using... function filter(array, fn) { var res = []; array.forEach(function (el) {if (fn.call(null, el)) res.push(el);}); return res; } // Now we can filter arrays using functions and regexes filter(['a', 'ba', 'ab', 'b'], XRegExp('^a')); // -> ['a', 'ab']

This may be optimization-hostile at first glance, but VMs optimize call and apply with special caching and inlining.

The bad old hardcoded-per-builtin optimization to avoid creating a useless-except-for-its-boolish-value result array is ideally generalized via inlining and type inference to cover many such results-allocating functions that are sometimes or often called just to test that they return non-null.

/be

# Felix Böhm (13 years ago)

I just realized that you may pass a second argument to the array methods. So instead of passing an regular expression as the first argument, you may just pass the test method as the first and the regular expression as the second argument. It's then used as the test method's this argument. When the function is cached, this method is (in the shortest case) two characters longer than passing a RegExp directly. This way, it's probably as fast as it can get.

It seems like somebody thought about this use case when adding the second argument. It's also possible to use other methods - Object.prototype.hasOwnProperty is a great candidate.

Invokable RegExps would have been a solution, although nobody would expect a RegExp to be invokable (because it's pretty exotic) and it probably wouldn't have been used anyways by a lot of people.

# Brendan Eich (13 years ago)

Felix Böhm wrote:

It seems like somebody thought about this use case when adding the second argument.

Yes, shaver and I talked about this way back when he added the array extras -- we wanted an optional |this| parameter for the callback.

Invokable RegExps would have been a solution, although nobody would expect a RegExp to be invokable (because it's pretty exotic) and it probably wouldn't have been used anyways by a lot of people.

You'd be surpised -- callable regexps were in SpiderMonkey for ages and other implementations copied.

But ES3 forbids any native object other than "function" (native meaning a built-in defined by ES3) from having [[Call]], and typeof x == "function" implies for a native object denoted x that x is in fact a Function instance.

Anyway, sounds like you're ok with the optional trailing |this| parameter.