1 of 176

Functional Programming

Brian Lonsdorf

@drboolean

http://drboolean.gitbooks.io/mostly-adequate-guide/

https://github.com/loop-recur/FPJS-Class

http://ramdajs.com/docs/

2 of 176

Functions

  1. Given the same input, will always return the same output
  2. Does not have any observable side effect.

3 of 176

��// 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]

4 of 176

// impurevar minimum = 21;��var checkAge = function(age) {� return age >= minimum;�}�

// pure

const minimum = 21;

var checkAge = function(age) {� return age >= minimum;�}

5 of 176

// impurevar greeting = function(name) { � console.log("hi, " + name + "!") �} �

// purevar greeting = function(name) { � return "hi, " + name + "!"; �} � �console.log(greeting("Jonas"))

6 of 176

//impure var signUp = function(attrs) {� var user = saveUser(attrs)

welcomeUser(user)�}

//pure var signUp = function(attrs) { � return function(){ � var user = saveUser(attrs)

welcomeUser(user)� }�}

7 of 176

Let’s play pure or impure

8 of 176

Let’s play pure or impure

var birthday = function(user) { � user.age += 1; � return user; �}

9 of 176

Let’s play pure or impure

var shout = function(word) { � return word.toUpperCase().concat("!"); �}

10 of 176

Let’s play pure or impure

var headerText = function(header_selector) { � return $(header_selector).text(); �} �

11 of 176

Let’s play pure or impure

var parseQuery = function() {� return location.search.substring(1).split('&').map(function(x){� return x.split('=')� }) �} �

12 of 176

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; �};

13 of 176

Let’s play pure or impure

var httpGet = function(url, params){ � return function() { return $.getJSON(url, params); } �};

14 of 176

15 of 176

16 of 176

17 of 176

18 of 176

19 of 176

20 of 176

21 of 176

22 of 176

23 of 176

24 of 176

// associativeadd(add(x, y), z) == add(x, add(y, z))��// commutativeadd(x, y) == add(y, x)��// identityadd(x, 0) == x��// distributiveadd(multiply(x, y), multiply(x, z)) == multiply(x, add(y,z))

25 of 176

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

26 of 176

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 ...

27 of 176

One input, one output

Input

Output

1

2

2

4

3

6

Domain

Range

… 1,2,3 ...

… 2,4,6...

28 of 176

One input, one output

29 of 176

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

30 of 176

Multiple arguments?

//+ add :: [Number, Number] -> Number

var add = function(numbers) {� return numbers[0] + numbers[1] }

31 of 176

Multiple arguments?

//+ add :: ? -> Number

var add = function() {� return arguments[0] + arguments[1]�}

32 of 176

Curried Function

A function that will return a new function until it receives all its arguments

33 of 176

Currying

//+ add :: Number -> Number -> Numbervar add = curry(function(x, y) {� return x + y })�

34 of 176

Currying

add�//=> function(x,y) { return x + y } � �add(2,3) �//=> 5 � �add(2) �//=> function(y) { return 2 + y }

35 of 176

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

36 of 176

Currying

//+ modulo :: Number -> Number -> Number

var modulo = curry(function(divisor, dividend) { � return dividend % divisor; �})

//+ isOdd :: Number -> Numbervar isOdd = modulo(2)

//=> function(dividend){ return dividend % 2 }

isOdd(2) �//=> 0 ��isOdd(3) �//=> 1

37 of 176

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]

38 of 176

Currying

//+ odds :: [a] -> [a] var odds = function(xs) { � return filter(function(x){ return isOdd(x) }, xs) �}

//+ odds :: [a] -> [a]var odds = filter(isOdd)

39 of 176

Currying

var emails = map(email) �

var users = [beyonce, martin, gary]

�emails(users) �//=> ["beyonce@knowles.org", "martin@lawrence.net", "gary@newman.com"]

40 of 176

Currying

http://jsbin.com/qehome/

var emails = map(email) �

var users = [beyonce, martin, gary]

�emails(users) �//=> ["beyonce@knowles.org", "martin@lawrence.net", "gary@newman.com"]

41 of 176

Currying

//+ goodArticles :: [Article] -> [Article]

var goodArticles = function(articles) {� return _.filter(articles, function(article){� return _.isDefined(article)� })�} ���//+ goodArticles :: [Article] -> [Article]var goodArticles = filter(isDefined)

