Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.