Small library for describing HKTs in TypeScript
npm install typeprops
After install, you can import types like so:
import { Generic, Parameters, unknown } from "typeprops";
This library is still in alpha mode, expect breaking changes for now. What's currently needed is lots of interaction with people trying to type complex examples. Feel free to join us at Gitter or to post issues on the repository with sample code.
This repository contains a lot of examples to show you how things work. Unfortunately, for usability purposes, the library is a little lenient in some places and too strict in others, so there's a razor thin edge when defining overly abstract types (like, say, a Monad HKT). Write lots of tests to make sure the type you wrote is behaving correctly. TypeProps tends to handle quite nicely for exactly the right type, even offering very good inference and assignment, but sometimes it requires some gentle love to get there.
This library uses a type-level pattern matcher to match types against a "type dictionary". The type dictionary contains types I call "TypeProps", which describe two things:
- Can I infer parameters for this type?
- Given another type, how do I construct a new type?
Here's the base type dictionary, which defines what do with arrays, null, undefined and the generic case:
interface TypeProps<T = {}, Params extends ArrayLike<any> = never> {
array: {
infer: T extends Array<infer A> ? [A] : never;
construct: Params[0][];
};
null: {
infer: null extends T ? [never] : never;
construct: null;
};
undefined: {
infer: undefined extends T ? [never] : never;
construct: undefined;
};
unfound: {
infer: [NonNullable<T>];
construct: Params[0];
};
}
Using "declaration merging", you can add more types to this dictionary, and the library also supports adding secondary dictionaries, to express things like Functor (Either e) a
in Haskell (functor only has one type parameter, Either has two, a secondary mapping is needed).
These were put in to support abstracting nullables as a Monad. The follow code demonstrates that:
const nullableFunctor: StaticFunctor<object | null | undefined> = {
map: <A, B>(
fn: (a: A) => B,
fa: A | null | undefined
): B | null | undefined => {
return fa != undefined ? fn(fa) : fa;
}
};
This is a very powerful idea, but be careful exposing it to other libraries that expect Static-land compliance. As far as I can tell, this function is not compliant with that spec. I might need to pull the feature out in the future if day-to-day use shows that it makes other types incorrect, but for now I haven't found such cases.