繁体   English   中英

你如何对 Haskell 中的类型类和数据类型进行递归?

[英]how do you do recursion with type classes and data type in Haskell?

我正在研究一个类型 class,用于分数、向量和矩阵运算(即加法、减法、乘法),但不能完全获得向量实例,因为我不知道如何使用 function 的递归性质。

这是代码:

class MathObject a where
    add :: a -> a -> a
    sub :: a -> a -> a
    mul :: a -> a -> a


type Vector = [Int]


data Vec = Vec Vector deriving (Show, Eq)


instance MathObject Vec where 
    add (Vec v1) (Vec v2) = addVecs (Vec v1) (Vec v2)
    sub (Vec v1) (Vec v2) = subVecs (Vec v1) (Vec v2)
    mul (Vec v1) (Vec v2) = mulVecs (Vec v1) (Vec v2)

addVecs :: Vec -> Vec -> [Int]
addVecs (Vec []) (Vec vec2)= (Vec [])
addVecs (Vec vec) (Vec []) = (Vec [])
addVecs (Vec (x:xs)) (Vec (y:ys)) = e1+e2:rest where
  e1 = x
  e2 = y
  rest = addVecs (Vec xs) (Vec ys)

subVecs :: Vec -> Vec -> [Int]
subVecs (Vec []) (Vec v2) = (Vec [])
subVecs (Vec (x:xs)) (Vec (y:ys)) = x-y:rest where
  rest = subVecs (Vec xs) (Vec ys)

mulVecs :: Vec -> Vec -> [Vec]
mulVecs (Vec []) (Vec vec2) = (Vec [])
mulVecs (Vec (x:xs)) (Vec (y:ys)) = e1 * e2:rest where
  e1 = x
  e2 = y
  rest = mulVecs (Vec xs) (Vec ys)

我制作了分数实例并且它可以工作,所以我对类型类的工作原理有一些基本的了解,但我只是不知道如何处理递归类型。

我要说的是,首先,您将自己与太多不同的东西混淆了,这些东西的名字听起来像“矢量”。 为什么 Vec 类型有一个名为 Vec 的构造函数和 Vector 类型的字段? Vec 和 Vector 之间到底有什么区别? 如果Vector是[Int]的别名,那为什么有时候用普通的[Int]来表示向量呢? 您不包括您得到的编译器错误,但我可以清楚地看到,由于这些名称之间的类型不匹配,至少会出现其中一些错误。

所以要做的第一件事就是简化它:只有一个 Vector 类型,它是[Int] newtype新类型(或者data ,如果你还没有接触过新类型的话):

newtype Vector = Vector [Int]

现在您始终知道您使用的是 Vector 还是 [Int],并且可以使用 Vector 构造函数在两者之间进行转换。

我的下一个观察是addVecs和朋友们显然有错误的类型:为什么他们接受两个Vector输入并返回一个[Int] 他们应该坚持使用一种类型,最好是Vector 这是您的类型错误之一的原因: add需要是a -> a -> a类型,但您已将add定义为addVecs ,它的类型是Vec -> Vec -> [Int] - 那不是适合! 所以让我们改写

addVecs :: Vector -> Vector -> Vector
-- ...

现在,考虑到您为它选择的糟糕类型,实现是正确的。 但是为了更好的类型,我们必须在将a+b转换为结果之前添加一个Vector包装器。 不过,我不会展示该实现,因为有一个更好的实现。 不要自己进行递归,而是使用 Haskell 的众多灵活工具之一来处理列表: zipWith

addVecs (Vector v1) (Vector v2) = Vector $ zipWith (+) v1 v2

这与您的实现完全相同,但所有繁琐的包装和展开只完成一次,并且递归列表处理完全由zipWith抽象掉。

您可以对subVecsmulVecs做同样的事情,但您很快会注意到这里有一些重复。 最好将其提取到 function 中,以便这三个函数可以共享公共代码:

pointwise :: (Int -> Int -> Int) -> (Vector -> Vector -> Vector)
pointwise f (Vector v1) (Vector v2) = Vector $ zipWith f v1 v2

addVecs = pointwise (+)
subVecs = pointwise (-)
mulVecs = pointwise (*)

此时你甚至不需要addVecs和 friends 的定义——你可以轻松地将它们内联到MathObject实例中:

instance MathObject Vector where
  add = pointwise (+)
  sub = pointwise (-)
  mul = pointwise (*)

暂无
暂无

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

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