编写binop以使用int和float

时间:2013-12-04 04:42:09

标签: haskell typechecking

我有一个我正在处理的解析器。没有进入所有细节,我想要一个将添加两个数值的函数:

add [VFloat a, VFloat b] = return $ VFloat (a + b)
add [VInt   a, VFloat b] = return $ VFloat (fromInteger a + b)
add [VFloat a, VInt   b] = return $ VFloat (a + fromInteger b)
add [VInt   a, VInt   b] = return $ VInt   (a + b)
add [_,_] = throwError "Currently only adding numbers"
add _ = throwError "Arity Error: Add takes 2 arguments"

酷,效果很好。现在我想要-*/<>==等相同的功能......

因此,我将+运算符分解出来,然后传递给运算符op :: Num a => a->a->a吗?

不太好。如果我只是将+替换为'op',则类型检查器会根据前三个版本告诉我op实际上是Double -> Double -> Double因此无法应用于Integer s在第四版中。

两个问题:

  1. 如何编写binop :: Num a => (a->a->a) -> [Value]-> EvalM Value以便它可以同时处理VInt和VFloat?
  2. 我所面临的情况的正确名称是什么,以便下次我可以谷歌答案?

3 个答案:

答案 0 :(得分:5)

这称为higher rank types,基本上是你想要的类型

 binop :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value

但你拥有的是

 binop :: forall a. Num a => (a -> a -> a) -> [Value] -> EvalM Value

你看到了区别吗?对于第一个函数,运算符在函数中实际上是多态的,它表示“给定一个函数可以采用类型Num a的任何a -> a -> a ...”。第二个说,“对于所有a,给定一个任意a -> a -> a的函数......”。

幸运的是,GHC支持更高级别的类型,

{-# LANGUAGE RankNTypes #-}

...
binop :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value
binop (+) [VFloat a, VFloat b] = return $ VFloat (a + b)
binop (+) [VInt   a, VFloat b] = return $ VFloat (fromInteger a + b)
binop (+) [VFloat a, VInt   b] = return $ VFloat (a + fromInteger b)
binop (+) [VInt   a, VInt   b] = return $ VInt   (a + b)
binop  _  [_,_] = throwError "Currently only adding numbers"
binop  _  _     = throwError "Arity Error: Add takes 2 arguments"

然而,类型推断器并没有使用更高级别的类型,因此您可能需要添加显式签名。

答案 1 :(得分:4)

据推测,你有一个像

这样的功能
binop :: Num a => (a -> a -> a) -> [Value] -> EvalM Value
binop op [VFloat a, VFloat b] = return $ VFloat (a `op` b)
...
binop op [VInt a, VInt b] = return $ VInt (a `op` b)

问题在于,您强制binop在第一种情况下使用第二种情况,而不进行任何类型的转换。在一个案例中,您说相同的运算符必须接受DoubleInteger。你可以很容易地解决这个问题,将最后一个案例转换为Double然后再转换回Integer,但你也可能会失去精确度,这可能不是你想要做的事情,如果你'重新将/视为整数除法。

相反,您可以使用RankNTypes指定您的运营商必须同时为所有 Num类型工作,而不是一次只能使用一个。

binop :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value
binop op [VFloat a, VFloat b] = return $ VFloat $ a `op` b
binop op [VInt a,     VInt b] = return $ VInt $ a `op` b
binop op [VFloat a,   VInt b] = return $ VFloat $ a `op` fromIntegral b
binop op [VInt a,   VFloat b] = return $ VFloat $ fromIntegral a `op` b

答案 2 :(得分:2)

您可以使用Rank2Types解决此问题,这仍然允许推理在大多数情况下都能正常工作。

{-# LANGUAGE Rank2Types #-}

data Value = VFloat Double | VInt Integer

op :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value
op (#) args = case args of
  [VInt a, VInt b]     -> return $ VInt $ a # b
  [VInt a, VFloat b]   -> return $ VFloat $ fromIntegral a # b
  [VFloat a, VInt b]   -> return $ VFloat $ a # fromIntegral b
  [VFloat a, VFloat b] -> return $ VFloat $ a # b

另外要小心,因为Num不足以表达需要(/)的{​​{1}}等运营商。