McP*_*ott 5 typescript typescript-generics
我正在尝试为函子映射创建一个通用的函数接口,该接口尊重所提供的接口。在下面显示的代码中,我希望value mb的类型Maybe<number>为type ,而不是实际的type Functor<number>。
我确实意识到,一种可能的解决方案是向接口添加重载FMap。我对此解决方案不满意的原因是,我希望将此代码驻留在一个程序包中,从而允许用户创建实现Functor并具有我在使用函数时所描述的行为map。
interface Functor<A> {
map<B>(fn: (a: A) => B): Functor<B>;
}
interface FMap {
<A, B>(fn: (a: A) => B, Fa: Functor<A>): Functor<B>;
}
const map: FMap = (fn, Fa) => (
Fa.map(fn)
);
class Maybe<A> implements Functor<A> {
constructor(private readonly a: A) {}
map<B>(fn: (a: A) => B): Maybe<B> {
return new Maybe<B>(fn(this.a));
}
}
const sqr = (x: number) => x*x;
const ma = new Maybe(5);
const mb = map(sqr, ma);
Run Code Online (Sandbox Code Playgroud)
我想要一些表达以下语义的方法:
// Theoretical Code
interface PretendFMap {
<A, B, FA extends Functor<A>>(fn: (a: A) => B, Fa: FA): FA extends (infer F)<A> ? F<B> : never;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果没有类型参数,则它不能用作通用接口,不是有效的TypeScript类型,即,诸如Functor要求将类型参数视为类型的接口,Functor其本身不是有效类型。
如果当前没有表达这些语义的手段,那么与解决方案有关的任何建议在用户方面都需要尽可能少的代码,将受到极大的赞赏。
预先感谢您的时间和考虑。
当您尝试将类型变量F作为类型参数传递给另一个类型变量T(例如)时T<F>,TS就是不允许的,即使您知道T实际上是通用接口,TS也不允许这样做。
关于这个话题的讨论可以追溯到2014年的github问题,目前仍在公开讨论中,因此TS团队可能不会在不久的将来提供支持。
此语言功能的术语称为高级类型。谷歌用那个搜索关键词带我去了兔子洞。
事实证明,存在一个非常聪明的解决方法!
通过利用TS 声明合并(也称为模块增强)功能,我们可以有效地定义一个空的“类型存储”接口,该接口的行为就像一个持有对其他有用类型的引用的普通对象。使用这项技术,我们就能克服这种障碍!
我将以您的案例为例来介绍这种技术的思想。如果您想更深入地学习,请在最后加入一些有用的链接。
这是指向最终结果的TS Playground链接(剧透警报)。可以现场观看。现在,让我们逐步分解(或者说建立它?)。
TypeStore接口,稍后再更新它的内容。// just think of it as a plain object
interface TypeStore<A> { } // why '<A>'? see below
// example of "declaration merging"
// it's not re-declaring the same interface
// but just adding new members to the interface
// so we can amend-update the interface dynamically
interface TypeStore<A> {
Foo: Whatever<A>;
Maybe: Maybe<A>;
}
Run Code Online (Sandbox Code Playgroud)
keyof TypeStore。注意,随着内容的TypeStore更新,$keys也会相应地更新。type $keys = keyof TypeStore<any>
Run Code Online (Sandbox Code Playgroud)
// the '$' generic param is not just `string` but `string literal`
// think of it as a unique symbol
type HKT<$ extends $keys, A> = TypeStore<A>[$]
// where we mean `Maybe<A>`
// we can instead use:
HKT<'Maybe', A> // again, 'Maybe' is not string type, it's string literal
Run Code Online (Sandbox Code Playgroud)
interface Functor<$ extends $keys, A> {
map<B>(f: (a: A) => B): HKT<$, B>
}
class Maybe<A> implements Functor<'Maybe', A> {
constructor(private readonly a: A) {}
map<B>(f: (a: A) => B): HKT<'Maybe', B> {
return new Maybe(f(this.a));
}
}
// HERE's the key!
// You put the freshly declare class back into `TypeStore`
// and give it a string literal key 'Maybe'
interface TypeStore<A> {
Maybe: Maybe<A>
}
Run Code Online (Sandbox Code Playgroud)
FMap:// `infer $` is the key here
// remember what blocked us?
// we cannot "infer Maybe from T" then apply "Maybe<A>"
// but we can "infer $" then apply "HKT<$, A>"!
interface FMap {
<A, B, FA extends { map: Function }>
(f: (a: A) => B, fa: FA): FA extends HKT<infer $, A> ? HKT<$, B> : any
}
const map: FMap = (fn, Fa) => Fa.map(fn);
Run Code Online (Sandbox Code Playgroud)
参考
fp-ts lib由@gcantihkts 由@pelotom libtypeprops lib由@SimonMeskens| 归档时间: |
|
| 查看次数: |
134 次 |
| 最近记录: |