[ /js/lambda ]

Next level Javascript

  • If you've been programming for a while, you've probably seen people talking about lambdas. Maybe you've seen functions written down like (λ (x) x), and couldn't figure out what that was supposed to mean.
  • Lambdas aren't that complicated, and most modern programming languages support their use, but they're probably one of the least understood features of any programming language. That's unfortunate, cause they're often the most powerful feature as well.

  • That freaky notation above is just a definition of a lambda, or anonymous function, which takes a single argument x, and returns it.
  • There aren't a ton of people programming with that notation (lisp), but learning a little bit about lambdas will make just about any language make a lot more sense to you. I'm going to demonstrate some simple applications of the concept in javascript, which is far lispier than most people realize.

Lambdas (anonymous functions) in Javascript

In javascript, that function looks a little different:

function(x){
    return x;
};

Usually you would either see a function declared with a name, like:

function useless(x){
    return x;
};

... to be used later, or as the first example, but stored in a variable:

var useless = function(x){
    return x;
};
  • The second and third examples are practically identical, though there are subtle differences. The first example, however, is never stored in memory, so unless you use it, it won't be accessible by any other functions.
  • Functions don't necessarily need to be named to be called by name. Just like any other object in javascript, a function can be wrapped in parentheses, passed around as a value within other expressions, or be called immediately.
(function(x){
    return x*x*x*x;
})(2);
  • The example above doesn't actually need the parentheses, they're just there for ornamentation.
  • You may need them for other kinds of expressions, though:
var condition = true;

(condition?
 function(x){  return x;  }:
 function(x){  return x*2;}
 )(5);
  • This example is kind of silly, given that we know 'condition' will evaluate as true.
  • I only mean to demonstrate the kinds of expressions that can return functions: in short, ALL OF THEM.
  • That's actually a good thing, in a lot of cases. If you're going to use a function a bunch of times, it makes sense to store it. If you will only use it once, however, your program will have a slimmer profile if you instantiate the function at runtime. It's not obviously applicable until you shift your mindset a little bit.

for/foreach vs map

  • You've probably seen this pattern before:
var A = [0,1,2,3,4,5]; // start with some data
for(var i=0,l=A.length;i<l;i++){ // set up your loop
    A[i] = A[i] * A[i]; // do something with each element
};
console.log(A); // now do something with your data
  • Maybe you really needed that data for something, but now it's in your program's memory, and it's gonna stay there. It's only a couple integers, but that won't always be the case.
  • Instead of this, let's take a look at the array.map() method. Map takes a bit of code, and applies it to each element in an array, returning an array of the same length with the new values. You can't just pass raw code, because it will be reduced into a single value before being passed to the map function. Instead, you need to put that code inside an anonymous function, which you pass to map. The code below is equivalent to that above.
console.log([0,1,2,3,4,5].map(
    function(x){
        return x*x;
}));
  • A lot shorter, isn't it? That's probably not enough to sell you on why you should spend time learning to program in this style. The code is a little bit shorter, and more readable (in my opinion) because you don't have to look at what each variable is instantiated with. There are no variables to worry about, just fixed values that get passed to the standard output after passing through a function, then everything disappears without leaving a mess.

Currying in JS

  • To really see the value, it helps to understand a slightly more complicated technique called Currying.
  • The core reason for learning to curry is that (at least in javascript) map only accepts unary functions. You can't pass a n-ary function to a n-dimensional array and have it magically sort everything out for you.
  • As such, situations arise where you need to convert a binary function into a unary function. So, in short, you need to make a function that accepts a function as an argument, and returns a function as its product.
  • To reduce the number of arguments, you need to supply one of them ahead of time, so that it gets baked into the returned function. This is pretty complicated as code, and much moreso as prose, so let's look at an example:
/*  assume you have some binary function F, which takes arguments A and B.  */

function fix1(f,a){
    return function(b){
        return f(a,b);
    };
};
  • Here we define fix1, which accepts your initial binary function, along with it's first argument 'a'. It then returns a new function, which will take a single argument 'b', and return the result of calling the function 'f' with both arguments 'a' and 'b'. Every time you call it, though, 'a' will have the same value.
  • So let's see it in action: We need a binary function to work with, so let's try an exponentiation function...
var expt = function(a,b){
    return Math.pow(a,b);
};

/* Now produce a unary function where a is fixed */

var fiveToTheN = fix1(expt,5); // done!

/* Now use it on an array */
[0,1,2,3,4,5,6].map(fiveToTheN);
  • Not bad, eh? For a little more flexibility, we could use a 'fix2' function as well.
