Chapter 6

Composing functions

6.1 Clarity

profit = (50+(20/10)*(200-price))*price-(140+(100*(50+((20/10)*(200-price)))))





profit = (salePrice) ->
  (revenue salePrice) ? (cost salePrice)





revenue = (salePrice) -> 
(numberSold salePrice) * salePrice
cost = (salePrice) -> overhead + (numberSold salePrice) * costPrice





overhead = 140
costPrice = 100
numberSold = (salePrice) -> 50 + 20/10 * (200 - salePrice)





overhead = 140
costPrice = 100
numberSold = (salePrice) ->
50 + 20/10 * (200 - salePrice)
revenue = (salePrice) ->
(numberSold salePrice) * salePrice
cost = (salePrice) ->
overhead + (numberSold salePrice) * costPrice
profit = (salePrice) -> (revenue salePrice) ? (cost salePrice)





Listing 6.1 Profit from selling PhotomakerExtreme

profit = (salePrice) ->
  overhead = 140                          
  costPrice = 100                         
  numberSold = (salePrice) ->             
    50 + 20/10 * (200 - salePrice)        
  revenue = (salePrice) -> 
    (numberSold salePrice) * salePrice
  cost = (salePrice) -> 
overhead + (numberSold salePrice) * costPrice
(revenue salePrice) - (cost salePrice)





revenue salePrice         
revenue(salePrice)





revenue salePrice ? cost salePrice





revenue(salePrice(-(cost(salePrice))));





revenue(salePrice) - cost(salePrice) 





(revenue salePrice) - (cost salePrice) 





profit = (overhead, costPrice, numberSold, salePrice) ->
  revenue = (salePrice) -> 
    (numberSold salePrice) * salePrice
  cost = (salePrice) -> 
overhead + (numberSold salePrice) * costPrice
(revenue salePrice) ? (cost salePrice)





tenSold = -> 10
profit 10, 40, tenSold, 100
# 590





photoProOverhead = 140
photoProCostPrice = 100
photoProNumberSold = (salePrice) -> 50 + 20/10 * (200 - salePrice)
profit photoProOverhead, photoProCostPrice, photoProNumberSold, 162
# 7672
pixelKingOverhead = 140 pikelKingCostPrice = 100
pixelKingNumberSold = (salePrice) -> 50 + 20/10 * (200 - salePrice)
profit pixelKingOverhead, pixelKingCostPrice, pixelKingNumberSold, 200 # 14860





photoProProfit 162         
# 7672
pixelKingProfit 200 # 14860





profit = (overhead, costPrice, numberSold) ->      
  revenue = (salePrice) ->
     (numberSold salePrice) * salePrice
  cost = (salePrice) ->
    overhead + (numberSold salePrice) * costPrice 
  (salePrice) ->                                    
    (revenue salePrice) - (cost salePrice)    





x1Overhead = 140
x1CostPrice = 100
x1NumberSold = (salePrice) -> 50 + 20/10 * (200 - salePrice)
x1Profit = profit x1Overhead, x1CostPrice, x1NumberSold x1Profit 162 # 7672





http://www.agtronsemporium.com/api/product/photomakerExtreme





{
  "PhotomakerExtreme": {
    "manufacturer": "Photo Co",
    "stock": 3,
    "cost": 100,
    "base": {                    
      "price": 200,
      "sold": 50
    },
    "reduction": {               
      "discount": 20,
      "additionalSold": 10
    }
  }
}





Listing 6.2 How not to write a program