42 of 176

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)

43 of 176

Composition

Function composition is applying one function to the results of another

44 of 176

Composition

//+ compose :: (b -> c) -> (a -> b) -> a -> c

var compose = curry(function(f, g, x) {� return f(g(x))�})

45 of 176

Composition

//+ head :: [a] -> a

var head = function(x) { return x[0] }

//+ last :: [a] -> avar last = compose(head, reverse)��last(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'uppercut'

46 of 176

Composition

//+ wordCount :: String -> Number

var wordCount = function(sentence) {� var count = split(' ', sentence)� return length(count)�}�

//+ wordCount :: String -> Numbervar wordCount = compose(length, split(' '))

wordCount("I am a sentence with seven words")�//=> 7

47 of 176

Composition

compose(toUpperCase, last)('Function Husbandry')

'Y' <- 'y' <- 'Function Husbandry'

48 of 176

Composition

49 of 176

Composition

50 of 176

Composition

51 of 176

Pulling it into JS

52 of 176

Composition

// associativity�compose(f, compose(g, h)) == compose(compose(f, g), h)

53 of 176

Composition

compose(toUpperCase, compose(head, reverse))��// or�compose(compose(toUpperCase, head), reverse)

54 of 176

Composition

//+ lastUpper :: [String] -> String

var lastUpper = compose(toUpperCase, head, reverse)��lastUpper(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'UPPERCUT'��//+ lastUpper :: [String] -> Stringvar loudLastUpper = compose(exclaim, toUpperCase, head, reverse)��loudLastUpper(['jumpkick', 'roundhouse', 'uppercut'])�//=> 'UPPERCUT!'

55 of 176

Composition

compose(toUpperCase, map, reverse)(['Function', 'Husbandry'])

? <- map(['Husbandry', 'Function']) <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']

56 of 176

Composition

compose(map(toUpperCase), reverse)(['Function', 'Husbandry'])

['HUSBANDRY', 'FUNCTION'] <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']

57 of 176

Composition

http://jsbin.com/rufidu

['HUSBANDRY', 'FUNCTION'] <- ['Husbandry', 'Function'] <- ['Function', 'Husbandry']

compose(map(toUpperCase), reverse)(['Function', 'Husbandry'])

58 of 176

Pointfree

httpGet('/post/2', function(json){� renderPost(json)�})��httpGet('/post/2', function(json, err){� renderPost(json, err)�})��httpGet('/post/2', renderPost)

59 of 176

Pointfree

//+ clientApp :: Params -> Html

var clientApp = compose(render, doThings, httpGet('/posts')) � �//+ serverApp :: Query -> JSONvar serverApp = compose(sendJSON, doThings, Db.all('posts')) � �//+ shellApp :: _ -> Stringvar shellApp = compose(display, doThings, prompt("what's up?"))

60 of 176

//url :: String -> URLvar url = function (t) { return 'http://gdata.youtube.com/feeds/api/videos?q=' + t + '&alt=json' }�� // src :: YoutubeEntry -> URLvar 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 DOMvar widget = compose(map(images), getJSON, url);�� /////////////////////////////////////////////////////////////////////////////////////� widget('cats').fork(log, setHtml($('#youtube')));

61 of 176

recognize most loops are one of

map

filter

reduce

62 of 176

If you write a loop you get an F !

Loops

63 of 176

Category Theory

compose :: (b -> c) -> (a -> b) -> (a -> c)��id :: a -> a

64 of 176

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))

65 of 176

66 of 176

Nulls

67 of 176

Nulls

Callbacks

68 of 176

Nulls

Callbacks

Errors

69 of 176

Nulls

Callbacks

Errors

Side effects

70 of 176

Objects

  1. Containers/Wrappers for values
  2. No methods
  3. Not nouns
  4. Probably won’t be making your own often

*think of it this way for now.

71 of 176

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}

72 of 176

Objects

capitalize("flamethrower")�//=> "Flamethrower"

�capitalize(Container.of("flamethrower"))

//=> [object Object]

73 of 176

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”)

74 of 176

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”)

75 of 176

Objects

Container.of(3).map(add(1))

//=> Container(4)

[3].map(add(1))

//=> [4]

76 of 176

the true map

goes within

77 of 176

Objects

