我一直试图看看Haskell如何应对子类型,所以我提出了以下代码段:
{-# LANGUAGE RankNTypes #-}
f1 :: () -> Int
f1 _ = 5
f2 :: () -> (forall a. Integral a => a)
f2 = f1
第f2 = f1
行失败并显示意外错误消息:
Couldn't match type ‘a’ with ‘Int’
‘a’ is a rigid type variable bound by
the type signature for:
f2 :: forall a. Integral a => () -> a
似乎存在主义已被提升为普遍性,当然这是无意识的。
我的猜测是,在实现中,f2
需要返回一个值和一个相应的字典,而f1
只返回一个Int
类型的值。但是,从逻辑角度来看,f2
的合同是从()
到Integral
"的某个未知实例的函数。并且f1
完全满足。
GHC应该做一些隐含的魔法让它发挥作用还是我错过了什么?
答案 0 :(得分:6)
您a
中的f2
类型是普遍量化的,不是存在量化的。 GHC没有直接支持您正在寻找的存在类型。
但是,您可以通过将其包装在新的数据类型中来获得类似的内容:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ConstraintKinds #-}
data Constrained c = forall a. c a => Constrained a
f1 :: () -> Int
f1 _ = 5
f2 :: () -> Constrained Integral
f2 unit = Constrained (f1 unit)
现在,这通常并不是非常有用,因为所有已完成的事情是我们抛弃了有关f2 ()
的所有(类型)信息,除了它的类型是{{Integral
的实例1}}。你不再知道它是Int
。也可以跟踪这些信息,但根据您正在做的事情,这可能有用也可能没用。
您正在做的事情和您希望看到的内容的更多背景信息可以让我更轻松地了解有关我应该添加的这类事情的信息。
作为旁注:没有必要将这些作为参与的函数。您可以拥有f1 :: Int
和f2 :: Constrained Integral
。
另外,如果我没有提到这一点,我会觉得有点疏忽,尽管在这个答案中,I wrote an answer that describes some potentially practical uses for constrained existential types更早地在Haskell中淡化了这些类型的实用程序。虽然我们对某个主题有所了解,但也可能值得指出ConstraintKinds
是一个强大的扩展,其使用超出了受限制的存在。
答案 1 :(得分:5)
你正在以错误的方式阅读f2
的类型。
f2
的类型说“如果你给我一个()
,我会给你一个(forall a. Integral a => a)
。也就是说,f2
承诺给你一个值可以像使用任何类型Integral
一样使用。
然而,f2
的实施表明它只需拨打f1
就可以实现这一目标。但f1
只返回Int
!这确实是Integral
的成员,但它不能作为Integral
成员的任何类型使用。
实际上,类型f1
实际上是f2
类型的子类型,而不是相反!这实际上有效:
{-# LANGUAGE RankNTypes #-}
f1 :: () -> Int
f1 = f2
f2 :: () -> (forall a. Integral a => a)
f2 _ = 5
它的工作原理是因为数字文字是多态的,所以5
可以是任何Num
类型(而Num
是Integral
的超类,所以如果可以的话任何Num
它也可以是任何Integral
)。 f1
然后调用f2
,要求Int
成为a
的选择。
你注意到GHC似乎正在将你写的类型转换为f2 :: forall a. Integral a => () -> a
,而你是对的;它 实际上规范了您写入该类型的内容。原因是这两种类型实际上与GHC类型系统的工作方式相同。调用者实例化在整个f2
类型上量化的任何类型变量,调用者也将是接收返回值的那个,因此实例化仅在返回值上量化的任何类型变量。如果您在整个类型或仅仅函数的返回类型上进行范围调整,则引入具有forall
的类型变量是相同的;如果它的范围是箭头的左侧侧(这是更高级别的类型进入的地方),它只会产生影响。