Skip to content

Instantly share code, notes, and snippets.

@co1rowjp
Created October 6, 2012 19:39
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save co1rowjp/3845888 to your computer and use it in GitHub Desktop.
Save co1rowjp/3845888 to your computer and use it in GitHub Desktop.
TypeScript Maybe
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
}
}
@ririw
Copy link

ririw commented Sep 1, 2014

How about this:

interface Functor<A> {
     fmap<B>(fn: (a: A) => B): Functor<B>
  }

  interface Monad<A> extends Functor<A> {
     bind<B>(fn: (a: A) => Monad<B>): Monad<B>
  }
  interface Maybe<A> extends Monad<A> {}

  class Just<A> implements Maybe<A> {
     private value: A;
     constructor(a: A) {
        this.value = a
     }
     fmap<B>(fn: (a: A) => B): Just<B> {
        return new Just(fn(this.value));
     }
     bind<B>(fn: (a: A) => Just<B>): Just<B> {
        return fn(this.value);
     }
  }
  class Nothing<A> implements Maybe<A> {
     fmap<B>(fn: (a: A) => B): Nothing<B> {
        return new Nothing();
     }
     bind<B>(fn: (a: A) => Just<B>): Nothing<B> {
        return new Nothing();
     }
  }

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:

  interface Functor<A, This extends Functor> {
     fmap<B>(fn: (a: A) => B): This<B>
  }

But typescript won't allow unspecified type parameters.

@datokrat
Copy link

datokrat commented Apr 3, 2017

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