Table of Contents
- A Pull of the Lever: Prefaces
- Prelude: Values and Expressions
- CoffeeScript Ristretto
- The first sip: Functions
- Slurp: More About Functions and Scope
- References, Identity, Arrays, and Objects
- Stir the Espresso: Objects, Mutation, and State
- Finish the Cup: Instances and Classes
- An Extra Shot of Ideas
- A Golden Crema
A Pull of the Lever: Prefaces
“We always pour our coffee in the ristretto, or restricted, tradition. The coffee is restricted to the most flavourful part of the shot. This tradition offers the heaviest shot, thickest texture and finest flavour that the coffee has to offer.”–David Schomer
About This Book
Learning about “for” loops is not learning to program, any more than learning about pencils is learning to draw.–Bret Victor, Learnable Programming
Programming languages are characterized by their syntax and their semantics. The syntax of a language defines its user interface; If you understand a language’s syntax, you understand what it makes easy. The semantics of a language defines its capabilities; If you understand a language’s semantics, you understand what it does well.
CoffeeScript Ristretto is first and foremost about a book about programming with functions, because its flexible and powerful functions are what make the CoffeeScript programming language so capable, and what CoffeeScript does well.
how this book is organized
CoffeeScript Ristretto begins at the beginning, with values and expressions, and builds from there to discuss types, identity, functions, closures, scopes, and many more subjects up to working with classes and instances in Chapter Five. Chapter Six, “An Extra Shot of Ideas,” introduces advanced CoffeeScript idioms like method decorators. Since CoffeeScript Ristretto is a book about CoffeeScript’s semantics, each topic is covered thoroughly, without hand-waving or simplification.
As a result, CoffeeScript Ristretto is a rich, dense read, much like the Espresso Ristretto beloved by coffee enthusiasts everywhere.
Foreword by Jeremy Ashkenas
“I particularly enjoyed this small book because I’ve reached for it a hundred times before and come up empty-handed. Large and heavy manuals on object-oriented programming and JavaScript are all around us, but to find a book that tackles the fundamental features of functions and objects in a brief, strong gulp, is rare indeed.
“With the inimitable Mr. Braithwaite as your guide, you’ll explore the nature of functions, the nooks and crannies of lexical scope, the essence of reference, the method of mutation, the construction of classes, callbacks, prototypes, promises, and more. And you’ll learn a great deal about the internals of CoffeeScript and the semantics of JavaScript along the way. Every feature is broken apart into its basic pieces, and you put it back together yourself, brick by brick.
“This book is dense, but hopefully you’ve chosen it because you like your ristretto bold and strong. If Reg had pulled it any thicker you’d have to chew it.”—Jeremy Ashkenas, CoffeeScript’s creator
Legend
Some text in monospaced type like this
in the text represents some code being discussed. Some monospaced code in its own lines also represents code being discussed:
1
this
.
async
=
do
(
async
=
undefined
)
->
2
3
async
=
(
fn
)
->
4
(
argv
...,
callback
)
->
5
callback
(
fn
.
apply
(
this
,
argv
))
Sometimes it will contain some code for you to type in for yourself. When it does, the result of typing something in will often be shown using #=>
, like this:
1
2
+
2
2
#
=>
4
A paragraph marked like this is a “key fact.” It summarizes an idea without adding anything new. |
A paragraph marked like this is a suggested exercise to be performed on your own. |
Prelude: Values and Expressions
The following material is extremely basic, however like most stories, the best way to begin is to start at the very beginning.
Imagine we are visiting our favourite coffee shop. They will make for you just about any drink you desire, from a short, intense espresso ristretto through a dry cappuccino, up to those coffee-flavoured desert concoctions featuring various concentrated syrups and milks. (You tolerate the existence of sugary drinks because they provide a sufficient profit margin to the establishment to finance your hanging out there all day using their WiFi and ordering a $3 drink every few hours.)
You express your order at one end of their counter, the folks behind the counter perform their magic, and deliver the coffee you value at the other end. This is exactly how the CoffeeScript environment works for the purpose of this book. We are going to dispense with web servers, browsers and other complexities and deal with this simple model: You give the computer an expression, and it returns a value, just as you express your wishes to a barista and receive a coffee in return.
values and expressions
All values are expressions. Say you hand the barista a Cafe Cubana. Yup, you hand over a cup with some coffee infused through partially caramelized sugar. You say, “I want one of these.” The barista is no fool, she gives it straight back to you, and you get exactly what you want. Thus, a Cafe Cubana is an expression (you can use it to place an order) and a value (you get it back from the barista).
Let’s try this with something the computer understands easily:
1
42
Is this an expression? A value? Neither? Or both?
The answer is, this is both an expression and a value.1 The way you can tell that it’s both is very easy: When you type it into CoffeeScript, you get the same thing back, just like our Cafe Cubana:
1
42
2
#
=>
42
All values are expressions. That’s easy! Are there any other kinds of expressions? Sure! let’s go back to the coffee shop. Instead of handing over the finished coffee, we can hand over the ingredients. Let’s hand over some ground coffee plus some boiling water.
Now the barista gives us back an espresso. And if we hand over the espresso, we get the espresso right back. So, boiling water plus ground coffee is an expression, but it isn’t a value.2 Boiling water is a value. Ground coffee is a value. Espresso is a value. Boiling water plus ground coffee is an expression.
Let’s try this as well with something else the computer understands easily:
1
"CoffeeScript"
+
" "
+
"Ristretto"
2
#
=>
"CoffeeScript Ristretto"
These are “strings,” values featured in almost every contemporary computer language. We see that “strings” are values, and you can make an expression out of strings and an operator +
. Since strings are values, they are also expressions by themselves. But strings with operators are not values, they are expressions. Now we know what was missing with our “coffee grounds plus hot water” example. The coffee grounds were a value, the boiling hot water was a value, and the “plus” operator between them made the whole thing an expression that was not a value.
values and identity
In CoffeeScript, we test whether two values are identical with the is
operator, and whether they are not identical with the isnt
operator:
1
2
is
2
2
#
=>
true
3
4
'
hello
'
isnt
'
goodbye
'
5
#
=>
true
How does is
work, exactly? Imagine that you’re shown a cup of coffee. And then you’re shown another cup of coffee. Are the two cups “identical?” In CoffeeScript, there are four possibilities:
First, sometimes, the cups are of different types. One is a demitasse, the other a mug. This corresponds to comparing two things in CoffeeScript that have different types. For example, the string "2"
is not the same thing as the number 2
. Strings and numbers are different types, so strings and numbers are never identical:
1
2
is
'2'
2
#
=>
false
3
4
true
isnt
'
true
'
5
#
=>
true
Second, sometimes, the cups are of the same type–perhaps two espresso cups–but they have different contents. One holds a single, one a double. This corresponds to comparing two CoffeeScript values that have the same type but different “content.” For example, the number 5
is not the same thing as the number 2
.
1
true
is
false
2
#
=>
false
3
4
2
isnt
5
5
#
=>
true
6
7
'
two
'
is
'
five
'
8
#
=>
false
What if the cups are of the same type and the contents are the same? Well, CoffeeScript’s third and fourth possibilities cover that.
value types
Third, some types of cups have no distinguishing marks on them. If they are the same kind of cup, and they hold the same contents, we have no way to tell the difference between them. This is the case with the strings, numbers, and booleans we have seen so far.
1
2
+
2
is
4
2
#
=>
true
3
4
(
2
+
2
is
4
)
is
(
2
isnt
5
)
5
#
=>
true
Note well what is happening with these examples: Even when we obtain a string, number, or boolean as the result of evaluating an expression, it is identical to another value of the same type with the same “content.” Strings, numbers, and booleans are examples of what CoffeeScript calls “value” or “primitive” types. We’ll use both terms interchangeably.
We haven’t encountered the fourth possibility yet. Stretching the metaphor somewhat, some types of cups have a serial number on the bottom. So even if you have two cups of the same type, and their contents are the same, you can still distinguish between them.
reference types
So what kinds of values might be the same type and have the same contents, but not be considered identical to CoffeeScript? Let’s meet a data structure that is very common in contemporary programming languages, the Array (other languages sometimes call it a List or a Vector).
Here are some expressions for arrays you can try typing for yourself:
1
[1, 2, 3]
2
[1,2,2]
3
[1..3]
These are expressions, and you can combine []
with other expressions. Go wild with things like:
1
[2-1, 2, 2+1]
2
[1, 1+1, 1+1+1]
We aren’t going to spend a lot of time talking about it, but if you enable multiline mode (with ctrl-v), you can also type things like:
1
[
2
1
3
2
4
3
5
]
Notice that you are always generating arrays with the same contents. But are they identical the same way that every value of 42
is identical to every other value of 42
? Try these for yourself:
1
[1..3] is [1,2,3]
2
[1,2,3] is [1, 2, 3]
3
[1, 2, 3] is [1, 2, 3]
How about that! When you type [1, 2, 3]
or any of its variations, you are typing an expression that generates its own unique array that is not identical to any other array, even if that other array also looks like [1, 2, 3]
. It’s as if CoffeeScript is generating new cups of coffee with serial numbers on the bottom.
They look the same, but if you examine them with is
, you see that they are different. Every time you evaluate an expression (including typing something in) to create an array, you’re creating a new, distinct value even if it appears to be the same as some other array value. As we’ll see, this is true of many other kinds of values, including functions, the main subject of this book.
interlude…
Wikipedia on Ristretto:
”Ristretto is a very ‘short’ shot of espresso coffee. Originally this meant pulling a hand press faster than usual using the same amount of water as a regular shot of espresso. Since the water came in contact with the grinds for a much shorter time the caffeine is extracted in reduced ratio to the flavorful coffee oils. The resultant shot could be described as bolder, fuller, with more body and less bitterness.”
CoffeeScript Ristretto
The first sip: Functions
As Little As Possible About Functions, But No Less
In CoffeeScript, functions are values, but they are also much more than simple numbers, strings, or even complex data structures like trees or maps. Functions represent computations to be performed. Like numbers, strings, and arrays, they have a representation in CoffeeScript. Let’s start with the very simplest possible function. In CoffeeScript, it looks like this:3
1
->
This is a function that is applied to no values and produces no value. Hah! There’s the third thing. How do we represent “no value” in CoffeeScript? We’ll find out in a minute. First, let’s verify that our function is a value:
1
->
2
#
=>
[
Function
]
What!? Why didn’t it type back ->
for us? This seems to break our rule that if an expression is also a value, CoffeeScript will give the same value back to us. What’s going on? The simplest and easiest answer is that although the CoffeeScript interpreter does indeed return that value, displaying it on the screen is a slightly different matter. [Function]
is a choice made by the people who wrote Node.js, the JavaScript environment that hosts the CoffeeScript REPL. If you try the same thing in a browser (using “Try CoffeeScript” at coffeescript.org for example), you’ll get something else entirely that isn’t CoffeeScript at all, it’s JavaScript.
functions and identities
You recall that we have two types of values with respect to identity: Value types and reference types. Value types share the same identity if they have the same contents.Reference types do not.
Which kind are functions? Let’s try it. For reasons of appeasing the CoffeeScript parser, we’ll enclose our functions in parentheses:
1
(
->
)
is
(
->
)
2
#
=>
false
Like arrays, every time you evaluate an expression to produce a function, you get a new function that is not identical to any other function, even if you use the same expression to generate it. “Function” is a reference type.
applying functions
Let’s put functions to work. The way we use functions is to apply them to zero or more values called arguments. Just as 2 + 2
produces a value (in this case 4
), applying a function to zero or more arguments produces a value as well. Some folks call the arguments the inputs to a function. Whether you use the word “inputs” or “arguments,” it’s certainly a good thing to think of the function’s arrow as pointing from the inputs to the output!
Here’s how we apply a function to some values in CoffeeScript: Let’s say that fn_expr is an expression that when evaluated, produces a function. Let’s call the arguments args. Here’s how to apply a function to some arguments:
fn_expr(
args)
Right now, we only know about one such expression: ->
, so let’s use it. We’ll put it in parentheses5 to keep the parser happy, like we did above: (->)
. Since we aren’t giving it any arguments, we’ll simply write ()
after the expression. So we write:
1
(
->
)()
2
#
=>
undefined
What is this undefined
?
undefined
In CoffeeScript, the absence of a value is written undefined
, and it means there is no value. It will crop up again. undefined
is its own type of value, and it acts like a value type:
1
undefined
2
#
=>
undefined
Like numbers, booleans and strings, CoffeeScript can print out the value undefined
.
1
undefined
is
undefined
2
#
=>
true
3
(
->
)()
is
(
->
)()
4
#
=>
true
5
(
->
)()
is
undefined
6
#
=>
true
No matter how you evaluate undefined
, you get an identical value back. undefined
is a value that means “I don’t have a value.” But it’s still a value :-)
Speaking of is undefined
, a common pattern in CoffeeScript programming is to test wither something isnt undefined
:
1
undefined
isnt
undefined
2
#
=>
false
3
'
undefined
'
isnt
undefined
4
#
=>
true
5
false
isnt
undefined
6
#
=>
true
This is so common that a shortcut is provided, the suffix operator ?
:
1
undefined
?
2
#
=>
false
3
'
undefined
'
?
4
#
=>
true
5
false
?
6
#
=>
true
functions with no arguments
Back to our function. We evaluated this:
1
(
->
)()
2
#
=>
undefined
Let’s recall that we were applying the function ->
to no arguments (because there was nothing inside of ()
). So how do we know to expect undefined
? That’s easy. When we define a function, we write the arguments it expects to the left of the ->
and an optional expression to the right. This expression is called the function’s body. Like this:
(
args) ->
body
There is a funny rule: You can omit the body, and if you do, applying the function always evaluates to undefined
.6
What about functions that have a body? Let’s write a few. Here’s the rule: We can use anything we’ve already learned how to use as an expression. Cutting and pasting, that means that the following are all expressions that evaluate to functions:
1
->
2
2
->
2
+
2
3
->
"Hello"
+
" "
+
"CoffeeScript"
4
->
true
is
not
false
5
->
false
isnt
true
And you can evaluate them by typing any of these into CoffeeScript:
1
(
->
2
)()
2
#
=>
2
3
(
->
2
+
2
)()
4
#
=>
4
5
(
->
"Hello"
+
" "
+
"CoffeeScript"
)()
6
#
=>
"Hello CoffeeScript"
7
(
->
true
is
not
false
)()
8
#
=>
true
9
(
->
false
isnt
true
)()
10
#
=>
true
We haven’t discussed arguments yet, but let’s get clever with what we already have.
functions that evaluate to functions
If an expression that evaluates to a function is, well, an expression, and if a function expression can have any expression on its right side… Can we put an expression that evaluates to a function on the right side of a function expression?
Yes:
1
->
->
That’s a function! It’s a function that when applied, evaluates to a function that when applied, evaluates to undefined
. Watch and see:
1
->
->
2
#
=>
[
Function
]
It evaluates to a function…
1
(
->
->
)()
2
#
=>
[
Function
]
That when applied, evaluates to a function…
1
(
->
->
)()()
2
#
=>
undefined
That when applied, evaluates to undefined
. Likewise:
1
->
->
true
That’s a function! It’s a function that when applied, evaluates to a function, that when applied, evaluates to true
:
1
(
->
->
true
)()()
2
#
=>
true
Well. We’ve been very clever, but so far this all seems very abstract and computer science-y. Diffraction of a crystal is beautiful and interesting in its own right, but you can’t blame us for wanting to be shown a practical use for it, like being able to determine the composition of a star millions of light years away. So… In the next chapter, “I’d Like to Have an Argument, Please,” we’ll see how to make functions practical.
showering felicitous encouragement to incentivise the practice of skewing the distribution of expression length towards the minimal mode
Or, In praise of keeping it short.
When describing the behaviour of functions, we often use the expression “that when applied, evaluates to…” For example, “The function (x, y) -> x + y
is a function, that when applied to two integer arguments, evaluates to the sum of the arguments.”7 This is technically correct. But a mouthful. Another expression you will often hear is “returns,” as in “The function (x, y) -> x + y
is a function that returns the sum of its arguments.”
“Returns” is a little less precise, and is context dependant. But it suits our purposes, so we will often use it. But when we use it, we will always mean “when applied, evaluates to…”
And with that’s let’s move on!
Ah. I’d Like to Have an Argument, Please.
Up to now, we’ve looked at functions without arguments. We haven’t even said what an argument is, only that our functions don’t have any.
Let’s make a function with an argument:
1
(
room
)
->
This function has one argument, room
, and no body. Here’s a function with two arguments and no body:
1
(
room
,
board
)
->
I’m sure you are perfectly comfortable with the idea that this function has two arguments, room
, and board
. What does one do with the arguments? Use them in the body, of course. What do you think this is?
1
(
diameter
)
->
diameter
*
3.14159265
It’s a function for calculating the circumference of a circle given the radius. I read that aloud as “When applied to a value representing the diameter, this function gives (that’s my word for the arrow) the diameter times 3.14159265.”
Remember that to apply a function with no arguments, we wrote (->)()
. To apply a function with an argument (or arguments), we put the argument (or arguments) within the parentheses, like this:
1
((
diameter
)
->
diameter
*
3.14159265
)(
2
)
2
#
=>
6.2831853
You won’t be surprised to see how to write and apply a function to two arguments:
1
((
room
,
board
)
->
room
+
board
)(
800
,
150
)
2
#
=>
950
a quick summary of functions and bodiesHow arguments are used in a body’s expression is probably perfectly obvious to you from the examples, especially if you’ve used any programming language (except, possibly for the dialect of BASIC I recall from my secondary school that didn’t allow parameters when you called a procedure). Expressions consist either of representations of values (like This loose definition is recursive, so we can intuit (or use our experience with other languages) that since a function has an expression on its right hand side, we can write a function that has a function as its expression, or an array that contains another array expression. Or a function that gives an array, an array of functions, a function that gives an array of functions, and so forth:
|
call by value
Like most contemporary programming languages, CoffeeScript uses the “call by value” evaluation strategy. That’s a $2.75 way of saying that when you write some code that appears to apply a function to an expression or expressions, CoffeeScript evaluates all of those expressions and applies the functions to the resulting value(s).
So when you write:
1
((
diameter
)
->
diameter
*
3.14159265
)(
1
+
1
)
2
#
=>
6.2831853
What happened internally is that the expression 1 + 1
was evaluated first, resulting in 2
. Then our circumference function was applied to 2
.9
variables and bindings
Right now everything looks simple and straightforward, and we can move on to talk about arguments in more detail. And we’re going to work our way up from (diameter) -> diameter * 3.14159265
to functions like:
1
(
x
)
->
(
y
)
->
x
In order to talk about how this works, we should agree on a few terms (you may already know them, but let’s check-in together and “synchronize our dictionaries”). The first x
, the one in (x) ->
, is an argument. The y
in (y) ->
is another argument. The second x
, the one in -> x
, is not an argument, it’s an expression referring to a variable. Arguments and variables work the same way whether we’re talking about (x) -> (y) -> x
or just plain (x) -> x
.
Every time a function is invoked (“invoked” is a synonym for “applied to zero or more arguments”), a new environment is created. An environment is a (possibly empty) dictionary that maps variables to values by name. The x
in the expression that we call a “variable” is itself an expression that is evaluated by looking up the value in the environment.
How does the value get put in the environment? Well for arguments, that is very simple. When you apply the function to the arguments, an entry is placed in the dictionary for each argument. So when we write:
1
((
x
)
->
x
)(
2
)
2
#
=>
2
What happens is this:
- CoffeeScript parses this whole thing as an expression made up of several sub-expressions.
- It then starts evaluating the expression, including evaluating sub-expressions
- One sub-expression,
(x) -> x
evaluates to a function. - Another,
2
, evaluates to the number 2. - CoffeeScript now evaluates applying the function to the argument
2
. Here’s where it gets interesting… - An environment is created.
- The value ‘2’ is bound to the name ‘x’ in the environment.
- The expression ‘x’ (the right side of the function) is evaluated within the environment we just created.
- The value of a variable when evaluated in an environment is the value bound to the variable’s name in that environment, which is ‘2’
- And that’s our result.
When we talk about environments, we’ll use an unsurprising syntax for showing their bindings: {x: 2, ...}
. meaning, that the environment is a dictionary, and that the value 2
is bound to the name x
, and that there might be other stuff in that dictionary we aren’t discussing right now.
call by sharing
Earlier, we distinguished CoffeeScript’s value types from its reference types. At that time, we looked at how CoffeeScript distinguishes objects that are identical from objects that are not. Now it is time to take another look at the distinction between value and reference types.
There is a property that CoffeeScript strictly maintains: When a value–any value–is passed as an argument to a function, the value bound in the function’s environment must be identical to the original.
We said that CoffeeScript binds names to values, but we didn’t say what it means to bind a name to a value. Now we can elaborate: When CoffeeScript binds a name to a value type value, it makes a copy of the value and places the copy in the environment. As you recall, value types like strings and numbers are identical to each other if they have the same content. So CoffeeScript can make as many copies of strings, numbers, or booleans as it wishes.
What about reference types? CoffeeScript cannot place a copy of an array or object in an environment, because the copy would not be identical to the original. So instead, CoffeeScript does not place reference values in any environment. CoffeeScript places references to reference types in environments, and when the value needs to be used, CoffeeScript uses the reference to obtain the original.
Because many references can share the same value, and because CoffeeScript passes references as arguments, CoffeeScript can be said to implement “call by sharing” semantics. Call by sharing is generally understood to be a specialization of call by value, and it explains why some values are known as value types and other values are known as reference types.
And with that, we’re ready to look at closures. When we combine our knowledge of value types, reference types, arguments, and closures, we’ll understand why this function always evaluates to true
no matter what argument you apply it to:
1
(
value
)
->
2
((
copy
)
->
3
copy
is
value
4
)(
value
)
Closures and Scope
Before we explain (x) -> (y) -> x
, we’re going to toss in something that doesn’t directly affect our explanation, but makes things easier to see visually. Up to now, every function has looked like this: (
arguments) ->
body. There’s another way to write functions. For example here’s the other way to write (x) -> x
:
1
(
x
)
->
2
x
You get the idea: You can indent the body instead of putting it on the same line. Let’s introduce a new term: (x) ->
is the function’s signature, and x
is its body, just as we’ve mentioned before.
That means inductively we can also write (x) -> (y) -> x
in two other ways:
1
(
x
)
->
2
(
y
)
->
x
Or:
1
(
x
)
->
2
(
y
)
->
3
x
The indents help us see that the x
is the body “belonging to” a function with signature (y) ->
, and that it belongs to a function with signature (x) ->
.
Time to see how a function within a function works:
1
((
x
)
->
2
(
y
)
->
3
x
4
)(
1
)(
2
)
5
#
=>
1
First off, let’s use what we learned above. Given (
some function)(
some argument)
, we know that we apply the function to the argument, create an environment, bind the value of the argument to the name, and evaluate the function’s expression. So we do that first with this code:
1
((
x
)
->
2
(
y
)
->
3
x
4
)(
1
)
5
#
=>
[
Function
]
The environment belonging to the function with signature (x) ->
becomes {x: 1, ...}
, and the result of applying the function is another function value. It makes sense that the result value is a function, because the expression for (x) ->
’s body is:
1
(
y
)
->
2
x
So now we have a value representing that function. Then we’re going to take the value of that function and apply it to the argument 2
, something like this:
1
((
y
)
->
2
x
)(
2
)
So we seem to get a new environment {y: 2, ...}
. How is the expression x
going to be evaluated in that function’s environment? There is no x
in its environment, it must come from somewhere else.
If functions without free variables are pure, are closures impure?
The function (y) -> x
is interesting. It contains a free variable, x
.10 A free variable is one that is not bound within the function. Up to now, we’ve only seen one way to “bind” a variable, namely by passing in an argument with the same name. Since the function (y) -> x
doesn’t have an argument named x
, the variable x
isn’t bound in this function, which makes it “free.”
Now that we know that variables used in a function are either bound or free, we can bifurcate functions into those with free variables and those without:
- Functions containing no free variables are called pure functions.
- Functions containing one or more free variables are called closures.
Pure functions are easiest to understand. They always mean the same thing wherever you use them. Here are some pure functions we’ve already seen:
1
->
2
3
(
x
)
->
4
x
5
6
(
x
)
->
7
(
y
)
->
8
x
The first function doesn’t have any variables, therefore doesn’t have any free variables. The second doesn’t have any free variables, because its only variable is bound. The third one is actually two functions, one in side the other. (y) ->
has a free variable, but the entire expression refers to (x) ->
, and it doesn’t have a free variable: The only variable anywhere in its body is x
, which is certainly bound within (x) ->
.
From this, we learn something: A pure function can contain a closure.
If pure functions can contain closures, can a closure contain a pure function? Using only what we’ve learned so far, attempt to compose a closure that contains a pure function. If you can’t, give your reasoning for why it’s impossible. |
Pure functions always mean the same thing because all of their “inputs” are fully defined by their arguments. Not so with a closure. If I present to you this free function (x, y) -> x + y
, we know exactly what it does with (2, 2)
. But what about this closure: (y) -> x + y
? We can’t say what it will do with argument (2)
without understanding the magic for evaluating the free variable x
.
it’s always the environment
To understand how closures are evaluated, we need to revisit environments. As we’ve said before, all functions are associated with an environment. We also hand-waved something when describing our environment. Remember that we said the environment for ((x) -> (y) -> x)(1)
is {x: 1, ...}
and that the environment for ((y) -> x)(2)
is {y: 2, ...}
? Let’s fill in the blanks!
The environment for ((y) -> x)(2)
is actually {y: 2, '..': {x: 1, ...}}
. '..'
means something like “parent” or “enclosure” or “super-environment.” It’s (x) ->
’s environment, because the function (y) -> x
is within (x) ->
’s body. So whenever a function is applied to arguments, its environment always has a reference to its parent environment.
And now you can guess how we evaluate ((y) -> x)(2)
in the environment {y: 2, '..': {x: 1, ...}}
. The variable x
isn’t in (y) ->
’s immediate environment, but it is in its parent’s environment, so it evaluates to 1
and that’s what ((y) -> x)(2)
returns even though it ended up ignoring its own argument.
Functions can have grandparents too:
1
(
x
)
->
2
(
y
)
->
3
(
z
)
->
4
x
+
y
+
z
This function does much the same thing as:
1
(
x
,
y
,
z
)
->
2
x
+
y
+
z
Only you call it with (1)(2)(3)
instead of (1, 2, 3)
. The other big difference is that you can call it with (1)
and get a function back that you can later call with (2)(3)
.
shadowy variables from a shadowy planet
An interesting thing happens when a variable has the same name as an ancestor environment’s variable. Consider:
1
(
x
)
->
2
(
x
,
y
)
->
3
x
+
y
The function (x, y) -> x + y
is a pure function, because its x
is defined within its own environment. Although its parent also defines an x
, it is ignored when evaluating x + y
. CoffeeScript always searches for a binding starting with the functions own environment and then each parent in turn until it finds one. The same is true of:
1
(
x
)
->
2
(
x
,
y
)
->
3
(
w
,
z
)
->
4
(
w
)
->
5
x
+
y
+
z
When evaluating x + y + z
, CoffeeScript will find x
and y
in the great-grandparent scope and z
in the parent scope. The x
in the great-great-grandparent scope is ignored, as are both w
s. When a variable has the same name as an ancestor environment’s binding, it is said to shadow the ancestor.
There can be spirited discussions over shadowing variables. Some people argue that shadowing is a good thing, as it allows small pieces of code to be understood without checking any enclosing environments. Others argue that it’s a bad thing, as it creates a confusing situation where two things with the same name actually are different things, a lot like naming both of your twins “Lesley.”
which came first, the chicken or the egg?
This behaviour of pure functions and closures has many, many consequences that can be exploited to write software. We are going to explore them in some detail as well as look at some of the other mechanisms CoffeeScript provides for working with variables and mutable state.
But before we do so, there’s one final question: Where does the ancestry start? If there’s no other code in a file, what is (x) -> x
’s parent environment?
CoffeeScript always has the notion of at least one environment we do not control: A global environment in which many useful things are bound such as libraries full of standard functions. So when you invoke ((x) -> x)(1)
in the REPL, its full environment is going to look like this: {x: 1, '..':
global environment}
. When you use CoffeeScript to compile physical files for use in node or web applications, CoffeeScript does something interesting: It wraps your code in an invisible function, like this:
1
(
->
2
((
x
)
->
x
)(
1
)
3
)()
The effect of this is to insert a new, empty environment in between the global environment and your own functions: {x: 1, '..': {'..':
global environment}}
. As we’ll see when we discuss mutable state, this helps to prevent programmers from accidentally changing the global state that is shared by code in every file.
Summary
Functions
|
Slurp: More About Functions and Scope
Let Me Show You What To Do
let
Up to now, all we’ve really seen are anonymous functions, functions that don’t have a name. This feels very different from programming in most other languages, where the focus is on naming functions, methods, and procedures. Naming things is a critical part of programming, but all we’ve seen so far is how to name arguments.
There are other ways to name things in CoffeeScript, but before we learn some of those, let’s see how to use what we already have to name things. Let’s revisit a very simple example:
1
(
diameter
)
->
2
diameter
*
3.14159265
What is this “3.14159265” number? Pi, obviously. We’d like to name it so that we can write something like:
1
(
diameter
)
->
2
diameter
*
Pi
In order to bind 3.14159265
to the name Pi
, we’ll need a function with a parameter of Pi
and an argument of 3.14159265
:
1
((
Pi
)
->
2
???
3
)(
3.14159265
)
What do we put inside our new function that binds 3.14159265
to the name Pi
when evaluated? Our circumference function, of course:
1
((
Pi
)
->
2
(
diameter
)
->
3
diameter
*
Pi
4
)(
3.14159265
)
This expression, when evaluated, returns a function that calculates circumferences. It differs from our original in that it names the constant Pi. Let’s test it:
1
((
Pi
)
->
2
(
diameter
)
->
3
diameter
*
Pi
4
)(
3.14159265
)(
2
)
5
#
=>
6.2831853
That works! We can bind anything we want and use it in a function by wrapping the function in another function that is immediately invoked with the value we want to bind. This “functional programming pattern” was popularized in the Lisp programming language more than 50 years ago, where it is called let
.11 Although CoffeeScript doesn’t have a let
keyword, when we discuss this programming pattern we will call it let
.
let
works,12 but only a masochist would write programs this way in CoffeeScript. Besides all the extra characters, it suffers from a fundamental semantic problem: there is a big visual distance between the name Pi
and the value 3.14159265
we bind to it. They should be closer. Is there another way?
Yes.
do
CoffeeScript programmers often wish to create a new environment and bind some values to names within it as let
does. To make this easier to read and write, CoffeeScript provides some syntactic sugar called do
.13
This is what our example looks like using do
:
1
do
(
Pi
=
3.14159265
)
->
2
(
diameter
)
->
3
diameter
*
Pi
Much, MUCH cleaner.
If you need to create more than one binding, you separate them with commas:
1
do
(
republican
=
'
Romney
'
,
democrat
=
'
Obama
'
)
->
2
democrat
The value on the right side can be any expression. Try this for yourself:
|
Did you try the example above? Did you notice what we slipped in? Yes, obviously, the value of a binding can be any expression. But notice also that we can invoke a function on any expression evaluating to a function, including a variable that looks up a binding in the environment.
A Simple Question
Both of the following produce the exact same result:
1
do
(
Pi
=
3.14159265
)
->
2
(
diameter
)
->
3
diameter
*
Pi
And:
1
(
diameter
)
->
2
do
(
Pi
=
3.14159265
)
->
3
diameter
*
Pi
Why do we habitually prefer the former?
To understand this, we’re going to take a simple step towards more complex, state-full programs by introducing sequences of expressions. If we had no other tools, we could evaluate a series of expressions with some legerdemain like this:
1
do
(
ignore1
=
foo
(),
2
ignore2
=
bar
(),
3
ignore3
=
blitz
(),
4
value
=
bash
())
->
5
bash
Or perhaps like this:
1
do
(
ignore
=
[
foo
(),
bar
(),
blitz
()],
value
=
bash
())
->
2
bash
Or even this:
1
[
2
foo
()
3
bar
()
4
blitz
()
]
and bash()
Any of these would evaluate foo()
, bar()
, blitz()
, and then return the value of bash()
(whatever they might be).
Why doesn’t |
But let’s learn another handy CoffeeScript feature, again because it helps us focus on what is actually going on. Whenever you want to work with the body of a function, you can always have it evaluate a simple sequence of one or more expressions by indenting them. The value of the body is the value of the final expression.
So in that case, we can write something like:
1
do
->
2
foo
()
3
bar
()
4
blitz
()
5
bash
()
Anywhere a simple expression is allowed, you could use a do
with a sequence. This doesn’t come up as much as you might think, because many of the places you want to do this, CoffeeScript already lets you indent and include more than one expression. For example, in a function body:
1
(
foo
)
->
2
bar
(
foo
)
3
bash
(
foo
)
4
foo
(
'
blitz
'
)
So back to our question. Here’s a test framework:
1
do
(
circumference
=
do
(
Pi
=
3.14159265
)
->
2
(
diameter
)
->
3
diameter
*
Pi
)
->
4
circumference
(
1
)
5
circumference
(
2
)
6
circumference
(
3
)
7
circumference
(
4
)
8
circumference
(
5
)
9
circumference
(
6
)
10
circumference
(
7
)
11
circumference
(
8
)
12
circumference
(
9
)
13
circumference
(
10
)
14
#
=>
31.4159265
Let’s think about how many functions we are invoking. When this is first invoked, We invoke the outer do (circumference = (...) ->
. As part of doing that, we invoke do (Pi = 3.14159265) ->
and bind the result to circumference
. Then every time we invoke circumference
, we invoke (diameter) ->
. All together, twelve.
But with:
1
do
(
circumference
=
(
diameter
)
->
2
do
(
Pi
=
3.14159265
)
->
3
diameter
*
Pi
)
->
4
circumference
(
1
)
5
circumference
(
2
)
6
circumference
(
3
)
7
circumference
(
4
)
8
circumference
(
5
)
9
circumference
(
6
)
10
circumference
(
7
)
11
circumference
(
8
)
12
circumference
(
9
)
13
circumference
(
10
)
14
#
=>
31.4159265
What happens? There’s one outer do (circumference = (...) ->
, same as before. And then every time we invoke circumference
, we also invoke do (Pi = 3.14159265) ->
, so we have a total of twenty-one function invocations. This is nearly twice as expensive.
Making Things Easy
In CoffeeScript Ristretto, we are focusing on CoffeeScript’s semantics, the meaning of CoffeeScript programs. As we go along, we’re learning just enough CoffeeScript to understand the next concept simply and directly.
CoffeeScript actually supports a number of syntactic conveniences for making programs extremely readable, by which we mean, making them communicate their intent without asking the programmer to struggle in a Turing Tarpit, no matter how elegant.
if i were a rich man
For example, it is possible to implement boolean logic using functions, by carefully combining the Identity ((x) -> x
), Kestrel ((x) -> (y) -> x
), and Vireo ((x) -> (y) -> (z) -> z(x(y))
) functions using a clever trick. It look something like this:
1
do
(
I
=
((
x
)
->
x
),
2
K
=
((
x
)
->
(
y
)
->
x
),
3
V
=
((
x
)
->
(
y
)
->
(
z
)
->
z
(
x
(
y
)))
4
)
->
5
do
(
t
=
K
,
f
=
K
(
I
))
->
6
#
...
7
#
implement
logical
operators
here
8
#
...
This is extraordinarily fascinating computer science stuff, but you can read about that elsewhere. CoffeeScript supplies true
, false
, and
, or
, and not
so you don’t need to roll your own out of functions. But while we’re talking about logic, CoffeeScript also supplies conditional branches of execution, and we’ll use those in examples to come.
The syntax is remarkably simple. Here’s a conditional expression:
1
if
d
<
32
then
'
freezing
'
else
'
warm
'
Since it’s an expression, you can put it in parentheses and stick it anywhere you like, including inside another conditional:
1
(
d
)
->
2
if
d
<
32
then
'
solid
'
else
if
d
<
212
then
'
liquid
'
else
'
gas
'
Like function bodies, there is an indented form that can be more readable:
1
(
d
)
->
2
if
d
<
32
3
'
solid
'
4
else
5
if
d
<
212
6
'
liquid
'
7
else
8
'
gas
'
And the indented lines can have multiple expressions should you so desire:
1
if
frobbish
?
2
alert
(
"Frobbish value: #{frobbish}"
)
3
snarglivate
(
frobbish
)
4
frobbish
Summary
More About Functions And Scope
|
interlude…
Michael Allen Smith on Ristretto:
“It must have been 1996. I was living in South Tampa at the time and the area finally got a great coffee house. The place was Jet City Espresso. Don’t go looking for it. It is no longer there. As the name implies, the owner Jessica was from Seattle and shared her coffee knowledge with her customers. After ordering numerous americanos and espressos, Jessica thought it was time I tried a ristretto. I expected the short pull of the espresso shot would result in a more bitter flavor. To my delight the shot was actually a sweeter and more intense version of her espresso blend.”
References, Identity, Arrays, and Objects
a simple question
Consider this code:
1
do
(
x
=
'
June
14
,
1962
'
)
->
2
do
(
y
=
x
)
->
3
x
is
y
4
#
=>
true
This makes obvious sense, because we know that strings are a value type, so no matter what expression you use to derive the value ‘June 14, 1962’, you are going to get a string with the exact same identity.
But what about this code?
1
do
(
x
=
[
2012
,
6
,
14
])
->
2
do
(
y
=
x
)
->
3
x
is
y
4
#
=>
true
Also true, even though we know that every time we evaluate an expression such as [2012, 6, 14]
, we get a new array with a new identity. So what is happening in our environments?
arguments and references
In our discussion of closures, we said that environments bind values (like [2012, 6, 14]
) to names (like x
and y
), and that when we use these names as expressions, the name evaluates as the value.
What this means is that when we write something like do (y = x) ->
, the name x
is looked up in the current environment, and its value is a specific array that was created when the expression [2012, 6, 14]
was first evaluated. We then bind that exact same value to the name y
in a new environment, and thus x
and y
are both bound to the exact same value, which is identical to itself.
The same thing happens with binding a variable through a more conventional means of applying a function to arguments:
1
do
(
x
=
[
2012
,
6
,
14
])
->
2
((
y
)
->
3
x
is
y
)(
x
)
4
#
=>
true
x
and y
both end up bound to the exact same array, not two different arrays that look the same to our eyes.
arguments and arrays
CoffeeScript provides two different kinds of containers for values. We’ve met one already, the array. Let’s see how it treats values and identities. For starters, we’ll learn how to extract a value from an array. We’ll start with a function that makes a new value with a unique identity every time we call it. We already know that every function we create is unique, so that’s what we’ll use:
1
do
(
unique
=
(
->
->
))
->
2
3
unique
()
4
#
=>
[
Function
]
5
unique
()
is
unique
()
6
#
false
Let’s verify that what we said about references applies to functions as well as arrays:
1
do
(
x
=
unique
())
->
2
do
(
y
=
x
)
->
3
x
is
y
4
#
=>
true
Ok. So what about things inside arrays? We know how to create an array with something inside it:
1
[
unique
()
]
2
#
=>
[
[
Function
]
]
That’s an array with one of our unique functions in it. How do we get something out of it?
1
do
(
a
=
[
'
hello
'
])
->
2
a
[
0
]
3
#
=>
'
hello
'
Cool, arrays work a lot like arrays in other languages and are zero-based. The trouble with this example is that strings are value types in CoffeeScript, so we have no idea whether a[0]
always gives us the same value back like looking up a name in an environment, or whether it does some magic that tries to give us a new value.
We need to put a reference type into an array. If we get the same thing back, we know that the array stores a reference to whatever you put into it. If you get something different back, you know that arrays store copies of things.14
Let’s test it:
1
do
(
unique
=
(
->
->
))
->
2
do
(
x
=
unique
())
->
3
do
(
a
=
[
x
])
->
4
a
[
0
]
is
x
5
#
=>
true
If we get a value out of an array using the []
suffix, it’s the exact same value with the same identity. Question: Does that apply to other locations in the array? Yes:
1
do
(
unique
=
(
->
->
))
->
2
do
(
x
=
unique
(),
y
=
unique
(),
z
=
unique
())
->
3
do
(
a
=
[
x
,
y
,
z
])
->
4
a
[
0
]
is
x
and
a
[
1
]
is
y
and
a
[
2
]
is
z
5
#
=>
true
references and objects
CoffeeScript also provides objects. The word “object” is loaded in programming circles, due to the widespread use of the term “object-oriented programming” that was coined by Alan Kay but has since come to mean many, many things to many different people.
In CoffeeScript, Objects15 are values that can store other values by name (including functions). The most common syntax for creating an object is simple:
1
{
year
:
2012
,
month
:
6
,
day
:
14
}
Two objects created this way have differing identities, just like arrays:
1
{
year
:
2012
,
month
:
6
,
day
:
14
}
is
{
year
:
2012
,
month
:
6
,
day
:
14
}
2
#
=>
false
Objects use []
to access the values by name, using a string:
1
{
year
:
2012
,
month
:
6
,
day
:
14
}[
'
day
'
]
2
#
=>
14
Values contained within an object work just like values contained within an array:
1
do
(
unique
=
(
->
->
))
->
2
do
(
x
=
unique
(),
y
=
unique
(),
z
=
unique
())
->
3
do
(
o
=
{
a
:
x
,
b
:
y
,
c
:
z
})
->
4
o
[
'a'
]
is
x
and
o
[
'b'
]
is
y
and
o
[
'c'
]
is
z
5
#
=>
true
Names needn’t be alphanumeric strings. For anything else, enclose the label in quotes:
1
{
'
first
name
'
:
'
reginald
'
,
'
last
name
'
:
'
lewis
'
}[
'
first
name
'
]
2
#
=>
'
reginald
'
If the name is an alphanumeric string conforming to the same rules as names of variables, there’s a simplified syntax for accessing the values:
1
{
year
:
2012
,
month
:
6
,
day
:
14
}[
'
day
'
]
is
2
{
year
:
2012
,
month
:
6
,
day
:
14
}.
day
3
#
=>
true
All containers can contain any value, including functions or other containers:
1
do
(
Mathematics
=
{
abs
:
(
a
)
->
if
a
<
0
then
-
a
else
a
})
->
2
Mathematics
.
abs
(
-
5
)
3
#
=>
5
Funny we should mention Mathematics
. If you recall, CoffeeScript provides a global environment that contains some existing object that have handy functions you can use. One of them is called Math
, and it contains functions for abs
, max
, min
, and many others. Since it is always available, you can use it in any environment provided you don’t shadow Math
.
1
Math
.
abs
(
-
5
)
2
#
=>
5
Stir the Espresso: Objects, Mutation, and State
So far, we have discussed what many call “pure functional” programming, where every expression is necessarily idempotent, because we have no way of changing state within a program using the tools we have examined.
It’s time to change everything.
Reassignment and Mutation
Like most imperative programming languages, CoffeeScript allows you to re-assign the value of variables. The syntax is familiar to users of most popular languages:
1
do
(
age
=
49
)
->
2
age
=
50
3
age
4
#
=>
50
We took the time to carefully examine what happens with bindings in environments. Let’s take the time to fully explore what happens with reassigning values to variables. The key is to understand that we are rebinding a different value to the same name in the same environment.
So let’s consider what happens with a shadowed variable:
1
do
(
age
=
49
)
->
2
do
(
age
=
50
)
->
3
#
yadda
yadda
4
age
5
#
=>
49
Binding 50
to age in the inner environment does not change age
in the outer environment because the binding of age
in the inner environment shadows the binding of age
in the outer environment. We go from:
1
{
age
:
49
,
'
..
'
:
global
-
environment
}
To:
1
{
age
:
50
,
'
..
'
:
{
age
:
49
,
'
..
'
:
global
-
environment
}}
Then back to:
1
{
age
:
49
,
'
..
'
:
global
-
environment
}
However, if we don’t shadow age
, reassigning it in a nested environment changes the original:
1
do
(
age
=
49
)
->
2
do
(
height
=
1.85
)
->
3
age
=
50
4
age
5
#
=>
50
Like evaluating variable labels, when a binding is rebound, CoffeeScript searches for the binding in the current environment and then each ancestor in turn until it finds one. It then rebinds the name in that environment.
mutation and aliases
Now that we can reassign things, there’s another important factor to consider: Some values can mutate. Their identities stay the same, but not their structure. Specifically, arrays and objects can mutate. Recall that you can access a value from within an array or an object using []
. You can reassign a value using []
as well:
1
do
(
oneTwoThree
=
[
1
,
2
,
3
])
->
2
oneTwoThree
[
0
]
=
'
one
'
3
oneTwoThree
4
#
=>
[
'
one
'
,
2
,
3
]
You can even add a value:
1
do
(
oneTwoThree
=
[
1
,
2
,
3
])
->
2
oneTwoThree
[
3
]
=
'
four
'
3
oneTwoThree
4
#
=>
[
1
,
2
,
3
,
'
four
'
]
You can do the same thing with both syntaxes for accessing objects:
1
do
(
name
=
{
firstName
:
'
Leonard
'
,
lastName
:
'
Braithwaite
'
})
->
2
name
.
middleName
=
'
Austin
'
3
name
4
#
=>
{
firstName
:
'
Leonard
'
,
5
#
lastName
:
'
Braithwaite
'
,
6
#
middleName
:
'
Austin
'
}
We have established that CoffeeScript’s semantics allow for two different bindings to refer to the same value. For example:
1
do
(
allHallowsEve
=
[
2012
,
10
,
31
])
->
2
halloween
=
allHallowsEve
Both halloween
and allHallowsEve
are bound to the same array value within the local environment. And also:
1
do
(
allHallowsEve
=
[
2012
,
10
,
31
])
->
2
do
(
allHallowsEve
)
->
3
#
...
Hello, what’s this? What does do (allHallowsEve) ->
mean? Well, when you put a name in the argument list for do ->
but you don’t supply a value, CoffeeScript assumes you are deliberately trying to shadow a variable. It acts as if you’d written:
1
((
allHallowsEve
)
->
2
#
...
3
)(
allHallowsEve
)
There are two nested environments, and each one binds the name allHallowsEve
to the exact same array value. In each of these examples, we have created two aliases for the same value. Before we could reassign things, the most important point about this is that the identities were the same, because they were the same value.
This is vital. Consider what we already know about shadowing:
1
do
(
allHallowsEve
=
[
2012
,
10
,
31
])
->
2
do
(
allHallowsEve
)
->
3
allHallowsEve
=
[
2013
,
10
,
31
]
4
allHallowsEve
5
#
=>
[
2012
,
10
,
31
]
The outer value of allHallowsEve
was not changed because all we did was rebind the name allHallowsEve
within the inner environment. However, what happens if we mutate the value in the inner environment?
1
do
(
allHallowsEve
=
[
2012
,
10
,
31
])
->
2
do
(
allHallowsEve
)
->
3
allHallowsEve
[
0
]
=
2013
4
allHallowsEve
5
#
=>
[
2013
,
10
,
31
]
This is different. We haven’t rebound the inner name to a different variable, we’ve mutated the value that both bindings share.
The same thing is true whenever you have multiple aliases to the same value:
1
do
(
greatUncle
=
undefined
,
grandMother
=
undefined
)
->
2
greatUncle
=
{
firstName
:
'
Leonard
'
,
lastName
:
'
Braithwaite
'
}
3
grandMother
=
greatUncle
4
grandMother
[
'
firstName
'
]
=
'
Lois
'
5
grandMother
[
'
lastName
'
]
=
'
Barzey
'
6
greatUncle
7
#
=>
{
firstName
:
'
Lois
'
,
lastName
:
'
Barzey
'
}
This example uses the letrec
pattern for declaring bindings. Now that we’ve finished with mutation and aliases, let’s have a look at it.
letrec
One way to exploit reassignment is to “declare” your bindings with do
and bind them to something temporarily, and then rebind them inline, like so:
1
do
(
identity
=
undefined
,
kestrel
=
undefined
)
->
2
identity
=
(
x
)
->
x
3
kestrel
=
(
x
)
->
(
y
)
->
x
This pattern is called letrec
after the Lisp special form. Recall that let
looks like this in CoffeeScript:
1
do
(
identity
=
((
x
)
->
x
),
kestrel
=
(
x
)
->
(
y
)
->
x
)
->
To see how letrec
differs from let
, consider writing a recursive function16 like pow
. pow
takes two arguments, n
and p
, and returns n
raised to the p
th power. For simplicity, we’ll assume that p
is an integer.
1
do
(
pow
=
undefined
)
->
2
pow
=
(
n
,
p
)
->
3
if
p
<
0
4
1
/
pow
(
n
,
-
p
)
5
else
if
p
is
0
6
1
7
else
if
p
is
1
8
n
9
else
10
do
(
half
=
pow
(
n
,
Math
.
floor
(
p
/
2
)),
remainder
=
pow
(
n
,
p
%
2
))
->
11
half
*
half
*
remainder
In order for pow
to call itself, pow
must be bound in the environment in which pow
is defined. This wouldn’t work if we tried to bind pow
in the do
itself. Here’s a misguided attempt to create a recursive function using let
:
1
do
(
odd
=
(
n
)
->
if
n
is
0
then
false
else
not
odd
(
n
-
1
))
->
2
odd
(
5
)
To see why this doesn’t work, recall that this is equivalent to writing:
1
((
odd
)
->
2
odd
(
5
)
3
)(
(
n
)
->
if
n
is
0
then
false
else
not
odd
(
n
-
1
)
)
The expression (n) -> if n is 0 then false else not odd(n-1)
is evaluated in the parent environment, where odd
hasn’t been bound yet. Whereas, if we wrote odd
with letrec
, it would look like this:
1
do
(
odd
=
undefined
)
->
2
odd
=
(
n
)
->
if
n
is
0
then
false
else
not
odd
(
n
-
1
)
3
odd
(
5
)
Which is equivalent to:
1
((
odd
)
->
2
odd
=
(
n
)
->
if
n
is
0
then
false
else
not
odd
(
n
-
1
)
3
odd
(
5
)
4
)(
undefined
)
Now the odd
function is bound in an environment that has a binding for the name odd
. letrec
also allows you to make expressions that depend upon each other, recursively or otherwise, such as:
1
do
(
I
=
undefined
,
K
=
undefined
,
T
=
undefined
,
F
=
undefined
)
->
2
I
=
(
x
)
->
x
3
K
=
(
x
)
->
(
y
)
->
x
4
T
=
K
5
F
=
K
(
I
)
takeaway
CoffeeScript permits the reassignment of new values to existing bindings, as well as the reassignment and assignment of new values to elements of containers such as arrays and objects. Mutating existing objects has special implications when two bindings are aliases of the same value. The |
Normal Variables
Now that we’ve discussed reassignment, it’s time to discuss assignment.
In CoffeeScript, the syntax for assignment is identical to the syntax for reassignment:
1
birthday
=
{
year
:
1962
,
month
:
6
,
day
:
14
}
The difference comes when there is no value bound to the name birthday
in any of the user-defined environments. In that case, CoffeeScript creates one in the current function’s environment. The current function is any of the following:
- A function created with an arrow operator (
->
that we’ve seen, and=>
that we’ll see when we look at objects in more detail). - A function created with the
do
syntax. - When compiling CoffeeScript in files, an empty
do ->
is invisibly created to enclose the entire file.
One good consequence of this feature is that you can dispense with all of the nested do (...) ->
expressions you’ve seen so far if you wish. You can boldly write things like:
1
identity
=
(
x
)
->
x
2
kestrel
=
(
x
)
->
(
y
)
->
x
3
truth
=
kestrel
4
falsehood
=
kestrel
(
identity
)
You can also do your assignments wherever you like in a function, not just at the top. Some feel this makes code more readable by putting variable definitions closer to their use.
There are two unfortunate consequences. The first is that a misspelling creates a new binding rather than resulting in an error:
1
do
(
age
=
49
)
->
2
#
...
3
agee
=
50
4
#
...
5
age
6
#
=>
49
,
not
50
The second is that you may accidentally alias an existing variable if you are not careful. If you’re in the habit of creating a lot of your variables with assignments rather than with do
, you must be careful to scan the source of all of your function’s parents to ensure you haven’t accidentally reused the name of an existing binding.17
un-do
So, should we use do
to bind variables or should we use “normal” variables? This is a very interesting question. Using do
has a certain number of technical benefits. Then again, Jeremy Ashkenas, CoffeeScript’s creator, only uses do
when it’s necessary, and most CoffeeScript programmers follow his lead. It hasn’t done them any harm.
So here’s what we suggest:
When writing new software, use Normal variables as much as possible. If and when you find there’s a scoping problem, you can refactor to do
, meaning, you can change a normal variable into a variable bound with do
to solve the problem.
Programming philosophy is a little outside of the scope of this book, but there is a general principle worth knowing: A good programmer is familiar with many design patterns, idioms, and constructions. However, the good programmer does not attempt to design them into every piece of code from the outset. Instead, the good programmer proceeds along a simple, direct, and clear path until difficulties arise. Then, and only then, does the good programmer refactor to a pattern. In the end, the code is simple where it does not solve a difficult or edge case, and uses a technique or idiom where there is a problem that needed solving.
do
is a good pattern to know and deeply understand, but it is generally sufficient to write with normal variables. If you see a lot of do
in this book, that is because we are writing to be excruciatingly clear, not to construct software that is easy to read and maintain in a team setting.
Comprehensions
If you’re the type of person who can “Write Lisp in any language,” you could set about writing entire CoffeeScript programs using let
and letrec
patterns such that you don’t have any normal variables. But being a CoffeeScript programmer, you will no doubt embrace normal variables. As you dive into CoffeeScript, you’ll discover many helpful features that aren’t “Lisp-y.” Eschewing them is to cut against CoffeeScript’s grain. One of those features is the comprehension, a mechanism for working with collections that was popularized by Python.
Here’s a sample comprehension:
1
names
=
[
'
algernon
'
,
'
sabine
'
,
'
rupert
'
,
'
theodora
'
]
2
3
"Hello #{yourName}"
for
yourName
in
names
4
#
=>
[
'
Hello
algernon
'
,
5
#
'
Hello
sabine
'
,
6
#
'
Hello
rupert
'
,
7
#
'
Hello
theodora
'
]
An alternate syntax for the same thing that supports multiple expressions is:
1
for
yourName
in
names
2
"Hello #{yourName}"
3
#
=>
[
'
Hello
algernon
'
,
4
#
'
Hello
sabine
'
,
5
#
'
Hello
rupert
'
,
6
#
'
Hello
theodora
'
]
Here’s a question: There’s a variable reference yourName
in this code. Is it somehow bound to a new environment in the comprehension? Or is it a “normal variable” that is either bound in the current function’s environment or in a parent function’s environment?
Let’s try it and see:
1
yourName
=
'
clyde
'
2
"Hello #{yourName}"
for
yourName
in
names
3
yourName
4
#
=>
'
theodora
'
It’s a normal variable. If it was somehow ‘local’ to the comprehension, yourName
would still be clyde
as the comprehension’s binding would shadow the current environment’s binding. This is usually fine, as creating a new environment for every comprehension could have performance implications.
However, there are two times you don’t want that to happen. First, you might want yourName
to shadow the existing yourName
binding. You can use do
to fix that:
1
yourName
=
'
clyde
'
2
do
(
yourName
)
->
3
"Hello #{yourName}"
for
yourName
in
names
4
yourName
5
#
=>
`
clyde
`
Recall that when you put a name in the argument list for do ->
but you don’t supply a value, CoffeeScript assumes you are deliberately trying to shadow a variable. It acts as if you’d written:
1
yourName
=
'
clyde
'
2
((
yourName
)
->
3
"Hello #{yourName}"
for
yourName
in
names
4
)(
yourName
)
5
yourName
6
#
=>
`
clyde
`
So technically, the inner yourName
will be bound to the same value as the outer yourName
initially, but as the comprehension is evaluated, that value will be overwritten in the inner environment but not the outer environment.
preventing a subtle comprehensions bug
Consider this variation of the above comprehension:
1
for
myName
in
names
2
(
yourName
)
->
"Hello #{yourName}, my name is #{myName}"
Now what we want is four functions, each of which can generate a sentence like “Hello reader, my name is rupert”. We can test that with a comprehension:
1
fn
(
'
reader
'
)
for
fn
in
for
myName
in
names
2
(
yourName
)
->
"Hello #{yourName}, my name is #{myName}"
3
#
=>
[
'
Hello
reader
,
my
name
is
theodora
'
,
4
#
'
Hello
reader
,
my
name
is
theodora
'
,
5
#
'
Hello
reader
,
my
name
is
theodora
'
,
6
#
'
Hello
reader
,
my
name
is
theodora
'
]
WTF!?
If we consider our model for binding, we’ll quickly discover the problem. Each of the functions we generate has a closure that consists of a function and a local environment. yourName
is bound in its local environment, but myName
is bound in the comprehension’s environment. At the time each closure was created, myName
was bound to one of the four names, but at the time the closures are evaluated, myName
is bound to the last of the four names.
Each of the four closures has its own local environment, but they share a parent environment, which means they share the exact same binding for myName
. We can fix it using the “shadow” syntax for do
:
1
fn
(
'
reader
'
)
for
fn
in
for
myName
in
names
2
do
(
myName
)
->
3
(
yourName
)
->
"Hello #{yourName}, my name is #{myName}"
4
#
=>
[
'
Hello
reader
,
my
name
is
algernon
'
,
5
#
'
Hello
reader
,
my
name
is
sabine
'
,
6
#
'
Hello
reader
,
my
name
is
rupert
'
,
7
#
'
Hello
reader
,
my
name
is
theodora
'
]
Now, each time we create a function we’re first creating its own environment and binding myName
there, shadowing the comprehension’s binding of myName
. Thus, the comprehension’s changes to myName
don’t change each closure’s binding.
takeaway
Comprehensions are extraordinarily useful for working with collections, but their loop variables are normal variables and may require special care to obtain the desired results. Also worth noting: Comprehensions may be the only place where |
Encapsulating State with Closures
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.–Alan Kay
We’re going to look at encapsulation using CoffeeScript’s functions and objects. We’re not going to call it object-oriented programming, mind you, because that would start a long debate. This is just plain encapsulation18, with a dash of information-hiding.
what is hiding of state-process, and why does it matter?
In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).
Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy.
Consider a stack data structure. There are three basic operations: Pushing a value onto the top (push
), popping a value off the top (pop
), and testing to see whether the stack is empty or not (isEmpty
). These three operations are the stable interface.
Many stacks have an array for holding the contents of the stack. This is relatively stable. You could substitute a linked list, but in CoffeeScript, the array is highly efficient. You might need an index, you might not. You could grow and shrink the array, or you could allocate a fixed size and use an index to keep track of how much of the array is in use. The design choices for keeping track of the head of the list are often driven by performance considerations.
If you expose the implementation detail such as whether there is an index, sooner or later some programmer is going to find an advantage in using the index directly. For example, she may need to know the size of a stack. The ideal choice would be to add a size
function that continues to hide the implementation. But she’s in a hurry, so she reads the index
directly. Now her code is coupled to the existence of an index, so if we wish to change the implementation to grow and shrink the array, we will break her code.
The way to avoid this is to hide the array and index from other code and only expose the operations we have deemed stable. If and when someone needs to know the size of the stack, we’ll add a size
function and expose it as well.
Hiding information (or “state”) is the design principle that allows us to limit the coupling between components of software.
how do we hide state using coffeescript?
We’ve been introduced to CoffeeScript’s objects, and it’s fairly easy to see that objects can be used to model what other programming languages call (variously) records, structs, frames, or what-have-you. And given that their elements are mutable, they can clearly model state.
Given an object that holds our state (an array and an index19), we can easily implement our three operations as functions. Bundling the functions with the state does not require any special “magic” features. CoffeeScript objects can have elements of any type, including functions:
1
stack
=
do
(
obj
=
undefined
)
->
2
obj
=
3
array:
[]
4
index:
-
1
5
push:
(
value
)
->
6
obj
.
array
[
obj
.
index
+=
1
]
=
value
7
pop:
->
8
do
(
value
=
obj
.
array
[
obj
.
index
])
->
9
obj
.
array
[
obj
.
index
]
=
undefined
10
obj
.
index
-=
1
if
obj
.
index
>=
0
11
value
12
isEmpty:
->
13
obj
.
index
<
0
14
15
stack
.
isEmpty
()
16
#
=>
true
17
stack
.
push
(
'
hello
'
)
18
#
=>
'
hello
'
19
stack
.
push
(
'
CoffeeScript
'
)
20
#
=>
'
CoffeeScript
'
21
stack
.
isEmpty
()
22
#
=>
false
23
stack
.
pop
()
24
#
=>
'
CoffeeScript
'
25
stack
.
pop
()
26
#
=>
'
hello
'
27
stack
.
isEmpty
()
28
#
=>
true
method-ology
In this text, we lurch from talking about functions belong to an object to methods. Other languages may separate methods from functions very strictly, but in CoffeeScript every method is a function but not all functions are methods.
The view taken in this book is that a function is a method of an object if it belongs to that object and interacts with that object in some way. So the functions implementing the operations on the queue are all absolutely methods of the queue.
But these wouldn’t be methods. Although they belong to an object, they don’t interact with it:
1
{
2
min:
(
x
,
y
)
->
if
x
<
y
then
x
else
y
3
max:
(
x
,
y
)
->
if
x
>
y
then
x
else
y
4
}
hiding state
Our stack does bundle functions with data, but it doesn’t hide its state. “Foreign” code could interfere with its array or index. So how do we hide these? We already have a closure, let’s use it:
1
stack
=
do
(
array
=
[],
index
=
-
1
)
->
2
push:
(
value
)
->
3
array
[
index
+=
1
]
=
value
4
pop:
->
5
do
(
value
=
array
[
index
])
->
6
array
[
index
]
=
undefined
7
index
-=
1
if
index
>=
0
8
value
9
isEmpty:
->
10
index
<
0
11
12
stack
.
isEmpty
()
13
#
=>
true
14
stack
.
push
(
'
hello
'
)
15
#
=>
'
hello
'
16
stack
.
push
(
'
CoffeeScript
'
)
17
#
=>
'
CoffeeScript
'
18
stack
.
isEmpty
()
19
#
=>
false
20
stack
.
pop
()
21
#
=>
'
CoffeeScript
'
22
stack
.
pop
()
23
#
=>
'
hello
'
24
stack
.
isEmpty
()
25
#
=>
true
We don’t want to repeat this code every time we want a stack, so let’s make ourselves a “stack maker:”
1
StackMaker
=
->
2
do
(
array
=
[],
index
=
-
1
)
->
3
push:
(
value
)
->
4
array
[
index
+=
1
]
=
value
5
pop:
->
6
do
(
value
=
array
[
index
])
->
7
array
[
index
]
=
undefined
8
index
-=
1
if
index
>=
0
9
value
10
isEmpty:
->
11
index
<
0
12
13
stack
=
StackMaker
()
Now we can make stacks freely, and we’ve hidden their internal data elements. We have methods and encapsulation, and we’ve built them out of CoffeeScript’s fundamental functions and objects. In Instances and Classes, we’ll look at CoffeeScript’s support for class-oriented programming and some of the idioms that functions bring to the party.
Composition and Extension
composition
A deeply fundamental practice is to build components out of smaller components. The choice of how to divide a component into smaller components is called factoring, after the operation in number theory 20.
The simplest and easiest way to build components out of smaller components in CoffeeScript is also the most obvious: Each component is a value, and the components can be put together into a single object or encapsulated with a closure.
Here’s an abstract “model” that supports undo and redo composed from a pair of stacks (see Encapsulating State) and a Plain Old CoffeeScript Object:
1
#
helper
function
2
shallowCopy
=
(
source
)
->
3
do
(
dest
=
{},
key
=
undefined
,
value
=
undefined
)
->
4
dest
[
key
]
=
value
for
own
key
,
value
of
source
5
dest
6
7
#
our
model
maker
8
ModelMaker
=
(
initialAttributes
=
{})
->
9
do
(
attributes
=
shallowCopy
(
initialAttributes
),
10
undoStack
=
StackMaker
(),
11
redoStack
=
StackMaker
(),
12
obj
=
undefined
)
->
13
obj
=
{
14
set
:
(
attrsToSet
=
{})
->
15
undoStack
.
push
(
shallowCopy
(
attributes
))
16
redoStack
=
StackMaker
()
unless
redoStack
.
isEmpty
()
17
attributes
[
key
]
=
value
for
own
key
,
value
of
attrsToSet
18
obj
19
undo
:
->
20
unless
undoStack
.
isEmpty
()
21
redoStack
.
push
(
shallowCopy
(
attributes
))
22
attributes
=
undoStack
.
pop
()
23
obj
24
redo
:
->
25
unless
redoStack
.
isEmpty
()
26
undoStack
.
push
(
shallowCopy
(
attributes
))
27
attributes
=
redoStack
.
pop
()
28
obj
29
get
:
(
key
)
->
30
attributes
(
key
)
31
has
:
(
key
)
->
32
attributes
.
hasOwnProperty
(
key
)
33
attributes
:
->
34
shallowCopy
(
attributes
)
35
}
36
obj
The techniques used for encapsulation work well with composition. In this case, we have a “model” that hides its attribute store as well as its implementation that is composed of of an undo stack and redo stack.
extension
Another practice that many people consider fundamental is to extend an implementation. Meaning, they wish to define a new data structure in terms of adding new operations and semantics to an existing data structure.
Consider a queue:
1
QueueMaker
=
->
2
do
(
array
=
[],
head
=
0
,
tail
=
-
1
)
->
3
pushTail:
(
value
)
->
4
array
[
tail
+=
1
]
=
value
5
pullHead:
->
6
if
tail
>=
head
7
do
(
value
=
array
[
head
])
->
8
array
[
head
]
=
undefined
9
head
+=
1
10
value
11
isEmpty:
->
12
tail
<
head
Now we wish to create a deque by adding pullTail
and pushHead
operations to our queue.21 Unfortunately, encapsulation prevents us from adding operations that interact with the hidden data structures.
This isn’t really surprising: The entire point of encapsulation is to create an opaque data structure that can only be manipulated through its public interface. The design goals of encapsulation and extension are always going to exist in tension.
Let’s “de-encapsulate” our queue:
1
QueueMaker
=
->
2
do
(
queue
=
undefined
)
->
3
queue
=
4
array
:
[]
5
head:
0
6
tail:
-
1
7
pushTail:
(
value
)
->
8
queue
.
array
[
queue
.
tail
+=
1
]
=
value
9
pullHead:
->
10
unless
queue
.
isEmpty
()
11
do
(
value
=
queue
.
array
[
queue
.
head
])
->
12
queue
.
array
[
queue
.
head
]
=
undefined
13
queue
.
head
+=
1
14
value
15
isEmpty:
->
16
queue
.
tail
<
queue
.
head
Now we can extend a queue into a deque, with a little help from a helper function extend
:
1
#
helper
function
2
extend
=
(
object
,
extensions
)
->
3
object
[
key
]
=
value
for
key
,
value
of
extensions
4
object
5
6
#
a
deque
maker
7
DequeMaker
=
->
8
do
(
deque
=
QueueMaker
())
->
9
extend
(
deque
,
10
size
:
->
11
deque
.
tail
-
deque
.
head
+
1
12
pullTail
:
->
13
unless
deque
.
isEmpty
()
14
do
(
value
=
deque
.
array
[
deque.tail
]
)
->
15
deque
.
array
[
deque.tail
]
=
undefined
16
deque
.
tail
-=
1
17
value
18
pushHead
:
do
(
INCREMENT
=
4
)
->
19
(
value
)
->
20
if
deque
.
head
is
0
21
for
i
in
[
deque.tail..deque.head
]
22
deque
.
array
[
i
+
INCREMENT
]
=
deque
.
array
[
i
]
23
deque
.
tail
+=
INCREMENT
24
deque
.
head
+=
INCREMENT
25
deque
.
array
[
deque.head
-=
1
]
=
value
26
)
Presto, we have reuse through extension, at the cost of encapsulation.
Encapsulation and Extension exist in a natural state of tension. A program with elaborate encapsulation resists breakage but can also be difficult to refactor in other ways. Be mindful of when it’s best to Compose and when it’s best to Extend. |
This and That
Let’s take another look at extensible objects. Here’s a Queue:
1
QueueMaker
=
->
2
do
(
queue
=
undefined
)
->
3
queue
=
4
array
:
[]
5
head:
0
6
tail:
-
1
7
pushTail:
(
value
)
->
8
queue
.
array
[
queue
.
tail
+=
1
]
=
value
9
pullHead:
do
(
value
=
undefined
)
->
10
->
11
unless
queue
.
isEmpty
()
12
value
=
queue
.
array
[
queue
.
head
]
13
queue
.
array
[
queue
.
head
]
=
undefined
14
queue
.
head
+=
1
15
value
16
isEmpty:
->
17
queue
.
tail
<
queue
.
head
18
19
queue
=
QueueMaker
()
20
queue
.
pushTail
(
'
Hello
'
)
21
queue
.
pushTail
(
'
CoffeeScript
'
)
Let’s make a copy of our queue using a handy extend
function and a comprehension to make sure we copy the array properly:
1
extend
=
(
object
,
extensions
)
->
2
object
[
key
]
=
value
for
key
,
value
of
extensions
3
object
4
5
copyOfQueue
=
extend
({},
queue
)
6
copyOfQueue
.
array
=
(
element
for
element
in
queue
.
array
)
7
8
queue
isnt
copyOfQueue
9
#
=>
true
And start playing with our copies:
1
copyOfQueue
.
pullHead
()
2
#
=>
'
Hello
'
3
4
queue
.
pullHead
()
5
#
=>
'
CoffeeScript
'
What!? Even though we carefully made a copy of the array to prevent aliasing, it seems that our two queues behave like aliases of each other. The problem is that while we’ve carefully copied our array and other elements over, the closures all share the same environment, and therefore the functions in copyOfQueue
all operate on the first queue.
Let’s take an impossibly optimistic flight of fancy:
1
AmnesiacQueueMaker
=
->
2
array:
[]
3
head:
0
4
tail:
-
1
5
pushTail:
(
myself
,
value
)
->
6
myself
.
array
[
myself
.
tail
+=
1
]
=
value
7
pullHead:
do
(
value
=
undefined
)
->
8
(
myself
)
->
9
unless
myself
.
isEmpty
(
myself
)
10
value
=
myself
.
array
[
myself
.
head
]
11
myself
.
array
[
myself
.
head
]
=
undefined
12
myself
.
head
+=
1
13
value
14
isEmpty:
(
myself
)
->
15
myself
.
tail
<
myself
.
head
16
17
queueWithAmnesia
=
AmnesiacQueueMaker
()
18
queueWithAmnesia
.
pushTail
(
queueWithAmnesia
,
'
Hello
'
)
19
queueWithAmnesia
.
pushTail
(
queueWithAmnesia
,
'
CoffeeScript
'
)
The AmnesiacQueueMaker
makes queues with amnesia: They don’t know who they are, so every time we invoke one of their functions, we have to tell them who they are. You can work out the implications for copying queues as a thought experiment: We don’t have to worry about environments, because every function operates on the queue you pass in.
The killer drawback, of course, is making sure we are always passing the correct queue in every time we invoke a function. What to do?
what’s all this
?
Any time we must do the same repetitive thing over and over and over again, we industrial humans try to build a machine to do it for us. CoffeeScript is one such machine:
1
BanksQueueMaker
=
->
2
array:
[]
3
head:
0
4
tail:
-
1
5
pushTail:
(
value
)
->
6
this
.
array
[
this
.
tail
+=
1
]
=
value
7
pullHead:
do
(
value
=
undefined
)
->
8
->
9
unless
this
.
isEmpty
()
10
value
=
this
.
array
[
this
.
head
]
11
this
.
array
[
this
.
head
]
=
undefined
12
this
.
head
+=
1
13
value
14
isEmpty:
->
15
this
.
tail
<
this
.
head
16
17
banksQueue
=
BanksQueueMaker
()
18
banksQueue
.
pushTail
(
'
Hello
'
)
19
banksQueue
.
pushTail
(
'
CoffeeScript
'
)
Every time you invoke a function that is a member of an object, CoffeeScript binds that object to the name this
in the environment of the function just as if it was an argument.22 Now we can easily make copies:
1
copyOfQueue
=
extend
({},
banksQueue
)
2
copyOfQueue
.
array
=
(
element
for
element
in
banksQueue
.
array
)
3
4
copyOfQueue
.
pullHead
()
5
#
=>
'
Hello
'
6
7
banksQueue
.
pullHead
()
8
#
=>
'
Hello
'
Presto, we now have a way to copy arrays. By getting rid of the closure and taking advantage of this
, we have functions that are more easily portable between objects, and the code is simpler as well.
Closures tightly couple functions to the environments where they are created limiting their flexibility. Using |
fat arrows are the cure for obese idioms
Wait a second! Let’s flip back a few pages and look at the code for a Queue:
1
QueueMaker
=
->
2
do
(
queue
=
undefined
)
->
3
queue
=
4
array
:
[]
5
head:
0
6
tail:
-
1
7
pushTail:
(
value
)
->
8
queue
.
array
[
queue
.
tail
+=
1
]
=
value
9
pullHead:
->
10
unless
queue
.
isEmpty
()
11
do
(
value
=
queue
.
array
[
queue
.
head
])
->
12
queue
.
array
[
queue
.
head
]
=
undefined
13
queue
.
head
+=
1
14
value
15
isEmpty:
->
16
queue
.
tail
<
queue
.
head
Spot the difference? Here’s the pullHead
function we’re using now:
1
pullHead
:
do
(
value
=
undefined
)
->
2
->
3
unless
this
.
isEmpty
()
4
value
=
this
.
array
[
this
.
head
]
5
this
.
array
[
this
.
head
]
=
undefined
6
this
.
head
+=
1
7
value
Sneaky: The version of the pullHead
function moves the do
outside the function. Why? Let’s rewrite it to look like the old version:
1
pullHead:
->
2
unless
this
.
isEmpty
()
3
do
(
value
=
this
.
array
[
this
.
head
])
->
4
this
.
array
[
this
.
head
]
=
undefined
5
this
.
head
+=
1
6
value
Notice that we have a function. We invoke it, and this
is set to our object. Then, thanks to the do
, we invoke another function inside that. The function invoked by the do
keyword does not belong to our object, so this
is not set to our object. Oops!
There are two solutions. The error-prone workaround is to write:
1
pullHead:
->
2
unless
this
.
isEmpty
()
3
do
(
value
=
this
.
array
[
this
.
head
],
that
=
this
)
->
4
that
.
array
[
that
.
head
]
=
undefined
5
that
.
head
+=
1
6
value
Besides its lack of pulchritude, there are many opportunities to mistakingly write this
when you meant to write that
. Or that
for this
. Or something, especially when refactoring some code.
The better way is to force the function to have the this
you want. CoffeeScript gives you the “fat arrow” or =>
for this purpose. Here it is:
1
pullHead:
->
2
unless
this
.
isEmpty
()
3
do
(
value
=
this
.
array
[
this
.
head
])
=>
4
this
.
array
[
this
.
head
]
=
undefined
5
this
.
head
+=
1
6
value
The fat arrow says, “Treat this function as if we did the this
and that
idiom, so that whenever I refer to this
, I get the outer one.” Which is exactly what we want if we don’t care to rearrange our code.
Summary
Objects, Mutation, and State
|
Finish the Cup: Instances and Classes
As discussed in References, Identity, Arrays, and Objects and again in Encapsulating State, CoffeeScript objects are very simple, yet the combination of objects, functions, and closures can create powerful data structures. That being said, there are language features that cannot be implemented with Plain Old CoffeeScript Objects, functions, and closures23.
One of them is inheritance. In CoffeeScript, inheritance provides a cleaner, simpler mechanism for extending data structures, domain models, and anything else you represent as a bundle of state and operations.
Prototypes are Simple, it’s the Explanations that are Hard To Understand
As you recall from our code for making objects extensible, we wrote a function that returned a Plain Old CoffeeScript Object. The colloquial term for this kind of function is a “Factory Function.”
Let’s strip a function down to the very bare essentials:
1
Ur
=
->
This doesn’t look like a factory function: It doesn’t have an expression that yields a Plain Old CoffeeScript Object when the function is applied. Yet, there is a way to make an object out of it. Behold the power of the new
keyword:
1
new
Ur
()
2
#
=>
{}
We got an object back! What can we find out about this object?
1
new
Ur
()
is
new
Ur
()
2
#
=>
false
Every time we call new
with a function and get an object back, we get a unique object. We could call these “Objects created with the new
keyword,” but this would be cumbersome. So we’re going to call them instances. Instances of what? Instances of the function that creates them. So given i = new Ur()
, we say that i
is an instance of Ur
.
For reasons that will be explained after we’ve discussed prototypes, we also say that Ur
is the constructor of i
, and that Ur
is a constructor function. Therefore, an instance is an object created by using the new
keyword on a constructor function, and that function is the instance’s constructor.
prototypes
There’s more. Here’s something you may not know about functions:
1
Ur
.
prototype
2
#
=>
{}
What’s this prototype? Let’s run our standard test:
1
(
->
).
prototype
is
(
->
).
prototype
2
#
=>
false
Every function is initialized with its own unique prototype
. What does it do? Let’s try something:
1
Ur
.
prototype
.
language
=
'
CoffeeScript
'
2
3
continent
=
new
Ur
()
4
#
=>
{}
5
continent
.
language
6
#
=>
'
CoffeeScript
'
That’s very interesting! Instances seem to behave as if they had the same elements as their constructor’s prototype. Let’s try a few things:
1
continent
.
language
=
'
JavaScript
'
2
continent
3
#
=>
{
language
:
'
JavaScript
'
}
4
continent
.
language
5
#
=>
'
JavaScript
'
6
Ur
.
prototype
.
language
7
'
CoffeeScript
'
You can set elements of an instance, and they “override” the constructor’s prototype, but they don’t actually change the constructor’s prototype. Let’s make another instance and try something else.
1
another
=
new
Ur
()
2
#
=>
{}
3
another
.
language
4
#
=>
'
CoffeeScript
'
New instances don’t acquire any changes made to other instances. Makes sense. And:
1
Ur
.
prototype
.
language
=
'
Sumerian
'
2
another
.
language
3
#
=>
'
Sumerian
'
Even more interesting: Changing the constructor’s prototype changes the behaviour of all of its instances. This strongly implies that there is a dynamic relationship between instances and their constructors, rather than some kind of mechanism that makes objects by copying.24
Speaking of prototypes, here’s something else that’s very interesting:
1
continent
.
constructor
2
#
=>
[
Function
]
3
4
continent
.
constructor
is
Ur
5
#
=>
true
Every instance acquires a constructor
element that is initialized to their constructor. This is true even for objects we don’t create with new
in our own code:
1
{}.
constructor
2
#
=>
[
Function
:
Object
]
If that’s true, what about prototypes? Do they have constructors?
1
Ur
.
prototype
.
constructor
2
#
=>
[
Function
]
3
Ur
.
prototype
.
constructor
is
Ur
4
#
=>
true
Very interesting! We will take another look at the constructor
element when we discuss class extension.
revisiting this
idea of queues
Let’s rewrite our Queue to use new
and .prototype
, using this
and =>
:
1
extend
=
(
object
,
extensions
)
->
2
object
[
key
]
=
value
for
key
,
value
of
extensions
3
object
4
5
Queue
=
->
6
extend
(
this
,
{
7
array:
[]
8
head:
0
9
tail:
-
1
10
})
11
12
extend
(
Queue
.
prototype
,
13
pushTail:
(
value
)
->
14
this
.
array
[
this
.
tail
+=
1
]
=
value
15
pullHead:
->
16
unless
this
.
isEmpty
()
17
do
(
value
=
this
.
array
[
this
.
head
])
=>
18
this
.
array
[
this
.
head
]
=
undefined
19
this
.
head
+=
1
20
value
21
isEmpty:
->
22
this
.
tail
<
this
.
head
23
)
You recall that when we first looked at this
, we only covered the case where a function that belongs to an object is invoked. Now we see another case: When a function is invoked by the new
operator, this
is set to the new object being created. Thus, our code for Queue
initializes the queue.
You can see why this
is so handy in CoffeeScript: We wouldn’t be able to define functions in the prototype that worked on the instance if CoffeeScript didn’t give us an easy way to refer to the instance itself.
objects everywhere?
Now that you know about prototypes, it’s time to acknowledge something that even small children know: Everything in CoffeeScript behaves like an object, everything in CoffeeScript behaves like an instance of a function, and therefore everything in CoffeeScript behaves as if it inherits some methods from its constructor’s prototype and/or has some elements of its own.
For example:
1
3.14159265
.
toPrecision
(
5
)
2
#
=>
'
3.1415
'
3
4
'
FORTRAN
,
SNOBOL
,
LISP
,
BASIC
'
.
split
(
'
,
'
)
5
#
=>
[
'
FORTRAN
'
,
6
#
'
SNOBOL
'
,
7
#
'
LISP
'
,
8
#
'
BASIC
'
]
9
10
[
'
FORTRAN
'
,
11
'
SNOBOL
'
,
12
'
LISP
'
,
13
'
BASIC
'
].
length
14
#=> 5
Functions themselves are instances, and they have methods. For example, we know that CoffeeScript treats the fat arrow as if you were using the this
and that
idiom. But if you didn’t have a fat arrow and you didn’t want to take a chance on getting the idiom wrong, you could take advantage of the fact that every function has a method call
.
Call’s first argument is a context: When you invoke .call
on a function, it invoked the function, setting this
to the context. It passes the remainder of the arguments to the function.
So, if we have:
1
pullHead:
->
2
unless
this
.
isEmpty
()
3
do
(
value
=
this
.
array
[
this
.
head
])
=>
4
this
.
array
[
this
.
head
]
=
undefined
5
this
.
head
+=
1
6
value
We could also write it like this:
1
pullHead:
->
2
unless
this
.
isEmpty
()
3
((
value
)
->
4
this
.
array
[
this
.
head
]
=
undefined
5
this
.
head
+=
1
6
value
7
).
call
(
this
,
this
.
array
[
this
.
head
])
It seems like are objects everywhere in CoffeeScript!
impostors
You may have noticed that we use “weasel words” to describe how everything in CoffeeScript behaves like an instance. Everything behaves as if it was created by a function with a prototype.
The full explanation is this: As you know, CoffeeScript has “value types” like String
, Number
, and Boolean
. As noted in the first chapter, value types are also called primitives, and one consequence of the way CoffeeScript implements primitives is that they aren’t objects. Which means they can be identical to other values of the same type with the same contents, but the consequence of certain design decisions is that value types don’t actually have methods or constructors. They aren’t instances of some constructor.
So. Value types don’t have methods or constructors. And yet:
1
"Spence Olham"
.
split
(
' '
)
2
#
=>
[
"Spence"
,
"Olham"
]
Somehow, when we write "Spence Olham".split(' ')
, the string "Spence Olham"
isn’t an instance, it doesn’t have methods, but it does a damn fine job of impersonating an instance of a String
constructor. How does "Spence Olham"
impersonate an instance?
CoffeeScript pulls some legerdemain. When you do something that treats a value like an object, CoffeeScript checks to see whether the value actually is an object. If the value is actually a primitive,25 CoffeeScript temporarily makes an object that is a kinda-sorta copy of the primitive and that kinda-sorta copy has methods and you are temporarily fooled into thinking that "Spence Olham"
has a .split
method.
These kinda-sorta copies are called String instances as opposed to String primitives. And the instances have methods, while the primitives do not. How does CoffeeScript make an instance out of a primitive? With new
, of course. Let’s try it:
1
new
String
(
"Spence Olham"
)
2
#
=>
"Spence Olham"
The string instance looks just like our string primitive. But does it behave like a string primitive? Not entirely:
1
new
String
(
"Spence Olham"
)
is
"Spence Olham"
2
#
=>
false
Aha! It’s an object with its own identity, unlike string primitives that behave as if they have a canonical representation. If we didn’t care about their identity, that wouldn’t be a problem. But if we carelessly used a string instance where we thought we had a string primitive, we could run into a subtle bug:
1
if
userName
is
"Spence Olham"
2
getMarried
()
3
goCamping
()
That code is not going to work as we expect should we accidentally bind new String("Spence Olham")
to userName
instead of the primitive "Spence Olham"
.
This basic issue that instances have unique identities but primitives withthe same contents have the same identities–is true of all primitive types, including numbers and booleans: If you create an instance of anything with new
, it gets its own identity.
There are more pitfalls to beware. Consider the truthiness of string, number and boolean primitives:
1
if
''
then
'
truthy
'
else
'
falsy
'
2
#
=>
'
falsy
'
3
if
0
then
'
truthy
'
else
'
falsy
'
4
#
=>
'
falsy
'
5
if
false
then
'
truthy
'
else
'
falsy
'
6
#
=>
'
falsy
'
Compare this to their corresponding instances:
1
if
new
String
(
''
)
then
'
truthy
'
else
'
falsy
'
2
#
=>
'
truthy
'
3
if
new
Number
(
0
)
then
'
truthy
'
else
'
falsy
'
4
#
=>
'
truthy
'
5
if
new
Boolean
(
false
)
then
'
truthy
'
else
'
falsy
'
6
#
=>
'
truthy
'
Our notion of “truthiness” and “falsiness” is that all instances are truthy, even string, number, and boolean instances corresponding to primitives that are falsy.
There is one sure cure for “CoffeeScript Impostor Syndrome.” Just as new PrimitiveType(...)
creates an instance that is an impostor of a primitive, PrimitiveType(...)
creates an original, canonicalized primitive from a primitive or an instance of a primitive object.
For example:
1
String
(
new
String
(
"Spence Olham"
))
is
"Spence Olham"
2
#
=>
true
Getting clever, we can write this:
1
original
=
(
unknown
)
->
2
unknown
.
constructor
(
unknown
)
3
4
original
(
true
)
is
true
5
#
=>
true
6
original
(
new
Boolean
(
true
)
is
true
7
#
=>
true
Of course, original
will not work for your own creations unless you take great care to emulate the same behaviour. But it does work for strings, numbers, and booleans.
A Touch of Class
CoffeeScript has “classes,” for some definition of “class.” You’ve met them already, they’re constructors that are designed to work with the new
keyword and have behaviour in their .prototype
element. You can create one any time you like by:
- Writing the constructor so that it performs any initialization on
this
, and: - Putting all of the method definitions in its prototype.
This is simple enough, but there are some advantages to making it even simpler, so CoffeeScript does. Here’s our queue again:
1
class
Queue
2
constructor:
->
3
extend
(
this
,
4
array:
[]
5
head:
0
6
tail:
-
1
7
)
8
pushTail:
(
value
)
->
9
this
.
array
[
this
.
tail
+=
1
]
=
value
10
pullHead:
->
11
unless
this
.
isEmpty
()
12
do
(
value
=
this
.
array
[
this
.
head
])
=>
13
this
.
array
[
this
.
head
]
=
undefined
14
this
.
head
+=
1
15
value
16
isEmpty:
->
17
this
.
tail
<
this
.
head
18
19
q
=
new
Queue
()
20
q
.
pushTail
(
'
hello
'
)
21
q
.
pushTail
(
'
CoffeeScript
'
)
Behind the scenes, CoffeeScript acts as if you’d written things out by hand, with several small but relevant details.
the constructor method
As you’ve probably noticed, CoffeeScript turns what may look like a constructor
method into the body of the Queue
function. You recall that every object in CoffeeScript has a constructor
element initialized to the function that created it. So it’s natural that in the class statement, you use constructor
to define the body of the function.
scope
CoffeeScript wraps the entire class statement in a do ->
so that you can work with some normal variables if you need them.
Here’s a gratuitous example:
1
class
Queue
2
empty
=
'
UNUSED
'
3
constructor:
->
4
extend
(
this
,
5
array:
[]
6
head:
0
7
tail:
-
1
8
)
9
pushTail:
(
value
)
->
10
this
.
array
[
this
.
tail
+=
1
]
=
value
11
pullHead:
->
12
unless
this
.
isEmpty
()
13
do
(
value
=
this
.
array
[
this
.
head
])
=>
14
this
.
array
[
this
.
head
]
=
empty
15
this
.
head
+=
1
16
value
17
isEmpty:
->
18
this
.
tail
<
this
.
head
The value 'UNUSED'
is bound to the name empty
within the class “statement” but not outside it (unless you are aliasing an empty
variable). CoffeeScript allows this kind of thing but will get hissy if you try to get fancy and write something like:
1
class
Queue
2
do
(
empty
=
'
UNUSED
'
)
->
3
constructor:
->
4
extend
(
this
,
5
array:
[]
6
head:
0
7
tail:
-
1
8
)
9
#
...
That won’t work, you can’t wrap a do
around the instance methods of the class.
at-at walkers
CoffeeScript, in what may be an homage to Ruby, provides an abbreviation for this.
, you can preface any label with an @
as a shortcut. This small detail could easily be ignored, except for the fact that there’s one place where it’s mandatory. With that teaser in place, let’s discuss a use case.
Let’s modify our Queue to count how many queues have been created:
1
class
Queue
2
constructor:
->
3
Queue
.
queues
+=
1
4
extend
(
this
,
5
array:
[]
6
head:
0
7
tail:
-
1
8
)
9
#
...
10
11
Queue
.
queues
=
0
To make this work properly, CoffeeScript has to wrap our code in a do
so that the code in the constructor always refers to the correct function, even if we subsequently change the binding for Queue
in the outer environment. CoffeeScript does this.
Assigning values to elements of the function outside of the class
statement is awkward, so CoffeeScript lets us put Queue.queues = 0
inside, anywhere we’d like. The top is fine. But interestingly, CoffeeScript also sets the context of the body of the class
statement to be the class itself. So we can write:
1
class
Queue
2
this
.
queues
=
0
3
constructor:
->
4
Queue
.
queues
+=
1
5
extend
(
this
,
6
array:
[]
7
head:
0
8
tail:
-
1
9
)
10
#
...
And back to our shortcut. We can also write:
1
class
Queue
2
@
queues
=
0
3
constructor:
->
4
Queue
.
queues
+=
1
5
extend
(
this
,
6
array:
[]
7
head:
0
8
tail:
-
1
9
)
10
pushTail:
(
value
)
->
11
@
array
[
@
tail
+=
1
]
=
value
12
pullHead:
->
13
unless
@
isEmpty
()
14
do
(
value
=
@
array
[
@
head
])
=>
15
@
array
[
@
head
]
=
undefined
16
@
head
+=
1
17
value
18
isEmpty:
->
19
@
tail
<
@
head
Everything up to now has been a matter of taste. But should you wish, you can write:
1
class
Queue
2
@
queues
:
0
3
#
...
Putting the @
prefix (and not this.
) on a label as part of the structure inside the class statement indicates that the element belongs to the constructor (or “class”) and not the prototype. Obviously, if you put functions in the constructor, you get constructor methods and not instance methods. For example:
1
class
Queue
2
@
queues
:
0
3
@
resetQueues
:
->
4
@
queues
=
0
5
#
...
We’ve added a constructor method to reset the count.
Classes CoffeeScript’s |
Object Methods
An instance method is a function defined in the constructor’s prototype. Every instance acquires this behaviour unless otherwise “overridden.” Instance methods usually have some interaction with the instance, such as references to this
or to other methods that interact with the instance. A constructor method is a function belonging to the constructor itself.
There is a third kind of method, one that any object (obviously including all instances) can have. An object method is a function defined in the object itself. Object methods usually have some interaction with the object, such as references to this
or to other methods that interact with the object.
Object methods are really easy to create with Plain Old CoffeeScript Objects, because they’re the only kind of method you can use. Recall from This and That:
1
QueueMaker
=
->
2
array:
[]
3
head:
0
4
tail:
-
1
5
pushTail:
(
value
)
->
6
this
.
array
[
this
.
tail
+=
1
]
=
value
7
pullHead:
->
8
unless
this
.
isEmpty
()
9
do
(
value
=
this
.
array
[
this
.
head
])
=>
10
this
.
array
[
this
.
head
]
=
undefined
11
this
.
head
+=
1
12
value
13
isEmpty:
->
14
this
.
tail
<
this
.
head
pushTail
, pullHead
, and isEmpty
are object methods. Also, from encapsulation:
1
stack
=
do
(
obj
=
undefined
)
->
2
obj
=
3
array:
[]
4
index:
-
1
5
push:
(
value
)
->
6
obj
.
array
[
obj
.
index
+=
1
]
=
value
7
pop:
->
8
do
(
value
=
obj
.
array
[
obj
.
index
])
->
9
obj
.
array
[
obj
.
index
]
=
undefined
10
obj
.
index
-=
1
if
obj
.
index
>=
0
11
value
12
isEmpty:
->
Although they don’t refer to the object, push
, pop
, and isEmpty
semantically interact with the opaque data structure represented by the object, so they are object methods too.
object methods within instances
Instances of constructors can have object methods as well. Typically, object methods are added in the constructor. Here’s a gratuitous example, a widget model that has a read-only id
. We’re using the class statement, but it could just as easily be rolled by hand:
1
class
WidgetModel
2
constructor:
(
id
,
attrs
=
{})
->
3
this
[
key
]
=
value
for
key
,
value
of
own
attrs
4
@
id
=
->
5
id
6
this
7
set:
(
attrs
)
->
8
#
...
9
get:
(
key
)
->
10
#
...
11
has:
(
key
)
->
12
#
...
set
, get
, and has
are instance methods, but id
is an object method: Each object has its own id
closure, where id
is bound to the id of the widget by the argument id
in the constructor. The advantage of this approach is that instances can have different object methods, or object methods with their own closures as in this case. The disadvantage is that every object has its own methods, which uses up much more memory than instance methods, which are shared amongst all instances.
Canonicalization
Early in this book, we discussed how objects, arrays, and functions are reference types. When we create a new object, even if it has the same contents as some other object, it is a different value, as we can tell when we test its identity with is
:
1
{
foo
:
'
bar
'
}
is
{
foo
:
'
bar
'
}
2
#
=>
false
Sometimes, this is not what you want. A non-trivial example is the HashLife algorithm for computing the future of Conway’s Game of Life. HashLife aggressively caches both patterns on the board and their futures, so that instead of iteratively simulating the cellular automaton a generation at a time, it executes in logarithmic time.
In order to take advantage of cached results, HashLife must canonicalize square patterns. Meaning, it must guarantee that if two square patterns have the same contents, they must be the same object and share the same identity. This ensures that updates are shared everywhere.
One way to make this work is to eschew having all the code create new objects with a constructor. Instead, the construction of new objects is delegated to a cache. When a function needs a new object, it asks the cache for it. If a matching object already exists, it is returned. If not, a new one is created and placed in the cache.
This is the algorithm used by recursiveuniver.se, an experimental implementation of HashLife in CoffeeScript. The fully annotated source code for canonicalization is online, and it contains this method for the Square.cache
object:
1
for:
(
quadrants
,
creator
)
->
2
found
=
Square
.
cache
.
find
(
quadrants
)
3
if
found
4
found
5
else
6
{
nw
,
ne
,
se
,
sw
}
=
quadrants
7
Square
.
cache
.
add
_for
(
quadrants
,
creator
)
Instead of enjoying a stimulating digression explaining how that works, let’s make our own. We’re going to build a class for cards in a traditional deck. Without canonicalization, it looks like this:
1
class
Card
2
ranks
=
[
2..10
].
concat
[
'J'
,
'Q'
,
'K'
,
'A'
]
3
suits
=
[
'C'
,
'D'
,
'H'
,
'S'
]
4
constructor:
({
@
rank
,
@
suit
})
->
5
throw
"#{@rank} is a bad rank"
unless
@
rank
in
ranks
6
throw
"#{@suit} is a bad suit"
unless
@
suit
in
suits
7
toString:
->
8
''
+
@
rank
+
@
suit
The instances are not canonicalized:
1
new
Card
({
rank
:
4
,
suit
:
'S'
})
is
new
Card
({
rank
:
4
,
suit
:
'S'
})
2
#
=>
false
If a constructor function explicitly returns a value, that’s what is returned. Otherwise, the newly constructed object is returned. Unlike other functions and methods, the last evaluated value is not returned by default. |
We can take advantage of that to canonicalize cards:
1
class
Card
2
ranks
=
[
2..10
].
concat
[
'J'
,
'Q'
,
'K'
,
'A'
]
3
suits
=
[
'C'
,
'D'
,
'H'
,
'S'
]
4
cache
=
{}
5
constructor:
({
@
rank
,
@
suit
})
->
6
throw
"#{@rank} is a bad rank"
unless
@
rank
in
ranks
7
throw
"#{@suit} is a bad suit"
unless
@
suit
in
suits
8
return
cache
[
@
toString
()]
or
=
this
9
toString:
->
10
''
+
@
rank
+
@
suit
Now the instances are canonicalized:
1
new
Card
({
rank
:
4
,
suit
:
'S'
})
is
new
Card
({
rank
:
4
,
suit
:
'S'
})
2
#
=>
true
Wonderful!
caveats
Using techniques like this to canonicalize instances of a class has many drawbacks and takes careful consideration before use. First, while this code illustrates the possibilities inherent in having a constructor return a different object, it is wasteful in that it creates an object only to throw it away if it is already in the cache.
If there are a tractable number of possible instances of a class (such as cards in a deck), it may be more practical to enumerate them all in advance rather than lazily create them, and/or to use a factory method to retried them rather than changing the behaviour of the constructor.
More serious is that the engine that executes CoffeeScript programs does not support weak references.26 As a result, if you wish to perform cache eviction for memory management purposes, you will have to implement your own reference management scheme. This may be non-trivial.
If you have many, many possible instances, your cache can end up holding onto what some programmers call zombie objects: Objects that are not in use anywhere in your program except the cache. If they are never accessed again, the memory they take up will never be released for reuse. An early version of the HashLife implementation did not clear objects from the cache. Some computations would consume as much as 700MB of data for the cache before the virtual machine was unable to continue. Most of that memory was consumed by zombie objects.
All that being said, canonicalization is sometimes the appropriate path forward, and even if it isn’t, it serves to illustrate the possibilities latent in writing constructors that return objects explicitly.
This Section Needs No Title
CoffeeScript is fundamentally an object-oriented language in the sense that Alan Kay first described object orientation. His vision was of software constructed from entities that communicate with message passing, with the system being extremely dynamic (what he described as “extreme late-binding”). However, words and phrases are only useful when both writer and reader share a common understanding, and for many people the words “object-oriented” carry with them a great deal of baggage related to constructing ontologies of domain entities.
The word “Inheritance” also means many different things to many different people. Some people take it extremely seriously, tugging thoughtfully on their long white beards as they ponder things like Strict Liskov Equivalence. We will avoid this term as well.
What we will discuss is extension. In the next section, we’re going to show how functions that create instances can extend each other through their prototypes. Since we just finished looking at the class statement, we’ll start by chaining two classes together, and then generalize extension so that you can use it with any two functions that create instances.
We’ll finish by looking at the excellent support CoffeeScript provides so that you can accomplish all of this with a single keyword.
Extending Classes
You recall from Composition and Extension that we extended a Plain Old CoffeeScript Queue to create a Plain Old CoffeeScript Deque. But what if we have decided to use CoffeeScript’s prototypes and class statements instead of Plain Old CoffeeScript Objects? How do we extend a queue into a deque?
Here’s our Queue
:
1
class
Queue
2
constructor:
->
3
@
array
=
[]
4
@
head
=
0
5
@
tail
=
-
1
6
pushTail:
(
value
)
->
7
@
array
[
@
tail
+=
1
]
=
value
8
pullHead:
->
9
unless
@
isEmpty
()
10
do
(
value
=
@
array
[
@
head
])
=>
11
@
array
[
@
head
]
=
undefined
12
@
head
+=
1
13
value
14
isEmpty:
->
15
@
tail
<
@
head
And our Deque
before we wire things together:
1
class
Deque
2
size:
->
3
@
tail
-
@
head
+
1
4
pullTail:
->
5
unless
@
isEmpty
()
6
do
(
value
=
@
array
[
@
tail
])
=>
7
@
array
[
@
tail
]
=
undefined
8
@
tail
-=
1
9
value
10
INCREMENT
=
4
11
pushHead:
(
value
)
->
12
if
@
head
is
0
13
for
i
in
[
@
tail
..
@
head
]
14
@
array
[
i
+
INCREMENT
]
=
@
array
[
i
]
15
@
tail
+=
INCREMENT
16
@
head
+=
INCREMENT
17
@
array
[
@
head
-=
1
]
=
value
So what do we want from dequeues?
- A
Deque
function that initializes a deque when invoked withnew
-
Deque.prototype
must have all the behaviour of a deque and all the behaviour of a queue.
Hmmm. So, should we copy everything from Queue.prototype
into Deque.prototype
? No, there’s a better idea. Prototypes are objects, right? Why must they be Plain Old CoffeeScript Objects? Can’t a prototype be an instance?
Yes they can. Imagine that Deque.prototype
was a proxy for an instance of Queue
. It would, of course, have all of a queue’s behaviour through Queue.prototype
. We don’t want it to be an actual instance, mind you. It probably doesn’t matter with a queue, but some of the things we might work with might make things awkward if we make random instances. A database connection comes to mind, we may not want to create one just for the convenience of having access to its behaviour.
Here’s such a proxy:
1
QueueProxy
=
->
2
3
QueueProxy
.
prototype
=
Queue
.
prototype
Our QueueProxy
isn’t actually a Queue
, but its prototype
is an alias of Queue.prototype
. Thus, it can pick up Queue
’s behaviour. We want to use it for our Deque
’s prototype. Let’s insert that code in our class:
1
class
Deque
2
QueueProxy
=
->
3
QueueProxy
.
prototype
=
Queue
.
prototype
4
Deque
.
prototype
=
new
QueueProxy
()
5
size:
->
6
@
tail
-
@
head
+
1
7
#
...
Before we rush off to try this, we’re missing something. How are we going to initialize our deques? We’d better call Queue
’s constructor:
1
constructor:
->
2
Queue
.
prototype
.
constructor
.
call
(
this
)
Here’s what we have so far:
1
class
Deque
2
QueueProxy
=
->
3
QueueProxy
.
prototype
=
Queue
.
prototype
4
@
prototype
=
new
QueueProxy
()
5
constructor:
->
6
Queue
.
prototype
.
constructor
.
call
(
this
)
7
#
...
And it seems to work:
1
d
=
new
Deque
()
2
d
.
pushTail
(
'
Hello
'
)
3
d
.
pushTail
(
'
CoffeeScript
'
)
4
d
.
pushTail
(
'!'
)
5
d
.
pullHead
()
6
#
=>
'
Hello
'
7
d
.
pullTail
()
8
#
=>
'!'
9
d
.
pullHead
()
10
#
=>
'
CoffeeScript
'
Wonderful!
getting the constructor element right
How about some of the other things we’ve come to expect from instances?
1
d
.
constructor
is
Deque
2
#
=>
false
Oops! Messing around with Dequeue’s prototype broke this important equivalence. Luckily for us, the constructor
property is mutable for objects we create. So, let’s make a small change to QueueProxy
:
1
class
Deque
2
QueueProxy
=
->
3
@
constructor
=
Deque
4
this
5
QueueProxy
.
prototype
=
Queue
.
prototype
6
@
prototype
=
new
QueueProxy
();
7
#
...
Now it works:
1
d
.
constructor
is
Deque
2
#
=>
true
The QueueProxy
function now sets the constructor
for every instance of a QueueProxy
(hopefully just the one we need for the Deque
class). It returns the object being created (it could also return undefined
and work. But if it carelessly returned something else, that would be assigned to Deque
’s prototype, which would break our code).
extracting the boilerplate
Let’s turn our extension modifications into a function:
1
xtend
=
(
child
,
parent
)
->
2
do
(
proxy
=
undefined
)
->
3
proxy
=
->
4
@
constructor
=
child
5
this
6
proxy
.
prototype
=
parent
.
prototype
7
child
.
prototype
=
new
proxy
()
And use it in Deque
:
1
class
Deque
2
xtend
(
Deque
,
Queue
)
3
constructor:
->
4
Queue
.
prototype
.
constructor
.
call
(
this
)
5
size:
->
6
@
tail
-
@
head
+
1
7
pullTail:
->
8
unless
@
isEmpty
()
9
do
(
value
=
@
array
[
@
tail
])
=>
10
@
array
[
@
tail
]
=
undefined
11
@
tail
-=
1
12
value
13
INCREMENT
=
4
14
pushHead:
(
value
)
->
15
if
@
head
is
0
16
for
i
in
[
@
tail
..
@
head
]
17
@
array
[
i
+
INCREMENT
]
=
@
array
[
i
]
18
@
tail
+=
INCREMENT
19
@
head
+=
INCREMENT
20
@
array
[
@
head
-=
1
]
=
value
And you can use xtend
even if you don’t want to use the class statement:
1
A
=
->
2
B
=
->
3
xtend
(
B
,
A
)
It’s such a nice idea. Wouldn’t it be great if CoffeeScript had it built-in? Behold:
1
B
extends
A
Most helpful! In fact, CoffeeScript’s keyword is superior to the xtend
function: It provides support for extending functions that have other properties, not just the prototype.27
How about the class statement? CoffeeScript does a lot more work for you if you wish. You can write:
1
class
Deque
2
Deque
extends
Queue
3
constructor:
->
4
Queue
.
prototype
.
constructor
.
call
(
this
)
5
#
...
But there’s more. If you instead write:
1
class
Deque
extends
Queue
2
#
...
- CoffeeScript will do even more work for you. If you aren’t doing any extra setup, you can leave the constructor out. CoffeeScript will handle calling the extended function’s constructor for you.
- If you do wish to do some extra setup, write your own constructor. Like Ruby, you can call
super
in any method to access the extended version of the same method. - CoffeeScript’s
class
andextends
keywords handle a lot of the boilerplate and play nicely together. Use them unless you have specific needs they don’t cover.
Summary
Instances and Classes
|
interlude…
Aaron De Lazzer on Ristretto:
“The ristretto shot of espresso is one of the most fiercely debated and favourite topics amongst the coffee cognoscenti. It is the purists pour. The cutting edge of espresso extraction, flying in the face of the “Big Gulp” coffee drinker like nothing else around.
In anything at all, perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.–Antoine de Saint-Exupery
“Antoine would have drank ristretto shots.
“There is no where to hide with a straight unadulterated shot of espresso. Even more (less?) so with a ristretto shot. Any weakness in the blend or in the preparation of the coffee will be brought to light here. Either the heavens open up and the angels sing after that first sip or….something significantly less. Which is always such a disappointment knowing all the potential distilled into the dribble of coffee liquor that barely coats the bottom of your cup.”
An Extra Shot of Ideas
Refactoring to Combinators
The word “combinator” has a precise technical meaning in mathematics:
“A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments.”–Wikipedia
In this book, we will be using a much looser definition of “combinator:” Pure functions that act on other functions to produce functions. Combinators are the adverbs of functional programming.
memoize
Let’s begin with an example of a combinator, memoize
. Consider that age-old interview quiz, writing a recursive fibonacci function (there are other ways to derive a fibonacci number, of course). Here’s simple implementation:
1
fibonacci
=
(
n
)
->
2
if
n
<
2
3
n
4
else
5
fibonacci
(
n
-
2
)
+
fibonacci
(
n
-
1
)
We’ll time it:
1
s
=
(
new
Date
()).
getTime
()
2
new
Fibonacci
(
45
).
toInt
()
3
(
(
new
Date
()).
getTime
()
-
s
)
/
1000
4
#
=>
28.565
Why is it so slow? Well, it has a nasty habit of recalculating the same results over and over and over again. We could rearrange the computation to avoid this, but let’s be lazy and trade space for time. What we want to do is use a lookup table. Whenever we want a result, we look it up. If we don’t have it, we calculate it and write the result in the table to use in the future. If we do have it, we return the result without recalculating it.
We could write something specific for fibonacci and then generalize it, but let’s skip right to a general solution (we’ll discuss extracting a combinator below). First, a new feature. Within any function the name arguments
is always bound to an object that behaves like a collection of all the arguments passed to a function. Using arguments
, here is a memoize
implementation that works for many28 kinds of functions:
1
memoized
=
(
fn
)
->
2
do
(
lookupTable
=
{},
key
=
undefined
,
value
=
undefined
)
->
3
->
4
key
=
JSON
.
stringify
(
arguments
)
5
lookupTable
[
key
]
or
=
fn
.
apply
(
this
,
arguments
)
We can apply memoized
to a function and we will get back a new function that memoizes its results.
Let’s try it:
1
fastFibonacci
=
2
memoized
(
n
)
->
3
if
n
<
2
4
n
5
else
6
fastFibonacci
(
n
-
2
)
+
fastFibonacci
(
n
-
1
)
7
8
fastFibonacci
(
45
)
9
#
=>
1134903170
We get the result back instantly. It works!
Exercise: Optimistic Ophelia tries the following code:
Does |
By using a combinator instead of tangling lookup code with the actual “domain logic” of fibonacci, our fastFibonacci
code is easy to read and understand. As a bonus, we DRY up our application, as the same memoize
combinator can be used in many different places.
the once and future combinator refactoring
Combinators can often be extracted from your code. The result is cleaner than the kinds of refactorings possible in languages that have less flexible functions. Let’s walk through the process of discovering a combinator to get a feel for the refactoring to combinator process.
Some functions should only be evaluated once, but might be invoked more than once. You want to evaluate them the first time they are called, but thereafter ignore the invocation.
We’d start with a function like this, it assumes there’s some “it” that needs to be initialized once, and several pieces of code might call this function before using “it:”
1
ensureItIsInitialized
=
do
(
itIsInitialized
=
false
)
->
2
->
3
unless
itIsInitialized
4
itIsInitialized
=
true
5
#
...
6
#
initialization
code
7
#
...
The typical meta-pattern is when several different functions all share a common precondition such as loading certain constant data from a server or initializing a resource. We can see that we’re tangling the concern of initializing once with the concern of how to perform the initialization. Let’s extract a method:
1
initializeIt
=
->
2
#
...
3
#
initialization
code
4
#
...
5
ensureItIsInitialized
=
do
(
itIsInitialized
=
false
)
->
6
->
7
unless
itIsInitialized
8
itIsInitialized
=
true
9
initializeIt
()
In many other languages, we’d stop right there. But in CoffeeScript, we can see that ensureItIsInitialized
is much more generic than its name suggests. Let’s convert it to a combinator with a slight variation on the extracting a parameter. We’ll call the combinator once
:
1
once
=
(
fn
)
->
2
do
(
done
=
false
)
->
3
->
4
unless
done
5
done
=
true
6
fn
.
apply
(
this
,
arguments
)
And now our code is very clean:
1
initializeIt
=
->
2
#
...
3
#
initialization
code
4
#
...
5
ensureItIsInitialized
=
once
(
initializeIt
)
This is so clean you could get rid of initializeIt
as a named function:
1
ensureItIsInitialized
=
once
->
2
#
...
3
#
initialization
code
4
#
...
The concept of a combinator is more important than having a specific portfolio of combinators at your fingertips (although that is always nice). The meta-pattern is that when you are working with a function, identify the core “domain logic” the function should express. Try to extract that and turn what is left into one or more combinators that take functions as parameters rather than single-purpose methods.
(The memoize
and once
combinators are available in the underscore.js library along with several other handy functions that operate on functions such as throttle
and debounce
.)
Composition and Combinators
Although you can write nearly any function and use it as a combinator, one property that is nearly essential is composability. It should be possible to pass the result of one combinator as the argument to another.
Let’s consider this combinator as an example:
1
requiresValues
=
(
fn
)
->
2
->
3
throw
"Value Required"
unless
arg
?
for
arg
in
arguments
4
fn
.
apply
(
this
,
arguments
)
And this one:
1
requiresReturnValue
=
(
fn
)
->
2
->
3
do
(
result
=
fn
.
apply
(
this
,
arguments
))
->
4
throw
"Value Required"
unless
result
?
5
result
You can use both of these combinators and the once
combinator on a function to add some runtime validation that both input arguments and the returned value are defined:
1
checkedFibonacci
=
once
requiresValues
requiresReturnValue
(
n
)
->
2
if
n
<
2
3
n
4
else
5
fibonacci
(
n
-
2
)
+
fibonacci
(
n
-
1
)
Combinators should be designed by default to compose. And speaking of composition, it’s easy to compose functions in CoffeeScript:
1
checkBoth
=
(
fn
)
->
2
requiresValues
requiresReturnValue
fn
Libraries like Functional also provide compose
and sequence
functions, so you can write things like:
1
checkBoth
=
Functional
.
compose
(
requiresValues
,
requiresReturnValue
)
All of this is made possible by the simple property of composability, the property of combinators taking a function as an argument and returning a function that operates on the same arguments and returns something meaningful to code expecting to call the original function.
Method Decorators
Now that we’ve seen how function combinators can make our code cleaner and DRYer, it isn’t a great leap to ask if we can use combinators with methods. After all, methods are functions. That’s one of the great strengths of CoffeeScript, since methods are “just” functions, we don’t need to have one kind of tool for functions and another for methods, or a messy way of turning methods into functions and functions into methods.
And the answer is, “Yes we can.” With some caveats. Let’s get our terminology synchronized. A combinator is a function that modifies another function. A method decorator is a combinator that modifies a method expression used inline. So, all method decorators are combinators, but not all combinators are method decorators.29
decorating object methods
As you recall, an object method is a method belonging directly to a Plain Old CoffeeScript object or an instance. All combinators work as decorators for object methods. For example:
1
class
LazyInitializedMechanism
2
constructor:
->
3
@
initialize
=
once
->
4
#
...
5
#
complicated
stuff
6
#
...
7
someInstanceMethod:
->
8
@
initialize
()
9
#
...
10
anotherInstanceMethod:
(
foo
)
->
11
@
initialize
()
12
#
...
decorating constructor methods
Decorating constructor methods works just as well as decorating instance methods, for example:
1
class
LazyClazz
2
@
setUpLazyClazz
:
once
->
3
#
...
4
#
complicated
stuff
5
#
...
6
constructor:
->
7
this
.
constructor
.
setUpLazyClazz
()
8
#
...
For this class, there’s some setup to be done, but it’s deferred until the first instance is created.
decorating instance methods
Decorating instance methods can be tricky if they rely on closures to encapsulate state of any kind. For example, this will not work:
1
class
BrokenMechanism
2
initialize:
once
->
3
#
...
4
#
complicated
stuff
5
#
...
6
someInstanceMethod:
->
7
@
initialize
()
8
#
...
9
anotherInstanceMethod:
(
foo
)
->
10
@
initialize
()
11
#
...
If you have more than one BrokenMechanism
, only one will ever be initialized. There is one initialize
method, and it belongs to BrokenMechanism.prototype
, so once it is called for the first BrokenMechanism
instance, all others calling it for the same or different instances will not execute.
The initialize
method could be converted from an instance method to an object method as above. An alternate approach is to surrender the perfect encapsulation of once
, and write a decorator designed for use on instance methods:
1
once
=
(
name
,
method
)
->
2
->
3
unless
@
[
name
]
4
@
[
name
]
=
true
5
method
.
apply
(
this
,
arguments
)
Now the flag for being done has been changed to an element of the instance, and we use it like this:
1
class
WorkingMechanism
2
initialize:
once
'
doneInitializing
'
,
->
3
#
...
4
#
complicated
stuff
5
#
...
6
someInstanceMethod:
->
7
@
initialize
()
8
#
...
9
anotherInstanceMethod:
(
foo
)
->
10
@
initialize
()
11
#
...
Since the flag is stored in the instance, the one function works with all instances. (You do need to make sure that each method using the decorator has its own unique name.)
a decorator for fluent interfaces
Fluent interfaces are a style of API often used for configuration. The principle is to return an instance that has meaningful methods for the next thing you want to do. The simplest (but not only) type of fluent interface is a cascade of methods configuring the same object, such as:
1
car
=
new
Automobile
()
2
.
withBucketSeats
(
2
)
3
.
withStandardTransmission
(
5
)
4
.
withDoors
(
4
)
To implement an interface with this simple API, methods need to return this
. It’s one line and easy to do, but you look at the top of the method to see its name and the bottom of the method to see what it returns. If there are multiple return paths, you must take care that they all return this
.
It’s easy to write a fluent decorator:
1
fluent
=
(
method
)
->
2
->
3
method
.
apply
(
this
,
arguments
)
4
this
And it’s easy to use:
1
class
Automobile
2
withBucketSeats:
fluent
(
num
)
->
3
#
...
4
withStandardTransmission:
fluent
(
gears
)
->
5
#
...
6
withDoors:
fluent
(
num
)
->
7
#
...
combinators for making decorators
Quite a few of the examples involve initializing something before doing some work. This is a very common pattern: Do something before invoking a method. Can we extract that into a combinator? Certainly. Here’s a combinator that takes a method and returns a decorator:
1
before
=
2
(
decoration
)
->
3
(
base
)
->
4
->
5
decoration
.
apply
(
this
,
arguments
)
6
base
.
apply
(
this
,
arguments
)
You would use it like this:
1
forcesInitialize
=
before
->
@
initialize
()
2
3
class
WorkingMechanism
4
initialize:
once
'
doneInitializing
'
,
->
5
#
...
6
#
complicated
stuff
7
#
...
8
someInstanceMethod:
forcesInitialize
->
9
#
...
10
anotherInstanceMethod:
forcesInitialize
(
foo
)
->
11
#
...
Of course, you could put anything in there, including the initialization code if you wanted to:
1
class
WorkingMechanism
2
forcesInitialize
=
before
->
3
#
...
4
#
complicated
stuff
5
#
...
6
someInstanceMethod:
forcesInitialize
->
7
#
...
8
anotherInstanceMethod:
forcesInitialize
(
foo
)
->
9
#
...
When writing decorators, the same few patterns tend to crop up regularly:
- You want to do something before the method’s base logic is executed.
- You want to do something after the method’s base logic is executed.
- You want to wrap some logic around the method’s base logic.
- You only want to execute the method’s base logic provided some condition is truthy.
We saw before
above. Here are three more combinators that are very useful for writing method decorators:
1
after
=
2
(
decoration
)
->
3
(
base
)
->
4
->
5
decoration
.
call
(
this
,
__value__
=
base
.
apply
(
this
,
arguments
))
6
__value__
7
8
around
=
9
(
decoration
)
->
10
(
base
)
->
11
(
argv
...)
->
12
__value__
=
undefined
13
callback
=
=>
14
__value__
=
base
.
apply
(
this
,
argv
)
15
decoration
.
apply
(
this
,
[
callback
].
concat
(
argv
))
16
__value__
17
18
provided
=
19
(
condition
)
->
20
(
base
)
->
21
->
22
if
condition
.
apply
(
this
,
arguments
)
23
base
.
apply
(
this
,
arguments
)
All four of these, and many more can be found in the method combinators module. They can be used with all CoffeeScript and JavaScript projects.
Callbacks and Promises
Like nearly all languages in widespread use, CoffeeScript expresses programs as expressions that are composed together with a combination of operators, function application, and control flow constructs such as sequences of statements.
That’s all baked into the underlying language, so it’s easy to use it without thinking about it. Much as a fish (perhaps) exists in the ocean without being aware that there is an ocean. In this chapter, we’re going to examine how to compose functions together when we have non-traditional forms of control-flow such as asynchronous function invocation.
composition
The very simplest example of composing functions is simply “pipelining” the values. CoffeeScript’s optional parentheses make this quite readable. Given:
1
getIdFromSession
=
(
session
)
->
2
#
...
3
4
fetchCustomerById
=
(
id
)
->
5
#
...
6
7
currentSession
=
#
...
You can write either:
1
customerList
.
add
(
2
fetchCustomerById
(
3
getIdFromSession
(
4
currentSession
5
)
6
)
7
)
Or:
1
customerList
.
add
fetchCustomerById
getIdFromSession
currentSession
The “flow” of data is from right-to-left. Some people find it more readable to go from left-to-right. The sequence
function accomplishes this:
1
sequence
=
->
2
do
(
fns
=
arguments
)
->
3
(
value
)
->
4
(
value
=
fn
(
value
))
for
fn
in
fns
5
value
6
7
sequence
(
8
getIdFromSession
,
9
fetchCustomerById
,
10
customerList
.
add
11
)(
currentSession
)
asynchronous code
CoffeeScript executes within an environment where code can be invoked asynchronously. For example, a browser application can asynchronously invoke a request to a remote server and invoke handler code when the request is satisfied or deemed to have failed.
A very simple example is that in a browser application, you can defer invocation of a function after all current processing has been completed:
1
defer
(
fn
)
->
2
window
.
setTimeout
(
fn
,
0
)
The result is that if you write:
1
defer
->
console
.
log
(
'
Hello
'
)
2
console
.
log
(
'
Asynchronicity
'
)
The console will show:
1
Asynchronicity
2
Hello
The computer has no idea whether the result should be “Asynchronicity Hello” or “Hello Asynchronicity” or sometimes one and sometimes the other. But if we intend that the result be “Asynchronicity Hello,” we say that the function -> console.log('Hello')
depends upon the code console.log('Asynchronicity')
.
This might be what you want. If it isn’t, you need to have a way to force the order of evaluation when there is supposed to be a dependency between different evaluations. There are a number of different models and abstractions for controlling these dependencies.
We will examine two of them (callbacks and promises) briefly. Not for the purpose of learning the subtleties of using either model, but rather to obtain an understanding of how functions can be used to implement an abstraction over an underlying model.
callbacks
The underlying premise of the callback model is that every function that invoked code asynchronously is responsible for invoking code that depends on it. The simplest protocol for this is also the most popular: Functions that invoke asynchronous code take an extra parameter called a callback. That parameter is a function to be invoked when they have completed.
So our defer
function looks like this if we want to use callbacks:
1
defer
(
fn
,
callback
)
->
2
window
.
setTimeout
(
->
callback
fn
),
0
Instead of handing fn
directly to window.setTimeout
, we’re handing it a function that invokes fn
and pipelines the result (if any) to callback
. Now we can ensure that the output is in the correct order:
1
defer
(
->
console
.
log
'
hello
'
),
(
->
console
.
log
'
Asynchronicity
'
)
2
3
#=> Hello
4
# Asynchronicity
Likewise, let’s say we have a displayPhoto
function that is synchronous, and not callback-aware:
1
displayPhoto
=
(
photoData
)
->
2
#
...
synchronous
function
...
It can also be converted to take a callback:
1
displayPhotoWithCallback
=
(
photoData
,
callback
)
->
2
callback
(
displayPhoto
(
photoData
))
There’s a combinator we can extract:
1
callbackize
=
(
fn
)
->
2
(
arg
,
callback
)
->
3
callback
(
fn
(
arg
))
You recall that with ordinary functions, you could chain them with function application. With callbacks, you can also chain them manually. Here’s an example inspired by a blog post, fetching photos from a remote photo sharing site using their asynchronous API:
1
tag
=
'
ristretto
'
2
3
fotositeGetPhotosByTag
tag
,
(
photoList
)
->
4
fotositeGetOneFromList
photos
,
(
photoId
)
->
5
fotositeGetPhoto
photoId
,
displayPhoto
We can also create a callback-aware function that represents the composition of functions:
1
displayPhotoForTag
=
(
tag
,
callback
)
->
2
fotositeGetPhotosByTag
tag
,
(
photoList
)
->
3
fotositeGetOneFromList
photos
,
(
photoId
)
->
4
fotositeGetPhoto
photoId
,
displayPhoto
This code is considerably less messy in CoffeeScript than other languages that require a lot of additional syntax for functions. As a bonus, although it has some extra scaffolding and indentation, it’s already in sequence order from top to bottom and doesn’t require re-ordering like normal function application did. That being said, you can avoid the indentation and extra syntax by writing a sequenceWithCallbacks
function:
1
I
=
(
x
)
->
x
2
3
sequenceWithCallbacks
=
->
4
do
(
fns
=
arguments
,
5
lastIndex
=
arguments
.
length
-
1
,
6
helper
=
undefined
)
->
7
helper
=
(
arg
,
index
,
callback
=
I
)
->
8
if
index
>
lastIndex
9
callback
arg
10
else
11
fns
[
index
]
arg
,
(
result
)
->
12
helper
result
,
index
+
1
,
callback
13
(
arg
,
callback
)
->
14
helper
arg
,
0
,
callback
15
16
displayPhotoForTag
=
sequenceWithCallbacks
(
17
fotositeGetPhotosByTag
,
18
fotositeGetOneFromList
,
19
fotositeGetPhoto
,
20
displayPhotoWithCallback
21
)
sequenceWithCallbacks
is more complex than sequence
, but it does help us make callback-aware code “linear” instead of nested/indented.
As we have seen, we can compose linear execution of asynchronous functions, using either the explicit invocation of callbacks or using sequenceWithCallbacks
to express the execution as a list.
solving this problem with promises
Asynchronous control flow can also be expressed using objects and methods. One model is called promises. A promise is an object that acts as a state machine.30 Its permissible states are:
- unfulfilled
- fulfilled
- failed
The only permissible transitions are from unfulfilled to fulfilled and from unfulfilled to failed. Once in either the fulfilled or failed states, it remains there permanently.
Each promise must at a bare minimum implement a single method, .then(fulfilledCallback, failedCallback)
.31fulfilledCallback
is a function to be invoked by a fulfilled promise, failedCallback
by a failed promise. If the promise is already in either state, that function is invoked immediately.
.then
returns another promise that is fulfilled when the appropriate callback is fulfilled or fails when the appropriate callback fails. This allows chaining of .then
calls.
If the promise is unfulfilled, the function(s) provided by the .then
call are queued up to be invoked if and when the promise transitions to the appropriate state. In addition to this being an object-based protocol, the promise model also differs from the callback model in that .then
can be invoked on a promise at any time, whereas callbacks must be specified in advance.
Here’s how our fotosite API would be used if it implemented promises instead of callbacks (we’ll ignore handling failures):
1
fotositeGetPhotosByTag
(
tag
)
2
.
then
fotositeGetOneFromList
3
.
then
fotositeGetPhoto
4
.
then
displayPhoto
Crisp and clean, no caffeine. The promises model provides linear code “out of the box,” and it “scales up” to serve as a complete platform for managing asynchronous code and remote invocation. Be sure to look at libraries supporting promises like q, and when.
Summary
An Extra Shot of Ideas
|
the last word…
A Golden Crema
How to run the examples
If you follow the instructions at coffeescript.org to install NodeJS and CoffeeScript,32 you can run an interactive CoffeeScript REPL on your command line simply by typing coffee
. This is how the examples in this book were tested, and what many programmers will do. When running CoffeeScript on the command line, ctrl-V switches between single-line and multi-line input mode. If you need to enter more than one line of code, be sure to enter multi-line mode.
Some websites function as online REPLs, allowing you to type CoffeeScript programs right within a web page and see the results (as well as a translation from CoffeeScript to JavaScript). The examples in this book have all been tested on coffeescript.org. You simply type a CoffeeScript expression into the blank window and you will see its JavaScript translation live. Clicking “Run” evaluates the expression in the browser.
To actually see the result of your expressions, you’ll need to either include a call to console.log
(and be using a browser that supports console logging) or you could go old-school and use alert
, e.g. alert 2+2
will cause the alert box to be displayed with the message 4
.
Thanks!
Daniel Friedman and Matthias Felleisen
CoffeeScript Ristretto was inspired by The Little Schemer by Daniel Friedman and Matthias Felleisen. But where The Little Schemer’s primary focus is recursion, CoffeeScript Ristretto’s primary focus is functions as first-class values.
Richard Feynman
Richard Feynman’s QED was another inspiration: A book that explains Quantum Electrodynamics and the “Sum of the Histories” methodology using the simple expedient of explaining how light reflects off a mirror, and showing how most of the things we think are happening–such as light travelling on a straight line, the angle of reflection equalling the angle of refraction, or that a beam of light only interacts with a small portion of the mirror, or that it reflects off a plane–are all wrong. And everything is explained in simple, concise terms that build upon each other logically.
Trevor Burnham
Trevor Burnham provided invaluable assistance with this book. Trevor is the author of CoffeeScript: Accelerated JavaScript Development, an excellent resource for CoffeeScript programmers.
JavaScript Allongé
JavaScript Allongé is the companion book to CoffeeScript Ristretto.
Copyright Notice
The original words in this sample preview of CoffeeScript Ristretto are (c) 2012, Reginald Braithwaite. This sample preview work is licensed under an Attribution-NonCommercial-NoDerivs 3.0 Unported license.
images
- The picture of the author is (c) 2008, Joseph Hurtado, All Rights Reserved.
- Double ristretto menu (c) 2010, Michael Allen Smith. Some rights reserved.
- Short espresso shot in a white cup with blunt handle (c) 2007, EVERYDAYLIFEMODERN. Some rights reserved.
- Espresso shot in a caffe molinari cup (c) 2007, EVERYDAYLIFEMODERN. Some rights reserved.
- Beans in a Bag (c) 2008, Stirling Noyes. Some Rights Reserved.
- Free Samples (c) 2011, Myrtle Bech Digitel. Some Rights Reserved.
- Free Coffees image (c) 2010, Michael Francis McCarthy. Some Rights Reserved.
- La Marzocco (c) 2009, Michael Allen Smith. Some rights reserved.
- Cafe Diplomatico (c) 2011, Missi. Some rights reserved.
- Sugar Service (c) 2008 Tiago Fernandes. Some rights reserved.
- Biscotti on a Rack (c) 2010 Kirsten Loza. Some rights reserved.
- Coffee Spoons (c) 2010 Jenny Downing. Some rights reserved.
- Drawing a Doppio (c) 2008 Osman Bas. Some rights reserved.
- Cupping Coffees (c) 2011 Dennis Tang. Some rights reserved.
- Three Coffee Roasters (c) 2009 Michael Allen Smith. Some rights reserved.
- Blue Diedrich Roaster (c) 2010 Michael Allen Smith. Some rights reserved.
- Red Diedrich Roaster (c) 2009 Richard Masoner. Some rights reserved.
- Roaster with Tree Leaves (c) 2007 ting. Some rights reserved.
- Half Drunk (c) 2010 Nicholas Lundgaard. Some rights reserved.
- Anticipation (c) 2012 Paul McCoubrie. Some rights reserved.
- Ooh! (c) 2012 Michael Coghlan. Some rights reserved.
- Intestines of an Espresso Machine (c) 2011 Angie Chung. Some rights reserved.
- Bezzera Espresso Machine (c) 2011 Andrew Nash. Some rights reserved. *Beans Ripening on a Branch (c) 2008 John Pavelka. Some rights reserved.
- Cafe Macchiato on Gazotta Della Sport (c) 2008 Jon Shave. Some rights reserved.
- Jars of Coffee Beans (c) 2012 Memphis CVB. Some rights reserved.
- Types of Coffee Drinks (c) 2012 Michael Coghlan. Some rights reserved.
- Coffee Trees (c) 2011 Dave Townsend. Some rights reserved.
- Cafe do Brasil (c) 2003 Temporalata. Some rights reserved.
- Brown Cups (c) 2007 Michael Allen Smith. Some rights reserved.
- Mirage (c) 2010 Mira Helder. Some rights reserved.
- Coffee Van with Bullet Holes (c) 2006 Jon Crel. Some rights reserved.
About The Author
When he’s not shipping CoffeeScript, Ruby, JavaScript and Java applications scaling out to millions of users, Reg “Raganwald” Braithwaite has authored libraries for CoffeeScript, JavaScript and Ruby programming such as Method Combinators, Katy, JQuery Combinators, YouAreDaChef, andand, and others.
He writes about programming on his “Homoiconic” un-blog as well as general-purpose ruminations on his posterous space. He is also known for authoring the popular raganwald programming blog from 2005-2008.
contact
Twitter: @raganwald
Email: reg@braythwayt.com
- Technically, it’s a representation of a value using Base10 notation, but we needn’t worry about that in this book. You and I both understand that this means “42,” and so does the computer.↩
- In some languages, expressions are a kind of value unto themselves and can be manipulated. The grandfather of such languages is Lisp. CoffeeScript is not such a language, expressions in and of themselves are not values.↩
- If you have dabbled in CoffeeScript or look at other people’s CoffeeScript programs, you may discover that it is also legal to write
->
. Conceptually,->
is a function with no arguments and no body.->
is a function with an empty list of arguments and no body. Generally, CoffeeScript programmers prefer->
, so let’s do that.↩ - The exact same thing will happen to you once you figure out how to make an array that contains itself. You’ll try to print it out and you’ll get
[[Circular]]
back. Never mind, internally CoffeeScript has constructed a perfectly fine Ouroborian array even if it won’t try to print it out for you.↩ - If you’re used to other programming languages, you’ve probably internalized the idea that sometimes parentheses are used to group operations in an expression like math, and sometimes to apply a function to arguments. If not… Welcome to the ALGOL family of programming languages!↩
- Elsewhere, we’ve pledged to avoid optional bits that don’t add a lot to our understanding. This optional bit gives us an excuse to learn about
undefined
, so that’s why it’s in. Now that we know this, we see that our expression->
evaluates to a function taking no arguments and having no expression, therefore when you apply it to no arguments with(->)()
, you getundefined
.↩ - It does something else when the first argument is a string, but let’s ignore that bit of pedantry for now.↩
- The Argument Sketch from “Monty Python’s Previous Record” and “Monty Python’s Instant Record Collection”↩
- We said that you can’t apply a function to an expression. You can apply a function to one or more functions. Functions are values! This has interesting applications, and they will be explored much more thoroughly in Functions That Are Applied to Functions.↩
- You may also hear the term “non-local variable.” Both are correct. ↩
-
let
has made its way into other languages like JavaScript.↩ -
let
is limited in some ways. For example, you can’t define a recursive function without some fixed point combinator backflips. This will be discussed later when we look at the related patternletrec
.↩ - “Syntactic sugar causes cancer of the semicolon”–Alan Perlis↩
- Arrays in all contemporary languages store references and not copies, so we can be forgiven for expecting them to work the same way in CoffeeScript. Nevertheless, it’s a useful exercise to test things for ourself.↩
- Tradition would have us call objects that don’t contain any functions “POCOs,” meaning Plain Old CoffeeScript Objects. Given that poco a poco means “little by little” in musical notation, I’m tempted to go along with that.↩
- You may also find fixed point combinators interesting.↩
- It could be worse. One very popular language assumes that if you haven’t otherwise declared a variable local to a function, you must want a global variable that may clobber an existing global variable used by any piece of code in any file or module.↩
- “A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.”–Wikipedia↩
- Yes, there’s another way to track the size of the array, but we don’t need it to demonstrate encapsulation and hiding of state.↩
- And when you take an already factored component and rearrange things so that it is factored into a different set of subcomponents without altering its behaviour, you are refactoring.↩
- Before you start wondering whether a deque is-a queue, we said nothing about types and classes. This relationship is called was-a, or “implemented in terms of a.”↩
- CoffeeScript also does other things with
this
as well, but this is all we care about right now.↩ - Since the CoffeeScript that we have presented so far is computationally universal, it is possible to perform any calculation with its existing feature set, including emulating any other programming language. Therefore, it is not theoretically necessary to have any further language features; If we need macros, continuations, generic functions, static typing, or anything else, we can greenspun them ourselves. In practice, however, this is buggy, inefficient, and presents our fellow developers with serious challenges understanding our code.↩
- For many programmers, the distinction between a dynamic relationship and a copying mechanism too fine to worry about. However, it makes many dynamic program modifications possible.↩
- Recall that Strings, Numbers, Booleans and so forth are value types and primitives. We’re calling them primitives here.↩
- A weak reference is a reference that does not protect the referenced object from collection by a garbage collector; unlike a strong reference. An object referenced only by weak references is considered unreachable (or weakly reachable) and so may be collected at any time.↩
- You should almost always use
extends
rather than rolling your own code to chain functions and instances. And even if you let CoffeeScript do the work, you should always understand what CoffeeScript is doing for you.↩ - To be precise, it works for functions that take arguments that can be expressed with JSON. So you can’t memoize a function that is applied to functions, but it’s fine for strings, numbers, arrays of JSON, POCOs of JSON, and so forth.↩
- The term “method decorator” is borrowed from the Python programming language↩
- A state machine is a mathematical model of computation used to design both computer programs and sequential logic circuits. It is conceived as an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition, this is called a transition. A particular FSM is defined by a list of its states, and the triggering condition for each transition.↩
- Interactive promises also support
.get
and.call
methods for interacting with a potentially remote object.↩ - Instructions for installing NodeJS and modules like CoffeeScript onto a desktop computer is beyond the scope of this book, especially given the speed with which things advance. Fortunately, there are always up-to-date instructions on the web.↩