var fix2 = function(f,b){
    return function(a){
        return f(a,b);
    };
};
  • But what do we do with this? For one, we could use fix2 to fix the argument being passed to fix1. I'll give you a simple example, and you can use your imagination to fill in the rest..
var sameShitDifferentDay = fix2(fix1,3);
  • this ^ returns a unary function which will accept a binary function as an argument, and return a unary function which takes a function as an argument, yielding another unary function which uses its argument as the second argument of the earlier function (which has its first argument fixed as 3).
  • If you think that's a total mindfuck, I completely agree. However, if you can wrap your head around it long enough to put it into a recursive form, you have an incredibly powerful way of mapping an arbitrary number of functions over an arbitrarily complex multidimensional array.
  • As complicated as it is to think about, that's a ridiculously powerful function.
  • The entire Haskell language revolves around this idea of currying, but there it is in a handful of functions.

Filtering out the junk

  • My next function after 'map' would have to be 'filter'. Filter is an array method which returns a new array, minus any elements that fail to satisfy a supplied predicate.
  • We'll make a predicate, then try using it to filter an array.
var isEven = function(n){
    return n % 2 == 0;
};

console.log([0,1,2,3,4,5,6,7,8].filter(isEven));
>> [0,2,4,6,8]
  • Pretty straightforward, uneven numbers were removed, and the resulting array was returned without modifying the original array.
  • I'm getting a little sleepy, so I'll give you one more example which shows off inline functions and another important trick.. negating a predicate.
  • First we'll code 'negate', which takes a predicate 'p' and returns a function that checks if a value is 'not p'.
  • Then we'll use that to find all odd numbers.
var negate = function(p){
    return function(x){
        return !p(x);
    };
};

[0,1,2,3,4,5,6,7,8,9].filter(negate(isEven));
>> [1,3,5,7,9]
  • Simple, but effective. With negate, you will only ever have to code half of your predicates, and you can instantly generate their complements.
  • Be mindful if you're trying to automate this process, however, as you can accidentally produce wasteful functions like not-not-not-not-not-not-not-isEven. Your processor isn't going to know the difference, however obvious it may be to you.
  • You can even try currying your predicates in different ways:
var thisAndThat = function(f,g){
    return function(x){
        return f(x) && g(x);
    };
};

var thisOrThat = function(f,g){
    return function(x){
        return f(x) || g(x);
    };
};

Crunching data with 'reduce'

  • The last of my top three methods for functional javascript is reduce. Like map and filter, it acts on arrays, returning a result without modifying the original data.
  • Map returns an array of equal length to the original. Filter is guaranteed to return an array with no more elements than the original. Reduce takes an array and returns a single value. It's most useful in situations where you would otherwise need to rely on side effects and an accumulator to accrue your final result.
  • Let's sum an array as an example:
[1,2,3,4,5,6,7,8].reduce(function(a,b){
    return a+b;
});
  • reduce adds the first two numbers together, for a result of 3. It then adds each successive element to this amount, until there are no elements remaining.
  • You could define a factorial function in the same fashion:
[1,2,3,4,5].reduce(function(a,b){return a*b;});
  • That will work when your function is commutative, like addition or multiplication. Otherwise, you may want to go in the opposite direction. You could simply call reverse on the array first, like so:
[1,2,3,4,5,6].reverse().reduce(function(a,b){
    return a/b;
});
  • ...but there's an easier way:
[1,2,3,4,5,6].reduceRight(function(a,b){
    return a/b;
});
  • Either will yield the same result, but reduceRight is slightly shorter and more efficient since it never allocates space for the intermediary reversed array.

Breaking out of pure functional programming with side effects.

  • It's worth noting that all my examples so far have included the return keyword. It's perfectly acceptable in many cases to simply use these methods for their side effects.
  • consider:
[0,1,2,3,4,5,6].map(function(x){
    console.log(x);
});
  • You'll run in to problems if you try to feed the resulting array into another statement, as all of its values will be null.
  • I recommend following some kind of naming convention to help you remember which functions produce results, and which produce side effects.
  • If you want your code to be flexible, I'd also advise against coding functions to do both, except for debugging purposes.

Dynamic function composition

  • Now that we are aware of all these functions, we can work on combining them effectively to even greater effect.
  • I'm going to implement function composition:
var compose = function(f,g){
    return function(x){
        return g(f(x));
    };
};
  • Compose takes two functions, and returns a third function which passes the result of the second into the first.
var square = function(x){
    return x*x;
};

