TypeScript 中的通用类型参数和单子
Generic type parameter and monads in TypeScript
我目前正在学习 monads - 并尝试使用 TypeScript 更好地理解它们。
我想做以下事情:
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
type Unit<M, A> = (a: A) => M<A>
但我收到以下 TypeScript 错误:
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Unit<M, A> = (a: A) => M<A>
~~~~
有没有办法指定类型参数 M 应该是通用的?或者还有其他明智的方法吗?
我的想法是我稍后可以通过 Maybe
代替 M,其中 Maybe 是一个通用类型:
type Maybe<T> = { kind: 'nothing' } | { kind: 'some', value: T }
不,TypeScript 不直接支持定义 Bind
或 Unit
所必需的 更高种类的类型 。 microsoft/TypeScript#1213 上有一个长期存在的开放功能请求要求这样做,目前尚不清楚何时或是否会实施。
TypeScript 目前缺乏抽象类型函数的能力。 (类型函数作用于类型的方式与常规函数作用于值的方式相同;它采用一些输入类型并产生输出类型。)它可以表示单个具体类型函数,例如 Array
,并且您可以定义新的个别具体类型函数,如:
type Foo<T> = {x: T} // Foo is a type function
但是你不能一般地谈论 about 类型函数(没有 Foo
本身可以引用),也不能将类型函数传递给其他高阶类型职能。因为你的类型参数 M
需要是一个任意类型的函数才能工作,所以没有办法直接做。
请注意,我说它不直接支持更高种类的类型。可以模拟更高种类的类型,但我所知道的所有这样做的方法都很笨拙,需要大量的手动干预和样板代码。我不知道我会称之为“疯狂”,但我也不一定会说这是“另一种明智的做法”。
如果您想使用现有的库,可以查看 fp-ts。否则,实现的草图可能如下所示:
interface TypeFunction<T> { }
type TypeFunctionName = keyof TypeFunction<any>
type Apply<F extends TypeFunctionName, T> = TypeFunction<T>[F];
对于您想讨论的每个类型函数,您需要 merge 进入 TypeFunction<T>
。例如:
interface TypeFunction<T> {
Array: Array<T>
}
和
interface TypeFunction<T> {
Foo: Foo<T>
}
完成后,您可以将 M<T>
的任何用法更改为 Apply<M, T>
,其中 M
现在是您为 TypeFunction<T>
中的类型函数指定的键。所以你可以说 Apply<"Array", string>
,它将是 string[]
,或者 Apply<"Foo", string>
,它将是 {x: string}
。
鉴于这样的草图,我们可以这样写Bind
和Unit
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>;
(请注意 A
和 B
的范围发生了怎样的变化...您希望单个 Bind<"Array">
对所有 A
和 B
) 甚至定义其他操作,如 fmap:
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)))
为了好玩,我们可以为 Array
和 Foo
monads 实现 bind
和 unit
:
namespace ArrayMonad {
const bind: Bind<"Array"> = ma => mf => ma.flatMap(mf);
const unit: Unit<"Array"> = a => [a];
const map = fmap(bind, unit);
const mapYell = map((x: number) => x + "!");
const strs = mapYell([1, 2, 3]);
console.log(strs) // ["1!", "2!", "3!"]
}
namespace FooMonad {
const bind: Bind<"Foo"> = ma => mf => mf(ma.x);
const unit: Unit<"Foo"> = a => ({ x: a });
const map = fmap(bind, unit);
const mapYell = map((x: number) => x + "!");
const fooStr = mapYell({ x: 1 });
console.log(fooStr) // {x: "1!"}
}
看起来不错,或多或少。我对不得不写 Bind<"Foo">
而不是 Bind<Foo>
并不感到兴奋,但由于没有 Foo
可参考,这与您目前所能得到的最接近。
我会把实施 Maybe
留给你。
我目前正在学习 monads - 并尝试使用 TypeScript 更好地理解它们。
我想做以下事情:
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
type Unit<M, A> = (a: A) => M<A>
但我收到以下 TypeScript 错误:
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Bind<M, A, B> = (ma: M<A>) => (a_mb: ((a: A) => M<B>)) => M<B>
~~~~
error TS2315: Type 'M' is not generic.
type Unit<M, A> = (a: A) => M<A>
~~~~
有没有办法指定类型参数 M 应该是通用的?或者还有其他明智的方法吗?
我的想法是我稍后可以通过 Maybe
代替 M,其中 Maybe 是一个通用类型:
type Maybe<T> = { kind: 'nothing' } | { kind: 'some', value: T }
不,TypeScript 不直接支持定义 Bind
或 Unit
所必需的 更高种类的类型 。 microsoft/TypeScript#1213 上有一个长期存在的开放功能请求要求这样做,目前尚不清楚何时或是否会实施。
TypeScript 目前缺乏抽象类型函数的能力。 (类型函数作用于类型的方式与常规函数作用于值的方式相同;它采用一些输入类型并产生输出类型。)它可以表示单个具体类型函数,例如 Array
,并且您可以定义新的个别具体类型函数,如:
type Foo<T> = {x: T} // Foo is a type function
但是你不能一般地谈论 about 类型函数(没有 Foo
本身可以引用),也不能将类型函数传递给其他高阶类型职能。因为你的类型参数 M
需要是一个任意类型的函数才能工作,所以没有办法直接做。
请注意,我说它不直接支持更高种类的类型。可以模拟更高种类的类型,但我所知道的所有这样做的方法都很笨拙,需要大量的手动干预和样板代码。我不知道我会称之为“疯狂”,但我也不一定会说这是“另一种明智的做法”。
如果您想使用现有的库,可以查看 fp-ts。否则,实现的草图可能如下所示:
interface TypeFunction<T> { }
type TypeFunctionName = keyof TypeFunction<any>
type Apply<F extends TypeFunctionName, T> = TypeFunction<T>[F];
对于您想讨论的每个类型函数,您需要 merge 进入 TypeFunction<T>
。例如:
interface TypeFunction<T> {
Array: Array<T>
}
和
interface TypeFunction<T> {
Foo: Foo<T>
}
完成后,您可以将 M<T>
的任何用法更改为 Apply<M, T>
,其中 M
现在是您为 TypeFunction<T>
中的类型函数指定的键。所以你可以说 Apply<"Array", string>
,它将是 string[]
,或者 Apply<"Foo", string>
,它将是 {x: string}
。
鉴于这样的草图,我们可以这样写Bind
和Unit
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>;
(请注意 A
和 B
的范围发生了怎样的变化...您希望单个 Bind<"Array">
对所有 A
和 B
) 甚至定义其他操作,如 fmap:
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)))
为了好玩,我们可以为 Array
和 Foo
monads 实现 bind
和 unit
:
namespace ArrayMonad {
const bind: Bind<"Array"> = ma => mf => ma.flatMap(mf);
const unit: Unit<"Array"> = a => [a];
const map = fmap(bind, unit);
const mapYell = map((x: number) => x + "!");
const strs = mapYell([1, 2, 3]);
console.log(strs) // ["1!", "2!", "3!"]
}
namespace FooMonad {
const bind: Bind<"Foo"> = ma => mf => mf(ma.x);
const unit: Unit<"Foo"> = a => ({ x: a });
const map = fmap(bind, unit);
const mapYell = map((x: number) => x + "!");
const fooStr = mapYell({ x: 1 });
console.log(fooStr) // {x: "1!"}
}
看起来不错,或多或少。我对不得不写 Bind<"Foo">
而不是 Bind<Foo>
并不感到兴奋,但由于没有 Foo
可参考,这与您目前所能得到的最接近。
我会把实施 Maybe
留给你。