Skip to content

Instantly share code, notes, and snippets.

@i-am-tom
Created April 27, 2017 08:44
Show Gist options
  • Save i-am-tom/286cb133f74404305814e311e7162351 to your computer and use it in GitHub Desktop.
Save i-am-tom/286cb133f74404305814e311e7162351 to your computer and use it in GitHub Desktop.
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