Skip to content

Instantly share code, notes, and snippets.

@earldouglas
Last active October 3, 2015 22:21
Show Gist options
  • Save earldouglas/1f2bce2fff32b62b2d33 to your computer and use it in GitHub Desktop.
Save earldouglas/1f2bce2fff32b62b2d33 to your computer and use it in GitHub Desktop.
APIcon 2014: Real-World Functional Programming

Real-World Functional Programming

May 28, 2014

This code is a companion to the APIcon presentation Real-World Functional Programming by @jearldouglas and @kelleyrobinson. The slides are available here.

In this code, we compare imperative and functional implementations of a withdraw function that might be used in a banking ATM.

Imperative transactions

In a conventionally imperative style, a withdraw function might:

  1. Take an amount argument
  2. Look up an external balance
  3. Decrease the external balance by amount
  4. Return amount
function withdraw(amount) {
  if (balance >= amount) {
    balance = balance - amount
    return amount
  } else {
    return 0
  }
}

The big problem with this implementation is its dependence on an out-of-context balance that it both accesses and mutates.

Functional transactions

In a functional style, a withdraw function might instead:

  1. Take an amount argument
  2. Return a new function that itself takes a balance argument, and:
  3. Computes the new balance without modifying the incoming one
  4. Returns both the new balance and amount
function withdraw(amount) {
  return function(balance) {
    if (balance >= amount) {
      return [amount, balance - amount]
    } else {
      return [0, balance]
    }
  }
}

The main advantage of this is that withdraw can be executed, e.g. withdraw(20), without any triggering any interaction with a balance (external or otherwise). This leaves us free to take the resulting callback and do other things with it (e.g. compose it with other callbacks, map over it, bind it to other callback-returning functions, or simply run it with an immutable balance argument).

Examples

To run each example, use node <filename.js>:

$ node imperative.js 
old balance: ???
amount withdrawn: 20
new balance: 10
$ node functional.js 
old balance: 30
amount withdrawn: 20
new balance: 10

old balance: 10
amount withdrawn: 0
new balance: 10
$ node functional-composition.js 
old balance: 30
amount withdrawn: 25
new balance: 5

