Skip to content

Commit

Permalink
Add applicative functors 馃殌
Browse files Browse the repository at this point in the history
  • Loading branch information
kutyel committed Dec 15, 2017
1 parent bbc51a0 commit f1f6b81
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 81 deletions.
69 changes: 69 additions & 0 deletions __tests__/applicatives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const { add, curry, reduce } = require('..')
const { liftA2, Maybe, Task, IO } = require('../functors')

// TEST HELPERS
// =====================

const getPost = i =>
new Task((rej, res) =>
setTimeout(() => res({ id: i, title: 'Love them futures' }), 300)
)

const getComments = i =>
new Task((rej, res) =>
setTimeout(
() =>
res(['This book should be illegal', 'Monads are like space burritos']),
300
)
)

// fib browser for test
const localStorage = {}

describe('You are now an APPLICATIVE FUNCTOR genius!!! 馃 馃憦馃徏 馃憦馃徏 馃憦馃徏', () => {
// Exercise 1
test('Write a function that adds two possibly null numbers together using Maybe and ap().', () => {
// ex1 :: Number -> Number -> Maybe Number
const ex1 = (x, y) =>
Maybe.of(add)
.ap(Maybe.of(x))
.ap(Maybe.of(y))
expect(ex1(2, 3)).toEqual(Maybe.of(5))
expect(ex1(null, 3)).toEqual(Maybe.of(null))
})

// Exercise 2
test("Now write a function that takes 2 Maybe's and adds them. Use liftA2 instead of ap().", () => {
// ex2 :: Maybe Number -> Maybe Number -> Maybe Number
const ex2 = liftA2(add)
expect(ex2(Maybe.of(2), Maybe.of(3))).toEqual(Maybe.of(5))
expect(ex2(Maybe.of(null), Maybe.of(3))).toEqual(Maybe.of(null))
})

// Exercise 3
test('Run both getPost(n) and getComments(n) then render the page with both. (The n arg is arbitrary.)', () => {
const makeComments = reduce((acc, c) => `${acc}<li>${c}</li>`, '')
const render = curry(
({ title }, cs) => `<div>${title}</div>${makeComments(cs)}`
)
// ex3 :: Task Error HTML
const ex3 = liftA2(render, getPost(13), getComments(13))
ex3.fork(console.log, html =>
expect(html).toBe(
'<div>Love them futures</div><li>This book should be illegal</li><li>Monads are like space burritos</li>'
)
)
})

// Exercise 4
test('Write an IO that gets both player1 and player2 from the cache and starts the game.', () => {
localStorage.player1 = 'toby'
localStorage.player2 = 'sally'
const getCache = x => new IO(() => localStorage[x])
const game = curry((p1, p2) => `${p1} vs ${p2}`)
// ex4 :: IO String
const ex4 = liftA2(game, getCache('player1'), getCache('player2'))
expect(ex4.unsafePerformIO()).toBe('toby vs sally')
})
})
96 changes: 15 additions & 81 deletions functors.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const Task = require('data.task')
const { compose, curry, identity } = require('.')

// const inspect = x => (x && x.inspect ? x.inspect() : x)

/**
* Identity
*/
Expand All @@ -20,10 +18,6 @@ class Identity {
}
}

// Identity.prototype.inspect = function () {
// return 'Identity(' + inspect(this.__value) + ')'
// }

/**
* Maybe
*/
Expand All @@ -36,6 +30,10 @@ class Maybe {
return new Maybe(x)
}

ap (functor) {
return this.isNothing() ? Maybe.of(null) : functor.map(this.__value)
}

isNothing (f) {
return this.__value === null || this.__value === undefined
}
Expand All @@ -44,30 +42,11 @@ class Maybe {
return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value))
}

// chain(f) {
// return this.map(f).join()
// }

join () {
return this.isNothing() ? Maybe.of(null) : this.__value
}
}

// Maybe.prototype.ap = function (other) {
// return this.isNothing() ? Maybe.of(null) : other.map(this.__value)
// }

// Maybe.prototype.inspect = function () {
// return 'Maybe(' + inspect(this.__value) + ')'
// }

// Either
// const Either = function () {}

// Either.of = function (x) {
// return new Right(x)
// }

/**
* Left
*/
Expand All @@ -86,22 +65,6 @@ class Left {
}
}

// Left.prototype.ap = function (other) {
// return this
// }

// Left.prototype.join = function () {
// return this
// }

// Left.prototype.chain = function () {
// return this
// }

// Left.prototype.inspect = function () {
// return 'Left(' + inspect(this.__value) + ')'
// }

/**
* Right
*/
Expand All @@ -120,32 +83,6 @@ class Right {
}
}

// Right.prototype.join = function () {
// return this.__value
// }

// Right.prototype.chain = function (f) {
// return f(this.__value)
// }

// Right.prototype.ap = function (other) {
// return this.chain(function (f) {
// return other.map(f)
// })
// }

// Right.prototype.join = function () {
// return this.__value
// }

// Right.prototype.chain = function (f) {
// return f(this.__value)
// }

// Right.prototype.inspect = function () {
// return 'Right(' + inspect(this.__value) + ')'
// }

/**
* IO
*/
Expand All @@ -158,6 +95,14 @@ class IO {
return new IO(() => x)
}

chain (f) {
return this.map(f).join()
}

ap (functor) {
return this.chain(f => functor.map(f))
}

map (f) {
return new IO(compose(f, this.unsafePerformIO))
}
Expand All @@ -167,20 +112,6 @@ class IO {
}
}

// IO.prototype.chain = function (f) {
// return this.map(f).join()
// }

// IO.prototype.ap = function (a) {
// return this.chain(function (f) {
// return a.map(f)
// })
// }

// IO.prototype.inspect = function () {
// return 'IO(' + inspect(this.unsafePerformIO) + ')'
// }

const unsafePerformIO = x => x.unsafePerformIO()

const either = curry((f, g, e) => {
Expand All @@ -192,6 +123,8 @@ const either = curry((f, g, e) => {
}
})

const liftA2 = curry((f, fx, fy) => fx.map(f).ap(fy))

Task.prototype.join = function () {
return this.chain(identity)
}
Expand All @@ -201,6 +134,7 @@ module.exports = {
Identity,
IO,
Left,
liftA2,
Maybe,
Right,
Task,
Expand Down

0 comments on commit f1f6b81

Please sign in to comment.