Container.of([1,2,3]).map(reverse).map(first)�//=> Container(3)

�Container.of("flamethrower").map(length).map(add(1))

//=> Container(13)

78 of 176

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)

79 of 176

Objects

map(match(/cat/g), Container.of("catsup"))

//=> Container([“cat”])

map(compose(first, reverse), Container.of("dog"))

//=> Container(“g”)

80 of 176

Functor

“An object or data structure you can map over”

functions: map

81 of 176

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']

82 of 176

Them pesky nulls

//+ getElement :: String -> DOM

var getElement = document.querySelector

//+ getNameParts :: String -> [String]var getNameParts = compose(split(' '), getText, getElement)��getNameParts('#fullname')�//=> Boom!

83 of 176

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

84 of 176

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”)

85 of 176

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)

86 of 176

Maybe

//+ firstMatch :: String -> String

var firstMatch = compose(first, match(/cat/g))

�firstMatch("dogsup")�//=> Boom!

87 of 176

Maybe

//+ firstMatch :: String -> Maybe(String)

var firstMatch = compose(map(first), Maybe.of, match(/cat/g))

�firstMatch("dogsup")�//=> Maybe(null)

88 of 176

Maybe

//+ firstMatch :: String -> Maybe(String)

var firstMatch = compose(map(first), Maybe.of, match(/cat/g))

�firstMatch("catsup")�//=> Maybe(“cat”)

89 of 176

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

90 of 176

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

91 of 176

92 of 176

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")

93 of 176

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.

94 of 176

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”

95 of 176

unsafePerformIO()

96 of 176

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

97 of 176

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() });

}

98 of 176

EventStream

An infinite list of results

Dual of array

Its map is sometimes lazy

Calls the mapped function each time an event happens

99 of 176

EventStream

var ids = map(function(e) { return '#'+e.id }, Bacon.fromEventTarget(document, "click"))�//=> EventStream(String)��ids.onValue(function(id) { alert('you clicked ' +id) })�

100 of 176

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) })�

101 of 176

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

102 of 176

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) })�

103 of 176

Future

//+ makeHtml :: Post -> HTML

var makeHtml = function(title){ return "<div>"+title+"</div>"}

//+ createPage :: Post -> HTMLvar 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) })�

104 of 176

Pointed Functors

of :: a -> F a

aka: pure, return, unit, point

105 of 176

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'})

106 of 176

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

http://jsbin.com/xetilo

answers: http://jsbin.com/sefodi

107 of 176

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

108 of 176

Laws & Properties are useful!

109 of 176

Functor Laws

// identity�map(id) == id

// composition�compose(map(f), map(g)) == map(compose(f, g))

110 of 176

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]

111 of 176

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]

112 of 176

Natural Transformations

nt :: F a -> T a

“Takes one functor to another without knowing anything about the values”

113 of 176

Natural Transformations

maybeToArray :: Maybe a -> Array a

maybeToArray(Maybe(2))�//=> [2]��maybeToArray(Maybe(null))�//=> []

114 of 176

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]

115 of 176

Card Game

116 of 176

Card Game #1

"Make an api call with an id and possibly retrieve a post"

117 of 176

Card Game #1

"Make an api call with an id and possibly retrieve a post"

Future(Maybe(Post))

118 of 176

Card Game #2

"Click a navigation link and insert the corresponding html on the page"

119 of 176

Card Game #2

"Click a navigation link and insert the corresponding html on the page"

EventStream(IO(Dom))

120 of 176

Card Game #3

"Submit a signup form & return errors or make an API call that will create a user”

121 of 176

Card Game #3

"Submit a signup form & return errors or make an API call that will create a user”

EventStream(Either(Future(User)))

122 of 176

Monads

“Nest computations”

functions: join, chain

123 of 176

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

124 of 176

Monads

join(Container.of(Container.of(2)))

//=> Container(2)

125 of 176

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)

126 of 176

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)

127 of 176

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();

128 of 176

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();

129 of 176

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)

130 of 176

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)

131 of 176

Monads

//+ chain :: (a -> M b) -> M a -> M b

var chain = function(f) {

return compose(join, map(f))

}

a.k.a: flatMap, bind

132 of 176

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)

133 of 176

Monads

//+ chain :: (a -> M b) -> M a -> M b

var chain = function(f) {

return compose(join, map(f))

}

