Functional Programming in JavaScript using LiveScript - Part 2

9 Jul 2012, updated 30 Jul 2012 - George Zahariev

A couple of weeks ago we discussed functional programming in JavaScript using LiveScript. Since then LiveScript has received many updates (including some features suggested by Hacker News readers), so it's now time for Part 2!

If you haven't read Part 1, Functional Programming in JavaScript using LiveScript and prelude.ls, I would highly recommend you do so now.

A brief refresher: LiveScript is a language that compiles to JavaScript. It's just JavaScript with some syntax improvements and feature additions, making functional style programming easier. prelude.ls is its standard library, based off of Haskell's Prelude module.

Constants

A major concept in functional programming is immutable values. This means using variables which you can't reassign or modify. Doing this makes it easier to reason about your code, as you never have to worry about the state of your variables - they will only ever have one value. This helps reduce the amount of bugs in your code.

JavaScript does not currently support immutable values, but thanks to @satyr, LiveScript now has constants! Errors are thrown at compile time if any reassignment, crementing, or redeclaration of a constant is found.

They are defined with the const keyword:

const x = 10
var x;
x = 10;

The compiled JavaScript is no different, because as mentioned above there isn't currently widespread support in for constants in JS. Everything is checked at compile time.

All the below would throw errors after the constant x was defined as above.

x = 0  # error: redeclaration of constant "x" on line 2
x++    # error: increment of constant "x" on line 2
x += 2 # error: assignment to constant "x" on line 2

However, property modification is still allowed:

const y = {name: 'amy'}
y.name = 'taylor'   # fine - results in y == {name: 'taylor'}

y = {name: 'hanna'} # not okay - error: redeclaration of constant "y" on line 4

Typing out const all the time may be annoying for those who come from languages with immutable variables and want all their variables to be constants.

Use the -k or --const flags when compiling to make the compiler treat all your variables as constants. For example:

livescript -ck file.ls

Partial Application

Curried functions are very useful, but what should you do when the functions you are dealing with aren't curried or don't have their arguments in the order you want?

You can now partially apply functions by calling them using an underscore _ as a placeholder for the values you don't want to bind. When you partially apply a function, instead of executing that function, a new function is returned with the arguments you supplied already bound, and the whose arguments are the ones you put the placeholder on.


filterNums = filter _, [1 to 5]

filterNums even  #=> [2, 4]
filterNums odd   #=> [1, 3, 5]
filterNums (< 3) #=> [1, 2]

# 'filter', 'even', and 'odd' are from prelude.ls
var filterNums, slice$ = [].slice;
filterNums = partialize$(filter, [void 8, [1, 2, 3, 4, 5]], [0]);
filterNums(even);
filterNums(odd);
filterNums((function(it){
  return it < 3;
}));
function partialize$(f, args, where){
  return function(){
    var params = slice$.call(arguments), i,
        len = params.length, wlen = where.length,
        ta = args ? args.concat() : [], tw = where ? where.concat() : [];
    for(i = 0; i < len; ++i) { ta[tw[0]] = params[i]; tw.shift(); }
    return len < wlen && len ? partialize$(f, ta, tw) : f.apply(this, ta);
  };
}

If you call a partially applied function with no arguments, it will execute as is instead of returning itself, allowing you to use default arguments.

Partially applied functions are also really useful for piping if the functions you are using don't have a nice argument order and aren't curried (like in underscore.js for instance).

[1, 2, 3] |> _.map _, (* 2) |> _.reduce _, (+), 0 #=> 12
var slice$ = [].slice;
partialize$(_.reduce, [
  void 8, curry$(function(x$, y$){
    return x$ + y$;
  }), 0
], [0])(
partialize$(_.map, [
  void 8, (function(it){
    return it * 2;
  })
], [0])(
[1, 2, 3]));
function partialize$(f, args, where){
  return function(){
    var params = slice$.call(arguments), i,
        len = params.length, wlen = where.length,
        ta = args ? args.concat() : [], tw = where ? where.concat() : [];
    for(i = 0; i < len; ++i) { ta[tw[0]] = params[i]; tw.shift(); }
    return len < wlen && len ? partialize$(f, ta, tw) : f.apply(this, ta);
  };
}
function curry$(f, args){
  return f.length > 1 ? function(){
    var params = args ? args.concat() : [];
    return params.push.apply(params, arguments) < f.length && arguments.length ?
      curry$.call(this, f, params) : f.apply(this, params);
  } : f;
}

Implicit Access and Lookup Functions

This feature came as an idea from a Hacker News comment.

When mapping and filtering over collections of objects, it is really useful to have a succinct way to express functions which access properties or call methods.

(.prop)

is equivalent to

((it) -> it.prop)

and

(obj.)

is equivalent to

((it) -> obj[it])

Some examples:


data = [{name: 'alice', age: 19},
        {name: 'tessa', age: 17}]

map (.name), data        #=> ['alice', 'tessa']

filter (.age > 18), data #=> [{name: 'alice', age: 19}] 

map (.toUpperCase!), ['hi', 'there'] #=> ['HI', 'THERE']



map (.join '-'), [[1, 2, 3], [4, 5]] #=> ['1-2-3', '4-5']



m4 = {blength: 15, color: 'black', sites: 'iron'}

map (m4.), ['color', 'sites'] #=> ['black', 'iron']
var data, m4;
data = [ { name: 'alice', age: 19 },
         { name: 'tessa', age: 17 } ];

map(function(it){ return it.name; }, data);

filter(function(it){ return it.age > 18; }, data);

map(function(it){
  return it.toUpperCase();
}, ['hi', 'there']);

map(function(it){
  return it.join('-');
}, [[1, 2, 3], [4, 5]]);

m4 = { blength: 15, color: 'black', sites: 'iron' };

map(function(it){
  return m4[it];
}, ['color', 'sites']);

Include prelude.ls

We've been using functional functions from prelude.ls like map, filter, and so on.

Having to import prelude.ls in every file could become annoying. That's why a compiler flag has been added to automatically import prelude.ls - just use -d or --prelude. For example:

livescript -cd file.ls

This adds

if (typeof window != 'undefined' && window !== null) {
  prelude.installPrelude(window);
} else {
  require('prelude-ls').installPrelude(global);
}

to the top of every file. The installPrelude function has a check to make sure it only installs itself once if you are including multiple files on the page.

Arguments Shorthand

Another recent addition is shorthand for accessing a function's arguments.

&0 is the first argument, &1 is the second, and so on.

A good use case for this is when defining custom functions on which to fold, as those functions require two arguments and can't take advantage of some the other function defining shorthand available.

fold1 (-> &0 + &1 * &0), [1, 2, 3] 
#=> 12
fold1(function(){
  return arguments[0] + arguments[1] * arguments[0];
}, [1, 2, 3]);

Destructuring

Destructuring is a powerful way to extract values from variables. Rather than assigning to a simple variable, you can assign to data structures, which extract the values. For example:

[first, second] = [1, 2]
first  #=> 1
second #=> 2
var ref$, first, second;
ref$ = [1, 2], first = ref$[0], second = ref$[1];
first;
second;

You can also use splats:

[head, ...tail] = [1 to 5]
head #=> 1
tail #=> [2, 3, 4, 5]



[first, ...middle, last] = [1 to 5]
first  #=> 1
middle #=> [2, 3, 4]
last   #=> 5
var ref$, head, tail, first, i$, middle, last, slice$ = [].slice;
ref$ = [1, 2, 3, 4, 5], head = ref$[0], tail = slice$.call(ref$, 1);
head;
tail;
ref$ = [1, 2, 3, 4, 5], first = ref$[0], middle = 1 < (i$ = ref$.length - 1) ? slice$.call(ref$, 1, i$) : (i$ = 1, []), last = ref$[i$];
first;
middle;
last;

...and objects too!

{name, age} = {weight: 110, name: 'emma', age: 20}
name #=> 'emma'
age  #=> 20
var ref$, name, age;
ref$ = {
  weight: 110,
  name: 'emma',
  age: 20
}, name = ref$.name, age = ref$.age;
name;
age;