# Important: Do not write like this
http = require 'http'
url = require 'url'
{users, products} = require './db'
server = http.createServer (req, res) -> path = url.parse(req.url).path parts = path.split /\// switch parts[1] when 'profit' res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' if parts[2] and /^[0-9]+$/gi.test parts[2] price = parts[2] profit = (50+(20/10)*(200-price))* price-(140+(100*(50+((20/10)*(200-price))))) res.end (JSON.stringify { profit: profit parts[2] }) else res.end JSON.stringify { profit: 0 } when 'user' res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' if req.method is "GET" if parts[2] and /^[a-z]+$/gi.test parts[2] users.get parts[2], (error, user) -> res.end JSON.stringify user, 'utf8' else users.all (error, users) -> res.end JSON.stringify users, 'utf8' else if parts[2] and req.method is "POST" user = parts[2] requestBody = '' req.on 'data', (chunk) -> requestBody += chunk.toString() req.on 'end', -> pairs = requestBody.split /&/g decodedRequestBody = for pair in pairs o = {} splitPair = pair.split /\=/g o[splitPair[0]] = splitPair[1] o users.set user, decodedRequestBody, -> res.end 'success', 'utf8' else res.writeHead 404, 'Content-Type': 'text/plain;charset=utf-8' res.end '404' when 'product' res.writeHead 200, 'Content-Type': 'text/plain;charset=utf-8' if req.method is "GET" products.get parts[2], (product) -> res.end JSON.stringify product, 'utf8' else if parts[2] and req.method is "POST" product = parts[2] requestBody = '' req.on 'data', (chunk) -> requestBody += chunk.toString() req.on 'end', -> pairs = requestBody.split /&/g decodedRequestBody = for pair in pairs o = {} splitPair = pair.split /\=/g o[splitPair[0]] = splitPair[1] o product.set user, decodedRequestBody, -> res.end 'success', 'utf8' requestBody = '' req.on 'data', (chunk) -> requestBody += chunk.toString() req.on 'end', -> decodedRequestBody = requestBody res.end decodedRequestBody, 'utf8' else res.writeHead 404, 'Content-Type': 'text/plain;charset=utf-8' res.end '404' else res.writeHead 200, 'Content-Type': 'text/html;charset=utf-8'
res.end 'The API'
server.listen 8080, '127.0.0.1'
# Important: Do not write like this





profit = (50+(20/10)*(200-price))*price-(140+(100*(50+((20/10)*(200-price)))))





profit = (overhead, costPrice, numberSold, salePrice) ->
  revenue = (salePrice) -> 
    (numberSold salePrice) * salePrice
  cost = (salePrice) -> 
overhead + (numberSold salePrice) * costPrice
(revenue salePrice) ? (cost salePrice)





6.2 State and mutability

state = on
state = off





numberSold = (salePrice) -> 
  50 + 20/10 * (200 - salePrice)





numberSold = 0
calculateNumberSold = (salePrice) ->
  numberSold = 50 + 20/10 * (200 - salePrice) 





calculateNumberSold 220
# 10





calculateRevenue = (salePrice) ->
  numberSold * salePrice





for price in [140..145]
  calculateRevenue price
# [1400,1410,1420,1430,1440,1450]





for price in [140..145]
  calculateNumberSold price
  calculateRevenue price
# [23800,23688,23572,23425,23328,23200]





Listing 6.3 Local state and shared state

numberSold = 0
calculateNumberSold = (salePrice) ->
numberSold = 50 + 20/10 * (200 - salePrice)
calculateRevenue = (salePrice, callback) ->
callback numberSold * salePrice
revenueBetween = (start, finish) -> totals = [] for price in [start..finish] calculateNumberSold price addToTotals = (result) -> totals.push result calculateRevenue price, addToTotals
totals
revenueBetween 140, 145 # [ 23800, 23688, 23572, 23452, 23328, 23200 ]





oneSecond = 1000
calculateRevenue = (callback) ->
  setTimeout ->
    callback numberSold * salePrice
  , oneSecond





revenueBetween 140, 145
# []





numberSold = 0                      
calculateNumberSold = (salePrice) ->
numberSold = 50 + 20/10 * (200 - salePrice)
calculateRevenue = (salePrice, callback) ->
callback numberSold * salePrice
revenueBetween = (start, finish, callback) -> totals = [] receivedResponses = 0 expectedResponses = 0 for price in [start..finish] calculateNumberSold price expectedResponses++ addToTotals = (result) -> totals.push result receivedResponses++ if receivedResponses == expectedResponses callback totals calculateRevenue price, addToTotals





numberSold = 0
calculateNumberSold = (salePrice) ->
numberSold = 50 + 20/10 * (200 - salePrice)
calculateRevenue = (salePrice, callback) ->
callback numberSold * salePrice
log = (message) -> console.log message
numberSold = 'uh oh'
revenueBetween = (start, finish, callback) -> totals = [] receivedResponses = 0 expectedResponses = 0 for price in [start..finish] calculateNumberSold price expectedResponses++ addToTotals = (result) -> totals.push result receivedResponses++ if receivedResponses == expectedResponses callback totals else log 'waiting' calculateRevenue price, addToTotals





