简体   繁体   English

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

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

I am working on a type class for fraction, vector, and matrix arithmetic (ie add, sub, mul) but can't quite get the vector instance because I don't know how to work with the recursive nature of the function.我正在研究一个类型 class,用于分数、向量和矩阵运算(即加法、减法、乘法),但不能完全获得向量实例,因为我不知道如何使用 function 的递归性质。

here's the code:这是代码:

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)

I made the fraction instance and it works so I have some basic understanding of how type classes work but I just don't know how to deal with a recursive type.我制作了分数实例并且它可以工作,所以我对类型类的工作原理有一些基本的了解,但我只是不知道如何处理递归类型。

I would say, first, you have confused yourself with too many different things named something that sounds like "vector".我要说的是,首先,您将自己与太多不同的东西混淆了,这些东西的名字听起来像“矢量”。 Why does the Vec type have a constructor named Vec and fields of type Vector?为什么 Vec 类型有一个名为 Vec 的构造函数和 Vector 类型的字段? What's the difference really between a Vec and a Vector? Vec 和 Vector 之间到底有什么区别? If Vector is an alias for [Int], why do you use ordinary [Int] to represent vectors sometimes?如果Vector是[Int]的别名,那为什么有时候用普通的[Int]来表示向量呢? You don't include the compiler errors you get, but I can see clearly that at least some of them will arise due to type mismatches between these names.您不包括您得到的编译器错误,但我可以清楚地看到,由于这些名称之间的类型不匹配,至少会出现其中一些错误。

So the first thing to do is simplify it: just have one Vector type, which is a newtype (or data if you haven't gotten to newtypes yet) wrapper around [Int] :所以要做的第一件事就是简化它:只有一个 Vector 类型,它是[Int] newtype新类型(或者data ,如果你还没有接触过新类型的话):

newtype Vector = Vector [Int]

Now you always know whether you're working with a Vector or an [Int], and can convert between the two using the Vector constructor.现在您始终知道您使用的是 Vector 还是 [Int],并且可以使用 Vector 构造函数在两者之间进行转换。

My next observation would be that addVecs and friends clearly have the wrong type: why do they take two Vector inputs and return an [Int] ?我的下一个观察是addVecs和朋友们显然有错误的类型:为什么他们接受两个Vector输入并返回一个[Int] They should stick with one type, preferably Vector .他们应该坚持使用一种类型,最好是Vector This is the cause of one of your type errors: add needs to be of type a -> a -> a , but you've defined add to be addVecs , which has type Vec -> Vec -> [Int] - that doesn't fit!这是您的类型错误之一的原因: add需要是a -> a -> a类型,但您已将add定义为addVecs ,它的类型是Vec -> Vec -> [Int] - 那不是适合! So let's instead write所以让我们改写

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

Now, the implementation is right given the poor type you chose for it.现在,考虑到您为它选择的糟糕类型,实现是正确的。 But to work for the better type, we'd have to add a Vector wrapper around a+b before consing it to the result.但是为了更好的类型,我们必须在将a+b转换为结果之前添加一个Vector包装器。 I won't show that implementation, though, because there's a much better one available.不过,我不会展示该实现,因为有一个更好的实现。 Instead of doing the recursion yourself, use one of Haskell's many flexible tools for working with lists: zipWith .不要自己进行递归,而是使用 Haskell 的众多灵活工具之一来处理列表: zipWith

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

This does exactly the same thing as your implementation, but all the tedious wrapping and unwrapping is done only once, and recursive list processing is completely abstracted away by zipWith .这与您的实现完全相同,但所有繁琐的包装和展开只完成一次,并且递归列表处理完全由zipWith抽象掉。

You could do the same thing for subVecs and mulVecs , but you would quickly notice there's some duplication here.您可以对subVecsmulVecs做同样的事情,但您很快会注意到这里有一些重复。 It would be better to extract that out into a function, so that those three functions can all share the common code:最好将其提取到 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 (*)

At this point you don't even really need definitions for addVecs and friends - you could easily inline them into the MathObject instance:此时你甚至不需要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