fp typescript

  • TypeScript で型パラメータに型パラメータ取りたいっていたのは Higher-kinded types だった
    • TSでは実現できないので, fp-ts とかではエンコードしている

HKTのencode

interface Functor {
	map: <A, B>(f: (a: A) => B, fa: ?) => ?
}

Option<T> に対してinstanceを定義したい

interface HKT<F, A> {
  _URI: F
  _A: A
}

F: コンテナ型の識別子
A: 中身の型
ex. HKT<"Option", string> = Option<string>

interface Functor {
	map: <A, B>(f: (a: A) => B, fa: HKT<F, A>) => HKT<F, A>
}

これを使えるように書き換える

const URI = 'Option'
 
export class None {
  readonly _URI!: URI;
  readonly _A!: never
  readonly tags: "None" = "None";
}
 
export class Some<A> {
  readonly _URI!: URI;
  readonly _A!: A
  readonly tags: "Some" = "Some";
  constructor(readonly value: A) {}
}
 
export type Option<A> = None | Some<A>;
 
const map = <A, B>(f: (a: A) => B, fa: Option<A>): Option<B> => {
  switch (fa.tag) {
    case "None":
      return fa;
    case "Some":
      return some(f(fa.value));
  }
};
 
const option: Functor<URI> = {
  // error
  map,
};

HKT<"Option", A> != Option<A> なのでエラーが出る.

const map = <A, B>(f: (a: A) => B, hfa: HKT<URI, A>): Option<B> {};

型を合わせるが, HKT<"Option", A> と推論されてしまう.
実際の型に変換する型レベルの辞書を用意して拡張していく. TSは interface 複数宣言するとmergeされるので.

const a: Type<"Option", number> = some(1); // Option<number>
 
interface Functor<F extends URIS> {
  map: <A, B>(f: (a: A) => B, fa: Type<F, A>) => Type<F, B>
}

参考文献