Created
April 27, 2017 08:44
-
-
Save i-am-tom/286cb133f74404305814e311e7162351 to your computer and use it in GitHub Desktop.
Pair to Writer, with a little help from monoids.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const daggy = require('daggy') | |
const { uncurryN } = require('wi-jit') // Shameless plug :-) | |
// We'll need these instances for an example later... | |
Function.prototype.map = function (that) { | |
return x => that(this(x)) | |
} | |
Function.prototype.ap = function (that) { | |
return x => that(x)(this(x)) | |
} | |
// Aaand a sneaky little monoid: | |
Sum = daggy.tagged('value') | |
Sum.prototype.concat = function (that) { | |
return Sum(this.value + that.value) | |
} | |
Sum.empty = () => Sum(0) | |
// Our fully-constructed Pair type. | |
const Pair = T => { | |
const Pair_ = daggy.tagged('_1', '_2') | |
Pair_.prototype.map = function (f) { | |
return Pair_(this._1, f(this._2)) | |
} | |
Pair_.prototype.ap = function (fs) { | |
return Pair_(fs._1.concat(this._1), | |
fs._2(this._2)) | |
} | |
Pair_.of = x => Pair_(T.empty(), x) | |
Pair_.prototype.chain = function (f) { | |
const that = f(this._2) | |
return Pair_(this._1.concat(that._1), | |
that._2) | |
} | |
return Pair_ | |
} | |
/* === COST PAIR EXAMPLE === */ | |
const CostPair = Pair(Sum) | |
//- Database lookups are pretty costly... | |
//+ userFromDB :: Int | |
//+ -> Pair (Sum Int) String | |
const nameFromDB = id => CostPair( | |
Sum(100), /* Do some DB stuff */ "Maureen") | |
//- ... but ordering/counting is harder. | |
//+ rankFromDB :: Int | |
//+ -> Pair (Sum Int) Int | |
const rankFromDB = id => CostPair( | |
Sum(500), /* Do some DB stuff */ 3) | |
// I skipped this in the post because it wasn't | |
// super-relevant, but lift2 needs to be partial | |
// application-friendly for the second example. | |
const lift2 = uncurryN(f => a => b => b.ap(a.map(f))) | |
//- Do both jobs, end up with Sum(600)! | |
//+ getUserData :: Int | |
//+ -> Pair (Sum Int) User | |
console.log( | |
( id => lift2( | |
x => y => ({ name: x, rank: y }), | |
nameFromDB(id), rankFromDB(id)) | |
)(5) | |
) | |
// ===================== // | |
//- By the way, we can use `Function` as an | |
//- `Applicative` as we did last time, and | |
//- that lets us write this with a couple | |
//- of `lift2` calls! | |
//+ getUserData_ :: Int -> Pair (Sum Int) User | |
console.log( | |
lift2( | |
lift2(x => y => ({ name: x, rank: y })), | |
nameFromDB, rankFromDB | |
)(5) | |
) | |
// Sneaky monkey patch! | |
Array.empty = () => [] | |
LogPair = Pair(Array) | |
// Some users... | |
const users = [ | |
{ | |
name: 'Tom', | |
hair: 'brown', | |
height: 182, | |
likes: 'biscuits' | |
}, | |
{ | |
name: 'Dick', | |
hair: 'blonde', | |
height: 160, | |
likes: 'clichés' | |
}, | |
{ | |
name: 'Harry', | |
hair: 'none', | |
height: 200, | |
likes: 'Convoluted programming examples' | |
} | |
] | |
console.log( | |
// Lift the users into the LogPair type. | |
// (The left side at this point is []) | |
LogPair.of(users) | |
// Keep only the users with brown hair | |
.chain(users => LogPair(['Brown hair'], | |
users.filter(user => | |
user.hair === 'brown'))) | |
// Keep only the users over 180cm tall | |
.chain(users => LogPair(['Tall'], | |
users.filter(user => | |
user.height > 180))) | |
// Count the remaining users | |
.map(users => users.length) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment