在Haskell中如何使用类型类和数据类型进行递归?

e4eetjau  于 2022-12-13  发布在  其他
关注(0)|答案(1)|浏览(165)

我正在为fraction、vector和matrix算术(即add、sub、穆尔)编写一个类型类,但无法获得vector示例,因为我不知道如何处理函数的递归性质。
代码如下:

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)

我做了fraction示例,它可以工作,所以我对类型类的工作有一些基本的了解,但是我不知道如何处理递归类型。

uurity8g

uurity8g1#

我想说,首先,你已经把自己和太多不同的东西混淆了,这些东西的名字听起来像“vector”。为什么Vec类型有一个名为Vec的构造函数和Vector类型的字段?Vec和Vector之间的区别 * 真的 * 是什么?如果Vector是[Int]的别名,为什么有时你用普通的[Int]来表示向量?你没有包括你得到的编译器错误。但我可以清楚地看到,至少有一些错误是由于这些名称之间的类型不匹配而引起的。
所以首先要做的就是简化它:只有一个Vector类型,它是newtype(或者data,如果您还没有使用newtypes的话) Package 器, Package [Int]

newtype Vector = Vector [Int]

现在,您始终知道是使用Vector还是[Int],并且可以使用Vector构造函数在两者之间进行转换。
我的下一个观察结果是addVecs和friends的类型显然是错误的:为什么它们接受两个Vector输入并返回一个[Int]?它们应该坚持使用一个类型,最好是Vector。这是导致您的一个类型错误的原因:add必须是a -> a -> a类型,但是您已经将add定义为addVecs,而addVecs的类型为Vec -> Vec -> [Int]-这不符合!因此,让我们改为编写

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

现在,这个实现是正确的,因为你选择了一个很差的类型。但是为了使用更好的类型,我们必须在a+b周围添加一个Vector Package 器,然后再将其转化为结果。不过,我不会展示这个实现,因为有一个更好的实现。不要自己做递归,使用Haskell的许多灵活的工具中的一个来处理列表:zipWith .

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

这与您的实现所做的事情完全相同,但是所有繁琐的 Package 和解 Package 只完成一次,并且zipWith完全抽象了递归列表处理。
你可以对subVecsmulVecs做同样的事情,但是你很快就会注意到这里有一些重复。最好把它提取到一个函数中,这样这三个函数就可以共享公共代码:

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 (*)

相关问题