You can also name the entity which you are destructuring using :label, as well as arbitrarily nest the destructuring.

[[x, ...xs]:list1, [y, ...ys]:list2] = [[1, 2, 3], [4, 5, 6]]
x     #=> 1
xs    #=> [2, 3]
list1 #=> [1, 2, 3]
y     #=> 4
ys    #=> [5, 6]
list2 #=> [4, 5, 6]
var ref$, list1, x, xs, list2, y, ys, slice$ = [].slice;
ref$ = [[1, 2, 3], [4, 5, 6]], list1 = ref$[0], x = list1[0], xs = slice$.call(list1, 1), list2 = ref$[1], y = list2[0], ys = slice$.call(list2, 1);
x;
xs;
list1;
y;
ys;
list2;

Switch

Switch statements are really useful when defining functions. They allow you to cleanly check and return edge cases before the rest of your function body.

When you don't switch on anything, it is implied that you are switching on true and can thus use expressions evaluating to booleans for your cases.

switch
case 5 == 6 
  'never'
case false
  'also never'
case 6 / 2 is 3
  'here'
switch (false) {
case 5 !== 6:
  'never';
  break;
case !false:
  'also never';
  break;
case 6 / 2 !== 3:
  'here';
}

(It compiles to switching on false so it can cast the cases with a single not ! rather than two.)

Along with using the case keyword, you can use | for case and => for then. Furthermore, if you follow an arrow ->, assign =, or colon : with a case or |, an empty switch statement is implied.

As well, using either otherwise or _ after a case statement compiles to default.

state = | 2 + 2 == 5 => 'I love Big Brother'
        | _          => 'I love Julia'
var state;
state = (function(){
  switch (false) {
  case 2 + 2 !== 5:
    return 'I love Big Brother';
  default:
    return 'I love Julia';
  }
}());

Great for function definitions:

take(n, [x, ...xs]:list) =
  | n <= 0     => []
  | empty list => []
  | otherwise  => [x] +++ take n - 1, xs
var take, slice$ = [].slice;
take = curry$(function(n, list){
  var x, xs;
  x = list[0], xs = slice$.call(list, 1);
  switch (false) {
  case !(n <= 0):
    return [];
  case !empty(list):
    return [];
  default:
    return [x].concat(take(n - 1, xs));
  }
});
function curry$(f, args){
  return f.length > 1 ? function(){
    var params = args ? args.concat() : [];
    return params.push.apply(params, arguments) < f.length && arguments.length ?
      curry$.call(this, f, params) : f.apply(this, params);
  } : f;
}

Piping Updates

The forward pipe operator |> now has increased precedence, allowing you to assign a variable to the result of several steps without surrounding the entire thing with parentheses.

result = 4 |> (+ 1) |> (* 2)
result #=> 10
var result;
result = (function(it){
  return it * 2;
})(
(function(it){
  return it + 1;
})(
4));
result;

You can now also partially apply the pipe operator, with multiple arguments.

map (<| 4, 2), [(+), (*), (/)]
#=> [6, 8, 2]
map((function(it){
  return it(4, 2);
}), [
  curry$(function(x$, y$){ return x$ + y$; }),
  curry$(function(x$, y$){ return x$ * y$; }),
  curry$(function(x$, y$){ return x$ / y$;
  })
]);
function curry$(f, args){
  return f.length > 1 ? function(){
    var params = args ? args.concat() : [];
    return params.push.apply(params, arguments) < f.length && arguments.length ?
      curry$.call(this, f, params) : f.apply(this, params);
  } : f;
}

Conclusion

This has been a further overview of some of the features available in LiveScript which assist in functional style programming. While we have focused on functional programming, LiveScript is a multi-paradigm language, and has rich support for imperative and object oriented programming - including classes, mixins, super, and more.

Many people have started using LiveScript in their projects - you can check out a partial list of projects written in LiveScript, and projects which support LiveScript.

Have any ideas or suggestions? You can create a ticket on GitHub or ask general questions at the LiveScript Google Group.

And finally, for more information, check out the LiveScript site.


For more on LiveScript and prelude.ls, .

comments powered by Disqus