TypeScript 中的类型漏洞
第一个例子
第二个例子
Sandy Maguire 最近写了一篇关于类型漏洞的精彩文章(用类型来实现,而不是你的大脑!),这篇博文是移植到 TypeScript 的。
什么是类型洞?
其理念是先实现函数中你知道如何实现的一小部分,然后向编译器寻求帮助,完成剩余部分。这是一个迭代的过程,需要与编译器进行讨论。每一步,你都会离正确答案更近一步,经过足够多的迭代后,你的函数就能够自动生成了——即使你并不完全确定具体是如何实现的。
TypeScript 不支持类型洞,但可以进行某种模拟。
第一个例子
让我们看看文章中的第一个例子
declare function jonk<A, B>(
ab: (a: A) => B,
ann: (an: (a: A) => number) => number
): (bn: (b: B) => number) => number
为了模拟类型漏洞,我将使用以下函数声明
declare function _<T>(): T
让我们把它放进jonk
身体里
function jonk<A, B>(
ab: (a: A) => B,
ann: (an: (a: A) => number) => number
): (bn: (b: B) => number) => number {
return _()
}
如果将鼠标移到“类型孔”上,_
您可以看到 TypeScript 对其类型参数的推断T
(bn: (b: B) => number) => number
那么类型检查器告诉我们什么呢?有两件事
- 我们要替换的表达式
_()
必须具有类型(bn: (b: B) => number) => number
。 - 我们有一些本地绑定(
ab
,,,及其类型)可用于帮助实现。ann
jonk
由于我们的洞有类型,(bn: (b: B) => number) => number
我们应该将其绑定bn
在 lambda 中(从现在开始我将只编写函数体)
return bn => _() // inferred type: number
新的推断类型是什么?number
。我们如何生成number
?我们可以使用ab
,ann
或bn
。由于 和 都ann
返回bn
,number
我们选择ann
作为一个猜测。
return bn => ann(_()) // inferred type: (a: A) => number
我们的新洞有一个函数类型,所以让我们引入一个 lambda
return bn => ann(a => _()) // inferred type: number
我们需要再次制作number
,我们选择bn
这次
return bn => ann(a => bn(_())) // inferred type: B
现在我们需要生成一个B
。我们有一个函数可以做到这一点,ab: (a: A) => B
return bn => ann(a => bn(ab(_()))) // inferred type: A
最后,我们有一个类型为 的洞A
。因为我们有一个A
(a
参数),所以我们就用它吧
function jonk<A, B>(
ab: (a: A) => B,
ann: (an: (a: A) => number) => number
): (bnn: (b: B) => number) => number {
return bn => ann(a => bn(ab(a)))
}
现在我们有了一个完整的实现,几乎由类型检查器驱动。
第二个例子
让我们来处理博客文章的第二个例子:zoop
declare function zoop<A, B>(abb: (a: A) => (b: B) => B, b: B, as: Array<A>): B
我们注意到as
有类型Array<A>
,让我们使用foldLeft
import { foldLeft } from 'fp-ts/lib/Array'
import { pipe } from 'fp-ts/lib/pipeable'
function zoop<A, B>(abb: (a: A) => (b: B) => B, b: B, as: Array<A>): B {
return pipe(
as,
foldLeft(
() => _(), // inferred type: B
(head, tail) => _() // inferred type: B
)
)
}
我们需要B
为“nil”的情况(即数组为空时)生成一个。既然已经有了B
,就直接用它吧(以后我只写函数体)。
return pipe(
as,
foldLeft(
() => b,
(head, tail) => _() // inferred type: B
)
)
同样,我们想B
为另一种情况生成一个,并调用abb
。因为它需要两个参数,所以我们给它两个孔
return pipe(
as,
foldLeft(
() => b,
(head, tail) =>
abb(
_() // inferred type: A
)(
_() // inferred type: B
)
)
)
head
有类型A
,所以我们使用它
return pipe(
as,
foldLeft(
() => b,
(head, tail) =>
abb(head)(
_() // inferred type: B
)
)
)
现在我们必须生成一个B
我们想要使用的,tail
其类型为Array<A>
。我们唯一的选择是使用zoop
其自身
function zoop<A, B>(abb: (a: A) => (b: B) => B, b: B, as: Array<A>): B {
return pipe(
as,
foldLeft(() => b, (head, tail) => abb(head)(zoop(abb, b, tail)))
)
}
// p.s. `zoop` is `reduceRight`
鏂囩珷鏉ユ簮锛�https://dev.to/gcanti/type-holes-in-typescript-2lck其工作原理被称为免费定理,其粗略地指出,我们可以推断出有关类型签名的许多事实(假设它是正确的)。