Skip to content

Instantly share code, notes, and snippets.

@sgoguen
Last active July 22, 2024 18:47
Show Gist options
  • Save sgoguen/c2564ce40d95c65ab7ac08706ed6529a to your computer and use it in GitHub Desktop.
Save sgoguen/c2564ce40d95c65ab7ac08706ed6529a to your computer and use it in GitHub Desktop.
Converting GADTs with OOP Examples to TypeScript
// This is a fun experiement where we try reducing the amount of boilerplate code
// to implement the more verbose types in gadt.ts
abstract class Exp<T> {
abstract eval(): T;
}
/// Let's create a function that create a class that extends Exp
function createExp<TInput, T>(evalFn: (input: TInput) => T) {
return class extends Exp<T> {
constructor(private input: TInput) {
super();
}
eval(): T {
return evalFn(this.input);
}
}
}
/// Each of the following statements is the corresponds to defining a new class that extends Exp<T>
const Lit = createExp((n: number) => n);
const StringLit = createExp((s: string) => s);
const Plus = createExp((e: { left: Exp<number>, right: Exp<number> }) => e.left.eval() + e.right.eval());
const Equals = createExp((e: { left: Exp<number>, right: Exp<number> }) => e.left.eval() === e.right.eval());
/// The following definitions aren't classes, but generic functions (with no arguments) that returns a class.
/// It should probably be memoized.
const Cond = <A>() => createExp((e: { cond: Exp<boolean>, left: Exp<A>, right: Exp<A> }) => e.cond.eval() ? e.left.eval() : e.right.eval());
const Tuple = <A, B>() => createExp((e: { left: Exp<A>, right: Exp<B> }): [A, B] => [e.left.eval(), e.right.eval()]);
const Fst = <A, B>() => createExp((t: Exp<[A, B]>) => t.eval()[0]);
const Snd = <A, B>() => createExp((t: Exp<[A, B]>) => t.eval()[1]);
/// You may want to define helper functions to make creating instances of these classes easier
/// and to reduce the amount of boilerplate code
const lit = (value: number) => new Lit(value);
const plus = (left: Exp<number>, right: Exp<number>) => new Plus({ left, right });
const equals = (left: Exp<number>, right: Exp<number>): Exp<boolean> => new Equals({ left, right });
const cond = <A>(cond: Exp<boolean>, left: Exp<A>, right: Exp<A>): Exp<A> => (new (Cond<A>())({ cond, left, right }));
const tuple = <A, B>(left: Exp<A>, right: Exp<B>): Exp<[A, B]> => new (Tuple<A, B>())({ left, right });
const fst = <A, B>(tuple: Exp<[A, B]>): Exp<A> => new (Fst<A, B>())(tuple);
const snd = <A, B>(tuple: Exp<[A, B]>): Exp<B> => new (Snd<A, B>())(tuple);
const ex1: Exp<boolean> = equals(lit(1), lit(2));
const lit1: Exp<number> = lit(1);
const tup1: Exp<[boolean, number]> = tuple(ex1, lit1);
const ex2: Exp<boolean> = fst(tup1);
const ex3: Exp<number> = snd(tup1);
(* OCaml supports GADTs as of version 4.0 *)
type _ term =
| Lit : int -> int term
| Inc : int term -> int term
| IsZ : int term -> bool term
| If : bool term * 'a term * 'a term -> 'a term
| Pair : 'a term * 'b term -> ('a * 'b) term
| Fst : ('a * 'b) term -> 'a term
| Snd : ('a * 'b) term -> 'b term
let rec eval : type a. a term -> a = function
| Lit i -> i
| Inc t -> eval t + 1
| IsZ t -> eval t = 0
| If(b,t,e) -> if eval b then eval t else eval e
| Pair(a,b) -> (eval a, eval b)
| Fst t -> fst (eval t)
| Snd t -> snd (eval t)
// This is a simple translation of Andrew Kennedy's GADT's
//
// data Exp t where
// Lit :: Int → Exp Int
// Plus :: Exp Int → Exp Int → Exp Int
// Equals :: Exp Int → Exp Int → Exp Bool
// Cond :: Exp Bool → Exp a → Exp a → Exp a
// Tuple :: Exp a → Exp b → Exp (a, b)
// Fst :: Exp (a, b) → Exp a
abstract class Exp<T> {
abstract eval(): T;
}
class Lit extends Exp<number> {
constructor(private value: number) {
super();
}
eval() {
return this.value;
}
}
class Plus extends Exp<number> {
constructor(private left: Exp<number>, private right: Exp<number>) {
super();
}
eval() {
return this.left.eval() + this.right.eval();
}
}
class Equals extends Exp<boolean> {
constructor(private left: Exp<number>, private right: Exp<number>) {
super();
}
eval() {
return this.left.eval() === this.right.eval();
}
}
class Cond<A> extends Exp<A> {
constructor(private cond: Exp<boolean>, private left: Exp<A>, private right: Exp<A>) {
super();
}
eval() {
return this.cond.eval() ? this.left.eval() : this.right.eval();
}
}
class Tuple<A, B> extends Exp<[A, B]> {
constructor(private left: Exp<A>, private right: Exp<B>) {
super();
}
eval(): [A, B] {
return [this.left.eval(), this.right.eval()];
}
}
class Fst<A, B> extends Exp<A> {
constructor(private tuple: Exp<[A, B]>) {
super();
}
eval(): A {
return this.tuple[0].eval();
}
}
class Snd<A, B> extends Exp<B> {
constructor(private tuple: Exp<[A, B]>) {
super();
}
eval(): B {
return this.tuple[1].eval();
}
}
const lit = (value: number) => new Lit(value);
const plus = (left: Exp<number>, right: Exp<number>) => new Plus(left, right);
const equals = (left: Exp<number>, right: Exp<number>): Exp<boolean> => new Equals(left, right);
const cond = <A>(cond: Exp<boolean>, left: Exp<A>, right: Exp<A>): Exp<A> => new Cond(cond, left, right);
const tuple = <A, B>(left: Exp<A>, right: Exp<B>): Exp<[A, B]> => new Tuple(left, right);
const fst = <A, B>(tuple: Exp<[A, B]>): Exp<A> => new Fst(tuple);
const snd = <A, B>(tuple: Exp<[A, B]>): Exp<B> => new Snd(tuple);
const ex1: Exp<boolean> = equals(lit(1), lit(2));
const lit1: Exp<number> = lit(1);
const tup1: Exp<[boolean, number]> = tuple(ex1, lit1);
const ex2: Exp<boolean> = fst(tup1);
const ex3: Exp<number> = snd(tup1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment