深入思考这个计算中的类型,真的很深
id id
从右到左,第一个 id
的类型为
a -> a
这意味着第一个作为参数的第二个 id
必须具有类型
(a -> a) -> (a -> a)
这些类型不匹配!好的你可以说第一个 id
实际上有这个后来的类型,但这会使第二个 id
类型为
((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))
我认为,尽管函数可以具有泛型或变量类型,但是在计算时绑定到某些函数的函数。在我看来,似乎每次调用都定义了 new id
函数。
答案 0 :(得分:8)
你是对的。 id
是多态的。 每个使用都会独立实例化其类型。例如,考虑像id "Hello" ++ show (id 5)
这样的哑巴。在第一次使用时,它是id :: String -> String
。在第二次使用中,它是id :: Integer -> Integer
(至少与多态文字的正常默认规则一致)。你通过嵌套调用id来做的事情是同样的事情 - 只是在最内层使用一些类型变量代替具体类型。
但这并不意味着实施会发生变化。 id
始终是相同的功能。每次使用都会改变的唯一方法是类型检查器如何处理它。
顺便说一句,这可以用来崩溃GHC的类型检查器。您已经注意到id
类型的大小在您将其应用于自身几次时会呈指数级增长。再做几次,它需要的内存比你的系统代表最外层id
的类型要多。
答案 1 :(得分:3)
处理这种情况的正确技术是多态。身份功能
id x = x
有类型∀α。 α→α,其被读作“对于每种类型α,id
将类型α的元素映射到类型α的元素”。为应用程序提供类型
id id
我们认为如下:
id
的类型为∀α。 α→α,id
具有类型∀β。 β→β,其中我们将约束变量α重命名为β以避免混淆,id
中的α设置为∀β。 β→β然后我们看到第一个id
映射了∀β类型的元素。 β→β对typeβ型元素的影响。 β→β,没有神秘感。进一步阅读:System F。