为什么不通过存在和约束进行子类型化?

时间:2017-08-17 19:03:06

标签: haskell typeclass existential-type subtyping

我一直试图看看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应该做一些隐含的魔法让它发挥作用还是我错过了什么?

2 个答案:

答案 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 :: Intf2 :: 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类型(而NumIntegral的超类,所以如果可以的话任何Num它也可以是任何Integral)。 f1然后调用f2,要求Int成为a的选择。

你注意到GHC似乎正在将你写的类型转换为f2 :: forall a. Integral a => () -> a,而你是对的;它 实际上规范了您写入该类型的内容。原因是这两种类型实际上与GHC类型系统的工作方式相同。调用者实例化在整个f2类型上量化的任何类型变量,调用者也将是接收返回值的那个,因此实例化仅在返回值上量化的任何类型变量。如果您在整个类型或仅仅函数的返回类型上进行范围调整,则引入具有forall的类型变量是相同的;如果它的范围是箭头的左侧侧(这是更高级别的类型进入的地方),它只会产生影响。