Javascript Lambda Expressions

I’ve been working with Linq lately and wished that I could use it in other languages. Since I code in Javascript a good bit and it’s a prototype language, I figured I could add the Linq functionality to the Javascript Array object through it’s prototype.

I’ll start with my target, which is the ability to do something like:

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ] ;     
 
// use lambda expression to filter out odd numbers
var a2 = a.where( "( n ) => n % 2 == 0" );

First, I need to parse out the C# style Lambda expression into function arguments and body.

The following regular expression should do the trick:

/\((.*)\)\s*=>\s*(.*)/

In a nutshell: look for an open parenthesis, skip it, capture everything that isn’t a close parenthesis and save it. Then skip the close parenthesis, ‘=>’ and white space and capture everything that’s left.

In code:

var fn = l.match(/\((.*)\)\s*=>\s*(.*)/) ;

fn now contains a three element array containing the whole
lambda string, the lambda arguments and the lambda body:

[ "( n ) => n % 2 == 0", " n ", "n % 2 == 0" ]

We don’t need the first element, so remove it (with some sanity checking first):

if ( fn.length > 0 ) f.shift() ;

Now get the lambda body from the end of the array:

if ( fn.length > 0 ) b = f.pop() ;

And split the lambda arguments ( if any ) into an array by trimming the arguments string, removing the commas, normalizing the
rest, then splitting the string on the space boundaries.

if ( fn.length > 0 ) 
    p = fn.pop().replace(/^\s*|\s(?=\s)|\s*$|,/g, '').split(' ') ;

Note: The obvious method here would have been to use a regex split:

if ( fn.length > 0 ) p = fn.pop().split(/[\s|,]/g);

But the results vary by browser and I wanted to avoid browser detection logic.

Lambdas may include multiple statements and therefore will need to include an appropriately placed return statement. But for simple expressions like the one we are using as an example, we can leave it out and prepend it to the lambda body when we are creating the function:

fn = ( ( ! /\s*return\s+/.test( b ) ) ? "return " : "" ) + b ;

Now that we have the parameters and body, we can make a function.

We create the function using the Javascript Function object. The Function object takes a variable number of parameters, the last of which is the source for the function body. The other parameters are the arguments for the resulting function.

We can’t predict how many arguments were included in the lambda so we can’t just call Function directly. We can, however, make the call through the apply method of the Function object. The apply method accepts a ‘this’ pointer and an array that will get split into the separate arguments that the Function object expects.

First, we need all our arguments in an array. We can simply add the function body to the existing parameter array,

p.push( fn ) ;

And create our function. Since we don’t know if the lambda body is valid Javascript code, we’ll play it safe and wrap the call in a try/catch block:

try
{
  return Function.apply( {}, p ) ;
}
catch(e)
{
  return null ;
}

When we put it altogether, we have a useful little function to turn lambda expressions into Javascript functions:

function lambda( l )
{
   var fn = l.match(/\((.*)\)\s*=>\s*(.*)/) ;
   var p = [] ;
   var b = "" ;
 
   if ( fn.length > 0 ) fn.shift() ;
   if ( fn.length > 0 ) b = fn.pop() ;
   if ( fn.length > 0 ) p = fn.pop()
    .replace(/^\s*|\s(?=\s)|\s*$|,/g, '').split(' ') ;
 
   // prepend a return if not already there.
   fn = ( ( ! /\s*return\s+/.test( b ) ) ? "return " : "" ) + b ;   
 
   p.push( fn ) ;
 
   try
   {
      return Function.apply( {}, p ) ;
   }
   catch(e)
   {
        return null ;
   }
}

Next time I’ll develop the where function. See you then!

Be Sociable, Share!

One comment.

  1. Nice hack, Thanks for the code. Although I think if the expression syntax is little different then it will likely to break the regex expression like this: ( n => n % 2 ) == 0. Though I see the benefit of doing things this way, but not 100% sure about the robustness of the approach. One approach that I can think is a TDOP(Top-down operator parsing) on the lambda expression. Though I would like to know your thoughts.

    Thanks again for the cool effort. I definitely learned something.

    More info:
    http://javascript.crockford.com/tdop/tdop.html
    http://effbot.org/zone/simple-top-down-parsing.htm
    http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/

Post a comment.


Warning: Illegal string offset 'solo_subscribe' in /home3/paulfree/public_html/wp-content/plugins/subscribe-to-comments/subscribe-to-comments.php on line 304

Subscribe without commenting