我有一个我正在处理的解析器。没有进入所有细节,我想要一个将添加两个数值的函数:
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在第四版中。
两个问题:
binop :: Num a => (a->a->a) -> [Value]-> EvalM Value
以便它可以同时处理VInt和VFloat?答案 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
在第一种情况下使用和第二种情况,而不进行任何类型的转换。在一个案例中,您说相同的运算符必须接受Double
和Integer
。你可以很容易地解决这个问题,将最后一个案例转换为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}}等运营商。