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
?
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.