【问题标题】:Type of functions that can pass themselves as parameters可以将自身作为参数传递的函数类型
【发布时间】:2014-07-06 03:12:22
【问题描述】:

深入思考这个计算中的类型,非常深入

id id

从右到左,第一个id有类型

 a -> a

这意味着将第一个作为参数的第二个 id 必须具有类型

(a -> a) -> (a -> a)

类型不匹配!好吧,你可以说 first id 实际上有这个后来的类型,但是这会使 second id 成为类型

((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))

我认为尽管函数可以具有泛型或变量类型,但函数在计算时会绑定到某些类型。在我看来,似乎每次调用都定义了一个 new id 函数。

【问题讨论】:

  • 如果我们说id :: a -> a,我们实际上是指id :: forall a. a -> a (implicit quantification)。 (id :: forall a. a -> a) (id :: forall a. a -> a) 中的 a 并不都指同一个类型。
  • 如果你觉得这很有趣,你应该尝试将一堆fmaps 放在ghci 中。例如。 ::t fmap fmap fmap fmap fmap 返回:: Functor f => (a1 -> b) -> (a -> a1) -> f a -> f b

标签: haskell


【解决方案1】:

你是对的。 id 是多态的。 每个 使用都会独立实例化其类型。例如,考虑像id "Hello" ++ show (id 5) 这样的愚蠢的东西。第一次使用时,它是id :: String -> String。在第二次使用中,它是id :: Integer -> Integer(至少具有多态文字的正常默认规则)。通过嵌套对 id 的调用所做的事情是相同的——只是在最内层放入了一些类型变量,而不是具体类型。

不过,这并不意味着实现会发生变化。 id 始终是相同的功能。每次使用时唯一改变的是类型检查器如何处理它。

顺便说一句,这是可以用来使 GHC 的类型检查器崩溃的东西。您已经注意到 id 类型的大小在您将其应用于自身几次时呈指数增长。多做几次,它需要比你的系统更多的内存来表示最外层id的类型。

【讨论】:

  • Relevant 5 月发布的关于如何使用对 id 的嵌套调用使编译器崩溃的帖子。
【解决方案2】:

处理这种情况的正确技术是多态。恒等函数

id x = x

具有类型 ∀α 。 α → α,读作“对于每个类型 α,id 将类型 α 的元素映射到类型 α 的元素”。给应用一个类型

id id

我们争论如下:

  • 第一个 id 具有类型 ∀α 。 α → α,
  • 第二个id 具有类型 ∀β 。 β → β 其中我们将绑定变量 α 重命名为 β 以避免混淆,
  • 如果我们将第一个 id 中的 α 设置为 ∀β 。 β → β 然后我们看到第一个 id 映射类型为 ∀β 的元素。 β → β 到类型 ∀β 的元素。 β → β,
  • 因此整个表达式没问题并且具有类型 ∀β 。 β → β,与 ∀α 相同。 α → α。

这并不神秘。延伸阅读:System F.

【讨论】:

  • 之所以出现这个问题,是因为我听到了一位研究数学的人的说法:“可以将自身作为参数传递的函数没有类型”。如果您考虑静态类型,由于这种递归情况,您可能无法定义函数的类型,但多态性打破了这一点。对这个话题有什么想法吗?
  • @CristianGarcia 这一点也不神秘; id :: Int -> Intid :: (Int -> Int) -> (Int -> Int) 实际上是不同的功能。他们只是碰巧有相同的名字。数学中没有 id 函数,而是每个集合(或在 Haskell 中,每种类型)都有 an id 函数。
  • 数学家不习惯编程语言中出现的现象。你可以告诉你的数学家朋友,有一个(非平凡的)拓扑空间 $D$,它同胚于它自己的连续映射函数空间 $\mathcal{C}(D,D)$。因此(直到同构)在这个空间中,一个函数可以自我应用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
  • 2022-10-06
  • 2019-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多