Chapter 3
First-class functions
->
(a, b) -> a + b
add = (a, b) -> a + b
add 2, 3
# 5
3.1 Computation
3 * 4
# 12
threeTimesFour = -> 3 * 4
# [Function]
threeTimesFour()
# 12
journey = ->
'A call to adventure'
'Crossing the chasm'
'Transformation'
'Atonement'
'Back home'
journey()
# 'Back home'
multiply = (a, b) -> a * b
multiply(2, 7)
# 14
gigabytesToMegabytes = (gigabytes) -> gigabytes * 1024
gigabytesToMegabytes 7
# 7168
'Batman,Joker,Wolverine,Sabertooth,The Green Lantern,Sinestro'
text = 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'
daysInWeek = text.split(/,/)
daysInWeek.length
# 7
text = 'Spring,Summer,Autumn,Winter'
seasons = text.split(/,/)
seasons.length
# 4
Listing 3.1 Counting words
text = process.argv[2]
if text
words = text.split /,/
console.log "#{words.length} partygoers"
else
console.log 'usage: coffee 3.1.coffee [text]'
coffee listing.3.1.coffee Batman,Joker,Wolverine,Sabertooth
countWords = (text) -> text.split(/,/).length
# [Function]
countWords 'sight,smell,taste,touch,hearing'
# 5
countWords 'morning,noon,night'
# 3
countWords 'north,east,south,west'
# 4
(-> 42)()
# 42
beerStyles = 'Porter:Pilsner:Stout:Lager:Bock'
countWords = (text, delimiter) ->
words = text.split delimiter
words.length
countWords('Josie:Melody:Valerie:Alexandra', ':')
# 4
countWords('halloween/scream/maniac', '/')
# 3
countWords('re#brown#tag#table', '#')
# 4
countWords(beerStyles, ':')
# 5
planets = 'Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune'
countWords(planets)
# 8
countWords planets
# 8
sayHello = -> 'Hello!'
sayHello()
# 'Hello!
sayHello
# [Function]
returnsUndefined = function(a,b) {
a + b;
};
returnsUndefined();
// undefined
returnsUndefined = ->
return undefined
returnsUndefined()
# undefined
countWords = (text, delimiter) ->
words = text.split delimiter
words.length
countWords '', ':'
# 1
'abc'.split /,/
# ['abc']
''.split /,/
# ['']
countWords = (text, delimiter) ->
if text
words = text.split(delimiter || /,/)
words.length
else
0
countWords ''
# 0
count = (text, delimiter) ->
return 0 unless text
words = text.split(delimiter || /,/)
words.length
eat berries if not poisonous
eat berries unless poisonous
Listing 3.2 Count words comparison
CoffeeScript
countWords = (s, del) ->
if s
words = s.split del
words.length
else
0
|
JavaScript
var countWords = function (s, del) {
var words;
if (s) {
words = s.split(del);
return words.length;
} else {
return 0;
}
}
|
function obi(wan) {
return wan;
};
obi('kenobi');
misquote = """we like only like think like when like we like are like confronted like with like a like problem"""
everyOtherWord misquote
'we only think when we are confronted with a problem'
3.2 Events
<!doctype html>
<html>
<title>How many people are coming?</title>
<script src='attendees.js'></script>
<body>
<div id='how-many-attendees'>How many attendees?</div>
</body>
</html>
document.querySelector('#how-many-attendees').innerHTML = 55
partyMessage = -> console.log "It's party time!"
setTimeout partyMessage, 1000
# ... 1 second later
# It's party time!
interval = setInterval partyMessage, 1000
# ... 1 second later
# It's party time!
# ... 1 second later
# It's party time!
# and so on...
clearInterval interval
partyMessage()
# It's party time!
setTimeout partyMessage, 5000
1 + 1
# 2
# ... 5 seconds later
# It's party time!
timeout = setTimeout partyMessage, 1000
clearTimeout timeout
updateAttendeesCount = ->
document.querySelector('#how-many-attendees').innerHTML 55
setInterval updateAttendeesCount, 1000
3.3 I/O
url = "http://www.coffeescriptinaction.com/3/data.js"
get url, (response) ->
console.log response
<!doctype html>
<title>How many people are coming?</title>
<script src='attendees.js'></script>
<body>
<div id='how-many-attendees'>How many attendees?</div>
</body>
</html>
showAttendees = ->
url = "http://www.coffeescriptinaction.com/3/data.js"
get url, (response) ->
outputElement = document.querySelector("#how-many-attendees")
outputElement.innerHTML = "#{response} attendees!"
setInterval(showAttendees, 1000)
fs = require 'fs'
readAttendeesList = (error, fileContents) ->
console.log fileContents
fs.readFile 'partygoers.txt', readAttendeesList
Listing 3.3 Cat utility
fs = require 'fs'
file = process.argv[2]
fs.readFile file, 'utf-8', (error, contents) ->
if error
console.log error
else
console.log contents
Listing 3.4 Serve the current contents of a file
fs = require 'fs'
http = require 'http'
sourceFile = 'myfile'
fileContents = 'File not read yet.'
readSourceFile = ->
fs.readFile sourceFile, 'utf-8', (error, data) ->
if error
console.log error
else
fileContents = data
fs.watchFile sourceFile, readSourceFile
server = http.createServer (request, response) ->
response.end fileContents
server.listen 8080, '127.0.0.1'
'Talos,Gnut,Torg,Ash,Beta,Max,Bender'
3.4 Higher-order functions
monday = ->
wake()
work()
sleep()
thursday = ->
wake()
work()
catchJavelins()
sleep()
activities = (day) ->
switch day
when 'thursday' then catchJavelins()
day = (freeTime) ->
wake()
work()
freeTime()
sleep()
thursday = day catchJavelins
contains = (text, word) -> (text.search word) isnt -1
randomWords = 'door,comeuppance,jacuzzi,tent,hippocampus,gallivant'
contains randomWords, 'tent'
# true
contains randomWords, 'camp'
#true
wordCount = (text, delimiter) -> text.split(delimiter).length
split = (text, delimiter) -> text.split delimiter
wordCount = (text, delimiter) ->
words = split(text, delimiter)
words.length
contains = (text, delimiter, word) ->
words = split(text, delimiter)
word in words
contains randomWords, ',', 'camp'
# false
Listing 3.5 Counting words
fs = require 'fs'
split = (text) ->
text.split /\W/g
count = (text) ->
parts = split text
words = (word for word in parts when word.trim().length > 0)
words.length
countMany = (texts) ->
sum = 0
for text in texts
sum = sum + count text
sum
countWordsInFile = (fileName) ->
stream = fs.createReadStream fileName
stream.setEncoding 'ascii'
wordCount = 0
stream.on 'data', (data) ->
lines = data.split /\n/gm
wordCount = wordCount + countMany lines
stream.on 'close', () ->
console.log "#{wordCount} words"
file = process.argv[2]
if file
countWordsInFile file
else
console.log 'usage: coffee wordcount.coffee [file]'
invokeLater = -> console.log 'Please invoke in one second'
setTimeout invokeLater, 1000
'1,2,0,2,8,0,1,3'
numbers = [1,2,0,2,8,0,1,3]
sum numbers
# 17
sum = (numbers) ->
total = 0
for number in numbers
total = total + number
total
sum [1..5]
# 15
multiply = (initial, numbers) ->
total = initial or 1
for number in numbers
total = total * number
total
flatten = (arrays) ->
total = []
for array in arrays
total = total.concat array
total
accumulate = (initial, numbers, accumulator) ->
total = initial or 0
for number in numbers
total = accumulator total, number
total
sum = (acc, current) -> acc + current
accumulate(0, [5,5,5], sum)
# 15
flatten = (acc,current) -> acc.concat current
accumulate([], [[1,3],[2,8]], flatten)
# [1,3,2,8]
accumulate = (initial, numbers, accumulator) ->
total = initial or 0
# <rest of function omitted>
accumulate = (initial=0, numbers, accumulator) ->
total = initial
# <rest of function omitted>
logArgument = (logMe='default') -> console.log logMe
logArgument()
# 'default'
logArgument('not the default')
# 'not the default'
sumFractions ['2/6', '1/4']
greaterThan3 = (n) -> n > 3
keep [1,2,3,4], greaterThan3
3.5 Scope
accumulate = yes
scoped = ->
secret = 'A secret'
secret
# ReferenceError: secret is not defined
subProgramOne = ->
accumulate = (initial, numbers, accumulator) ->
total = initial or 0
for number in numbers
total = accumulator total, number
total
subProgramTwo = ->
accumulate = yes
(->
name = 'Ren'
)()
(->
name = 'Stimpy'
)()
do ->
name = 'Ren'
do ->
name = 'Stimpy'
var scope = function () {
var x = 1;
y = 2;
};
scope = ->
x = 1
y = 2
outer = 1
do ->
outer = 2
inner = 1
outer
# 2
inner
# ReferenceError
do ->
# <your program here>
(function() {
// <your compiled program goes here>
}).call(this);
flatten = (acc,current) -> acc.concat current
accumulate([], [[1,3],[2,8]], flatten)
flattenArray = (array) ->
flatten = (acc,current) -> acc.concat current
accumulate([], array, flatten)
flattenArray [[1,3],[2,8]]
# [1,3,2,8]
layerOne = ->
first = yes
second?
third?
layerTwo = ->
second = yes
first?
third?
layerThree = ->
third = yes
second?
first?
jones = (x) ->
smith = (y) ->
x
y
jones = (x) ->
smith = (x,y) ->
x
y
close = ->
closedOver = 1
-> closedOver
closure = close()
closure()
3.6 Closures
makeIncrementer = ->
n = 0
->
n = n + 1
n
incrementer = makeIncrementer()
incrementer()
# 1
incrementer()
# 2
up = makeIncrementer()
oneMore = makeIncrementer()
up()
# 1
up()
# 2
up()
# 3
oneMore()
# 1
makeMostRecent = (file1, file2) ->
mostRecent = 'Nothing read yet.'
sourceFileWatcher = (fileName) ->
sourceFileReader = ->
fs.readFile fileName, 'utf-8', (error, data) ->
mostRecent = data
fs.watch fileName, sourceFileReader
sourceFileWatcher file1
sourceFileWatcher file2
getMostRecent = ->
mostRecent
mostRecentTweedle = makeMostRecent 'tweedle.dee', 'tweedle.dum'
Contrariwise
mostRecentTweedle()
# Contrariwise
closedOverArgument = (x) ->
-> x
five = closedOverArgument 5
nine = closedOverArgument 9
five()
# 5
nine()
# 9
Listing 3.6 Serve multiple files
fs = require 'fs'
http = require 'http'
makeMostRecent = (file1, file2) ->
mostRecent = 'Nothing read yet.'
sourceFileWatcher = (fileName) ->
sourceFileReader = ->
fs.readFile fileName, 'utf-8', (error, data) ->
mostRecent = data
fs.watch fileName, sourceFileReader
sourceFileWatcher file1
sourceFileWatcher file2
getMostRecent = ->
mostRecent
makeServer = ->
mostRecent = makeMostRecent 'file1.txt', 'file2.txt'
server = http.createServer (request, response) ->
response.write mostRecent()
response.end()
server.listen '8080', '127.0.0.1'
server = makeServer()
3.7 Putting it together
Listing 3.6 The party website
fs = require 'fs'
http = require 'http'
coffee = require 'coffee-script'
attendees = 0
friends = 0
split = (text) ->
text.split /,/g
accumulate = (initial, numbers, accumulator) ->
total = initial or 0
for number in numbers
total = accumulator total, number
total
sum = (accum, current) -> accum + current
attendeesCounter = (data) ->
attendees = data.split(/,/).length
friendsCounter = (data) ->
numbers = (parseInt(string, 0) for string in split data)
friends = accumulate(0, numbers, sum)
readFile = (file, strategy) ->
fs.readFile file, 'utf-8', (error, response) ->
throw error if error
strategy response
countUsingFile = (file, strategy) ->
readFile file, strategy
fs.watch file, (-> readFile file, strategy)
init = ->
countUsingFile 'partygoers.txt', attendeesCounter
countUsingFile 'friends.txt', friendsCounter
server = http.createServer (request, response) ->
switch request.url
when '/'
response.writeHead 200, 'Content-Type': 'text/html'
response.end view
when '/count'
response.writeHead 200, 'Content-Type': 'text/plain'
response.end "#{attendees + friends}"
server.listen 8080, '127.0.0.1'
console.log 'Now running at http://127.0.0.1:8080'
clientScript = coffee.compile '''
get = (path, callback) ->
req = new XMLHttpRequest()
req.onload = (e) -> callback req.responseText
req.open 'get', path
req.send()
showAttendees = ->
out = document.querySelector '#how-many-attendees'
get '/count', (response) ->
out.innerHTML = "#{response} attendees!"
showAttendees()
setInterval showAttendees, 1000
'''
view = """
<!doctype html>
<title>How many people are coming?</title>
<body>
<div id='how-many-attendees'></div>
<script>
#{clientScript}
</script>
</body>
</html>
"""
init()
3.8 Summary