Expressions in Linquid JS

As you may have noticed, many of the functions of Linquid JS accept expressions. Most of the time they're named something more descriptive than "expression", like condition, selector, etc. They are all, however expressions.

Linquid JS supports expressions in two forms:
  • Functions
  • Lambda Expressions

Using Functions

A function may be declared either inline, or used via reference when passing to one of the Linquid JS methods. Declaring the function inline facilitates the use of closures in an expression.

Inline Declaration Example:
function myFunction(items) {
    var myVariable = 0;
    items.Each(function(o) { myVariable += o.someProperty; });
}


By Reference Example:
function myFunctionThatDoesSomething(o) {
    //Do cool stuff here
    //"o" in this case will be the current element from the iteration from "Each" below
}
var myArray = [1, 2, 3, 4, 5];
myArray.Each(myFunctionThatDoesSomething);

Using Lambda Expressions

Ok, so they're not really lambda expressions. These are the strings like "x => x == 2" that are all over the documentation. They're not nearly as versatile as the lambda expressions from .NET but they do provide a nice shorthand. We'll go into more of the workings of the expressions in a minute. First, note that any method in the Linquid JS that accepts an expression can accept either one of these string-form lambdas or a function as discussed above.

Lambda Usage Example:
var myArray = [1, 2, 3, 4, 5, 6];

//Selects the even numbers from the array
myArray.Select("x => x % 2 == 0");

//Again, selects the even numbers from the array
myArray.Select(function(x) { return x % 2 == 0; });


The fact these two functions do the exact same thing is not a coincidence, in fact the string-form lambda is passed to a function generator and the following steps are taken.
  1. Check to see if what we've been given is a string, if not, return it
  2. Check to see if the string we've been given exists in the Expression Cache, if it does, return it
  3. Take everything to the left of the goes to symbol (=>)
    1. Strip off any paranthesis
    2. Stick it inside _function(value goes here)
  4. Take everything to the right of the goes to symbol
    1. Determine whether the first non-whitespace character is an opening curly brace
    2. If it doesn't, check for a semi-colon at the end, if it doesn't have that either, prefix the string with return
    3. Make sure the text starts with an opening curly brace
    4. Make sure the text ends with a semi-colon (if it didn't start with an opening curly brace originally)
    5. Add a closing curly brace to the end if it didn't originally have an opening one
  5. Eval our new function into life and store it in the Expression Cache

As you can see, there are plenty of opportunities for this to fail with malformed code. So, if you plan on using the lambdas, be sure you're using them in a way that won't interfere with the above process.

Let's convert "x => x % 2 == 0" to a function:
Overview
leftPart = "x";
rightPart = "x % 2 == 0";
functionHeader = "function(" + leftPart +")"; //"function(x)"
IF !(rightPart STARTS WITH "{")
    IF !(rightPart ENDS WITH ";")
        rightPart = "return " + rightPart; //"return x % 2 == 0"
        rightPart += ";"l //"return x % 2 == 0;"
    FI
    rightPart = "{ " + rightPart + " }"; //"{ return x % 2 == 0; }"
FI
wholeThing = leftPart + rightPart; //"function(x){ return x % 2 == 0; }"
return ExpressionCache[wholeThing] = EVAL(wholeThing);

Output
function(x) { return x % 2 == 0; }


Next, let's convert "x => x % 2 == 0;" to a function:
Overview
leftPart = "x";
rightPart = "x % 2 == 0;";
functionHeader = "function(" + leftPart +")"; //"function(x)"
IF !(rightPart STARTS WITH "{")
    IF !(rightPart ENDS WITH ";") //Branch not taken
    FI
    rightPart = "{ " + rightPart + " }"; //"{ x % 2 == 0; }"
FI
wholeThing = leftPart + rightPart; //"function(x){ x % 2 == 0; }"
return ExpressionCache[wholeThing] = EVAL(wholeThing);

Output
function(x) { x % 2 == 0; }

As you can see, the above function does not return a value. So, if you intend to return a value from an expression without using curly braces, be sure not to include a semi-colon as the last, non-whitespace character in the string.

Also notice that this mechanism does not restrict you from calling multiple things in a braceless body. For example:
"x => doThing1(); doThing2()"

Would be created as:
function(x) { return doThing1(); doThing2(); }

So, doThing2 would never be called in this example.

Using curly braces inside the body whenever more than one method call is made or more than one evaluation is performed is always the best idea.

So, now we'll convert "x => { doThing1(); return doThing2(); }" to a function:
Overview
leftPart = "x";
rightPart = " { doThing1(); return doThing2(); }";
functionHeader = "function(" + leftPart +")"; //"function(x)"
IF !(rightPart STARTS WITH "{") //Branch not taken
FI
wholeThing = leftPart + rightPart; //"function(x){ doThing1(); return doThing2();  }"
return ExpressionCache[wholeThing] = EVAL(wholeThing);

Output
function(x){ doThing1(); return doThing2();  }


And there you have it, you now know all about expressions in Linquid JS

Last edited Sep 24, 2011 at 5:00 AM by mlorbetske, version 1

Comments

No comments yet.