Skip to content

Instantly share code, notes, and snippets.

@reidev275
Last active November 19, 2019 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reidev275/97da0fb29e72b1d5305011f31a1cbcf6 to your computer and use it in GitHub Desktop.
Save reidev275/97da0fb29e72b1d5305011f31a1cbcf6 to your computer and use it in GitHub Desktop.
Mars Rover Kata written in TypeScript with a DSL and monoids allowing the effect to be defined by the interpreter. No Higher Kinded Types required
export type Heading = "north" | "south" | "east" | "west";
export type Position = { heading: Heading; x: number; y: number };
//recursive data structure with our core commands and type
export type DslC =
| { kind: "position"; a: Position }
| { kind: "forward"; a: DslC }
| { kind: "right"; a: DslC };
export const pos = (a: Position): DslC => ({
kind: "position",
a
});
export const forward = (a: DslC): DslC => ({
kind: "forward",
a
});
export const right = (a: DslC): DslC => ({
kind: "right",
a
});
type Iso<A> = (a: A) => A;
// isomorphic functions (A -> A) form a monoid
// which means we can compose 0 -> n of them together
export const go = <A>(...as: Iso<A>[]): Iso<A> =>
as.reduce((a, b) => x => b(a(x)), x => x);
import { DslC, Position, Heading } from "./dsl"
const _right = (x: Heading) => {
switch (x) {
case "north":
return "east";
case "east":
return "south";
case "south":
return "west";
case "west":
return "north";
}
};
const _forward = (x: Position): Position => {
switch (x.heading) {
case "north":
return { ...x, y: x.y + 1 };
case "east":
return { ...x, x: x.x + 1 };
case "south":
return { ...x, y: x.y - 1 };
case "west":
return { ...x, y: x.x - 1 };
}
};
//simple recursive evaluation
export const evaluate = (a: DslC): Position => {
switch (a.kind) {
case "position":
return a.a;
case "forward":
const pf = evaluate(a.a);
return _forward(pf);
case "right":
const pr = evaluate(a.a);
return { ...pr, heading: _right(pr.heading) };
}
};
//could extend our implementation to any effect
//in this case we could use async communication
//to a remote server via promises
export const evaluateP = async (a: DslC): Promise<Position> => {
switch (a.kind) {
case "position":
return a.a;
case "forward":
const pf = await evaluate(a.a);
return _forward(pf);
case "right":
const pr = await evaluate(a.a);
return { ...pr, heading: _right(pr.heading) };
}
};
import { right, forward, pos, go } from "./dsl";
import { evaluate } from "./evaluators";
// read direction is right to left when functions are nested
//const knight2 = pos => forward(right(forward(forward(pos))));
//using a monoid to pass instructions allows us to write left to right
const knight = go(forward, forward, right, forward);
//we can implement directions in terms of other directions
const left = go(right, right, right);
const backward = go(right, right, forward, right, right);
const origin = pos({ heading: "north", x: 0, y: 0 });
const raw = knight(origin);
console.log(raw);
//{ kind: 'forward',
// a: { kind: 'right', a: { kind: 'forward', a: [Object] } } }
// we can now evaluate our program
const result = evaluate(raw);
console.log(result);
//{ heading: 'east', x: 1, y: 2 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment