我正在为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)
| ^^
我无法绕过这个。我有两个问题:
calculate
?我只是模糊地熟悉普遍量化的类型,所以如果这是问题的一部分,请轻轻解释。
答案 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)
...