Skip to content

Instantly share code, notes, and snippets.

@kmamykin
Last active January 29, 2016 22:48
Show Gist options
  • Save kmamykin/ee2e87db5c49f0613434 to your computer and use it in GitHub Desktop.
Save kmamykin/ee2e87db5c49f0613434 to your computer and use it in GitHub Desktop.
Exploration of Monad ideas in JavaScript
/node_modules

Installation

$ npm install

$ Run

$ babel-node <...>.js
const SuccessEvent = () => ({type: 'SuccessEvent'})
const FailureEvent = () => ({type: 'FailureEvent'})
const operationOnEntity3 = entity1 => entity2 => entity3 => {
if (entity1.state.isGood && entity2.state.isGreat) {
return entity3.append(SuccessEvent())
} else {
return entity3.append(FailureEvent())
}
}
const Command = fnP => {
const ap = argP => Command(fnP.then(f => argP.then(arg => f(arg))))
const then = (f, e) => fnP.then(f, e)
return {ap, then}
}
const op = Promise.resolve(operationOnEntity3)
const e1 = Promise.resolve({state:{isGood: true}})
const e2 = Promise.resolve({state:{isGreat: true}})
const e3 = Promise.resolve({
append: e => Promise.resolve(e)
})
//op.then(opFn => e1.then(e => opFn(e)))
// .then(opFn => e2.then(e => opFn(e)))
// .then(opFn => e3.then(e => opFn(e)))
// .then(console.log, console.error)
Command(op)
.ap(e1)
.ap(e2)
.ap(e3)
.then(console.log, console.error)
import daggy from 'daggy'
const UnitEvent = daggy.taggedSum({
UnitRegistered: ['serialNumber'],
UnitProvisioned: ['profile'],
UnitDecommissioned: ['time']
})
console.log(UnitEvent.UnitRegistered('123'))
console.log(typeof UnitEvent.UnitRegistered('123'))
console.log(UnitEvent.UnitRegistered('123') instanceof UnitEvent)
console.log(UnitEvent.UnitRegistered('123') instanceof UnitEvent.UnitRegistered)
console.log(UnitEvent.UnitRegistered('123') instanceof UnitEvent.UnitProvisioned)
UnitEvent.UnitRegistered('123').cata({
UnitRegistered: (sn) => console.log(sn),
UnitProvisioned: (sn, p) => console.log(sn, p)
})
const history = [
UnitEvent.UnitRegistered('123'),
UnitEvent.UnitProvisioned({a: 1, b: 2}),
UnitEvent.UnitDecommissioned(new Date())
]
const toEndofunction = event => event.cata({
UnitRegistered: (serialNumber) => state => ({status: 'registered', serialNumber}),
UnitProvisioned: (profile) => state => ({...state, status: 'provisioned', profile}),
UnitDecommissioned: (time) => state => ({...state, status: 'decommissioned', time})
})
const compose = (fns) => fns.reduce((composed, fn) => s => fn(composed(s)), s => s)
console.log(compose(history.map(toEndofunction))({}))
class UnitBaseEvent {
constructor(serialNumber) {
this.serialNumber = serialNumber
}
match (spec) {
return spec[this.constructor.name](this)
}
}
class UnitProvisioned extends UnitBaseEvent {
constructor (sn, a) {
super(sn)
this.a = a
}
}
class UnitRegistered extends UnitBaseEvent {
static REGID = 123
}
console.log(new UnitProvisioned('123', 1))
console.log(new UnitProvisioned('123', 2) instanceof UnitProvisioned)
console.log(new UnitProvisioned('123', 2) instanceof UnitBaseEvent)
const event = new UnitProvisioned('123', 2)
event.match({
UnitProvisioned: (e) => { console.log('UP ' + e.a) },
UnitRegistered: (e) => { console.log('UR') }
})
console.log([new UnitProvisioned('123', 1), new UnitProvisioned('123', 2), new UnitProvisioned('123', 3), new UnitRegistered('123')])
import {Free} from 'monet'
console.log(Free.Return(1))
const Nothing = () => ({
bind: _ => Nothing(),
run: f => f(null)
})
const Just = value => ({
bind: f => f(value),
run: f => f(value)
})
const half = number => number % 2 === 0 ? Just(number / 2) : Nothing()
Just(32).bind(half).bind(half).bind(half).run(console.log)
Just(41).bind(half).bind(half).bind(half).run(console.log)
{
"name": "js-monads",
"version": "1.0.0",
"description": "",
"main": "maybeMonad.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gist.github.com/ee2e87db5c49f0613434.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://gist.github.com/ee2e87db5c49f0613434"
},
"homepage": "https://gist.github.com/ee2e87db5c49f0613434",
"dependencies": {
"daggy": "0.0.1",
"monet": "^0.8.6"
},
"devDependencies": {
"babel-preset-es2015-node4": "^2.0.2",
"babel-preset-stage-0": "^6.3.13"
},
"babel": {
"presets": [
"es2015-node4",
"stage-0"
]
}
}
// http://adit.io/posts/2013-06-10-three-useful-monads.html
// f :: env -> result
const Reader = f => {
const run = env => f(env)
const bind = func => Reader(env => func(run(env)).run(env))
return { run, bind }
}
// Example 1
const sharedEnv = {
'first': 'First',
'second': 'Second',
'keys': ['first', 'second']
}
const r1 = Reader(env => env['first'])
console.log(r1.run(sharedEnv)) // 'First value'
const r2 = Reader(env => Object.keys(env))
console.log(r2.run(sharedEnv)) // [ 'first', 'second', 'keys' ]
console.log(Reader(env => env['keys']).bind(keys => Reader(env => keys.map(key => env[key]))).run(sharedEnv)) // [ 'First', 'Second' ]
// Example 2
const Database = () => {
const artists = [
{ id: 1, name: 'Ringo' },
{ id: 2, name: 'John' },
{ id: 3, name: 'George' },
{ id: 4, name: 'Paul' }
]
const bands = [{ id: 1, name: 'The Beatles', members: [1, 2, 3, 4] }]
return {
findBandByName: name => bands.find(band => band.name === name),
getArtist: id => artists.find(artist => artist.id === id)
}
}
const db = Database() // this is the shared environment
const lookupBand = name => Reader(db => db.findBandByName(name))
const artistsForBand = band => Reader(db => band.members.map(id => db.getArtist(id)))
const greatestBand = Reader(_ => 'The Beatles')
const greatestMusicians = greatestBand.bind(lookupBand).bind(artistsForBand)
console.log(greatestMusicians.run(db))
const combineReaders = (r1, r2) => v1 => env => {
const v2 = r1(v1)(env)
const v3 = r2(v2)(env)
return v3
}
// Example 1
const sharedEnv = {
'first': 'First',
'second': 'Second',
'keys': ['first', 'second']
}
const r1 = env => env['first']
console.log(r1(sharedEnv)) // 'First value'
const r2 = env => Object.keys(env)
console.log(r2(sharedEnv)) // [ 'first', 'second', 'keys' ]
console.log(combineReaders((name => env => env[name]), (keys => env => keys.map(key => env[key])))('keys')(sharedEnv)) // [ 'First', 'Second' ]
// Example 2
const Database = () => {
const artists = [
{ id: 1, name: 'Ringo' },
{ id: 2, name: 'John' },
{ id: 3, name: 'George' },
{ id: 4, name: 'Paul' }
]
const bands = [{ id: 1, name: 'The Beatles', members: [1, 2, 3, 4] }]
return {
findBandByName: name => bands.find(band => band.name === name),
getArtist: id => artists.find(artist => artist.id === id)
}
}
const db = Database() // this is the shared environment
const lookupBand = name => db => db.findBandByName(name)
const artistsForBand = band => db => band.members.map(id => db.getArtist(id))
const greatestMusicians = combineReaders(lookupBand, artistsForBand)('The Beatles')
console.log(greatestMusicians(db))
const combineReaders2 = (r1, r2) => env => r2(r1(env))(env)
const greatestMusicians2 = combineReaders2(lookupBand('The Beatles'), band => artistsForBand(band))
console.log(greatestMusicians2(db))
const Writer = (value, log = []) => {
const run = () => ({ value, log })
const bind = f => {
const r = f(value).run()
return Writer(r.value, [...log, ...r.log])
}
return { run, bind }
}
const incBy = number => x => Writer(x + number, [`incremented ${x} by ${number}`])
const multiplyBy = number => x => Writer(x * number, [`multiplied ${x} by ${number}`])
console.log(Writer(4).bind(multiplyBy(3)).bind(incBy(1)).run())
const Writer = (empty, append) => (value, accumulator = empty) => {
const run = () => ({ value, accumulator })
const bind = f => {
const r = f(value).run()
return Writer(empty, append)(r.value, append(accumulator, r.accumulator))
}
return { run, bind }
}
const NumberWithTrace = Writer([], (a, b) => a.concat(b))
const incBy = number => x => NumberWithTrace(x + number, [`incremented ${x} by ${number}`])
const multiplyBy = number => x => NumberWithTrace(x * number, [`multiplied ${x} by ${number}`])
console.log(NumberWithTrace(4).bind(multiplyBy(3)).bind(incBy(1)).run())
// Example with IO side-effect
const XXX = Writer(_ => _, (a, b) => () => b(a()))
const op1 = number => x => XXX(x + number, () => console.log(`incremented ${x} by ${number}`))
const op2 = number => x => XXX(x * number, () => console.log(`multiplied ${x} by ${number}`))
const res = XXX(4).bind(op1(10)).bind(op2(2)).run()
console.log(res.value)
res.accumulator()
const Writer = (empty, append) => (value, accumulator = empty) => {
const bind = f => {
const r = f(value)
return Writer(empty, append)(r.value, append(accumulator, r.accumulator))
}
return { value, accumulator, bind }
}
const NumberWithTrace = Writer([], (a, b) => a.concat(b))
const incBy = number => x => NumberWithTrace(x + number, [`incremented ${x} by ${number}`])
const multiplyBy = number => x => NumberWithTrace(x * number, [`multiplied ${x} by ${number}`])
console.log(NumberWithTrace(4).bind(multiplyBy(3)).bind(incBy(1)))
// Example with IO side-effect
const XXX = Writer(_ => _, (a, b) => () => b(a()))
const op1 = number => x => XXX(x + number, () => console.log(`incremented ${x} by ${number}`))
const op2 = number => x => XXX(x * number, () => console.log(`multiplied ${x} by ${number}`))
const res = XXX(4).bind(op1(10)).bind(op2(2))
console.log(res.value)
res.accumulator()
// Example with Promise IO, accumulator: Promise
const log = message => new Promise((resolve, _) => {
console.log(message);
resolve()
})
const Promised = Writer(Promise.resolve(), (a, b) => a.then(_ => b))
const res1 = Promised(1).bind(n => Promised(n + 2, log(`incremented ${n} by 2`)))
console.log(res1.value)
res1.accumulator.then(_=>console.log('done'))
// Example with Promise IO, accumulator: () => Promise
const Promised2 = Writer(() => Promise.resolve(), (a, b) => () => a().then(_ => b()))
const res2 = Promised2(1).bind(n => Promised2(n + 2, () => log(`incremented ${n} by 2`)))
console.log(res2.value)
res2.accumulator().then(_=>console.log('done'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment