Chapter 7
Style and semantics
7.1 Rest and spread parameters
highlight = (name) ->
color find(name), 'yellow'
highlight = (names) ->
for name in names
color find(name), 'yellow'
highlight = (names...) ->
for name in names
color find(name), 'yellow'
highlight()
highlight 'taipans'
highlight 'taipans', 'wolverines', 'sabertooths'
highlight = (first, rest...) ->
color find first 'gold'
for name in rest
color find(name), 'blue'
highlight 'taipans', 'sabertooths', 'wolverines'
teams = ['wolverines', 'sabertooths', 'mongooses']
highlight teams[0], teams[1], teams[2]
highlight teams...
highlight teams
highlight teams...
insert = (teams...) ->
teams
initialize = (teams...) ->
insert teams
initialize 'wolverines', 'sabertooths', 'mongooses'
# [ [ 'wolverines','sabertooths', 'mongooses' ] ]
initialize = (teams...) ->
insert teams...
initialize 'wolverines', 'sabertooths', 'mongooses'
# [ 'wolverines','sabertooths', 'mongooses' ]
Listing 7.1 Competition
find = (name) ->
document.querySelector ".#{name}"
color = (element, color) ->
element.style.background = color
insert = (teams...) ->
root = document.querySelector '.teams'
for team in teams
element = document.createElement 'li'
element.innerHTML = team
element.className = team
root.appendChild element
highlight = (first, rest...) ->
color find(first), 'gold'
for name in rest
color find(name), 'blue'
initialize = (ranked) ->
insert ranked...
first = ranked.slice(0, 1)
rest = ranked.slice 1
highlight first, rest...
window.onload = ->
initialize [
'wolverines'
'wildcats'
'mongooses'
]
7.2 Destructuring
makeToggler = (active, inactive) ->
->
temporary = active
active = inactive
inactive = temporary
[active, inactive]
toggler = makeToggler 'komodos', 'raptors'
toggler()
# ['raptors', 'komodos']
toggler()
# ['komodos', 'raptors']
active = 'komodos'
inactive = 'raptors'
[active, inactive] = [inactive, active]
active
# raptors
inactive
# komodos
toggler = (a, b) ->
-> [a,b] = [b,a]
toggle = toggler 'on', 'off'
toggle()
# ['off', 'on']
toggle()
# ['on', 'off']
relegate = (team) -> "#{team.name} got relegated"
rank = (array..., using) ->
array.sort (first, second) ->
first[using] < second[using]
competitors = [
name: 'wildcats'
points: 5
,
name: 'bobcats'
points: 3
]
[first, field..., last] = rank competitors..., 'points'
relegate last
# 'bobcats got relegated'
data =
team2311:
name: 'Honey Badgers'
stats:
scored: 22
conceded: 22
points: 11
team4326:
name: 'Mongooses'
stats:
scored: 14
conceded: 19
points: 8
for id, team of data
name = team.name
points = team.stats.points
{
name: name
points: points
}
# [ { name: 'Honey Badgers', points: 11 },
# { name: 'Mongooses', points: 8 } ]
for id, team of data
{name: team.name, points: team.stats.points}
# [ { name: 'Honey Badgers', points: 11 },
# { name: 'Mongooses', points: 8 } ]
{pad, trim, dirify} = require 'util'
util = require 'util'
pad = util.pad
trim = util.trim
dirify = util.dirify
makeCompetition = ->
find = ->
# function body omitted
color = ->
# function body omitted
highlight = ->
# function body omitted
initialize = ->
# function body omitted
highlight: highlight,
initialize: initialize
competition = makeCompetition()
# { highlight: [Function], initialize: [Function] }
highlight = ->
initialize = ->
object =
highlight: highlight
initialize: initialize
makeCompetition = ->
find = ->
color = ->
highlight = ->
initialize = -> 'initialized'
{highlight, initialize}
competition = makeCompetition()
competition.initialize()
# 'initialized'
makeCompetition = (options) ->
options.max
options.sort
makeCompetition = ({max, sort}) ->
{max, sort}
makeCompetition max: 5, sort: ->
# {max: 5, sort: [Function]}
makeCompetition sort: (->), max: 5
# {max: 5, sort: [Function]}
competitors = [
{ name: 'wildcats', points: 3 }
{ name: 'tigers', points: 1 }
{ name: 'taipans', points: 5 }
]
[first, middle..., last] = competitors
first
# { name: 'wildcats', points: 3 }
last
# { name: 'taipans', points: 5 }
[first, ..., last] = competitors
first
# { name: 'wildcats', points: 3 }
last
# { name: 'taipans', points: 5 }
{"A":[{"name":"Andy", "phone":"5551111"},...],"B":[...],...}
Listing 7.2 Competition with module pattern
makeCompetition = ({max, sort}) ->
find = (name) ->
document.querySelector ".#{name}"
color = (element, color) ->
element.style.background = color
insert = (teams...) ->
root = document.querySelector '.teams'
for team in teams
element = document.createElement 'li'
element.innerHTML = "#{team.name} (#{team.points})"
element.className = team.name
root.appendChild element
highlight = (first, rest...) ->
color find(first.name), 'gold'
for team in rest
color find(team.name), 'blue'
rank = (unranked) ->
unranked.sort(sort).slice(0, max)
initialize = (unranked) ->
ranked = rank unranked
insert ranked...
first = ranked.slice(0, 1)[0]
rest = ranked.slice 1
highlight first, rest...
{ initialize }
sortOnPoints = (a, b) ->
a.points > b.pointsv
window.onload = ->
competition = makeCompetition(max: 5, sort: sortOnPoints)
competition.initialize [
{ name: 'wolverines', points: 22 }
{ name: 'wildcats', points: 11 }
{ name: 'mongooses', points: 33 }
{ name: 'raccoons', points: 12 }
{ name: 'badgers', points: 19 }
{ name: 'baboons', points: 16 }
]
7.3 No nulls
roundSquare?
# false
user =
name:
title: 'Mr'
first: 'Data'
last: 'Object'
contact:
phone:
home: '555 2234'
mobile: '555 7766'
email:
primary: 'mrdataobject@coffeescriptinaction.com'
user.contact.phone.home
# '555 2234'
user =
name:
first: 'Haveno'
middle: 'Contact'
last: 'Details'
user.contact.phone.home
# TypeError: cannot read property 'phone' of undefined
if user.contact and user.contact.phone and user.contact.phone.home
user.contact.phone.home
try
user.contact.phone.home
catch e
'no contact number'
user?.contact?.phone?.home
render = (user) ->
"""
<html>
Home phone for #{user.name.first}: #{user.contact.phone.home}
"""
user =
name:
first: 'Donot'
last: 'Callme'
render user
# TypeError: cannot read property 'phone' of undefined
render = (user) ->
"""
<html>
Home phone for #{user?.name?.first}: #{user?.contact?.phone?.home}
"""
user = null
render user: null
# <html>
# Home phone for undefined: undefined
user = null
contact = user?.contact?.phone?.home || 'Not provided'
contact
# Not provided
user = {}
user?.contact?.phone?.home = '555 5555'
user.contact?
# false
phone = undefined
phone ?= '555 5555'
phone
# '555 5555'
phone = null
phone ?= '555 1111'
phone
# '555 1111'
phone ?= 'something else'
phone
# '555 1111'
variableYouNeverDeclared ?= 'something'
# error: the variable "variableYouNeverDeclared" can't be assigned with ?= because it has not been declared before
Listing 7.3 Using null soak in a view
makeCompetition = ({max, sort}) ->
render = (team) ->
"""
<tr class='#{team?.name||''}'>
<td>#{team?.name||''}</td>
<td>#{team?.points||''}</td>
<td>#{team?.goals?.scored||''}</td>
<td>#{team?.goals?.conceded||''}</td>
</tr>
"""
find = (name) ->
document.querySelector ".#{name}"
color = (element, color) ->
element.style.background = color
insert = (teams...) ->
root = document.querySelector '.teams'
for team in teams
root.innerHTML += render team
highlight = (first, rest...) ->
color find(first.name), 'gold'
for team in rest
color find(team.name), 'blue'
rank = (unranked) ->
unranked.sort(sort).slice(0, max).reverse()
initialize: (unranked) ->
ranked = rank unranked
insert ranked...
first = ranked.slice(0, 1)[0]
rest = ranked.slice 1
highlight first, rest...
sortOnPoints = (a, b) ->
a.points > b.points
window.onload = ->
competition = makeCompetition max:5, sort: sortOnPoints
competition.initialize [
name: 'wolverines'
points: 56
goals:
scored: 26
conceded: 8
,
name: 'wildcats'
points: 53
goals:
scored: 32
conceded: 19
,
name: 'mongooses'
points: 34
goals:
scored: 9
conceded: 9
,
name: 'raccoons'
points: 0
]
typeof null
# null
7.4 No types?the duck test
class Duck
walk: ->
quack: (distance) ->
daffy = new Duck
daffy.walk()
donald = new Duck
donald.quack()
daffy.quack()
class DuckRace
constructor: (@ducks) ->
go: ->
duck.walk() for duck in @ducks
class Hare
run: ->
walk: -> run()
hare = new Hare
race = new DuckRace [hare]
daffy = new Duck
typeof daffy
# 'object'
daffy instanceof Duck
# true
class DuckRace
constructor (applicants) ->
@ducks = d for d in applicants when d instanceof Duck
go: ->
duck.walk() for duck in @ducks
duck = new Duck
ultraDuckMarathon = new DuckRace [duck]
turnIntoSnake = ->
duck.walk = null
duck.slither = ->
turnIntoSnake duck
ultraDuckMarathon.go()
# TypeError: Property walk of object #<AsianDuck> is not a function
class Duck
daffy = new Duck
Duck:: = class Snake
daffy instanceof Duck
# false
class Duck
daffy = new Duck
daffy.constructor.name
# Duck
duck =
walk: ->
quack: ->
daffy = Object.create duck
daffy.constructor.name
# 'Object'
class DuckRace
duck: (contestant) ->
contestant.quack?.call and contestant.walk?.call
constructor: (applicants...) ->
@ducks = (applicant for applicant in applicants when @duck applicant)
duck =
name: 'Daffy'
quack: ->
walk: ->
cow =
name: 'Daisy'
moo: ->
walk: ->
race = new DuckRace duck, cow
race.ducks
# [ { name: 'Daffy', quack: [Function], walk: [Function] } ]
Listing 7.4 Competition
makeCompetition = ({max, sort}) ->
POINTS_FOR_WIN = 3
POINTS_FOR_DRAW = 1
GOALS_FOR_FORFEIT = 3
render = (team) ->
find = (name) ->
color = (element, color) ->
insert = (teams...) ->
highlight = (first, rest...) ->
rank = (unranked) ->
competitive = (team) ->
team?.players is 5 and team?.compete()?
blankTally = (name) ->
name: name
points: 0
goals:
scored: 0
conceded: 0
roundRobin = (teams) ->
results = {}
for teamName, team of teams
results[teamName] ?= blankTally teamName
for opponentName, opponent of teams when opponent isnt team
console.log "#{teamName} #{opponentName}"
results[opponentName] ?= blankTally opponentName
if competitive(team) and competitive(opponent)
# omitted
else if competitive team
# omitted
else if competitive opponent
# omitted
results
run = (teams) ->
scored = (results for team, results of roundRobin(teams))
ranked = rank scored
console.log ranked
insert ranked...
first = ranked.slice(0, 1)[0]
rest = ranked.slice 1
highlight first, rest...
{ run }
sortOnPoints = (a, b) ->
a.points > b.points
class Team
constructor: (@name) ->
players: 5
compete: ->
Math.floor Math.random()*3
window.onload = ->
competition = makeCompetition(max:5, sort: sortOnPoints)
disqualified = new Team "Canaries"
disqualified.compete = null
bizarros = ->
bizarros.players = 5
bizarros.compete = -> 9
competition.run {
wolverines : new Team "Wolverines"
penguins: { players: 5, compete: -> Math.floor Math.random()*3 }
injured: injured
sparrows: new Team "Sparrows"
bizarros: bizarros
}
Listing 7.5 The server
http = require 'http'
fs = require 'fs'
coffee = require 'coffee-script'
render = (res, head, body) ->
res.writeHead 200, 'Content-Type': 'text/html'
res.end """
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Chapter 7</title>
<style type='text/css'>
* { font-family: helvetica, arial, sans-serif; }
body { font-size: 120%; }
.teams td { padding: 5px; }
</style>
#{head}
</head>
<body>
#{body}
</body>
</html>
"""
listing = (id) ->
markup =
1: """
<ul class='teams'>
</ul>"""
2: """
<ul class='teams'>
</ul>"""
3: """
<table class='teams'>
<thead>
<tr>
<th>Team</th><th>Points</th><th>Scored</th><th>Conceded</th>
<tr>
</thead>
</table>"""
4: """
<table class='teams'>
<thead>
<tr>
<th>Team</th><th>Points</th><th>Scored</th><th>Conceded</th>
<tr>
</thead>
</table>"""
script =
1: "<script src='1.js'></script>"
2: "<script src='2.js'></script>"
3: "<script src='3.js'></script>"
4: "<script src='4.js'></script>"
head: script[id], body: markup[id]
routes = {}
for n in [1..6]
do ->
listingNumber = n
routes["/#{listingNumber}"] = (res) ->
render res, listing(listingNumber).head, listing(listingNumber).body
routes["/#{listingNumber}.js"] = (res) ->
script res, listingNumber
server = http.createServer (req, res) ->
handler = routes[req.url] or (res) ->
render res, '', '''
<ul>
<li><a href="/1">Listing 7.1</a></li>
<li><a href="/2">Listing 7.2</a></li>
<li><a href="/3">Listing 7.3</a></li>
<li><a href="/4">Listing 7.4</a></li>
</ul>
'''
handler res
script = (res, listing) ->
res.writeHead 200, 'Content-Type': 'application/javascript'
fs.readFile "7.#{listing}.coffee", 'utf-8', (e, source) ->
if e then res.end "/* #{e} */"
else res.end coffee.compile source
server.listen 8080, '127.0.0.1'
7.5 When to use comprehensions (and when not to)
evens = (num for num in [1..10] when num%2 == 0)
numbers = [1,2,3,4,5,6,7,8,9,10]
evens = []
for (var i = 0; i !== numbers.length; i++) {
if(numbers[i]%2 === 0) {
evens.push(numbers[i]);
}
evens = numbers.filter(function(item) {
return item%2 == 0;
});
evens = numbers.filter (item) -> item%2 == 0
paid = [10, 50, 200]
taxes = (price*0.1 for price in paid)
# [1, 5, 20]
taxes = paid.map (item) -> item*0.1
# [1, 5, 20]
friends = [
{ name: 'bob', location: 'CoffeeVille' },
{ name: 'tom', location: 'JavaLand' },
{ name: 'sam', location: 'PythonTown' },
{ name: 'jenny', location: 'RubyCity' }
]
friends.filter (friend) -> friend.location is 'CoffeeVille'
friend for friend in friends when friend.location is 'CoffeeVille'
mine = ['Greg Machine', 'Bronwyn Peters', 'Sylvia Rogers']
common = (friend for friend in mine when friend in yours)
friends = [
{ name: 'bob', owes: 10 }
{ name: 'sam', owes: 15 }
]
total = friends[0].owes + friends[1].owes
# 25
owed = 0
for friend in friends
owed += friend.owes
owing = (initial, friend) ->
if initial.owers then initial.owes + friend.owes
owed = friends.reduce owing
people = [ 'bill', 'ted' ]
greetings = {}
for person in people
greetings[person] = ->
"My name is #{person}"
greetings.bill()
people = [ 'bill', 'ted' ]
greetings = {}
for person in people
do ->
name = person
greetings[name] = ->
"My name is #{name}"
greetings.bill()
# My name is bill
people.forEach (name) -> greetings[name] = "My name is #{name}"
7.6 Fluent interfaces
scruffy.eat().sleep().wake()
class Turtle
forward: (distance) ->
# moves the turtle distance in the direction it is facing
this
rotate: (degrees) ->
# rotates the turtle 90 degrees clockwise
this
move: ({direction, distance}) ->
# moves the turtle in a given direction
this
stop: ->
# stops the turtle
turtle = new Turtle
turtle.forward 10
turtle.rotate()
turtle.forward 10
turtle.rotate()
turtle.forward 10
turtle.rotate()
turtle.forward 10
Turtle::square = (size) ->
@forward size
@rotate()
@forward size
@rotate()
@forward size
@rotate()
@forward size
Turtle::square = (size) ->
for side in [1..4]
@forward size
@rotate 90
turtle = new Turtle
turtle.square 4
turtle.forward 8
turtle.square 4
turtle = new Turtle();
with(turtle) {
left();
forward();
}
address = '123 Turtle Beach Road';
with (turtle) {
rotate(90);
address = '55 Dolphin Place';
}
turtle = new Turtle
turtle
.forward 2
.rotate 90
.forward 4
turtle
.forward 2
.rotate 90
.forward 4
turtle.forward(2..rotate(90..forward(4)));
turtle = new Turtle
turtle
.forward(2)
.rotate(90)
.forward(4)
turtle = new Turtle
turtle
.forward(2)
.rotate(90)
.forward(4)
NORTH = 0
turtle = new Turtle
turtle
.move
direction: NORTH
distance: 10
.stop()
makeCompetition
max: 5
sort: ->
makeCompetition
sort: ->
max: 5
turtle
.forward(3)
.rotate(90)
.forward(1)
turtle
.forward 3
.rotate 90
.forward 1
turtle.forward(2).rotate(90).forward(4);
turtle
.forward 3
.rotate 90
.forward 1
wait = (duration, callback) ->
setTimeout callback, duration
wait 5, ->
turtle
.forward 10
.forward 3
wait(5, function() {
return turtle.forward(1).rotate(90);
});
class Turtle
rotate: (degrees) ->
# rotate the turtle
@
class Turtle
rotate: (degrees) ->
# rotate the turtle
this
turtle = new Turtle
turtle.rotate(90).rotate(90)
canvas = document.getElementById 'example'
context = canvas.getContext '2d'
context.fillRect 25, 25, 100, 100
context.strokeRect 50, 50, 50, 50
using = (object, fn) -> fn.apply object
using turtle, ->
@forward 2
@rotate 90
@forward 4
chain(turtle)
.forward(2)
.rotate(90)
.forward(4)
chain turtle
.forward 2
.rotate 90
.forward 4
Listing 7.6 The chain
function
chain = (receiver) ->
wrapper = Object.create receiver
for key, value of wrapper
if value?.call
do ->
proxied = value
wrapper[key] = (args...) ->
proxied.call receiver, args...
wrapper
wrapper
turtle =
forward: (distance) ->
console.log "moving forward by #{distance}"
rotate: (degrees) ->
console.log "rotating #{degrees} degrees"
chain(turtle)
.forward(5)
.rotate(90)
7.7 Ambiguity
NORTH = 0
turtle
.move
direction: NORTH
distance: 10
.stop()
makeTurtle = ->
move: ->
# move the turtle
this
stop: ->
friends = ->
name: 'Bob'
address: '12 Bob Street Bobville'
,
name: 'Ralph'
address: '11 Ralph Parade Ralphtown'
friends = ->
[{
name: 'Bob'
address: '12 Bob Street Bobville'
},
{
name: 'Ralph'
address: '11 Ralph Parade Ralphtown'
}]
clarity = (important) ->
clarity()
(clarity 10)
clarity 10
clarity (10)
clarity (x) -> x
x y z 4
x(y(z(2)))
x = 5
do (x) ->
x = 3
console.log x
console.log x
# 3
# 5
shadowing = do (x) -> (y) ->
x = 3
x + y
shadowing 5
# 8
do (notPreviouslyDefined) -> notPreviouslyDefined = 9
# ReferenceError: notPreviouslyDefined is not defined
do (notPreviouslyDefined='') -> notPreviouslyDefined = 'It is now'
# 'It is now'
notPreviouslyDefined
# ReferenceError: notPreviouslyDefined is not defined
7.8 Summary