Fluent-Style Programming in JavaScript

I’ve been playing around with JavaScript a great deal lately and trying to find my way. I last programmed JS seriously about 10 years ago and it’s amazing to me how much the world has changed since then. For example, the fifth edition of ECMAScript (ES5) has recently been approved for standardization and it’s already widely implemented in modern browsers, including my favorite browser, IE9.

Fluent LINQ

However, I’m a big C# fan, especially the fluent API style of LINQ methods like Where, Select, OrderBy, etc. As an example, assume the following C# class:

class Person {
  public Person() { Children = new List<Person>(); }
  public string Name { get; set; }
  public DateTime Birthday { get; set; }
  public int Age { get { return (int)((DateTime.Now - Birthday).Days / 365.25); } }
  public ICollection<Person> Children { get; private set; }
  public override string ToString() { return string.Format("{0} ({1})", Name, Age); }
}
Creating a set of them is a matter of using the C# member initialization syntax:
var chris = new Person() {
  Name = "Chris",
  Birthday = new DateTime(1969, 6, 2),
  Children = {
    new Person() {
      Name = "John",
      Birthday = new DateTime(1994, 5, 5),
    },
    new Person() {
      Name = "Tom",
      Birthday = new DateTime(1995, 8, 30),
    },
  },
};
Without even realizing it, I used the word “set” to describe how I think of this collection of people. That’s how the LINQ methods encourage me to think and I like it – I’m working on sets of things, whether I’m filtering them, projecting them, ordering them, aggregating them in some way, etc. For example, here’s how I would print all of the items in my set:
var people = new Person[] { chris }.Union(chris.Children);
Console.WriteLine("People: " +
  people.Aggregate("", (s, p) => s + (s.Length == 0 ? "" : ", ") + p.ToString()));
Console.WriteLine("Teens: " +
  people.Where(p => p.Age > 12 && p.Age < 20).
    Aggregate("", (s, p) => s + (s.Length == 0 ? "" : ", ") + p.ToString()));
Here I’m using the Union method to combine all three elements into a new set, the Where method to filter one set to produce another and the Aggregate method to produce a single string. The output should be unsurprising:
People: Chris (41), John (16), Tom (15)
Teens: John (16), Tom (15)

Fluent JavaScript

LINQ is lousy with such set-oriented functions, e.g. Select, OrderBy, Take, Skip, First, Single, etc. and I use them a ton in my code (and my scripts). As it turns out, JS has these methods, too, both provided by popular JS toolkits like jQuery and in the language itself. Specifically, ES5 has a number of lovely methods for fluent programming. For example, given the same “class” in JS:
// Person constructor
function Person(args) {
  if (args.name) { this.name = args.name; }
  if (args.birthday) { this.birthday = args.birthday; }
  if (args.children) { this.children = args.children; }
}

// Person properties and methods
Person.prototype = Object.create(null, {
  name: { value: "", writable: true },
  birthday: { value: new Date(), writable: true },
  age: { get: function () { return Math.floor((new Date() - this.birthday) / 31557600000); } },
  children: { value: [], writable: true },
  toString: { value: function () { return this.name + " (" + this.age + ")"; } }
});

I can do several LINQ-style things on it:

var s = "";
var tom = new Person({ name: "tom", birthday: new Date(1995, 7, 30) });
var john = new Person({ name: "john", birthday: new Date(1994, 4, 5) });
var chris = new Person({ name: "chris", birthday: new Date(1969, 5, 2), children: [tom, john] });
var people = [tom, john, chris];

// select
s += "<h1>people</h1>" + people.map(function (p) { return p; }).join(", ");

// where
s += "<h1>teenagers</h1>" + people.filter(function (p) { return p.age > 12 && p.age < 20 }).join(", ");

// any
s += "<h1>any person over the hill?</h1>" + people.some(function (p) { return p.age > 40; });

// aggregate
s += "<h1>totalAge</h1>" + people.reduce(function (totalAge, p) { return totalAge += p.age; }, 0);

// take
s += "<h1>take 2</h1>" + people.slice(0, 2).join(", ");

