Haskell:在函数中的不同类型上使用相同的运算符

时间:2015-07-08 08:56:07

标签: haskell

我正在Haskell写一个简单的解释器。我有3种可能的变量类型:boolintstring。为了避免重复评估比较,我编写了一个带有2个表达式和一个运算符的函数:

data Value = IntVal Integer
           | StringVal String
           | BoolVal Bool
           | ...

evalComparison :: Ord a => Exp -> (a -> a -> Bool) -> Exp -> Result Value
evalComparison expr1 op expr2 = do
  val1 <- evalExp expr1
  val2 <- evalExp expr2
  return $ BoolVal $
    case (val1, val2) of
      (IntVal i1, IntVal i2)       -> op i1 i2 (*)
      (StringVal s1, StringVal s2) -> op s1 s2 (**)
      (BoolVal b1, BoolVal b2)     -> op b1 b2 (***)
      otherwise                    -> error "Expected values of the same type!"

它的用途是,例如:

evalExp :: Exp -> Result Value
...
evalExp (ELessThen e1 e2) = evalComparison e1 (<) e2

(等等其他比较运算符)。

问题是 - 它不起作用。 GHC表示无法将类型Integer与[Char]排成一行 (**),而类似整数与Bool一致 (***)

我想我知道问题出在哪里:一旦来自运营商类型的a被确定为行Integer中的(*),就无法更改。所以我的问题有两个:

  1. 为什么会出现此问题,前提是结果类型(Bool)始终相同,无论运算符的参数类型如何?

  2. 可以做些什么来完成这项工作?

1 个答案:

答案 0 :(得分:1)

类型签名a -> a -> Bool表示必须存在某些类型a op具有该类型。但是您希望这适用于多个类型a。你不能在Haskell&#39; 98中做到这一点。

如果你打开rank-2类型(或rank-N类型),那么你可以

evalComparison :: Exp -> (forall a. a -> a -> Bool) -> Exp -> Result Value

这表示无论您传入的op是什么,都必须适用于多个类型a。事实上,它说op必须适用于所有可能的类型 a。但那可能太多了。你想要的可能更接近

evalComparison :: Exp - &gt; (foral a.Od a =&gt; a - &gt; a - &gt; Bool) - &gt; Exp - &gt;结果值

这表示op必须适用于实现a的每个Ord

虽然坦白地说,你可以在你的case-expression中明确地调用compare。让evalComparison返回Ordering,然后对其应用Ordering -> Result ValueOrdering只是一种类型,它可以使事情变得更简单。