There was something that bugged me about Allen Wirfs-Brock‘s proposed “monocle-mustache” syntax for JS: it updates the properties of the object on the left-hand side, but it doesn’t look like it’s updating properties; it looks like some strange combination of creating a new object and accessing properties of the left-hand side:
this.{ foo: 17, bar: "hello", baz: true };
Then this week a couple things happened that got me thinking: Reg Braithwaite (aka @raganwald) posted his proposal for a CoffeeScript syntax to support the “fluent style” of programming, inspired by Smalltalk’s message cascades:
array
.pop()
.pop()
.pop()
path
.moveTo(10, 10)
.stroke("red")
.fill("blue")
.ellipse(50, 50)
Next I saw Bob Nystrom‘s post about a cascade syntax proposal for Dart, which is a small variation on Allen’s monocle-mustache:
document.query('#myTable').{
queryAll('.firstColumn').{
style.{
background = 'red',
border = '2px solid black'
},
text = 'first column'
},
queryAll('.lastColumn').{
style.background = 'blue',
text = 'last column'
}
};
This is really just a tiny tweak to Allen’s original syntax, but it makes a world of difference: it uses the = sign for assignment. Much clearer!
Now, assignments with commas don’t really look like JS. But since the point of cascades is to do imperative sequencing — i.e., to ignore the result of intermediate message sends and do each message send on the original object from the left-hand side — it makes perfect sense to use a statement-like syntax:
array.{
pop();
pop();
pop();
};
path.{
moveTo(10, 10);
stroke("red");
fill("blue");
ellipse(50, 50);
};
this.{
foo = 17;
bar = "hello";
baz = true;
};
Even sweeter, JavaScript’s automatic semicolon insertion kicks in and lets you do this very concisely:
array.{
pop()
pop()
pop()
};
path.{
moveTo(10, 10)
stroke("red")
fill("blue")
ellipse(50, 50)
};
this.{
foo = 17
bar = "hello"
baz = true
};
What’s so great about a cascade syntax is that when you want to do imperative programming on the same object, you don’t have to rely on the API creator to return this from every method. As the client of the API you don’t have to care what the method returns, you throw away the result anyway.
In fact, this is one of the things I don’t like about method chaining in JS today: you can’t actually tell whether the method is mutating the current object and returning itself or producing some entirely new object. And when you mix the two styles, it gets even blurrier. Look at Bob’s example from jQuery:
$('#myTable')
.find('.firstColumn')
.css({'background': 'red',
'border': '2px solid black'})
.text('first column')
.end()
.find('.lastColumn')
.css('background','blue')
.text('last column');
You just have to know that .find() produces a new object, and .css() and .text() modify it and produce the same object, but nothing about the syntax tells you this. (And personally, I always feld that .end() was where jQuery’s API jumps the shark.)
With a cascade syntax in addition to normal method calls, you can so much more easily distinguish when you’re doing something to the same object, and when you’re selecting new objects:
$('#myTable').{
find('.firstColumn').{
style.{
background = 'red'
border = '2px solid black'
}
text = 'first column'
}
find('.lastColumn').{
style.background = 'blue'
text = 'last column'
}
};
Gist here. These are just my initial thoughts; I’ll have to work on fleshing out a full proposal, including the full grammar spec.