// skip
s += "<h1>skip 2</h1>" + people.slice(2).join(", ");

// sort
s += "<h1>sorted by name</h1>" + people.slice(0).sort(
  function (lhs, rhs) { return lhs.name.localeCompare(rhs.name); }).join(", ");

// dump
document.getElementById("output").innerHTML = s;

Notice that several things are similar between JS and C# LINQ-style:

  • The array and object initialization syntax looks very similar so long as I follow the JS convention of passing in an anonymous object as a set of constructor parameters.
  • The JS Date type is like the .NET DateTime type except that months are zero-based instead of one-based (weird).
  • When a Person object is “added” to a string, JS is smart enough to automatically call the toString method.
  • The JS map function lets you project from one set to another like LINQ Select.
  • The JS filter function lets you filter a set like LINQ Where.
  • The JS some function lets you check if anything in a set matches a predicate like LINQ Any.
  • The JS reduce function lets you accumulate results from a set like the LINQ Aggregate.
  • The JS slice function is a multi-purpose array manipulation function that we’ve used here like LINQ Take and Skip.
  • The JS slice function also produces a copy of the array, which is handy when handing off to the JS sort, which acts on the array in-place.

The output looks as you’d expect:

image

We’re not all there, however. For example, the semantics of the LINQ First method are to stop looking once a match is found. Those semantics are not available in the JS filter method, which checks every element, or the JS some method, which stops once the first matching element is found, but returns a Boolean, not the matching element. Likewise, the semantics for Union and Single are also not available as well as several others that I haven’t tracked down. In fact, there are several JS toolkits available on the internet to provide the entire set of LINQ methods for JS programmers, but I don’t want to duplicate my C# environment, just the set-like thinking that I consider language-agnostic.

So, in the spirit of JS, I added methods to the build in types, like the Array type where all of the set-based intrinsics are available, to add the missing functionality:

Object.defineProperty(Array.prototype, "union", { value: function (rhs) {
  var rg = this.slice(0);
  rhs.forEach(function (v) { rg.unshift(v); })
  return rg;
}});

Object.defineProperty(Array.prototype, "first", { value: function (callback) {
  for (var i = 0, length = this.length; i < length; ++i) {
    var value = this[i];
    if (callback(value)) { return value; }
  }
  return null;
}});

Object.defineProperty(Array.prototype, "single", { value: function (callback) {
  var result = null;
  this.forEach(function (v) {
    if (callback(v)) {
      if (result != null) { throw "more than one result"; }
      result = v;
    }
  });
  return result;
}});

These aren’t perfectly inline with all of the semantics of the built-in methods, but they give you a flavor of how you can extend the prototype, which ends up feeling like adding extension methods in C#.

The reason to add methods to the Array prototype is that it makes it easier to continue to chain calls together in the fluent style that started all this experimentation, e.g.

// union
s += "<h1>chris's family</h1>" +
[chris].union(chris.children).map(function (p) { return p; }).join(", ");

Where Are We?

If you’re a JS programmer, it may be that you appreciate using it like a scripting language and so none of this “set-based” nonsense is important to you. That’s OK. JS is for everyone.

If you’re a C# programmer, you might dismiss JS as a “toy” language and turn your nose up at it. This would be a mistake. JS has a combination of ease-of-use for the non-programmer-programmer and raw power for the programmer-programmer that makes it worth taking seriously. Plus, with it’s popularity on the web, it’s hard to ignore.

If you’re a functional programmer, you look at all this set-based programming and say, “Duh. What took you so long?”

Me, I’m just happy I can program the way I like to in my new home on the web. : )



Comment Feed 15 comments on this post

Chris Sells:


BTW, I know there's a cool way to do the Person constructor using the JS "or" operator as the "default" operator, i.e.

function Person(args) {
  this.name = args.name || "";
  ...
}

I didn't do this for two reason:
1. The prototype for Person already contains the default value for the name property, so why set it again?
2. And why duplicate the same default in two places? When that default changes, I have to remember to change it in two places.

Saturday, Dec 11, 2010, 11:08 PM


Jim Raden:


