Higher kinded types with Flow

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