revenueBetween 140, 145
# [ 22400, NaN, NaN, NaN, NaN, NaN ]





class Camera                                 
  overhead: -> 140
  costPrice: -> 100
  profit: (salePrice) ->
    (@revenue salePrice) - (@cost salePrice)
  numberSold: (salePrice) ->
    50 + 20/10 * (200 - salePrice)
  revenue: (salePrice) ->
    (@numberSold salePrice) * salePrice
   cost: (salePrice) ->
@overhead() + (@numberSold salePrice) * @costPrice()
phototaka500 = new Camera phototaka500.profit 162 # 7672





class Camera                         
  constructor: (@price) ->
  calculateRevenue: ->
    @revenue = (50 + (20 / 10) * (200 - @price)) * @price
  calculateCost: ->
    @cost = 140 + (100 * (50 + ((20 / 10) * (200 - @price))))
  calculateProfit: ->
    @calculateRevenue()
    @calculateCost()
@profit = @revenue - @cost
phototaka500 = new Camera 162 phototaka500.calculateProfit() console.log phototaka500.profit # 7672





revenue = 0    
cost = 0       
sold = 0
calculateRevenue = (salePrice) ->
revenue = sold * salePrice
calculateCost = (salePrice) ->
cost = 140 + sold * 100
calculateNumberSold = (salePrice) ->
sold = 50 + 20/10 * (200 - salePrice)
calculateProfit = (salePrice) -> calculateNumberSold salePrice calculateRevenue salePrice calculateCost salePrice revenue ? cost





db.stock 'ijuf', (error, response) ->
  # handling code here





Listing 6.4 State in program or external?

http = require 'http'
db = (require './db').stock
stock = 30 serverOne = http.createServer (req, res) -> response = switch req.url when '/purchase' res.writeHead 200, 'Content-Type': 'text/plain;charset=utf8' if stock > 0 stock = stock - 1 "Purchased! There are #{stock} left." else 'Sorry! no stock left!' else res.writeHead 404, 'Content-Type': 'text/plain;charset=utf8' 'Go to /purchase'
res.end response
serverTwo = http.createServer (req, res) -> purchase = (callback) -> db.decr 'stock', (error, response) -> if error callback 0 else
callback response
render = (stock) -> res.writeHead 200, 'Content-Type': 'text/plain;charset=utf8' response = if stock > 0 "Purchased! There are #{stock} left." else 'Sorry! no stock left'
res.end response
switch req.url when '/purchase' purchase render else res.writeHead 404, 'Content-Type': 'text/plain;charset=utf8'
res.end 'Go to /purchase'
serverOne.listen 9091, '127.0.0.1' serverTwo.listen 9092, '127.0.0.1'





6.3 Abstraction

users.get parts[2], (error, user) ->
res.end JSON.stringify user, 'utf8'
products.get parts[2], (product) -> res.end JSON.stringify product, 'utf8'





loadUserData = (user, callback) ->
  users.get user, (data) ->
callback data
loadProductData = (product, callback) -> products.get product, (data) -> callback data





makeLoadData = (db) ->
  (entry, callback) ->
    db.get entry, (data) ->
callback data
makeSaveData = (type) -> (entry, value, callback) -> db.set entry, value, callback?()





loadUserData = makeLoadData 'user'
saveUserData = makeSaveData 'user'





loadAnythingData = makeLoadData 'anything'





makeDbOperator = (db) ->
  (operation) ->
    (entry, value=null, callback) ->
      db[operation] entry, value, (error, data) ->
        callback? error, data





makeDbOperator = (db) ->
  (operation) ->
    (entry, params...) ->
      db[operation] entry, params...





loadProductData = (makeDbOperator 'product') 'get'
saveProductData = (makeDbOperator 'product') 'set'
saveProductData 'photonify1100', 'data for the photonify1100'
loadProductData 'photonify1100'
# 'data for the photonify1100'





Listing 6.5 The improved program

