[英]How can I define this numeric operator generalization?
我正在制作一个简单的表达式评估器。 以下是类型:
data Value = IntV Integer | FloatV Float | BoolV Bool
这是我定义加法的方式(我知道 function 是部分的,但在这种情况下并不重要):
addValues :: Value -> Value -> Value
addValues (IntV n) (FloatV f) = FloatV (fromIntegral n + f)
addValues (FloatV f) (IntV n) = FloatV (fromIntegral n + f)
addValues (FloatV f1) (FloatV f2) = FloatV (f1 + f2)
addValues (IntV n1) (IntV n2) = IntV (n1 + n2)
如果 Integer 值与另一个浮点数一起操作,则它们将转换为浮点数。 如果不是,它们将保持为整数。
现在,这是我定义数字比较的方式:
isLessThan :: Value -> Value -> Value
isLessThan (IntV n) (FloatV f) = BoolV (fromIntegral n < f)
isLessThan (FloatV f) (IntV n) = BoolV (fromIntegral n < f)
isLessThan (FloatV f1) (FloatV f2) = BoolV (f1 < f2)
isLessThan (IntV n1) (IntV n2) = BoolV (n1 < n2)
如您所见,它开始变得重复。 这两个函数(以及许多其他函数,如减法、乘法等)之间的唯一区别是用于返回值的构造函数和在值内的数字之间使用的运算符。 我试图定义一个 function 来概括这种行为:
numericOperation :: (c -> Value) -> (a -> b -> c) -> Value -> Value -> Value
numericOperation resFun op (IntV n) (FloatV f) = resFun $ fromIntegral n `op` f
numericOperation resFun op (FloatV f) (IntV n) = resFun $ fromIntegral n `op` f
numericOperation resFun op (FloatV f1) (FloatV f2) = resFun $ f1 `op` f2
numericOperation resFun op (IntV n1) (IntV n2) = resFun $ n1 `op` n2
然后我就可以像这样重新定义以前的函数:
isLessThan :: Value -> Value -> Value
isLessThan v1 v2 = numericOperation BoolV (<) v1 v2
但是,这不起作用,因为运算符需要a
和 a b
,但在 function 定义中,它的使用就像它需要一个 Integer 和一个浮点数,或两个浮点数等。我该如何写这个 ZC1C425264C18A98?
一个简单的解决方案是为每种重载操作定义一个数据类型,例如不同的数量和重载规则。 在这里,您可能只有一个二进制 function 输入数字和 output 一些值:
data NumOp = NumOp
{ integral :: Integer -> Integer -> Value
, floating :: Float -> Float -> Value
}
然后您的numericOperation
类型可以处理促销并分派给适当的函数:
type Binary = Value -> Value -> Value
numericOperation :: NumOp -> Binary
numericOperation op (IntV n1) (IntV n2) = integral op n1 n2
numericOperation op (IntV n) (FloatV f) = floating op (fromIntegral n) f
numericOperation op (FloatV f) (IntV n) = floating op f (fromIntegral n)
numericOperation op (FloatV f1) (FloatV f2) = floating op f1 f2
操作本身可以直接构造:
isLessThan :: Binary
isLessThan = numericOperation less
where
less = NumOp
{ integral = fmap BoolV . (<) -- (<) @Integer
, floating = fmap BoolV . (<) -- (<) @Float
}
addValues :: Binary
addValues = numericOperation add
where
add = NumOp
{ integral = fmap IntV . (+) -- (+) @Integer
, floating = fmap FloatV . (+) -- (+) @Float
}
或者作为使用RankNTypes
等扩展的便利包装器,它允许您将多态 function 作为参数传递:
{-# Language RankNTypes #-}
relationalBinaryOp
:: (forall a. (Ord a) => a -> a -> Bool) -> NumOp
relationalBinaryOp f = NumOp
{ integral = fmap BoolV . f -- f @Integer
, floating = fmap BoolV . f -- f @Float
}
numericBinaryOp
:: (forall a. (Num a) => a -> a -> a) -> NumOp
numericBinaryOp f = NumOp
{ integral = fmap IntV . f -- f @Integer
, floating = fmap FloatV . f -- f @Float
}
isLessThan = numericOperation $ relationalBinaryOp (<)
isGreaterThan = numericOperation $ relationalBinaryOp (>)
addValues = numericOperation $ numericBinaryOp (+)
subtractValues = numericOperation $ numericBinaryOp (-)
-- …
当然,还有其他组织方式,例如跳过NumOp
并直接传递其字段——无论是作为单独的函数,还是作为使用上述RankNTypes
的单个多态 function。 由于您的两个示例具有不同的约束( Num
与Ord
),我可能会将它们分成numericOperation:: (forall a. (Num a) => a -> a -> a) -> Binary
和relationalOperation:: (forall a. (Ord a) => a -> a -> Bool) -> Binary
而不是试图抽象约束。
也可以使用 GADT 和类型类或类型族将更多动态信息反映到 static Haskell 类型中,但这涉及更多。
切线地,将任意精度的Integer
用于整数值,但使用紧凑的固定精度 32 位Float
用于小数值,这让我觉得有点奇怪。 从Integer
到Float
的转换是有损的——事实上,即使是Int
(通常为 64 位)到Double
(53 个有效位)也会有损。 如果您需要任意精度的小数类型, Data.Ratio
中的Ratio
类型提供通用有理数,并且有一个别名Rational
用于任意精度Integer
的比率。
> import Data.Ratio
> 0.1 + 0.2 :: Double
0.30000000000000004
> 1 % 10 + 2 % 10 :: Rational
3 % 10
> fromRational (1 % 10 + 2 % 10) :: Double
0.3
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.