Functional Programing
In JavaScript
λ
What is functional programming?
An alternative to procedural or object-oriented programming
functions are the basic building block
functional programming
- minimizes side-effects
- avoids mutation
- encourages explicit inputs and outputs
why?
- easy to think about in isolation
- easier to test
- higher level of abstraction
- easier to refactor
Some basic patterns
map and filter
var R = require('ramda')
//ramdajs.com
R.map(
(x) => 2 * x,
[1, 2, 3, 4, 5])
//returns [ 2, 4, 6, 8, 10 ]
R.filter(
(x) => x % 2 === 0,
[1, 2, 3, 4, 5])
//returns [ 2, 4 ]
reduce
R.reduce(
(sum, x) => sum + x,
0,
[1, 2, 3, 4, 5])
// returns 15
// (0, 1) => 1
// (1, 2) => 3
// (3, 3) => 6
// (6, 4) => 10
// (10, 5) => 15
curry
not a sauce! :P
var add_3 =
R.curry((a, b, c) => a + b + c)
//All of these return 6
add_3(1, 2, 3)
add_3(1, 2)(3)
add_3(1)(2, 3)
add_3(1)(2)(3)
//All ramda functions are auto-curried
var doubles = R.map((x) => 2 * x)
var evens = R.filter((x) => x % 2 === 0)
var sum = R.reduce((x, y) => x + y, 0)
doubles([1, 2, 3, 4, 5])
//returns [ 2, 4, 6, 8, 10 ]
evens([1, 2, 3, 4, 5])
//returns [ 2, 4 ]
sum([1, 2, 3, 4, 5])
//returns 15
compose
var f = (x) => 2 * x
var g = (x) => x + 1
var h = (x) => x / 2
var fogoh =
R.compose(f, g, h)
/* fogoh = (x) =>
2 * ( (x / 2) + 1) */
fogoh(2) //returns 4
compose + curry
var is_number = (x) => !isNaN(x)
var sum = R.reduce((x, y) => x + y, 0)
var sum_what_you_can =
R.compose(
sum,
R.filter(is_number),
R.map(parseInt))
sum_what_you_can([
12,
"42",
"potatoes",
87
])
//returns 141
Objects
var purchase = {
name: 'hover board',
price: 10000
}
var get_price = R.prop('price')
get_price(purchase)
//returns 10000
var change_price = R.assoc('price')
change_price(100, purchase)
/*
returns {
name: 'hover board',
price: 100
}
*/
var price_lens =
R.lens(
R.prop('price'),
R.assoc('price'))
var apply_discount =
R.curry((discount, purchase) =>
R.over(
price_lens,
R.subtract(R.__, discount),
purchase))
apply_discount(100, purchase)
/* returns {
name: 'Hover Board',
price: 9900
} */
var price_formatter_lens =
R.lens(
R.prop('price'),
R.assoc('formatted_price'))
var add_formatted_price =
R.over(
price_formatter_lens,
format_dollars)
add_formatted_price(purchase)
/*
returns {
name: 'hover board',
price: 10000,
formatted_price: '$10,000'
}
*/
No side Effects
//modifies value and returns it
var apply_discount = function (discount, purchase) {
purchase.price -= discount
return purchase
}
apply_discount(100, purchase)
/*changes value of purchase to {
name: 'Hover Board',
price: 9900
} */
apply_discount =
R.curry((discount, purchase) =>
R.over(
R.lensProp('price'),
R.subtract(R.__, discount),
purchase))
apply_discount(100, purchase)
/* returns {
name: 'Hover Board',
price: 9900
} */
var add = (a, b) => {
var sum = a + b
console.log(sum)
return sum
}
explicit inputs
//implicit
var purchase = {
name: 'hover board',
price: 10000,
apply_discount: function (discount) {
this.price -= discount
}
}
//explicit
apply_discount =
R.curry((discount, purchase) =>
R.over(
R.lensProp('price'),
R.subtract(R.__, discount),
purchase))
explicit outputs
var change_item_name =
function (purchase, name) {
purchase.name = name
}
change_item_name(purchase, 'light saber')
/* changes purchase to
{ name: 'light saber', price: 10000 }
*/
change_item_name = R.assoc('name')
change_item_name('flying car')
/* returns
{ name: 'flying car', price: 10000 }
*/
Easy to think about in isolation
var add = (a, b) => a + b
Easier to test
describe('add', () => {
var add = require('../add.js')
it(
'should return the sum of its two arguments',
function () {
expect(add(1, 1)).toEqual(2)
})
})
easy to refactor
var format_dollars_cents =
R.compose(
R.join('.'),
(money) => [
format_dollars(money[0]),
money[1] ? money[1].slice(0, 2) : '00'],
R.split('.'),
R.toString)
format_dollars_cents(100000)
//returns $100,000.00
format_dollars_cents(100000.234)
//returns $100,000.23
format_dollars_cents(.234)
//returns $0.23
higher level of abstraction
var comma_separate = (width) =>
R.compose(
R.join(','),
R.map(R.reverse),
R.reverse,
R.splitEvery(width),
R.reverse)
var format_dollars =
R.compose(
R.concat('$'),
comma_separate(3),
R.toString)
format_dollars(100000)
//returns $100,000
debugging
var comma_separate = (width) =>
R.compose(
R.join(','),
R.reverse,
R.splitEvery(width),
R.reverse)
var format_dollars =
R.compose(
R.concat('$'),
comma_separate(3),
R.toString)
format_dollars(100000)
//returns '$001,000', what gives?
var tap = function (x) {
console.log(x)
return x
}
var comma_separate = (width) =>
R.compose(
R.join(','),
tap,
R.reverse,
R.splitEvery(width),
R.reverse)
//prints [ '001', '000' ]
var comma_separate = (width) =>
R.compose(
R.join(','),
R.map(R.reverse),
R.reverse,
R.splitEvery(width),
R.reverse)
This is just the beginning
- Functors
- Maybe
- Either
- Monads
- Applicative Functors
- Category Theory
Resources
Professor Frisby's Mostly Adequate Guide to functional programming
https://drboolean.gitbooks.io/mostly-adequate-guide/content/
Ramda
ramdajs.com
lodash
lodash.com
lodash-contrib
github.com/node4good/lodash-contrib
Learn you a haskell for great good
learnyouahaskell.com
funkyjavascript.com
Functional Programming In JavaScript
By Steve Smith
Functional Programming In JavaScript
Learn how to use functional programming concepts in your javascript for fun and profit.
- 979