在Haskell中划分为Numeric类

时间:2016-03-02 07:32:23

标签: haskell division

我正在尝试定义一个函数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

1 个答案:

答案 0 :(得分:3)

不,因为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)

也就是说,对于固定的ab,告诉divF如何将两个a组合成b,或者如何转换为a bFraction a,您可以将两个b缩减为一个a = b。在您的所有情况divF :: (a -> a -> a) -> Fraction a -> a divF = divG id 中,我们可以定义另一个帮助程序:

toVal1

现在我们可以用toVal2来定义divFtoVal1 :: Integral n => Fraction n -> Fraction n -> n toVal1 = divF div toVal2 :: Fractional n => Fraction n -> Fraction n -> n toVal2 = divF (/)

toVal

话虽如此,toVal1toVal1 (Rational (Rational 2 3) (Rational 2 3)) = 0 :: Int 都会对整数引起有趣的行为:

x div x

div对任何x /= 0都应为1。如果您预处理积分Fraction,则不会出现此问题:

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)

请注意,由于Num,这需要Fraction *个实例。这样你就可以最大程度地控制实际的划分,只需要在最后转换元素:

toVal3 :: Integral n => Fraction n -> n
toVal3 = divF div . divF rationalDiv

在上面的示例中,哪个正确导致1。但回到主题:不,你不能只使用Num作为约束,你需要使用另一个实际上有分裂概念的人。