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