compose(square,square)(2);
  • The above function call will yield a value of 16, which is pretty boring, but it demonstrates the function.
  • If we wanted to create a single function that returned the result of several other functions in succession, we could use reduce.
  • Assume we have declared functions f, g, and h.
[f,g,h].reduce(compose)(2);
  • This produces a function equivalent to h(g(f(2)));
  • I used three functions because I'm lazy, you could use as many as you want.
  • Suppose you are performing some task that requires you to pass a single array A through a large amount of filters:
  • Assume we have declared filters F, G, and H.
var makeFilter = function(p){
    return function(a){
        return a.filter(p);
    };
};

[F,G,H].map(makeFilter)
    .reduce(compose)
        (A);
  • Tracing that might be a little tricky:
  • Predicates F, G, and H accept a single value, and return true or false.
  • makeFilter accepts such a predicate, and returns a function that takes an array, filters according to that predicate, and returns the result.
  • We map makeFilter over an array containing all our predicates, which turns them into functions which accept arrays and filter them according to that predicate.
  • We reduce the resulting array to a single function which accepts an array and filters it through all our original predicates by using compose.
  • Finally, A is successively passed through each filter, and the result is returned.
  • You could try optimizing your code by sorting computationally expensive functions to the end of the predicate array, to ensure that they are called on a minimal dataset.
  • If, for some reason, you want to try passing your results through these filters in reverse, you can use reduceRight.

Nesting higher order functions

  • Now let's play around a bit:
  • I mentioned multidimensional arrays earlier, and I realize it sounded pretty vague.
  • Let's start with two dimensions and see if we can work up from there.
  • Suppose we have two arrays, and we want to be able to find every possible combination of two values.
  • This is called the cartesian product. We can generalize this kind of operation to work for any binary function.

var carte = function(f,A,B){
    return A.map(function(a){
        return B.map(fix1(f,a));
    });
};

var DD = carte(function(x,y){
    return x+y;
    },[0,1,2,3,4,5]
    ,[3,4,5,6,7,8,9,0]));

>>> console.log(DD);
[   [ 3, 4, 5, 6, 7, 8, 9, 0 ],
    [ 4, 5, 6, 7, 8, 9, 10, 1 ],
    [ 5, 6, 7, 8, 9, 10, 11, 2 ],
    [ 6, 7, 8, 9, 10, 11, 12, 3 ],
    [ 7, 8, 9, 10, 11, 12, 13, 4 ],
    [ 8, 9, 10, 11, 12, 13, 14, 5 ] ]
  • I don't know about you, but I think that's pretty nifty.
  • You can use this for testing, to quickly check whether a function will work for a wide range of inputs.
  • Your arrays can contain numbers, but they can also contain strings, functions, or objects.
  • Likewise, your operator can be plain arithmetic, or it can be function composition, or currying.
  • Many data types can be arranged as multidimensional arrays, from truth tables to routing tables.

Flattening nested arrays

  • Suppose our goal is to find how many values in the table meet some condition, and we find the above table slightly unwieldy.
  • We can easily flatten the mattrix out using reduce, then we can count the number that satisfy a predicate by filtering.
  • Let's assume we want the quantity of even numbers.
DD.reduce(function(X,Y){
    return X.concat(Y);
}).filter(isEven).length;
  • We can extend this kind of behaviour to more deeply nested functions, but I'm going to come back to that..

In summary

  • You ought to be comforable passing around functions like this if you're working with nodejs, because most of nodejs' speed comes from the fact that it doesn't need to wait for some things to return.
  • Instead, it executes lots of functions in parallel, and relies on you handling this behaviour correctly when working with your data.
  • If you aren't careful, you can return a variable before a function has had the chance to instantiate it, which will throw an 'undefined' and probably wreck the rest of your code with mysterious errors.
  • So, lots of programming in nodejs requires you to use callbacks, which are functions that you intend to execute at the completion of another task.
  • depending on what database you're using, the calls might be asynchronous, so you can get yourself into a mess by having to nest callbacks.
getThingFromDB("parameterName", function(data){
        console.log("data to be printed once fetched"+data);
    });
  • This example is easy, but sometimes your callbacks need callbacks, and so on, and it turns to shit.
  • I like to declare things ahead of time, like the currying above, so that I can pass functions to other functions and prepare one single callback which I can then pass to the db request or whatever async process requires it.
  • map, filter, and reduce, along with various currying techniques make this much simpler than it would otherwise be.
  • It's possible to implement this in most languages, though javascript lends itself to the task especially well.
  • Things can definitely get a little weird when it becomes necessary to incorporate asynchronous functions:

  • Give it a try in the language of your choice, and see how your productivity improves.