Flow doesn’t support higher kinded types: https://github.com/facebook/flow/issues/30.
What does it means in practice?
Here’s a functor in PureScript
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b
Let’s try to translate the concept to Flow
interface Functor<F> {
map<A, B>(f: (a: A) => B, fa: F<A>): F<B>
}
Unfortunately Flow raises the following error
map<A, B>(f: (a: A) => B, fa: F<A>): F<B>
^^^^ Incorrect number of type parameters (expected 0)
There’s no way to define F as a unary type constructor, see Kind (type theory)
interface Functor<F<*>> {
map<A, B>(f: (a: A) => B, fa: F<A>): F<B>
}interface Functor<F<*>> {
^ Unexpected token <
Here I present a way of faking higher kinded types based on the paper Lightweight higher-kinded polymorphism and inspired by elm-brands.
The recipe
First let’s define an helper class HKT that represents a unary type constructor
class HKT<F, A> {}
Note that F and A are phantom types.
Now I can define the functor type class without Flow complaining
interface Functor<F> {
map<A, B>(f: (a: A) => B, fa: HKT<F, A>): HKT<F, B>
}
As an example, let’s implement the Maybe functor, we need
- a nominal type for F (let’s call it IsMaybe)
- a way to put a value of type ?A into Maybe<A> (let’s call it inj)
- a way to get back the value from Maybe<A> (let’s call it prj)
class IsMaybe {}type Maybe<A> = HKT<IsMaybe, A>;function inj<A>(a: ?A): Maybe<A> {
return ((a: any): Maybe<A>)
}function prj<A>(fa: Maybe<A>): ?A {
return ((fa: any): ?A)
}
Finally we can implement the functor instance
const Nothing: Maybe<any> = inj(null)function map<A, B>(f: (a: A) => B, fa: Maybe<A>): Maybe<B> {
const a = prj(fa)
return a == null ? Nothing : inj(f(a))
}map(n => n + 1, Nothing) // => null
map(n => n + 1, inj(3)) // => 4
Using this trick I wrote an implementation of common algebraic types: flow-static-land