Saturday, Dec 11, 2010, 5:57 PM
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); } }
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), }, }, };
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()));
People: Chris (41), John (16), Tom (15) Teens: John (16), Tom (15)
Fluent JavaScript
// 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:
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. : )
15 comments
on this post
Chris Sells:
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:
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:
Monday, Dec 13, 2010, 12:43 PM
Jason Bunting:
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:
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:
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:
Wednesday, Dec 15, 2010, 3:19 AM
Asim Khan:
Wednesday, Dec 15, 2010, 5:42 AM
Visitor:
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:
Wednesday, Dec 15, 2010, 2:36 PM
Duarte Cunha Leão:
Wednesday, Dec 15, 2010, 5:17 PM
Chris Sells:
Wednesday, Dec 15, 2010, 5:21 PM
Clement:
An implementation of the linq api in JS
Wednesday, Dec 15, 2010, 5:26 PM
Chris Sells:
Wednesday, Dec 15, 2010, 9:09 PM
mikerosss:
Monday, Apr 25, 2011, 6:20 PM