I would advise that you use a StringBuilder or StringWriter in your production Aggregate code. Concatenation tends to be relatively expensive compared to the alternatives, especially in a loop.

That being said, in the end it's best to profile the code and compare concatenation style (as in your example) with StringBuilder or StringWriter. It's possible that the LINQ-to-Objects provider might do something smart with your Aggregate code; it's often surprising. On the other hand, the optimizer is only so smart, so it's best, in my opinion, to make explicit your use of a builder or a writer.

Monday, Dec 13, 2010, 6:28 AM


David Clarke:


Great post, thank you.

Monday, Dec 13, 2010, 12:43 PM


Jason Bunting:


@Jim: no need to be pedantic about something that I am certain Chris already knows about, and is completely beside the point here...

Years ago, I started using MochiKit; it's a JavaScript toolkit heavily influenced by Python and contains implementations of many functional programming concepts (e.g. partial application of functions), including the set-based functions you've mentioned here. You should check it out, if for no other reason than to be amazed that such functionality has existed in a JavaScript toolkit for so long. MochiKit is geared much more towards engineers than jQuery is, which may be one of the reasons it never became very popular.....

Tuesday, Dec 14, 2010, 12:59 AM


Bill Woodruff:


Excellent article; it would have been "icing on the cake" to have also described how you'd use jQuery' built-in operators "fluently."

On a "semantic level" it was hard for me to get my mind to perceive the C# code example as being other than a class definition which contains an internal collection of instances of itself; and, then, an instance of the class definition that also instantiates the internal collection. In other words hard me to think of it as a "set." Also, the word "lousy" here is an very interesting usage :)

Thanks !

best, Bill

Wednesday, Dec 15, 2010, 12:01 AM


Richard Triance:


Extremely fluent article. It illustrates the best of both languages as well as the LINQ concepts (not to mention some JSONy bits and pieces).

We need more JS coverage and positive advertising. I am a C#er of 10 years and a JS of 15 - I like both and relish the thought of the thread of commonality between them.

Wednesday, Dec 15, 2010, 12:28 AM


jJ:


Have a look at underscore.js http://documentcloud.github.com/underscore/ it is aimed exactly at what you describe and uses new ES5 functions where available...

Wednesday, Dec 15, 2010, 3:19 AM


Asim Khan:


Very Informative...

Wednesday, Dec 15, 2010, 5:42 AM


Visitor:


"If you’re a C# programmer, you might dismiss JS as a “toy” language and turn your nose up at it. This would be a mistake."

I don't think it would be a mistake. Will JS allow me to generate the .NET assemblies out of which I get my $$$?

Wednesday, Dec 15, 2010, 10:53 AM


Kevin:


I was about to ask do people still turn their nose up at JS these days; then I got to visitor's post.

Wednesday, Dec 15, 2010, 2:36 PM


Duarte Cunha Leão:


Nice post. I had the same joy you had now with the JavaScript, C# and Linq similarities. There is one thing I think you are missing from Linq which is its laziness. I've made something similar that you can find at: https://github.com/dcleao/jsLazyLinq/

Wednesday, Dec 15, 2010, 5:17 PM


Chris Sells:


You're right, Duarte. The laziness of LINQ is a big benefit. However, in JS, isn't it the case that, since there are no real "data providers" that can interpret the LINQ queries and produce optimized queries, that you're pretty much stuck querying the data on the wire however you like and then just manipulating the results in memory? And if that's the case, is the laziness such a big deal?

Wednesday, Dec 15, 2010, 5:21 PM


Clement:


Checkout linqjs (http://linqjs.codeplex.com/)
An implementation of the linq api in JS

Wednesday, Dec 15, 2010, 5:26 PM


Chris Sells:


underscore.js looks way cool!

Wednesday, Dec 15, 2010, 9:09 PM


mikerosss:


Thanks for some quality points there. I am kind of new to online , so I printed this off to put in my file, any better way to go about keeping track of it then printing?

Monday, Apr 25, 2011, 6:20 PM





comment on this post

HTML tags will be escaped.

Powered By ASP.NET

Hosted by SecureWebs

Mensa

IEEE