Haskell 中除法 class
Division in Haskell for Numeric class
我正在尝试定义一个函数 toVal :: (Num a) => (Fraction a) -> a
。该函数采用分数并计算其数值。但是由于该函数使用除法,我可以执行以下操作,因为除法是由 Num a
的子类上的不同函数定义的:
data Fraction a = Constant a
|Rational{numerator :: (Fraction a), denominator :: (Fraction a)}
toVal1 :: (Integral a) => (Fraction a) -> a
toVal1 (Constant a) = a
toVal1 (Rational num den) = (toVal1 num) `div` (toVal1 den)
toVal2 :: (Fractional a) => (Fraction a) -> a
toVal2 (Constant a) = a
toVal2 (Rational num den) = (toVal2 num) / (toVal2 den)
有什么方法可以将这两个函数结合起来,以便我可以拥有一个通用函数toVal :: (Num a) => (Fraction a) -> a
?
不,因为 Num
没有除法的概念,或者用 C++ 术语来说,因为 Haskell 中没有 dynamic_cast<...>
。
你可以引入你自己的类型类:
class HasDivOp a where
divOp :: a -> a -> a
instance HasDivOp Int where divOp = div
instance HasDivOp Integer where divOp = div
instance HasDivOp Double where divOp = (/)
instance HasDivOp Float where divOp = (/)
然后有一个函数接受正确的 divOp
:
toVal :: (Num a, HasDivOp a) => (Fraction a) -> a
toVal (Constant a) = a
toVal (Rational a b) = toVal a `divOp` toVal b
减少代码重复的另一种方法是添加一个附加函数:
divG :: (a -> b) -> (a -> a -> b) -> Fraction a -> b
divG p _ (Constant x) = p x
divG p f (Rational num den) = f (divG p f num) (divF p f den)
即对于固定的a
和b
告诉divF
如何将两个a
组合成一个b
,或者如何将 a
转换为 b
,您可以将两个 Fraction a
减少为一个 b
。在你所有的情况下 a = b
,所以我们可以定义另一个助手:
divF :: (a -> a -> a) -> Fraction a -> a
divF = divG id
现在我们可以根据 divF
:
定义 toVal1
和 toVal2
toVal1 :: Integral n => Fraction n -> Fraction n -> n
toVal1 = divF div
toVal2 :: Fractional n => Fraction n -> Fraction n -> n
toVal2 = divF (/)
也就是说,toVal
和 toVal1
都会导致整数的有趣行为:
toVal1 (Rational (Rational 2 3) (Rational 2 3)) = 0 :: Int
但是 x <code>div
x 对于任何 x /= 0
应该是 1。如果你预处理你的积分 Fraction
s 这个问题就不会发生:
rationalDiv :: Integral n => Fraction a -> Fraction a -> Fraction a
rationalDiv (Constant a ) (Constant c ) = Rational a c
rationalDiv (Constant a ) (Rational c d) = Rational (a * d) c
rationalDiv (Rational a b) (Constant c ) = Rational a (b * c)
rationalDiv (Rational a b) (Rational c d) = Rational (a * d) (b * c)
请注意,由于 *
,这需要 Fraction
的 Num
实例。这样你就可以最大程度地控制实际的划分,只需要在最后转换元素:
toVal3 :: Integral n => Fraction n -> n
toVal3 = divF div . divF rationalDiv
这在我们上面的例子中正确地导致了 1
。言归正传:不,你不能只使用Num
作为约束,你需要使用另一个实际上有除法概念的约束。
我正在尝试定义一个函数 toVal :: (Num a) => (Fraction a) -> a
。该函数采用分数并计算其数值。但是由于该函数使用除法,我可以执行以下操作,因为除法是由 Num a
的子类上的不同函数定义的:
data Fraction a = Constant a
|Rational{numerator :: (Fraction a), denominator :: (Fraction a)}
toVal1 :: (Integral a) => (Fraction a) -> a
toVal1 (Constant a) = a
toVal1 (Rational num den) = (toVal1 num) `div` (toVal1 den)
toVal2 :: (Fractional a) => (Fraction a) -> a
toVal2 (Constant a) = a
toVal2 (Rational num den) = (toVal2 num) / (toVal2 den)
有什么方法可以将这两个函数结合起来,以便我可以拥有一个通用函数toVal :: (Num a) => (Fraction a) -> a
?
不,因为 Num
没有除法的概念,或者用 C++ 术语来说,因为 Haskell 中没有 dynamic_cast<...>
。
你可以引入你自己的类型类:
class HasDivOp a where
divOp :: a -> a -> a
instance HasDivOp Int where divOp = div
instance HasDivOp Integer where divOp = div
instance HasDivOp Double where divOp = (/)
instance HasDivOp Float where divOp = (/)
然后有一个函数接受正确的 divOp
:
toVal :: (Num a, HasDivOp a) => (Fraction a) -> a
toVal (Constant a) = a
toVal (Rational a b) = toVal a `divOp` toVal b
减少代码重复的另一种方法是添加一个附加函数:
divG :: (a -> b) -> (a -> a -> b) -> Fraction a -> b
divG p _ (Constant x) = p x
divG p f (Rational num den) = f (divG p f num) (divF p f den)
即对于固定的a
和b
告诉divF
如何将两个a
组合成一个b
,或者如何将 a
转换为 b
,您可以将两个 Fraction a
减少为一个 b
。在你所有的情况下 a = b
,所以我们可以定义另一个助手:
divF :: (a -> a -> a) -> Fraction a -> a
divF = divG id
现在我们可以根据 divF
:
toVal1
和 toVal2
toVal1 :: Integral n => Fraction n -> Fraction n -> n
toVal1 = divF div
toVal2 :: Fractional n => Fraction n -> Fraction n -> n
toVal2 = divF (/)
也就是说,toVal
和 toVal1
都会导致整数的有趣行为:
toVal1 (Rational (Rational 2 3) (Rational 2 3)) = 0 :: Int
但是 x <code>div
x 对于任何 x /= 0
应该是 1。如果你预处理你的积分 Fraction
s 这个问题就不会发生:
rationalDiv :: Integral n => Fraction a -> Fraction a -> Fraction a
rationalDiv (Constant a ) (Constant c ) = Rational a c
rationalDiv (Constant a ) (Rational c d) = Rational (a * d) c
rationalDiv (Rational a b) (Constant c ) = Rational a (b * c)
rationalDiv (Rational a b) (Rational c d) = Rational (a * d) (b * c)
请注意,由于 *
,这需要 Fraction
的 Num
实例。这样你就可以最大程度地控制实际的划分,只需要在最后转换元素:
toVal3 :: Integral n => Fraction n -> n
toVal3 = divF div . divF rationalDiv
这在我们上面的例子中正确地导致了 1
。言归正传:不,你不能只使用Num
作为约束,你需要使用另一个实际上有除法概念的约束。