http = require 'http'
url = require 'url'
{products, users} = require './db'
withCompleteBody = (req, callback) -> body = '' req.on 'data', (chunk) -> body += chunk.toString()
request.on 'end', -> callback body
paramsAsObject = (params) -> pairs = params.split /&/g result = {} for pair in pairs splitPair = pair.split /\=/g result[splitPair[0]] = splitPair[1]
result
header = (response, status, contentType='text/plain;charset=utf-8') ->
response.writeHead status, 'Content-Type': contentType
httpRequestMatch = (request, method) -> request.method is method isGet = (request) -> httpRequestMatch request, "GET"
isPost = (request) -> httpRequestMatch request, "POST"
render = (response, content) -> header response, 200
response.end content, 'utf8'
renderAsJson = (response, object) -> render response, JSON.stringify object
notFound = (response) -> header response, 404
response.end 'not found', 'utf8'
handleProfitRequest = (request, response, price, costPrice, overhead) -> valid = (price) -> price and /^[0-9]+$/gi.test price if valid price renderAsJson response, profit: profit price, costPrice, overhead else
renderAsJson response, profit: 0
makeDbOperator = (db) -> (operation) -> (entry, params...) ->
db[operation] entry, params...
makeRequestHandler = (load, save) -> rendersIfFound = (response) -> (error, data) -> if error notFound response else
renderAsJson response, data
(request, response, name) -> if isGet request load name, rendersIfFound response else if isPost request withCompleteBody request, -> save name, rendersIfFound response else
notFound response
numberSold = (salePrice) ->
50 + 20/10 * (200 - salePrice)
profit = (salePrice, costPrice, overhead) -> revenue = (salePrice) -> (numberSold salePrice) * salePrice cost = (salePrice) -> overhead + (numberSold salePrice) * costPrice
(revenue salePrice) - (cost salePrice)
loadProductData = (makeDbOperator products) 'get' saveProductData = (makeDbOperator products) 'set' loadUserData = (makeDbOperator users) 'get'
saveUserData = (makeDbOperator users) 'set'
handleUserRequest = makeRequestHandler loadUserData, saveUserData
handleProductRequest = makeRequestHandler loadProductData, saveProductData
onProductDataLoaded = (error, data) -> price = (parseInt (query.split '=')[1], 10)
handleProfitRequest request,response,price,data.costPrice,data.overhead
apiServer = (request, response) -> path = url.parse(request.url).path query = url.parse(request.url).query parts = path.split /\// switch parts[1] when 'user' handleUserRequest request, response, parts[2] when 'product'if parts.length == 4 and /^profit/.test parts[3] loadProductData parts[2], onProductDataLoaded else handleProductRequest request, response, parts[2] else
notFound response
server = http.createServer(apiServer).listen 8080, '127.0.0.1'
exports.server = server





request GET 'All work and no play'
response 'makes Jack a dull boy' (5 ms) 
request GET 'All work and no play'
response 'makes Jack a dull boy' (4 ms)
request GET 'All work and no play'
response 'makes Jack a dull boy' (2 ms) 
request GET 'All work and no play'
response 'makes Jack a dull boy' (5 ms)
request GET 'All work and no play'
response 'makes Jack a dull boy' (5 ms) 





makeDbOperator = (db) ->
  (operation) ->
    (entry, params...) ->
      db[operation] entry, params...





productDataCache = Object.create null               
loadProductData = (name, callback) ->
  cachedCall = (makeDbOperator products) 'get'
  if productDataCache.hasOwnProperty name
    console.log 'cache hit'
    console.log productDataCache[name]...
    callback productDataCache[name]...
  else
    cachedCall name, (results...) ->
      productDataCache[name] = results





withCachedCallback = (fn) ->
  cache = Object.create null
  (params...) ->
    key = params[0]
    callback = params[params.length - 1]
    if key of cache
      callback cache[key]...
    else
      paramsCopy = params[..]
      paramsCopy[params.length-1] = (params...) ->
        cache[key] = params
        callback params...
      fn paramsCopy...





loadProductData = withCachedCallback ((makeDbOperator products) 'get')





