簡體   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