TypeScript 中的类型漏洞 第一个例子 第二个例子

2025-06-08

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
Enter fullscreen mode Exit fullscreen mode

为了模拟类型漏洞,我将使用以下函数声明

declare function _<T>(): T
Enter fullscreen mode Exit fullscreen mode

让我们把它放进jonk身体里

function jonk<A, B>(
  ab: (a: A) => B,
  ann: (an: (a: A) => number) => number
): (bn: (b: B) => number) => number {
  return _()
}
Enter fullscreen mode Exit fullscreen mode

如果将鼠标移到“类型孔”上,_您可以看到 TypeScript 对其类型参数的推断T

(bn: (b: B) => number) => number
Enter fullscreen mode Exit fullscreen mode

那么类型检查器告诉我们什么呢?有两件事

  • 我们要替换的表达式_()必须具有类型(bn: (b: B) => number) => number
  • 我们有一些本地绑定(ab,,,及其类型)可用于帮助实现。annjonk

由于我们的洞有类型,(bn: (b: B) => number) => number我们应该将其绑定bn在 lambda 中(从现在开始我将只编写函数体)

return bn => _() // inferred type: number
Enter fullscreen mode Exit fullscreen mode

新的推断类型是什么?number。我们如何生成number?我们可以使用abannbn。由于 和 都ann返回bnnumber我们选择ann作为一个猜测。

return bn => ann(_()) // inferred type: (a: A) => number
Enter fullscreen mode Exit fullscreen mode

我们的新洞有一个函数类型,所以让我们引入一个 lambda

return bn => ann(a => _()) // inferred type: number
Enter fullscreen mode Exit fullscreen mode

我们需要再次制作number,我们选择bn这次

return bn => ann(a => bn(_())) // inferred type: B
Enter fullscreen mode Exit fullscreen mode

现在我们需要生成一个B。我们有一个函数可以做到这一点,ab: (a: A) => B

return bn => ann(a => bn(ab(_()))) // inferred type: A
Enter fullscreen mode Exit fullscreen mode

最后,我们有一个类型为 的洞A。因为我们有一个Aa参数),所以我们就用它吧

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)))
}
Enter fullscreen mode Exit fullscreen mode

现在我们有了一个完整的实现,几乎由类型检查器驱动。

第二个例子

让我们来处理博客文章的第二个例子:zoop

declare function zoop<A, B>(abb: (a: A) => (b: B) => B, b: B, as: Array<A>): B
Enter fullscreen mode Exit fullscreen mode

我们注意到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
    )
  )
}
Enter fullscreen mode Exit fullscreen mode

我们需要B为“nil”的情况(即数组为空时)生成一个。既然已经有了B,就直接用它吧(以后我只写函数体)。

return pipe(
  as,
  foldLeft(
    () => b,
    (head, tail) => _() // inferred type: B
  )
)
Enter fullscreen mode Exit fullscreen mode

同样,我们想B为另一种情况生成一个,并调用abb。因为它需要两个参数,所以我们给它两个孔

return pipe(
  as,
  foldLeft(
    () => b,
    (head, tail) =>
      abb(
        _() // inferred type: A
      )(
        _() // inferred type: B
      )
  )
)
Enter fullscreen mode Exit fullscreen mode

head有类型A,所以我们使用它

return pipe(
  as,
  foldLeft(
    () => b,
    (head, tail) =>
      abb(head)(
        _() // inferred type: B
      )
  )
)
Enter fullscreen mode Exit fullscreen mode

现在我们必须生成一个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`
Enter fullscreen mode Exit fullscreen mode

其工作原理被称为免费定理,其粗略地指出,我们可以推断出有关类型签名的许多事实(假设它是正确的)。

鏂囩珷鏉ユ簮锛�https://dev.to/gcanti/type-holes-in-typescript-2lck
PREV
构建企业级 Angular 项目结构
NEXT
fp-ts入门:Monad 问题:嵌套上下文 定义 好的,但是……为什么?Kleisli类别,我们一步步构建组合 定律 fp-ts中的Monad 结论