old balance: 22
amount withdrawn: 20
new balance: 2
$ node functional-mapping.js 
old balance: 30
amount withdrawn: 0.034782608695652174
new balance: 10
$ node functional-binding.js 
old balance: 30
amount withdrawn: 0.034782608695652174
new balance: 9.8
// returns a new function that, given a `balance`,
// creates a pair contianing the amount that was
// withdrawn, and a new balance computed from the
// old balance
function withdraw(amount) {
return function(balance) {
if (balance >= amount) {
return [amount, balance - amount]
} else {
return [0, balance]
}
}
}
// takes a transaction callback, applies it with
// an initial balance, and logs some information
// about what happened
function run(withdrawal, balance) {
var result = withdrawal(balance)
console.log('old balance: ' + balance)
console.log('amount withdrawn: ' + result[0])
console.log('new balance: ' + result[1])
console.log()
}
// a transaction callback representing the
// withdrawal of $20
var getTwenty = withdraw(20)
// a transaction callback representing the
// withdrawal of $5
var getFive = withdraw(5)
///////////////////////////////
// Example: transaction binding
///////////////////////////////
// takes a transaction callback and wraps it in
// one that binds its output to the input of a
// transaction callback produced by the function
// arugment f
function bind(withdrawal, f) {
return function(balance) {
var result1 = withdrawal(balance)
var result2 = f(result1[0])(result1[1])
return [result2[0], result2[1]]
}
}
// takes a transaction callback and wraps it in
// one that modifies its amount with the pure
// function argument f
function map(withdrawal, f) {
return function(balance) {
var result = withdrawal(balance)
return [f(result[0]), result[1]]
}
}
// a transaction that charges a fee of 1% of
// the amount withdrawn
function chargeFee(amount) {
return function(balance) {
var fee = amount * 0.01
return [amount, balance - fee]
}
}
// converts USD into BTC
function convertToBtc(amount) {
return amount / 575.0
}
// a transaction callback representing the
// withdrawal of $20, converted into BTC, with
// a transaction fee
var getTwentyInBtc = map(bind(getTwenty, chargeFee), convertToBtc)
// run the transaction callback
run(getTwentyInBtc, 30)
// returns a new function that, given a `balance`,
// creates a pair contianing the amount that was
// withdrawn, and a new balance computed from the
// old balance
function withdraw(amount) {
return function(balance) {
if (balance >= amount) {
return [amount, balance - amount]
} else {
return [0, balance]
}
}
}
// takes a transaction callback, applies it with
// an initial balance, and logs some information
// about what happened
function run(withdrawal, balance) {
var result = withdrawal(balance)
console.log('old balance: ' + balance)
console.log('amount withdrawn: ' + result[0])
console.log('new balance: ' + result[1])
console.log()
}
// a transaction callback representing the
// withdrawal of $20
var getTwenty = withdraw(20)
// a transaction callback representing the
// withdrawal of $5
var getFive = withdraw(5)
///////////////////////////////////
// Example: transaction composition
///////////////////////////////////
// composes two transaction callbacks by wrapping
// them in one that applies the first and feeds
// its output into the second
function compose(withdrawal1, withdrawal2) {
return function(balance) {
var result1 = withdrawal1(balance)
var result2 = withdrawal2(result1[1])
return [result1[0] + result2[0], result2[1]]
}
}
// a transaction callback representing the
// composition of the withdrawal of $20
// followed by the withdrawal of $5
var getTwentyFive = compose(getTwenty, getFive)
// run the composed transaction callbacks
run(getTwentyFive,30)
run(getTwentyFive,22)
// returns a new function that, given a `balance`,
// creates a pair contianing the amount that was
// withdrawn, and a new balance computed from the
// old balance
function withdraw(amount) {
return function(balance) {
if (balance >= amount) {
return [amount, balance - amount]
} else {
return [0, balance]
}
}
}
// takes a transaction callback, applies it with
// an initial balance, and logs some information
// about what happened
function run(withdrawal, balance) {
var result = withdrawal(balance)
console.log('old balance: ' + balance)
console.log('amount withdrawn: ' + result[0])
console.log('new balance: ' + result[1])
console.log()
}
// a transaction callback representing the
// withdrawal of $20
var getTwenty = withdraw(20)
// a transaction callback representing the
// withdrawal of $5
var getFive = withdraw(5)
///////////////////////////////
// Example: transaction mapping
///////////////////////////////
// takes a transaction callback and wraps it in
// one that modifies its amount with the function
// argument f
function map(withdrawal, f) {
return function(balance) {
var result = withdrawal(balance)
return [f(result[0]), result[1]]
}
}
// converts USD into BTC
function convertToBtc(amount) {
return amount / 575.0
}
// a transaction callback representing the
// withdrawal of $20, converted into BTC
var getTwentyInBtc = map(getTwenty, convertToBtc)
// run the transaction callback
run(getTwentyInBtc, 30)
// returns a new function that, given a `balance`,
// creates a pair contianing the amount that was
// withdrawn, and a new balance computed from the
// old balance
function withdraw(amount) {
return function(balance) {
if (balance >= amount) {
return [amount, balance - amount]
} else {
return [0, balance]
}
}
}
// takes a transaction callback, applies it with
// an initial balance, and logs some information
// about what happened
function run(withdrawal, balance) {
var result = withdrawal(balance)
console.log('old balance: ' + balance)
console.log('amount withdrawn: ' + result[0])
console.log('new balance: ' + result[1])
console.log()
}
// a transaction callback representing the
// withdrawal of $20
var getTwenty = withdraw(20)
// a transaction callback representing the
// withdrawal of $5
var getFive = withdraw(5)
///////////////////////////////////
// Example: basic transaction usage
///////////////////////////////////
// run the transaction callbacks
run(getTwenty,30)
run(getTwenty,10)
// initialize a global `balance` variable
var balance = 30
// withdraws `amount` from a global `balance` variable
function withdraw(amount) {
if (balance >= amount) {
balance = balance - amount
return amount
} else {
return 0
}
}
// withdraw from `balance` and return the amount
var withdrawn = withdraw(20)
// log the present state
console.log("old balance: ???")
console.log("amount withdrawn: " + withdrawn)
console.log("new balance: " + balance)
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment