简体   繁体   English

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

[英]Haskell: Understanding algebraic data types better

I'm trying to construct an algebraic data type that represents polynomials. 我正在尝试构造一个代表多项式的代数数据类型。 Given the definition that an Integer constant is a polynomial and that if you add two polynomials or multiply two polynomials, it results in a polynomial. 给定一个整数常量是多项式的定义,并且如果添加两个多项式或乘以两个多项式,则会得到多项式。

I'm having a difficult time understanding how algebraic data types work in general and how I would even go about producing this. 我很难理解代数数据类型的工作原理以及我如何制作这个数据类型。 I currently have 我现在有

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

However I don't know what this even means or how to use it, I'm simply going off of examples I've seen of algebraic data types. 但是我不知道这甚至意味着什么或者如何使用它,我只是简单地讨论了代数数据类型的例子。

I've seen types like 我见过类似的类型

data Tree = NullT |
            Node Int Tree Tree

That makes more sense to me, and how to use it. 这对我来说更有意义,以及如何使用它。 The polynomial example seems so abstract I don't know where to start. 多项式的例子看起来很抽象,我不知道从哪里开始。

Edit: When I try to implement simple testing functions like: 编辑:当我尝试实现简单的测试功能,如:

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

I'm met with the error 我遇到了错误

*Polynomial> evalPoly Poly 1

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

Edit again: Thank you for all your suggestions and help, it's helped me produce something that's working for my purposes! 再次编辑:感谢您的所有建议和帮助,这有助于我制作出符合我目的的东西!

Here's an alternative solution to the other one I posted. 这是我发布的另一个解决方案。

You seem to want to make an ADT for polynomials, where I'd use a Map, but let's go with a list of terms. 你似乎想为多项式做一个ADT,在那里我使用Map,但让我们看一下术语列表。 First some imports: 首先进口一些:

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

This way a polynomial is a list of terms, sorted with the highest power first, and a term is aX^n, represented by X an 这样,多项式是项列表,首先以最高功率排序,并且项是aX ^ n,由X an

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

Let's make some simple polynomials: 让我们做一些简单的多项式:

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]

Once we've defined the Num instance, we'll be able to make X 3 4 by writing 3*x^4 . 一旦我们定义了Num实例,我们就可以通过编写3*x^4来生成X 3 4 3*x^4

A standard thing to do with a polynomial is evaluate it with a particular value for x. 与多项式有关的标准事项是使用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

I'd like to be able to map on the coefficients, but I don't want to make this an instance of Functor because it's a classic student error to apply operations like squaring, applying trigonometrical or logarithmic functions or taking square roots term by term, when in fact only a tiny few things like scalar multiplication, differentiation and integration work like this. 我希望能够映射系数,但我不想让它成为Functor的一个实例,因为应用平方,应用三角函数或对数函数或按术语取平方根等操作是一个经典的学生错误实际上,只有像标量乘法,微分和积分这样的少数事情就像这样。 Providing fmap encourages you to do wong things like fmap (+1) instead of (+ (constant 1)) . 提供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

We'll need to add and multiply terms, and collect like terms. 我们需要添加和倍增术语,并收集类似的术语。 When we collect like terms, we sort in reverse order of power and omit terms with zero coefficients. 当我们收集类似的术语时,我们按功率的相反顺序排序并省略具有零系数的术语。

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                            

Now we can make the instances: 现在我们可以创建实例:

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']

In action: 在行动:

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}]}

You seem to want to make an ADT for polynomials, but I'd prefer to use a Map. 你似乎想为多项式做一个ADT,但我更喜欢使用Map。 First some imports: 首先进口一些:

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

A polynomial is a Map from powers of x to coefficients. 多项式是从x的幂到系数的映射。

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

Let's make some simple polynomials: 让我们做一些简单的多项式:

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

A standard thing to do with a polynomial is evaluate it with a particular value for x. 与多项式有关的标准事项是使用x的特定值来评估它。

The fold here takes the partially-calculated b and adds on the new term, a*x^n : 这里的折叠采用部分计算的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

If we want to use a Map function, we can lift it from Map na to Poly na . 如果我们想使用Map函数,我们可以将它从Map na提升到Poly na
I'd like to be able to map on the coefficients, but I don't want to make this an instance of Functor because it's a classic student error to apply operations like squaring, applying trigonometrical or logarithmic functions or taking square roots term by term, when in fact only a tiny few things like scalar multiplication, differentiation and integration work like this. 我希望能够映射系数,但我不想让它成为Functor的一个实例,因为应用平方,应用三角函数或对数函数或按术语取平方根等操作是一个经典的学生错误实际上,只有像标量乘法,微分和积分这样的少数事情就像这样。 Providing fmap encourages you to do wong things like fmap (+1) instead of (+ (constant 1)) . 提供fmap会鼓励你做一些像fmap (+1)而不是(+ (constant 1))

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

Maps already collect like terms automatically, but we'll want to omit terms with zero coefficients: 地图已经自动收集了相似的字词,但我们要省略零系数的字词:

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

Now we can make the instances: 现在我们可以创建实例:

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']

In action: 在行动:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM