Haskell:更好地理解代数数据类型

时间:2014-10-18 20:17:51

标签: haskell algebraic-data-types

我试图构建一个代表多项式的代数数据类型。给定一个整数常量是多项式的定义,并且如果你添加两个多项式或乘以两个多项式,它会得到一个多项式。

我很难理解代数数据类型的工作原理以及我如何制作这个数据类型。我目前有

data Poly = Const Int | 
            Add Poly Poly | 
            Mult Poly Poly

但是,我不知道这甚至意味着什么或如何使用它,我只是简单地谈论我已经看到的代数数据类型的例子。

我见过像

这样的类型
data Tree = NullT |
            Node Int Tree Tree

这对我来说更有意义,以及如何使用它。多项式的例子看起来很抽象,我不知道从哪里开始。

编辑:当我尝试实现简单的测试功能时:

evalPoly :: Poly -> Int
evalPoly (Const n) = n

我遇到了错误

*Polynomial> evalPoly Poly 1

<interactive>:25:10: Not in scope: data constructor ‘Poly’
*Polynomial> 

再次编辑:感谢您提出的所有建议和帮助,它帮助我制作出符合我目的的作品!

2 个答案:

答案 0 :(得分:3)

这是我发布的另一个解决方案。

你好像想要为多项式做一个ADT,我会使用Map,但是让我们看一下术语列表。首先进口一些:

import Data.Function (on)
import Data.List (sortBy, groupBy, foldl1')

这样,多项式是一个术语列表,首先以最高权力排序,术语是aX ^ n,由X a n

表示
newtype Poly a n = Poly {terms :: [Term a n]} deriving (Show)
data Term a n = X {coeff :: a, power :: n}  deriving (Eq,Show)

让我们做一些简单的多项式:

zero = Poly []
x = Poly [X 1 1]

constant :: (Num a,Eq a,Num n) => a -> Poly a n
constant 0 = zero
constant a = Poly [X a 0]

一旦我们定义了Num实例,我们就可以通过编写X 3 4来制作3*x^4

与多项式有关的标准事项是使用x的特定值对其进行评估。

subst :: (Num a, Integral n) => a -> Term a n -> a
subst x (X a n) = a * x ^ n

evalAt :: (Num a, Integral n) => a -> Poly a n -> a
evalAt x = sum . map (subst x) . terms

我希望能够映射系数,但我不想让它成为Functor的一个实例,因为应用诸如平方,应用三角函数或对数函数或取平方根等操作是一个经典的学生错误按术语来说,实际上只有像标量乘法,微分和积分这样的少数事情就像这样。提供fmap会鼓励您执行fmap (+1)而非(+ (constant 1))之类的操作。

mapCoeffs :: (a -> b) -> Poly a n -> Poly b n
mapCoeffs f = Poly . map f' . terms
  where f' (X a n) = X (f a) n

我们需要添加和倍增术语,并收集类似的术语。当我们收集类似的术语时,我们按功率的相反顺序排序,并省略零系数的术语。

addTerm (X a n) (X b m) | n == m = X (a+b) n
                        | otherwise = error "addTerm: mismatched powers" 
multTerm (X a n) (X b m) = X (a*b) (n+m)

collectLikeTerms :: (Num a, Ord n, Eq a) => Poly a n -> Poly a n
collectLikeTerms =  Poly . filter ((/= 0).coeff)            -- no zero coeffs
                         . map (foldl1' addTerm)            -- add the like powers
                         . groupBy ((==) `on` power)        -- group the like powers
                         . sortBy (flip compare `on` power) -- sort in reverse powers
                         . terms                            

现在我们可以制作实例:

instance (Eq a,Num a,Ord n,Num n) => Eq (Poly a n) where
   f == g = f - g == zero

instance (Eq a,Num a,Num n,Ord n) => Num (Poly a n) where
   fromInteger = constant . fromInteger
   signum (Poly []) = zero
   signum (Poly (t:_)) = constant . signum . coeff $ t
   abs =  mapCoeffs abs
   negate = mapCoeffs negate
   (+) = (collectLikeTerms.) . (Poly.) . ((++) `on` terms)
   (Poly ts) * (Poly ts') = collectLikeTerms $ Poly [multTerm t t' | t<-ts, t'<-ts']

行动中:

ghci> 5*x^2 + 6*x^7 + 2
Poly {terms = [X {coeff = 6, power = 7},X {coeff = 5, power = 2},X {coeff = 2, power = 0}]}

答案 1 :(得分:3)

您似乎想为多项式制作ADT,但我更喜欢使用Map。首先进口一些:

import qualified Data.Map as M
import Data.Function (on)

多项式是从x的幂到系数的映射。

newtype Poly a n = Poly {coeffMap :: M.Map n a} deriving (Show)
lift f = Poly . f . coeffMap

让我们做一些简单的多项式:

zero = Poly M.empty                  -- none of the powers have non-zero coefficients
x = Poly $ M.singleton 1 1           -- x^1 has coefficient 1

constant 0 = zero
constant a = Poly $ M.singleton 0 a  -- x^0 has coefficient a

与多项式有关的标准事项是使用x的特定值对其进行评估。

此处的折叠采用部分计算的b并添加新术语a*x^n

evalAt :: (Num a, Integral n) => a -> Poly a n -> a
evalAt x = M.foldrWithKey (\n a b -> b + a*x^n) 0 . coeffMap

如果我们想要使用地图功能,我们可以将其从Map n a提升到Poly n a
我希望能够对系数进行映射,但我不想将其作为Functor的一个实例,因为应用诸如平方,应用三角函数或对数函数等操作是一个经典的学生错误或者按术语逐渐占据平方根,实际上只有像标量乘法,微分和积分这样的少数东西就像这样。提供fmap会鼓励您执行fmap (+1)而非(+ (constant 1))之类的操作。

mapCoeffs :: (a -> b) -> Poly a n -> Poly b n
mapCoeffs f = lift (fmap f)

地图已经自动收集了相似的字词,但我们想要省略零系数的字词:

strikeZeros :: (Num a,Eq a) => Poly a n -> Poly a n
strikeZeros =  lift $  M.filter (/= 0)                      

现在我们可以制作实例:

instance (Eq a,Num a,Ord n,Num n) => Eq (Poly a n) where
   f == g = f - g == zero

instance (Eq a,Num a,Num n,Ord n) => Num (Poly a n) where
   fromInteger = constant . fromInteger
   signum (Poly m) | M.null m = zero
                   | otherwise = let (n,a) = M.findMax m in 
                        Poly $ M.singleton n (signum a)
   abs =  mapCoeffs abs
   negate = mapCoeffs negate
   (+) = (strikeZeros.) . (Poly.) . ((M.unionWith (+)) `on` coeffMap)
   (Poly m) * (Poly m') = Poly $ 
          M.fromListWith (+) [(n+n',a*a') | (n,a)<-M.assocs m, (n',a')<-M.assocs m']

行动中:

ghci> 3*x^4 + 6 + 2*x^7
Poly {coeffMap = fromList [(0,6),(4,3),(7,2)]}