Functional Programming
Brian Lonsdorf
@drboolean
http://drboolean.gitbooks.io/mostly-adequate-guide/
https://github.com/loop-recur/FPJS-Class
http://ramdajs.com/docs/
Functions
��// pure�xs.slice(0,3)�//=> [1,2,3]��xs.slice(0,3)�//=> [1,2,3]��xs.slice(0,3)�//=> [1,2,3]�
��// impure�xs.splice(0,3)�//=> [1,2,3]��xs.splice(0,3)�//=> [4,5]��xs.splice(0,3)�//=> []
var xs = [1,2,3,4,5]
// impure�var minimum = 21;��var checkAge = function(age) {� return age >= minimum;�}�
// pure
const minimum = 21;
�var checkAge = function(age) {� return age >= minimum;�}
// impure�var greeting = function(name) { � console.log("hi, " + name + "!") �} �
�// pure�var greeting = function(name) { � return "hi, " + name + "!"; �} � �console.log(greeting("Jonas"))
//impure �var signUp = function(attrs) {� var user = saveUser(attrs)
welcomeUser(user)�}
�//pure �var signUp = function(attrs) { � return function(){ � var user = saveUser(attrs)
welcomeUser(user)� }�}
Let’s play pure or impure
Let’s play pure or impure
�var birthday = function(user) { � user.age += 1; � return user; �}
Let’s play pure or impure
var shout = function(word) { � return word.toUpperCase().concat("!"); �}
Let’s play pure or impure
var headerText = function(header_selector) { � return $(header_selector).text(); �} �
Let’s play pure or impure
var parseQuery = function() {� return location.search.substring(1).split('&').map(function(x){� return x.split('=')� }) �} �
Let’s play pure or impure
�var parseQueryString = function(queryString) { � var params = {}, queries, temp, i, l; �� queries = queryString.split("&"); �� for ( i = 0, l = queries.length; i < l; i++ ) { � temp = queries[i].split('='); � params[temp[0]] = temp[1]; � } �� return params; �};
Let’s play pure or impure
var httpGet = function(url, params){ � return function() { return $.getJSON(url, params); } �};
// associative�add(add(x, y), z) == add(x, add(y, z))��// commutative�add(x, y) == add(y, x)��// identity�add(x, 0) == x��// distributive�add(multiply(x, y), multiply(x, z)) == multiply(x, add(y,z))
Set theoretically
Every function is
a single-valued collection of pairs
...
(-2, -1)
(0 , 0 )
(2 , 1 )
(4 , 2 )
(8 , 4 )
...
NOT THIS
(1, D)
(2, B)
(2, C)
THIS
One input, one output
...
(-2, -1)
(0 , 0 )
(2 , 1 )
(4 , 2 )
(8 , 4 )
...
Domain
Range
… -2 0 2 4 8 ...
… -1 0 1 2 4 ...
One input, one output
Input | Output |
1 | 2 |
2 | 4 |
3 | 6 |
Domain
Range
… 1,2,3 ...
… 2,4,6...
One input, one output
One input, one output
var toLowerCase = {"A":"a", "B": "b", "C": "c", "D": "d", "E": "e", "D": "d"}��toLowerCase["C"]�//=> "c"�
�var isPrime = {1: false, 2: true, 3: true, 4: false, 5: true, 6: false}��isPrime[3]�//=> true
Multiple arguments?
//+ add :: [Number, Number] -> Number
var add = function(numbers) {� return numbers[0] + numbers[1] }
Multiple arguments?
//+ add :: ? -> Number
var add = function() {� return arguments[0] + arguments[1]�}
Curried Function
A function that will return a new function until it receives all its arguments
Currying
//+ add :: Number -> Number -> Number�var add = curry(function(x, y) {� return x + y })�
Currying
add�//=> function(x,y) { return x + y } � �add(2,3) �//=> 5 � �add(2) �//=> function(y) { return 2 + y }
Currying
//+ get :: String -> {String: a} -> a var get = curry(function(prop, obj) { � return obj[prop]; �})
�
var user = {id: 32, name: "Gary", email: "gary@newman.com"}
�get('email', user)
//=> gary@newman.com
//+ email :: {String: a} -> a
var email = get('email')
//=> function(obj){ return obj[‘email’] }
�email(user) �//=> gary@newman.com
Currying
//+ modulo :: Number -> Number -> Number
var modulo = curry(function(divisor, dividend) { � return dividend % divisor; �})
�//+ isOdd :: Number -> Number �var isOdd = modulo(2)
//=> function(dividend){ return dividend % 2 }
isOdd(2) �//=> 0 ��isOdd(3) �//=> 1
Currying
//+ filter :: (a -> Bool) -> [a] -> [a]�var filter = curry(function(f, xs) { � return xs.filter(f); �}) �
//+ odds :: [a] -> [a] �var odds = filter(isOdd) �
odds([1,2,3]) �//=> [1, 3]
Currying
//+ odds :: [a] -> [a] var odds = function(xs) { � return filter(function(x){ return isOdd(x) }, xs) �}
�//+ odds :: [a] -> [a] �var odds = filter(isOdd)
Currying
var emails = map(email) �
var users = [beyonce, martin, gary]
�emails(users) �//=> ["beyonce@knowles.org", "martin@lawrence.net", "gary@newman.com"]
Currying
http://jsbin.com/qehome/
answers:
var emails = map(email) �
var users = [beyonce, martin, gary]
�emails(users) �//=> ["beyonce@knowles.org", "martin@lawrence.net", "gary@newman.com"]
Currying
//+ goodArticles :: [Article] -> [Article]
var goodArticles = function(articles) {� return _.filter(articles, function(article){� return _.isDefined(article)� })�} ���//+ goodArticles :: [Article] -> [Article] �var goodArticles = filter(isDefined)
Currying
//+ getChildren :: DOM -> [DOM]
var getChildren = function(el) {� return el.childNodes�}�//+ getALLChildren :: [DOM] -> [[DOM]] �var getAllChildren = function(els) {� return _.map(els, function(el) {� return getChildren(el)� })�}��var getChildren = get('childNodes')�var getAllChildren = map(getChildren)
Composition
Function composition is applying one function to the results of another
Composition
//+ compose :: (b -> c) -> (a -> b) -> a -> c
var compose = curry(function(f, g, x) {� return f(g(x))�})
Composition
//+ head :: [a] -> a
var head = function(x) { return x[0] }
//+ last :: [a] -> a�var last = compose(head, reverse)��last(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'uppercut'
Composition
//+ wordCount :: String -> Number
var wordCount = function(sentence) {� var count = split(' ', sentence)� return length(count)�}�
//+ wordCount :: String -> Number�var wordCount = compose(length, split(' '))
wordCount("I am a sentence with seven words")�//=> 7
Composition
compose(toUpperCase, last)('Function Husbandry')
'Y' <- 'y' <- 'Function Husbandry'
Composition
Composition
Composition
Pulling it into JS
Composition
// associativity�compose(f, compose(g, h)) == compose(compose(f, g), h)
Composition
compose(toUpperCase, compose(head, reverse))��// or�compose(compose(toUpperCase, head), reverse)
Composition
//+ lastUpper :: [String] -> String
var lastUpper = compose(toUpperCase, head, reverse)��lastUpper(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'UPPERCUT'��//+ lastUpper :: [String] -> String�var loudLastUpper = compose(exclaim, toUpperCase, head, reverse)��loudLastUpper(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'UPPERCUT!'
Composition
compose(toUpperCase, map, reverse)(['Function', 'Husbandry'])
? <- map(['Husbandry', 'Function']) <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']
Composition
compose(map(toUpperCase), reverse)(['Function', 'Husbandry'])
['HUSBANDRY', 'FUNCTION'] <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']
Composition
http://jsbin.com/rufidu
answers:
['HUSBANDRY', 'FUNCTION'] <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']
compose(map(toUpperCase), reverse)(['Function', 'Husbandry'])
Pointfree
httpGet('/post/2', function(json){� renderPost(json)�})��httpGet('/post/2', function(json, err){� renderPost(json, err)�})��httpGet('/post/2', renderPost)
Pointfree
//+ clientApp :: Params -> Html
var clientApp = compose(render, doThings, httpGet('/posts')) � �//+ serverApp :: Query -> JSON�var serverApp = compose(sendJSON, doThings, Db.all('posts')) � �//+ shellApp :: _ -> String �var shellApp = compose(display, doThings, prompt("what's up?"))
//url :: String -> URL� var url = function (t) { return 'http://gdata.youtube.com/feeds/api/videos?q=' + t + '&alt=json' }�� // src :: YoutubeEntry -> URL� var src = compose(_.get('url'), _.first, _.get('media$thumbnail'), _.get('media$group'));�� // srcs :: YoutubeSearch -> [URL]� var srcs = compose(map(src), _.get('entry'), _.get('feed'));�� // images :: YoutubeSearch -> [DOM]� var images = compose(map(imageTag), srcs);�� // widget :: Future DOM� var widget = compose(map(images), getJSON, url);�� /////////////////////////////////////////////////////////////////////////////////////� widget('cats').fork(log, setHtml($('#youtube')));
recognize most loops are one of
map
filter
reduce
If you write a loop you get an F !
Loops
Category Theory
compose :: (b -> c) -> (a -> b) -> (a -> c)��id :: a -> a
Category Laws
// left identity�compose(id, f) == f��// right identity�compose(f, id) == f��// associativity�compose(compose(f, g), h) == compose(f, compose(g, h))
Nulls
Nulls
Callbacks
Nulls
Callbacks
Errors
Nulls
Callbacks
Errors
Side effects
Objects
*think of it this way for now.
Objects
var Container = function(val) {� this.val = val;�}��//+ Container :: a -> Container(a)
Container.of = function(x) { return new Container(x); };�
Container.of(3)
//=> Container {val: 3}
Objects
capitalize("flamethrower")�//=> "Flamethrower"
�capitalize(Container.of("flamethrower"))
//=> [object Object]
Objects
//+ map :: (a -> b) -> Container(a) -> Container(b)
Container.prototype.map = function(f) {� return Container.of(f(this.val));�}��Container.of("flamethrower").map(function(s){ return capitalize(s) })
//=> Container(“Flamethrower”)
Objects
//+ map :: (a -> b) -> Container(a) -> Container(b)
_Container.prototype.map = function(f) {� return Container.of(f(this.val));�}��Container.of("flamethrower").map(capitalize)
//=> Container(“Flamethrower”)
Objects
Container.of(3).map(add(1))
//=> Container(4)
[3].map(add(1))
//=> [4]
the true map
goes within
Objects
Container.of([1,2,3]).map(reverse).map(first)�//=> Container(3)
�Container.of("flamethrower").map(length).map(add(1))
//=> Container(13)
Objects
//+ map :: Functor F => (a -> b) -> F a -> F b
var map = _.curry(function(f, obj) {� return obj.map(f)�})
Container.of(3).map(add(1)) // Container(4)
�map(add(1), Container.of(3)) // Container(4)
Objects
map(match(/cat/g), Container.of("catsup"))
//=> Container([“cat”])
map(compose(first, reverse), Container.of("dog"))
//=> Container(“g”)
Functor
“An object or data structure you can map over”
functions: map
Them pesky nulls
//+ getElement :: String -> DOM
var getElement = document.querySelector
//+ getNameParts :: String -> [String]�var getNameParts = compose(split(' '), getText, getElement)��getNameParts('#full_name')�//=> ['Jonathan', 'Gregory', 'Brandis']�
Them pesky nulls
//+ getElement :: String -> DOM
var getElement = document.querySelector
//+ getNameParts :: String -> [String]�var getNameParts = compose(split(' '), getText, getElement)��getNameParts('#fullname')�//=> Boom!�
Maybe
Captures a null check
The value inside may not be there
Sometimes has two subclasses Just / Nothing
Sometimes called Option with subclasses Some/None
Maybe
//+ map :: (a -> b) -> Maybe(a) -> Maybe(b)
var _Maybe.prototype.map = function(f) {� return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);�}�
�map(capitalize, Maybe.of("flamethrower"))�//=> Maybe(“Flamethrower”)
Maybe
//+ map :: (a -> b) -> Maybe(a) -> Maybe(b)
var _Maybe.prototype.map = function(f) {� return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);�}�
�map(capitalize, Maybe.of(null))�//=> Maybe(null)
Maybe
//+ firstMatch :: String -> String
var firstMatch = compose(first, match(/cat/g))
�firstMatch("dogsup")�//=> Boom!�
Maybe
//+ firstMatch :: String -> Maybe(String)
var firstMatch = compose(map(first), Maybe.of, match(/cat/g))
�firstMatch("dogsup")�//=> Maybe(null)�
Maybe
//+ firstMatch :: String -> Maybe(String)
var firstMatch = compose(map(first), Maybe.of, match(/cat/g))
�firstMatch("catsup")�//=> Maybe(“cat”)�
Either
Typically used for pure error handling
Like Maybe, but with an error message embedded
Has two subclasses: Left/Right
Maps the function over a Right, ignores the Left
Either
map(function(x) { return x + 1; }, Right.of(2))�//=> Right(3)��map(function(x) { return x + 1; }, Left.of(‘some message))�//=> Left(‘some message’)
*Left.of is not entirely correct
Either
//+ determineAge :: User -> Either(String, Number)
var determineAge = function(user){� return user.age ? Right.of(user.age) : Left.of("couldn’t get age");�}
//+ yearOlder :: User -> Either(String, Number)�var yearOlder = compose(map(add(1)), determineAge)��yearOlder({age: 22})�//=> Right(23)��yearOlder({age: null})�//=> Left("couldn’t get age")�
IO
A lazy computation “builder”
Typically used to contain side effects
You must runIO to perform the operation
Map appends the function to a list of things to run with the effectful value.
IO
//+ email_io :: IO(String)
var email_io = new IO(function(){ return $("#email").val() })
//+ msg_io :: IO(String)�var msg_io = map(concat("welcome "), email_io)
�msg_io.unsafePerformIO()
//=> ”welcome steve@foodie.net”
unsafePerformIO()
IO
//+ getBgColor :: JSON -> Color
var getBgColor = compose(get("background-color"), JSON.parse)
//+ bgPref :: _ -> IO(Color)�var bgPref = compose(map(getBgColor), Store.get("preferences"))
�//+ app :: _ -> IO(Color)�var app = bgPref()
//=> IO()
app.unsafePerformIO()
//=> #efefef�
IO
//+ email_io :: _ -> IO(String)
var email_io = new IO(function(){ return $("#email").val() }
//+ getValue :: String -> IO(String)
var getValue = function(sel){ � return new IO(function(){ return $(sel).val() });
}
EventStream
An infinite list of results
Dual of array
Its map is sometimes lazy
Calls the mapped function each time an event happens
EventStream
var ids = map(function(e) { return '#'+e.id }, Bacon.fromEventTarget(document, "click"))�//=> EventStream(String)��ids.onValue(function(id) { alert('you clicked ' +id) })�
EventStream
//+ ids :: EventStream(Event) -> EventStream(String)
var ids = map(function(e) { return '#'+e.id }, Bacon.fromEventTarget(document, "click"))
//+ elements :: EventStream(String) -> EventStream(DOM)
var elements = map(document.querySelector, ids)�//=> EventStream(DOM)��elements.onValue(function(el) { alert('The inner html is ' +el.innerHTML) })�
Future
Has an eventual value
Similar to a promise, but it’s “lazy”
You must fork it to kick it off
It takes a function as it’s value
Calls the function with it’s result once it’s there
Future
//+ makeHtml :: Post -> HTML
var makeHtml = function(post){ return "<div>"+post.title+"</div>"};
//+ page_f :: Future(Html)�var page_f = map(makeHtml, http.get('/posts/2'))��page_f.fork(function(err) { throw(err) },� function(page){ $('#container').html(page) })�
Future
//+ makeHtml :: Post -> HTML
var makeHtml = function(title){ return "<div>"+title+"</div>"}
//+ createPage :: Post -> HTML�var createPage = compose(makeHtml, get('title'))
//+ page_f :: Future(HTML)�var page_f = compose(map(createPage), http.get('/posts/2'))
�page_f.fork(function(err) { throw(err) },� function(page){ $('#container').html(page) })�
Pointed Functors
of :: a -> F a
aka: pure, return, unit, point
Pointed Functors
Container.of(2)�// Container(2)��Maybe.of(reverse)�// Maybe(reverse)�
�Future.of(match(/dubstep/))�// Future(match(/dubstep/))��IO.of(’shoegaze')�// IO(function(){ return 'shoegaze'})�
Future
//+ lineCount :: String -> Number
var lineCount = compose(length, split(/\n/))
//+ fileLineCount :: String -> Future(Number)�var fileLineCount = compose(map(lineCount), readFile)��fileLineCount("mydoc.txt").fork(log, log)�//=> 34
recognize map
[x].map(f) // [f(x)]�Maybe(x).attempt(f) // Maybe(f(x))�Promise(x).then(f) // Promise(f(x))�EventStream(x).subscribe(f) // EventStream(f(x))
map(f, [x]) // [f(x)]�map(f, Maybe(x)) // Maybe(f(x))�map(f, Promise(x)) // Promise(f(x))�map(f, EventStream(x)) // EventStream(f(x))
We see it is map
Custom names
Laws & Properties are useful!
Functor Laws
// identity�map(id) == id
�// composition�compose(map(f), map(g)) == map(compose(f, g))
Functors
reverse :: String -> String
//+ :: a -> [String]
var toArray = function (x) { return [x] }
�//+ :: String -> [String]�compose(toArray, reverse)("bingo")
//=> [ognib]
//+ :: String -> [String]
compose(map(reverse), toArray)("bingo")
//=> [ognib]
Functors
//+ :: String -> [String]
compose(toArray, compose(toUpper, reverse))("bingo")
//=> [ OGNIB]
//+ :: String -> [String]
compose(map(toUpper), map(reverse), toArray)("bingo")
//=> [OGNIB]
//+ :: String -> [String]
compose(map(compose(toUpper, reverse)), toArray)("bingo")
//=> [OGNIB]
Natural Transformations
nt :: F a -> T a
“Takes one functor to another without knowing anything about the values”
Natural Transformations
maybeToArray :: Maybe a -> Array a
maybeToArray(Maybe(2))�//=> [2]��maybeToArray(Maybe(null))�//=> []�
Natural Transformations
compose(nt, map(f)) == compose(map(f), nt)
//+ :: Maybe(a) -> [a]�compose(maybeToArray, map(add(1)))(Maybe(5))�// [6]
�//+ :: Maybe(a) -> [a]�compose(map(add(1)), maybeToArray)(Maybe(5))�// [6]
Card Game
Card Game #1
"Make an api call with an id and possibly retrieve a post"
Card Game #1
"Make an api call with an id and possibly retrieve a post"
Future(Maybe(Post))
Card Game #2
"Click a navigation link and insert the corresponding html on the page"
Card Game #2
"Click a navigation link and insert the corresponding html on the page"
EventStream(IO(Dom))
Card Game #3
"Submit a signup form & return errors or make an API call that will create a user”
Card Game #3
"Submit a signup form & return errors or make an API call that will create a user”
EventStream(Either(Future(User)))
Monads
“Nest computations”
functions: join, chain
Monads
join :: M M a -> M a
chain :: (a -> M b) -> M a -> M b
Pointed Functor + join|chain = Monad
aka: pure, return, unit, point
Monads
join(Container.of(Container.of(2)))
//=> Container(2)�
Monads
//+ getTrackingId :: Order -> Maybe(TrackingId)
var getTrackingId = compose(Maybe.of, get("tracking_id"))
//+ findOrder :: Number -> Maybe(Order)�var findOrder = compose(Maybe.of, Api.findOrder)
//+ getOrderTracking :: Number -> Maybe(Maybe(TrackingId))�var getOrderTracking = compose(map(getTrackingId), findOrder)
�//+ renderPage :: Number -> Maybe(Maybe(Html))�var renderPage = compose(map(map(renderTemplate)), getOrderTracking)
Monads
//+ getTrackingId :: Order -> Maybe(TrackingId)
var getTrackingId = compose(Maybe.of, get("tracking_id"))
//+ findOrder :: Number -> Maybe(Order)�var findOrder = compose(Maybe.of, Api.findOrder)
//+ getOrderTracking :: Number -> Maybe(TrackingId)�var getOrderTracking = compose(join, map(getTrackingId), findOrder)�
//+ renderPage :: Number -> Maybe(Html)�var renderPage = compose(map(renderTemplate), getOrderTracking)
Monads
//+ :: setSearchInput :: String -> IO(_)
var setSearchInput = function(x) { return new IO(function() { ("#input").val(x); }) }
//+ :: searchTerm :: IO(String)�var searchTerm = new IO(function() { getParam("term", location.search) })
//+ :: initSearchForm :: IO(IO(_))�var initSearchForm = map(setSearchInput, searchTerm)�
initSearchForm.unsafePerformIO().unsafePerformIO();
Monads
//+ :: setSearchInput :: String -> IO(_)
var setSearchInput = function(x) { return new IO(function() { ("#input").val(x); }) }
//+ :: searchTerm :: IO(String)�var searchTerm = new IO(function() { getParam("term", location.search) })
//+ :: initSearchForm :: IO(IO(_))�var initSearchForm = map(setSearchInput, searchTerm)�
join(initSearchForm).unsafePerformIO();
Monads
//+ sendToServer :: Params -> Future(Response)
var sendToServer = httpGet('/upload')
//+ uploadFromFile :: String -> Future(Response)�var uploadFromFile = compose(join, map(sendToServer), readFile)��uploadFromFile("/tmp/my_file.txt").fork(logErr, alertSuccess)
Monads
//+ sendToServer :: Params -> Future(Response)
var sendToServer = httpGet('/upload')
//+ uploadFromFile :: String -> Future(Response)�var uploadFromFile = compose(join, map(sendToServer), join, map(readFile), askUser)��uploadFromFile('what file?').fork(logErr, alertSuccess)
Monads
//+ chain :: (a -> M b) -> M a -> M b
var chain = function(f) {
return compose(join, map(f))
}
a.k.a: flatMap, bind
Monads
//+ sendToServer :: Params -> Future(Response)
var sendToServer = httpGet('/upload')
//+ uploadFromFile :: String -> Future(Response)
var uploadFromFile = compose(chain(sendToServer), chain(readFile), askUser)��uploadFromFile('what file?').fork(logErr, alertSuccess)
Monads
//+ chain :: (a -> M b) -> M a -> M b
var chain = function(f) {
return compose(join, map(f))
}
var join = chain(id)
Monads
Monad Laws
compose(join, map(g), join, map(f))
mcompose(g, f)
Monad Laws
// left identity�mcompose(M, f) == f��// right identity�mcompose(f, M) == f��// associativity�mcompose(mcompose(f, g), h) == mcompose(f, mcompose(g, h))
Applicative Functor
“Run full computations in a context”
functions: ap, liftA2, liftA..n
Applicatives
map(add(1), Container(2))�//=> Container(3)�
map(add, Container(2))�//=> Container(add(2))�
Applicatives
ap :: A (a -> b) -> A a -> A b
Pointed Functor + ap = Applicative
aka: <*>
Applicatives
Container.of(f).ap(Container(x))�//=> Container(f(x))�
Container.of(f).ap(Container(x)).ap(Container(y))�//=> Container(f(x, y))�
Applicatives
Container.of(add).ap(Container(1)).ap(Container(3))�//=> Container(4)
Applicatives
Maybe.of(add).ap(Maybe(1)).ap(Maybe(3))�//=> Maybe(4)��Maybe.of(add).ap(Maybe(1)).ap(Maybe(null))�//=> Maybe(null)
Applicatives
var loadPage = _.curry(function(products, reviews){ render(products.zip(reviews) })
�Future.of(loadPage).ap(Api.get('/products')).ap(Api.get('/reviews'))
Applicatives
var showLoaded = _.curry(function(tweetbtn, fbbtn){ alert('Done!') })
�EventStream.of(showLoaded).ap(tweet_btn.on('load’)).ap(fb_btn.on('load’))
Applicatives
var getVal = compose(Maybe, pluck('value'), document.querySelector)
var save = _.curry(function(email, pass){ return User(email, pass) })
�Maybe.of(save).ap(getVal('#email')).ap(getVal('#password'))�//=> Maybe(user)
Applicatives
var getVal = compose(Maybe, pluck('value'), document.querySelector)
var save = _.curry(function(email, pass){ return User(email, pass) })
�liftA2(save, getVal('#email'), getVal('#password'))�//=> Maybe(user)
Applicatives
var getVal = compose(Maybe, pluck('value'), document.querySelector)
var save = _.curry(function(nm, em, pass){ return User(nm, em, pass) })
�liftA3(save, getVal('#name'), getVal('#email'), getVal('#password'))�//=> Maybe(user)
Applicatives
var loadPage = _.curry(function(ships, orders, receipts){
return render(ships, orders, receipts)
})�
liftA3(loadPage, http.get('/shipments), http.get('/orders), http.get('/receipts))
//=> Future(Dom)
Applicative Laws
// identity�A(id).ap(m) == m�// composition
A(compose).ap(f).ap(g).ap(w) == f.ap(g.ap(w)))�// homomorphism�A(f).ap(A(x)) == A(f(x))�// interchange�u.ap(A(y)) == A(function(f) { return f(y) }).ap(u)
Monoids
“Combination/Accumulation”
functions: empty, concat, mconcat
Monoids
reduce(function(acc, x) {� return acc + x;�}, 0, [1,2,3])
Monoids
reduce(function(acc, x) {� return acc * x;�}, 1, [1,2,3])
Monoids
reduce(function(acc, x) {� return acc || x;�}, false, [false, false, true])
Monoids
reduce(function(acc, x) {� return acc && x;�}, true, [false, false, true])�
Monoids
Semigroup:
“Anything that has a concat (combination) method”
Monoids
Monoid:
“Any semigroup that has an empty() method”
Monoids
_Sum = function(v) { this.val = v; }��_Sum.prototype.concat = function(s2) {� return Sum(this.val + s2.val)�}��_Sum.prototype.empty = function() { return Sum(0) }�
Monoids
Sum(2).concat(Sum(3))�//=> Sum(5)
Monoids
Sum(2).concat(Sum(3)).concat(Sum(5))�//=> Sum(10)
Monoids
mconcat([Sum(2), Sum(3), Sum(5)])�//=> Sum(10)
Monoids
mconcat([Product(2), Product(3), Product(5)])�//=> Product(30)
Monoids
mconcat([Any(false), Any(true), Any(false)])�//=> Any(true)
Monoids
mconcat([All(false), All(true), All(false)])�//=> All(false)
Monoids
mconcat([Max(13), Max(2), Max(9)])�//=> Max(13)
Monoids
compose(getResult, mconcat, map(Sum))([1,2,3])�//=>6
Monoids
compose(getResult, mconcat, map(Any))([false,false,true])�//=>true
Monoids
mconcat([toUpperCase, reverse])(["bonkers"])�//=> “BONKERSsreknob”
Monoids
mconcat([Failure(["message1"]), Success(attrs), Failure(["message2"])])�//=> Failure(["message1", "message2"])
Monoids
mconcat([Success(attrs), Success(attrs)])�//=> Success(attrs)
Monoids
var checkValidations = mconcat([checkPassword, checkEmail, checkName])��checkValidations({name: "Burt"})�//=> Failure([“need a password”, “need an email”])�
Monoid Laws
// left identity�concat(empty, x) == x��// right identity�concat(x, empty) == x��// associativity�concat(concat(x, y), z) == concat(x, concat(y, z))
Category Laws
// left identity�compose(id, f) == f��// right identity�compose(f, id) == f��// associativity�compose(compose(f, g), h) == compose(f, compose(g, h))
Monad Laws
// left identity�mcompose(M, f) == f��// right identity�mcompose(f, M) == f��// associativity�mcompose(mcompose(f, g), h) == mcompose(f, mcompose(g, h))
Fantasy Land
Congratulations!
You’re a pioneer.
These techniques are new to the JavaScript ecosystem.
Libraries are Evolving
We’ll combine the best ones
• ramdajs / ramda
• baconjs / bacon.js
• fantasyland / fantasy-io
• DrBoolean / pointfree-fantasy
• folktale / data.either, data.future
https://github.com/DrBoolean/hardcorejs
https://vimeo.com/97575933