Search code examples
typescriptfunctional-programmingfp-ts

higher kinded type in typescript from fp-ts and URI


In fp-ts they have this workaround for higher kinded types:

export interface HKT<URI, A> {
  readonly _URI: URI;
  readonly _A: A;
}

And it can be used like this:

export interface Foldable<F> {
  readonly URI: F;
  reduce: <A, B>(fa: HKT<F, A>, b: B, f: (b: B, a: A) => B) => B;
}

What are the members _URI and what is _A?


Solution

  • This article written by the author of fp-ts explains very well,in combination with documentation on TypeScript's union types.

    _A is the type of the value in our HKT: HKT<"Option", string> => Option<string>. We'll see how mapping works in a bit.

    _URI is the identifier for the HKT, for example Option.ts. It has 2 instances, None and Some, both of which have "Option" as their _URI.

    Taking a look at the usage of F in Foldable we can see it's used in the Type type, along with A; Type<F, A>. Assume F == "Option" & A == string so Type<F, A> == Type<"Option", string>, which is very similar to Option<string>. Currently, we cannot pass a generic type to another generic as it's generic parameter: <F,A>(fa: F<A>) will not compile and this is the main problem.

    To achieve Type<"Option", string> => Option<string>, there are several "mapping" interfaces (this is explained in the article). We can see this done for Option here:

    declare module './HKT' {
      interface URI2HKT<A> {
        Option: Option<A>
      }
    }
    

    The leftmost Option is a string key, the rightmost is the actual Option type, this is a normal record interface after all. Let's have a quick look at the definition of Type: type Type<URI extends URIS, A> = URI2HKT<A>[URI].

    URI2HKIT is a map from our "Option" to our actual Option. It's allowing is to pass 2 generic parameters to get back our value type wrapped in our higher kinded type, solving the generics issue mentioned earlier.