Skip to content

Instantly share code, notes, and snippets.

@richdouglasevans
Forked from i-am-tom/extend.js
Last active November 1, 2018 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save richdouglasevans/61eae2a787bd616f04e63a642a0dca5d to your computer and use it in GitHub Desktop.
Save richdouglasevans/61eae2a787bd616f04e63a642a0dca5d 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 = () => [];
const Sum = daggy.tagged("Sum", ["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("Pair", ["_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("RoseTree", ["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("List", {
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