withExpiringCachedCallback = (fn, ttl) ->
  cache = Object.create null
  (params...) ->
    key = params[0]
    callback = params[params.length - 1]
    if cache[key]?.expires > Date.now()
      callback cache[key].entry...
    else
      paramsCopy = params[..]
      paramsCopy[params.length - 1] = (params...) ->
        console.log params
        cache[key] =
          entry: params
          expires: Date.now() + ttl
        console.log cache[key]
        callback params...
      fn paramsCopy...





factorial = (n) ->
  if n is 0 then 1
  else
n * (factorial n - 1)
factorial 0
# 1
factorial 4 # 24 factorial 5 # 120





logUserDataFor = (user) ->
  users.get user, (error, data) ->
    if error then console.log 'An error occurred'
else console.log 'Got the data'
logUserDataFor 'fred' # 'Got the data'





logUserDataFor 'fred'
# 'An error occurred'





logUserDataFor = (user) ->
  users.get user, (error, data) ->
    if error then users.get user, (error, data) ->
      if error then console.log 'An error occurred both times'
      else 'Got the data (on the second attempt)'
    else console.log 'Got the data'





logUserDataFor = (user) ->
  dbRequest = ->                           
    users.get user, (error, data) ->
      if error then dbRequest()            
      else console.log 'Got the data'
  dbRequest()





logUserDataFor 'fred'
# 'An error occurred'
# 'An error occurred'
# 'An error occurred'
# 'Got the data'





logUserDataFor = (user) ->
  dbRequest = (attempt) ->
    users.get user, (error, data) ->
      if error and attempt < 5
        setTimeout ->
         (dbRequest attempt + 1), 1000 
      else console.log 'Got the data'
  dbRequest()





advanceTime = (time, advanceBy) ->      
new Date time*1 + advanceBy
retryFor = (duration, interval) -> start = new Date retry = (fn, finalCallback) -> attempt = new Date if attempt < (advanceTime start, duration) proxyCallback = (error, data) -> if error console.log "Error: Retry in #{interval}" setTimeout -> retry fn, finalCallback , interval else finalCallback error, data fn proxyCallback else console.log "Gave up after #{duration}"





seconds = (n) -> 
1000*n
getUserData = (user) -> (callback) ->
users.get user, callback
getUserDataForFred = getUserData 'fred'
retryForFiveSeconds = (retryFor (seconds 5), (seconds 1)) retryForFiveSeconds getUserDataForFred, (error, data) -> console.log data





Error: Retry in 1000
Error: Retry in 1000
Error: Retry in 1000
Success





memoryEater = ->
memoryEater() 





memoryEater = -> memoryEater()
memoryEater()
# RangeError: Maximum call stack size exceeded





tenThousandCalls = (depth) ->
  if depth < 10000
    (tenThousandCalls depth + 1) 





6.4 Combinators

profit = ->
tax = (amount) ->
amount / 3
netProfit = (products) -> profits = (profit product) for product in products
profits.reduce (acc, p) -> acc + p
netProfitForProducts = netProfit products taxForProducts = tax netProfitForProducts





userSpend = (user) ->
spend = 100
loyaltyDiscount = (spend) -> if spend < 1000 then 0 else if spend < 5000 then 5 else if spend < 10000 then 10 else if spend < 50000 then 20
else if spend > 50000 then 40
fredSpend = userSpend fred loyaltyDiscountForFred = loyaltyDiscount fredSpend





initialValue = 5
intermediateValue = firstFunction initialValue
finalValue = secondFunction intermediateValue





initialValue = 5
secondFunction (firstFunction initialValue)





netProfitForProducts = netProfit products
taxForProducts = tax netProfitForProducts





taxForProducts = tax (netProfit products)





taxForProducts = tax (netProfit products)





compose = (f, g) -> (x) -> f g x 





taxForProducts = compose tax, netProfit
loyaltyDiscountForUser = compose loyaltyDiscount, userSpend





addFive = (x) -> x + 5
multiplyByThree = (x) -> x * 3
multiplyByThreeAndThenAddFive = compose addFive, multiplyByThree  
multiplyByThreeAndThenAddFive 10
# 35





sale = (user, product) ->
  auditLog "Sold #{product} to #{user}"
  # Some other stuff happens here





refund = (user, product) ->
  auditLog "Refund for #{product} to #{user}"
  # Some other stuff happens here





auditedRefund = withAuditLog refund





refund = withAuditLog refund





before = (decoration) ->
  (base) ->
    (params...) ->             
      decoration params...     
      base params...  





withAuditLog = before (params...) ->
  auditLog params...





after = (decoration) ->
  (base) ->
    (params...) ->
      result = base params...
      decoration params...
      result 





openConnection()
doSomethingToTheDb()
doSomethingElseToTheDb()
closeConnection()





dbConnectionIsOpen = openConnection()
if dbConnectionIsOpen
  doSomethingToTheDb()
  doSomethingElseToTheDb()
  closeConnection()





around = (decoration) ->
  (base) ->
    (params...) ->
      callback = -> base params...
      decoration ([callback].concat params)...





withOpenDb = around (dbActivity) ->
  openDbConnection()
  dbActivity()
  closeDbConnection()





getUserData = withOpenDb (users) ->
  users.get 'user123'





withOpenDb = around (dbActivity) ->
  if openDbConnection()
    dbActivity()
    closeDbConnection()





class Robot
  constructor: (@at=0) ->
  position: -> 
    @at
  move: (displacement) -> 
    @at += displacement
  startEngine: -> console.log 'start engine'
  stopEngine: -> console.log 'stop engine'
  forward: ->
    @startEngine()
    @move 1
    @stopEngine()
  reverse: ->
    @startEngine()
    @move -1
    @stopEngine()





class Robot
  withRunningEngine = around (action) ->
    @startEngine()
    action()
    @stopEngine()
  constructor: (@at=0) ->
  position: -> 
    @at
  move: (displacement) -> 
    console.log 'move'
    @at += displacement
  startEngine: -> console.log 'start engine'
  stopEngine: -> console.log 'stop engine'
  forward: withRunningEngine ->
    @move 1
  reverse: withRunningEngine ->
@move -1
bender = new Robot bender.forward() bender.forward() # TypeError: Object #<Object> has no method 'startEngine'





airplane = 
startEngine: -> 'Engine started!'
withRunningEngine = (first, second) -> @startEngine()
"#{first} then #{second}"
withRunningEngine 'Take-off', 'Fly'
# Object #<Object> has no method 'startEngine'
withRunningEngine.call airplane, 'Take-off', 'Fly' 'Take-off then Fly'





withRunningEngine.apply airplane, ['Take-off', 'Fly'] 
'Take-off then Fly'





Listing 6.6 Before, after, and around with function binding

before = (decoration) ->
  (base) ->
    (params...) ->
      decoration.apply @, params
base.apply @, params
after = (decoration) -> (base) -> (params...) -> result = base.apply @, params decoration.apply @, params
result
around = (decoration) -> (base) -> (params...) -> result = undefined func = => result = base.apply @, params decoration.apply @, ([func].concat params) result





bender = new Robot 3
bender.forward()
# start engine
# move
# stop engine
# 4
bender.forward() # start engine # move # stop engine
# 5
bender.reverse() # start engine # move # stop engine
# 4
bender.position() # 4





forward = (callback) ->
  setTimeout callback, 1000





forward ->                      
  forward ->                    
    forward ->                  
      forward ->                
        forward ->              
          console.log 'done!'





start = (callback) ->
  console.log 'started'
setTimeout callback, 200
forward = (callback) -> console.log 'moved forward' setTimeout callback, 200





startThenForward = compose forward, start
startThenForward (res) ->
  console.log res
# TypeError: undefined is not a function





composeAsync = (f, g) -> (x) -> g -> f x 





startThenForward = composeAsync forward, start
startThenForward ->
  console.log 'done'
# started
# moved forward
# done





Listing 6.7 Asynchronous before and after

beforeAsync = (decoration) ->
  (base) ->
    (params..., callback) ->
      result = undefined
      applyBase = =>
        result = base.apply @, (params.concat callback)
      decoration.apply @, (params.concat applyBase)
result
afterAsync = (decoration) -> (base) -> (params..., callback) -> decorated = (params...) => decoration.apply @, (params.concat -> (callback.apply @, params)) base.apply @, (params.concat decorated)





6.5 Summary