【问题标题】:Haskell: Understanding algebraic data types betterHaskell:更好地理解代数数据类型
【发布时间】:2014-10-18 20:17:50
【问题描述】:

我正在尝试构建一个表示多项式的代数数据类型。给定整数常量是多项式的定义,如果将两个多项式相加或将两个多项式相乘,就会得到一个多项式。

我很难理解代数数据类型的一般工作原理,以及我什至会如何生成它。我目前有

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> 

再次编辑:感谢您的所有建议和帮助,它帮助我制作了适合我的目的的东西!

【问题讨论】:

  • 您已经问过自己要对多项式执行什么样的操作,然后尝试实现它们。
  • @augustss 我有一些需要稍后实现,但现在我想重点了解它的定义以及如何使用定义。
  • 您将类型 (Poly) 与其构造函数 (Const, Add, Int) 混淆了; Const 1 构造一个PolyPoly 1不能
  • @bheklilr 啊,谢谢,这是有道理的。我认为出于这个小程序的目的,我不需要将 polys 发挥到 polys 的作用,只需显示 polys。感谢您的所有帮助!
  • FWIW,您最好将多项式(在单个变量中)表示为元组列表:x^2 + 2x^7 = [(1, 2), (2, 7)] :: [(Coefficient, Exponent)],比如说。

标签: haskell algebraic-data-types


【解决方案1】:

您似乎想为多项式制作 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 函数,我们可以将它从 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)]}

【讨论】:

    【解决方案2】:

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

    您似乎想为多项式制作 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 实例,我们就可以通过编写 3*x^4 来生成 X 3 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}]}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      相关资源
      最近更新 更多