var join = chain(id)

134 of 176

Monads

135 of 176

Monad Laws

compose(join, map(g), join, map(f))

mcompose(g, f)

136 of 176

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))

137 of 176

Applicative Functor

“Run full computations in a context”

functions: ap, liftA2, liftA..n

138 of 176

Applicatives

map(add(1), Container(2))�//=> Container(3)

map(add, Container(2))�//=> Container(add(2))

139 of 176

Applicatives

ap :: A (a -> b) -> A a -> A b

Pointed Functor + ap = Applicative

aka: <*>

140 of 176

Applicatives

Container.of(f).ap(Container(x))�//=> Container(f(x))

Container.of(f).ap(Container(x)).ap(Container(y))�//=> Container(f(x, y))

141 of 176

Applicatives

Container.of(add).ap(Container(1)).ap(Container(3))�//=> Container(4)

142 of 176

Applicatives

Maybe.of(add).ap(Maybe(1)).ap(Maybe(3))�//=> Maybe(4)��Maybe.of(add).ap(Maybe(1)).ap(Maybe(null))�//=> Maybe(null)

143 of 176

Applicatives

var loadPage = _.curry(function(products, reviews){ render(products.zip(reviews) })

�Future.of(loadPage).ap(Api.get('/products')).ap(Api.get('/reviews'))

144 of 176

Applicatives

var showLoaded = _.curry(function(tweetbtn, fbbtn){ alert('Done!') })

�EventStream.of(showLoaded).ap(tweet_btn.on('load’)).ap(fb_btn.on('load’))

145 of 176

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)

146 of 176

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)

147 of 176

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)

148 of 176

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)

http://jsbin.com/yahojo

answers: http://jsbin.com/puyime

149 of 176

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)

150 of 176

Monoids

“Combination/Accumulation”

functions: empty, concat, mconcat

151 of 176

Monoids

reduce(function(acc, x) {� return acc + x;�}, 0, [1,2,3])

152 of 176

Monoids

reduce(function(acc, x) {� return acc * x;�}, 1, [1,2,3])

153 of 176

Monoids

reduce(function(acc, x) {� return acc || x;�}, false, [false, false, true])

154 of 176

Monoids

reduce(function(acc, x) {� return acc && x;�}, true, [false, false, true])�

155 of 176

Monoids

Semigroup:

“Anything that has a concat (combination) method”

156 of 176

Monoids

Monoid:

“Any semigroup that has an empty() method”

157 of 176

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) }�

158 of 176

Monoids

Sum(2).concat(Sum(3))�//=> Sum(5)

159 of 176

Monoids

Sum(2).concat(Sum(3)).concat(Sum(5))�//=> Sum(10)

160 of 176

Monoids

mconcat([Sum(2), Sum(3), Sum(5)])�//=> Sum(10)

161 of 176

Monoids

mconcat([Product(2), Product(3), Product(5)])�//=> Product(30)

162 of 176

Monoids

mconcat([Any(false), Any(true), Any(false)])�//=> Any(true)

163 of 176

Monoids

mconcat([All(false), All(true), All(false)])�//=> All(false)

164 of 176

Monoids

mconcat([Max(13), Max(2), Max(9)])�//=> Max(13)

165 of 176

Monoids

compose(getResult, mconcat, map(Sum))([1,2,3])�//=>6

166 of 176

Monoids

compose(getResult, mconcat, map(Any))([false,false,true])�//=>true

167 of 176

Monoids

mconcat([toUpperCase, reverse])(["bonkers"])�//=> “BONKERSsreknob”

http://jsbin.com/xasoqu

answers: http://jsbin.com/mufebe

168 of 176

Monoids

mconcat([Failure(["message1"]), Success(attrs), Failure(["message2"])])�//=> Failure(["message1", "message2"])

169 of 176

Monoids

mconcat([Success(attrs), Success(attrs)])�//=> Success(attrs)

170 of 176

Monoids

var checkValidations = mconcat([checkPassword, checkEmail, checkName])��checkValidations({name: "Burt"})�//=> Failure([“need a password”, “need an email”])

171 of 176

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))

172 of 176

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))

173 of 176

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))

174 of 176

Fantasy Land

175 of 176

Congratulations!

You’re a pioneer.

These techniques are new to the JavaScript ecosystem.

176 of 176

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