Unfortunately, TypeScript currently does not provide built-in support for higher kinded types needed to define concepts like Bind
or Unit
. There has been a long-standing feature request open on microsoft/TypeScript#1213, but it remains uncertain whether this functionality will be implemented in the future.
The main issue lies in TypeScript's limitation to abstract over type functions, which are essential for manipulating types similar to how regular functions operate on values. While TypeScript can represent specific concrete type functions such as Array
, the abstraction and use of type functions in general is not supported. This impediment restricts the ability to pass around type functions to other higher-order functions, ultimately preventing direct implementation of certain functionalities that rely on arbitrary type functions as parameters.
To highlight, TypeScript doesn't offer direct support for higher kinded types. Though there are ways to emulate them, the existing methods often involve cumbersome workarounds and significant manual input to overcome TypeScript's limitations in this regard. Hence, while it's feasible to mimic higher kinded types, the process may not qualify as straightforward or intuitive.
If looking for alternatives, exploring libraries like fp-ts could prove beneficial. As an illustration, a basic implementation approach could entail:
interface TypeFunction<T> { }
type TypeFunctionName = keyof TypeFunction<any>
type Apply<F extends TypeFunctionName, T> = TypeFunction<T>[F];
For each targeted type function in scope, modifications within TypeFunction<T>
are necessary. For example:
interface TypeFunction<T> {
Array: Array<T>
}
Additionally,
interface TypeFunction<T> {
Foo: Foo<T>
}
Following these adjustments, references previously made with M<T>
require update to
Apply<M, T></code—using the key assigned to the respective type function within <code>TypeFunction<T></code. Consequently, expressions like <code>Apply<"Array", string>
would yield
string[]
, whereas
Apply<"Foo", string>
would produce
{x: string}
.
In light of this outline, you can proceed to devise implementations for Bind
, Unit
, and more as needed:
type Bind<M extends TypeFunctionName> =
<A>(ma: Apply<M, A>) => <B>(a_mb: ((a: A) => Apply<M, B>)) => Apply<M, B>;
type Unit<M extends TypeFunctionName> =
<A>(a: A) => Apply<M, A>;
(Note the revised scope for A
and
B</code... ensuring compatibility across all instances). Supporting operations like <a href="https://en.wikipedia.org/wiki/Functor_(functional_programming)" rel="nofollow noreferrer">fmap</a> becomes achievable too:</p>
<pre><code>const fmap = <M extends TypeFunctionName>(
bind: Bind<M>, unit: Unit<M>) => <A, B>(f: (a: A) => B) =>
(ma: Apply<M, A>) => bind(ma)(a => unit(f(a)))
As a playful exercise, implementing bind
and unit
for the Array
and Foo
monads could unfold like:
namespace ArrayMonad {
const bind: Bind<"Array"> = ma => mf => ma.flatMap(mf);
const unit: Unit<"Array"> = a => [a];
// Additional manipulations can follow...
}
namespace FooMonad {
const bind: Bind<"Foo"> = ma => mf => mf(ma.x);
const unit: Unit<"Foo"> = a => ({ x: a });
// Extra steps can be included...
}
The provided structure appears reasonable overall; however, minor quirks like specifying
Bind<"Foo"></code instead of <code>Bind<Foo></code reflect the current constraints faced. Further exploration into managing constructs like <code>Maybe
might lead to interesting insights.
Feel free to experiment with the code using the TypeScript Playground!