Created
October 6, 2012 19:39
-
-
Save co1rowjp/3845888 to your computer and use it in GitHub Desktop.
TypeScript Maybe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface Functor { | |
fmap: (any) => any; | |
} | |
interface Monad extends Functor { | |
bind: (any) => Monad; | |
} | |
interface Maybe extends Monad { | |
} | |
class Just implements Maybe { | |
private value: any; | |
constructor(a: any) { | |
this.value = a | |
} | |
fmap (f: (any) => any): Just { | |
return new Just(f(this.value)) | |
} | |
bind (f: (any) => Maybe): Maybe { | |
return f(this.value) | |
} | |
} | |
class Nothing implements Maybe { | |
fmap (f: (any) => any): Nothing { | |
return this | |
} | |
bind (f: (any) => Maybe): Maybe { | |
return this | |
} | |
} |
This might seem a bit dirty but it gives us the comfort of quite a lot of type inference:
// playground.ts
import {} from "./maybe";
const one = just("Hello World").value; // OK
const bad = nothing().value; // Compile error
const two = just("Hello World").or(nothing()).value; // OK
const three = nothing().or(just("Hello World")).value; // OK
const baaad = nothing().or(nothing()).value; // Compile error
const four = just("Hello World").map(x => `${x}!`).value; // OK
const five = nothing().map(x => `${x}!`).value; // Compile error
const six = just(just("Hello World")).flatten(); // OK; typeof six == Maybe<string>
const seven = just("Hello World").flatten(); // Compile error
function logMaybe(m: Maybe<string>) {
// Type guards work
if (isJust(m)) console.log(m.value); // OK
}
// maybe.ts
export function just<T>(value: T): Just<T> {
return new _Just<T>(value) as Just<T>;
}
export function nothing(): Nothing<any> {
return new _Nothing() as Nothing<any>;
}
export interface MBase<T> {
type: "just" | "nothing";
map<U>(this: Just<T>, project: (t: T) => U): Just<U>;
map<U>(this: Nothing<T>, project: (t: T) => U): Nothing<U>;
map<U>(project: (t: T) => U): Maybe<U>;
or(this: Just<T>, alternative: Maybe<T>): Just<T>;
or<U>(this: Nothing<T>, alternative: Just<U>): Just<U>;
or<U>(this: Nothing<T>, alternative: Nothing<U>): Nothing<U>;
or(this: Maybe<T>, alternative: Just<T>): Just<T>;
or<U>(this: Nothing<T>, alternative: Maybe<U>): Maybe<U>;
or(alternative: Maybe<T>): Maybe<T>;
flatten<U>(this: Maybe<Maybe<U>>): Maybe<U>;
};
export interface Just<T> extends MBase<T> {
type: "just";
readonly value: T;
};
export interface Nothing<T> extends MBase<T> {
type: "nothing";
}
export type Maybe<T> = Just<T> | Nothing<T>;
export function isJust<T>(x: Maybe<T>): x is Just<T> {
return x.type === "just";
}
export function isNothing<T>(x: Maybe<T>): x is Nothing<T> {
return x.type === "nothing";
}
abstract class _Maybe<T> implements MBase<T> {
public type: "nothing" | "just";
public map<U>(this: Just<T>, project: (t: T) => U): Just<U>;
public map<U>(this: Nothing<T>, project: (t: T) => U): Nothing<U>;
public map<U>(this: Maybe<T>, project: (t: T) => U): Maybe<U> {
const m: Maybe<T> = this;
return isJust(m) ? just(project(m.value)) : nothing();
}
public or(this: Just<T>, alternative: Maybe<T>): Just<T>;
public or(this: Nothing<T>, alternative: Just<T>): Just<T>;
public or(this: Nothing<T>, alternative: Nothing<T>): Nothing<T>;
public or(this: Nothing<T>, alternative: Maybe<T>): Maybe<T>;
public or(this: Maybe<T>, alternative: Just<T>): Just<T>;
public or(this: Maybe<T>, alternative: Maybe<T>): Maybe<T> {
return isJust(this) ? this : alternative;
}
public flatten<U>(this: Maybe<Maybe<U>>): Maybe<U> {
const m: Maybe<Maybe<U>> = this;
return isJust(m) ? m.value : nothing();
}
}
class _Nothing extends _Maybe<never> {
public readonly type = "nothing";
}
class _Just<T> extends _Maybe<T> {
public readonly type = "just";
constructor(public readonly value: T) { super() }
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How about this:
All I need is to show in the definitions of fmap and bind that we'll be returning an instance of the same type (ie, Maybe.fmap will never return list). I tried scala-style self type references, like:
But typescript won't allow unspecified type parameters.