Pair to Writer, with a little help from monoids.
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