Practically explain Monads in plain TypeScript

Programming by composing pure functions makes life easier, but real-world applications often depend on side effects. Functors and Monads provide ways of composing side effects operations within pure function chaining. This post provides a practical example to explain Monads in plain TypeScript.

import * as readline from "readline";

// ------------------------------------------------------

interface Functor<T> {
  // (a -> b) -> f a -> f b
  fmap<U>(func: (val: T) => U): Functor<U>;
}

interface Monad<T> {
  // (a -> m b) -> m b
  bind<U>(mona: (val: T) => Monad<U>): Monad<U>;
}

class IO<T> implements Functor<T>, Monad<T> {
  private constructor(private effect: () => T) {}

  static of<V>(effect: () => V): IO<V> {
    return new IO(effect);
  }

  run(): T {
    return this.effect();
  }

  fmap<U>(func: (val: T) => U): IO<U> {
    return IO.of(() => func(this.run()));
  }

  bind<U>(mona: (val: T) => IO<U>): IO<U> {
    return IO.of(() => mona(this.run()).run());
  }
}

// ------------------------------------------------------

const ri = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const ioLogResult = (p: Promise<[number, boolean]>): IO<Promise<void>> =>
  IO.of(() =>
    p.then(([tgt, result]) =>
      ri.write(`${result ? "correct" : "wrong"}, the ans is ${tgt}`)
    )
  );

const ioReadNum = (prompt: string): IO<Promise<number>> =>
  IO.of(
    () =>
      new Promise<number>((resolve) => {
        ri.question(prompt, (ans) => resolve(parseInt(ans)));
      })
  );
const ioReadNumAndCompare = (
  tgt: number,
  prompt: string
): IO<Promise<[number, boolean]>> =>
  ioReadNum(prompt).fmap((pVal) => pVal.then((val) => [tgt, val === tgt]));

const ioRand = IO.of(() => Math.floor(Math.random() * 3) + 1);

const prompt = "guess a num between 1-3\n";

// ---- pure functions chained without side effects -----

const expr = ioRand // IO<number>
  .bind((tgt) => ioReadNumAndCompare(tgt, prompt)) // IO<Promise<boolean>>
  .bind(ioLogResult); // IO<Promise<void>>

// expr is a pure expression without any actually IO action

// ------------------------------------------------------

// guess a num between 1-3
// 3
// correct, the ans is 3
expr.run().finally(() => {
  ri.close();
});


CC BY-SA 4.0

本文使用 CC BY-SA 4.0 授權

標籤:

分類:

更新時間: