为什么类型推断因应用于具有相同功能的不同输入的多态函数而失败

时间:2018-06-05 11:43:42

标签: haskell typechecking

我正在为C ++的一个子集制作一个解释器。解释器是用Haskell编写的。

my eval表达式函数返回一个新环境和一个值。我将值编码为名为Val的新类型。最小的例子:

data Val = I Integer | D Double

为了评估算术表达式,我想创建一个通用函数,它将(+)(*)等多态函数应用于Val构造函数中包含的数字。

我希望有这样的功能:

-- calculate :: Num a => (a -> a -> a) -> Val -> Val -> Val
calculate f (I i1) (I i2) = I (f i1 i2)
calculate f (D d1) (D d2) = D (f d1 d2)

这会出现以下错误:

tmp/example.hs:4:32: error:
    • Couldn't match expected type ‘Double’ with actual type ‘Integer’
    • In the first argument of ‘D’, namely ‘(f d1 d2)’
      In the expression: D (f d1 d2)
      In an equation for ‘calculate’:
          calculate f (D d1) (D d2) = D (f d1 d2)
  |
4 | calculate f (D d1) (D d2) = D (f d1 d2)
  |                                ^^^^^^^

tmp/example.hs:4:34: error:
    • Couldn't match expected type ‘Integer’ with actual type ‘Double’
    • In the first argument of ‘f’, namely ‘d1’
      In the first argument of ‘D’, namely ‘(f d1 d2)’
      In the expression: D (f d1 d2)
  |
4 | calculate f (D d1) (D d2) = D (f d1 d2)
  |                                  ^^

tmp/example.hs:4:37: error:
    • Couldn't match expected type ‘Integer’ with actual type ‘Double’
    • In the second argument of ‘f’, namely ‘d2’
      In the first argument of ‘D’, namely ‘(f d1 d2)’
      In the expression: D (f d1 d2)
  |
4 | calculate f (D d1) (D d2) = D (f d1 d2)
  |                                     ^^

我无法绕过这个。我有两个问题:

  1. 为什么这个程序无法输入检查?
  2. 我如何正确实施calculate
  3. 我只是模糊地熟悉普遍量化的类型,所以如果这是问题的一部分,请轻轻解释。

1 个答案:

答案 0 :(得分:7)

您已正确识别出需要通用量化。事实上,你已经有了普遍的量化 - 你的签名就像任何多态签名一样,基本上是

的简写
{-# LANGUAGE ExplicitForall, UnicodeSyntax #-}
calculate :: ∀ a . Num a => (a -> a -> a) -> Val -> Val -> Val

意思是:每当有人想要使用这个功能时,他们就可以选择某种类型来预先加入a。例如,他们可以选择Int,然后该功能将专门用于

calculate :: (Int -> Int -> Int) -> Val -> Val -> Val

然后在运行时使用。

但这对你没用,因为你需要将这个功能用于不同的数字类型。没有一个专业化将涵盖所有这些。

解决方案:延迟选择类型。这是通过将通用量子(您也可以将其写为forall)放在签名的组合函数部分中来实现的:

{-# LANGUAGE Rank2Types #-}
calculate :: (∀ a . Num a => a -> a -> a) -> Val -> Val -> Val

哪个会出现问题。它确实需要-XRank2Types扩展,因为这是一个相当复杂的野兽:现在你不能简单地将多态函数描述为具有混合单形类型的一系列特化,而是需要准备实例化函数, 在运行时,所提供的函数包含在数据结构中发生的任何类型。

即,它需要向函数传递一个额外的参数:一个包含Num类方法的“字典”。 GHC生成的底层实现是这样的:

data NumDict a = NumDict {
        addition :: a -> a -> a
      , subtraction :: a -> a -> a
      , multiplication :: a -> a -> a
      , abs :: a -> a
      ...
      }

calculate' :: (∀ a . NumDict a -> a -> a -> a) -> Val -> Val -> Val
calculate' f (I i1) (I i2) = I (f ndict i1 i2)
 where ndict = NumDict ((+) :: Integer -> Integer -> Integer)
                       ((-) :: Integer -> Integer -> Integer)
                       ...