Skip to content

Instantly share code, notes, and snippets.

@i-am-tom
Created June 12, 2017 19:38
Show Gist options
  • Save i-am-tom/71e351dd3c389a052e99324f152571d3 to your computer and use it in GitHub Desktop.
Save i-am-tom/71e351dd3c389a052e99324f152571d3 to your computer and use it in GitHub Desktop.
All the example code from the Extend article of Fantas, Eel, and Specification.
const daggy = require('daggy')
const { uncurryN } = require('wi-jit')
Array.prototype.empty = () => []
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)
}
Pair_.prototype.sneakyPeekMap = function (f) {
return Pair_(this._1, f(this))
}
Pair_.prototype.extend = Pair_.prototype.sneakyPeekMap
return Pair_
}
//- Sum represents "hunger"
const Adventurer = Pair(Sum)
//+ type User = { name :: String, isHungry :: Bool }
const exampleUser = {
name: 'Tom',
isHungry: false
}
// You get the idea... WorkPair again!
//+ slayDragon :: User -> Adventurer User
const slayDragon = user =>
Adventurer(Sum(100), user)
//+ slayDragon :: User -> Adventurer User
const runFromDragon = user =>
Adventurer(Sum(50), user)
//- Eat IF we're hungry
//+ eat :: User -> Adventurer User
const eat = user =>
user.isHungry
? Adventurer(Sum(-100), Object.assign(
{}, user, { isHungry: false }
))
: Adventurer(Sum(0), user)
//- How could we know when we're hungry?
//- This function goes the other way...
//+ areWeHungry :: Adventurer User -> User
const areWeHungry = ({ _1: { value: hunger }, _2: user}) =>
hunger > 200
? Object.assign({}, user, { isHungry: true })
: user
// Now, we do a thing, check our hunger,
// and eat if we need to!
// WE ARE SELF-AWARE.
// SKYNET
console.log(
slayDragon(exampleUser)
.sneakyPeekMap(areWeHungry).chain(eat)
// Adventurer(Sum(100), not hungry)
.chain(slayDragon)
.sneakyPeekMap(areWeHungry).chain(eat)
// Adventurer(Sum(200), not hungry)
.chain(runFromDragon)
.sneakyPeekMap(areWeHungry).chain(eat)
// Adventurer(Sum(150), not hungry)!
)
//- A root and a list of children!
//+ type RoseTree a = (a, [RoseTree a])
const RoseTree = daggy.tagged(
'root', 'forest'
)
RoseTree.prototype.map = function (f) {
return RoseTree(
f(this.root), // Transform the root...
this.forest.map( // and other trees.
rt => rt.map(f)))
}
//+ extend :: RoseTree a
//+ ~> (RoseTree a -> b)
//+ -> RoseTree b
RoseTree.prototype.extend =
function (f) {
return RoseTree(
f(this),
this.forest.map(rt =>
rt.extend(f))
)
}
const MyTree = RoseTree(10, [
RoseTree(1, [
RoseTree(2, [])
]),
RoseTree(3, []),
RoseTree(4, [
RoseTree(5, [
RoseTree(6, [])
])
]),
RoseTree(7, []),
RoseTree(8, [
RoseTree(9, []),
RoseTree(10, [])
])
])
console.log(
JSON.stringify(
// Now, it's super easy to do this!
MyTree.extend(({ root, forest }) =>
forest.length < 1
? Object.assign({}, root, { colour: 'RED' })
: forest.length < 5
? Object.assign({}, root, { colour: 'ORANGE' })
: Object.assign({}, root, { colour: 'GREEN' }))
, null, ' ')
)
const List = daggy.taggedSum({
Cons: ['head', 'tail'],
Nil: []
})
const { Cons, Nil } = List
//- Usually, if you can write something for
//- Array, you can write it for List!
List.prototype.map = function (f) {
return this.cata({
// Do nothing, we're done!
Nil: () => Nil,
// Transform head, recurse on tail
Cons: (head, tail) =>
Cons(f(head), tail.map(f))
})
}
const arrayToList = xs => {
if (!xs.length) return Nil
const [ head, ... tail ] = xs
return Cons(head, arrayToList(tail))
}
List.prototype.toArray = function () {
return this.cata({
Cons: (head, tail) => ([
head, ... tail.toArray()
]),
Nil: () => []
})
}
// Starting from today... (in celsius!)
const temperatures = arrayToList(
[23, 19, 19, 18, 18, 20, 24])
//+ List a ~> (List a -> b) -> List b
List.prototype.extend = function (f) {
return this.cata({
// Extend the list, repeat on tail.
Cons: (head, tail) => Cons(
f(this), tail.extend(f)
),
Nil: () => Nil // We're done!
})
}
console.log(
// [YAY, YAY, YAY, YAY, BOO, BOO, ???]
temperatures
.extend(({ head, tail }) =>
tail == Nil
? '???'
: head < tail.head
? 'BOO' : 'YAY')
.toArray()
)
Array.prototype.extend = function (f) {
if (this.length === 0) return []
const [ _, ... tail ] = this
return [ f(this), ... tail.extend(f) ]
}
console.log(
// Now, we can use array-destructuring :)
[23, 19, 19, 18, 18, 20, 24].extend(
([head, ... tail]) =>
tail.length == 0
? '???'
: head < tail[0]
? 'BOO' : 'YAY')
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment