2009-06-15 67 views
16

假設我有以下幾點:哈斯克爾多態性,並列出

class Shape a where 
    draw a :: a -> IO() 

data Rectangle = Rectangle Int Int 

instance Shape Rectangle where 
    draw (Rectangle length width) = ... 

data Circle = Circle Int Int 

instance Shape Circle where 
    draw (Circle center radius) = ... 

有什麼辦法,我定義形狀的列表,遍歷了名單,並呼籲每個形狀繪製函數?下面的代碼將無法編譯,因爲列表中的元素並不都是相同類型:

shapes = [(Circle 5 10), (Circle 20, 30), (Rectangle 10 15)] 

我知道我想在OO方式,並試圖將其應用到哈斯克爾,這可能不是最好的方法。對於需要處理不同類型對象集合的程序,最好的Haskell方法是什麼?

回答

21

如果你真的需要做到這一點,然後使用existential

{-# LANGUAGE GADTs #-} 


class IsShape a where 
    draw :: a -> IO() 

data Rectangle = Rectangle Int Int 

instance IsShape Rectangle where 
    draw (Rectangle length width) = ... 

data Circle = Circle Int Int 

instance IsShape Circle where 
    draw (Circle center radius) = ... 

data Shape where 
    Shape :: IsShape a => a -> Shape 

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)] 

(我改名類的會有一個名稱衝突與數據類型,否則,並且具有命名此方式似乎更自然)。

此解決方案優於涉及具有不同構造函數的單個數據類型的其他答案的優點是它是open;您可以在任何地方定義新實例IsShape。另一個答案的優點是它更「功能性」,而且在某些情況下,封閉性可能是一種優勢,因爲這意味着客戶確切知道應該期待什麼。

+1

我無法讓你的例子編譯。但是,你參考的維基頁面完美地回答了我的問題。 – 2009-06-15 14:38:29

+0

立即嘗試 - 我有一個`Shape`,我應該在`Shape`的數據構造函數的簽名中擁有`IsShape`。 – 2009-06-15 15:06:18

+2

對於未來的讀者來說值得注意的是,這種方法的缺點是代碼從`Shape`容器中獲取圓和矩形將不能使用* IsShape實例中給出的任何其他屬性;你無法獲取座標,無法判斷它是一個`Circle`還是`Rectangle`,並且不能調用任何專門用於圓形或矩形的其他函數,而不是任何形狀。這是Ganesh正在談論的開放性的一個基本結果(當你感到被迫「沮喪」時,出現在靜態類型的OO編程中)。 – Ben 2014-02-27 20:29:27

13

考慮使用單個類型而不是單獨的類型和類型類型。

data Shape = Rectangle Int Int 
      | Circle Int Int 

draw (Rectangle length width) = ... 
draw (Circle center radius) = ... 

shapes = [Circle 5 10, Circle 20 30, Rectangle 10 15] 
+6

雖然這工作,我一直在尋找一個解決方案,使新的數據類型定義在別處,使得處理圖形列表中的代碼並不需要了解每一個類型的形狀的。 – 2009-06-15 14:36:56

5

正如加尼甚說,你確實可以使用GADTs有更多類型的安全性。但是,如果你不想(或需要),這是我的承擔:

正如你所知,列表中的所有元素需要是相同的類型。擁有不同類型元素的列表並不是很有用,因爲那樣會拋棄您的類型信息。然而,在這種情況下,由於您想要丟棄類型信息(您只是對值的可繪製部分感興趣),因此您會建議將值的類型更改爲僅可繪製的類型。

type Drawable = IO() 

shapes :: [Drawable] 
shapes = [draw (Circle 5 10), draw (Circle 20 30), draw (Rectangle 10 15)] 

據推測,您的實際Drawable將東西不僅僅是IO()更有趣的(​​也許是這樣的:MaxWidth -> IO())。

而且,由於懶惰的評估,直到您強制使用類似sequence_之類的列表時纔會繪製實際值。所以你不必擔心副作用(但你可能已經看到了shapes的類型)。


只是要完成(並納入我的評論這個答案):這是一個比較普遍的實現,有用的,如果Shape具有更多的功能:

type MaxWith = Int 

class Shape a where 
    draw :: a -> MaxWidth -> IO() 
    size :: a -> Int 

type ShapeResult = (MaxWidth -> IO(), Int) 

shape :: (Shape a) => a -> ShapeResult 
shape x = (draw x, size x) 

shapes :: [ShapeResult] 
shapes = [shape (Circle 5 10), shape (Circle 20 30), shape (Rectangle 10 15)] 

這裏,shape函數將一個Shape a通過簡單地調用Shape類中的所有函數,可以將值轉換爲ShapeResult的值。由於懶惰,在你需要它們之前,沒有一個值是實際計算出來的。

說實話,我不認爲我真的會使用這樣的結構。我要麼使用上面的Drawable-方法,要麼使用更通用的解決方案,請使用GADT。這就是說,這是一個有趣的練習。

5

一種方式做到這一點是有虛函數表:

data Shape = Shape { 
    draw :: IO(), 
    etc :: ... 
} 

rectangle :: Int -> Int -> Shape 
rectangle len width = Shape { 
    draw = ..., 
    etc = ... 
} 

circle :: Int -> Int -> Shape 
circle center radius = Shape { 
    draw = ..., 
    etc = ... 
} 
1

如何應對形狀在Haskell異構名單 - 抽象多態性與類型類:通過@pastebin

CODE http://pastebin.com/hL9ME7qP

{-# LANGUAGE GADTs #-} 

class Shape s where 
area :: s -> Double 
perimeter :: s -> Double 

data Rectangle = Rectangle { 
width :: Double, 
height :: Double 
} deriving Show 

instance Shape Rectangle where 
area rectangle = (width rectangle) * (height rectangle) 
perimeter rectangle = 2 * ((width rectangle) + (height rectangle)) 

data Circle = Circle { 
radius :: Double 
} deriving Show 

instance Shape Circle where 
area circle = pi * (radius circle) * (radius circle) 
perimeter circle = 2.0 * pi * (radius circle) 

r=Rectangle 10.0 3.0 
c=Circle 10.0 
list=[WrapShape r,WrapShape c] 

data ShapeWrapper where 
WrapShape :: Shape s => s -> ShapeWrapper 

getArea :: ShapeWrapper -> Double 
getArea (WrapShape s) = area s 

getPerimeter :: ShapeWrapper -> Double 
getPerimeter (WrapShape s) = perimeter s 

areas = map getArea list 
perimeters = map getPerimeter list 
0

使用存在性量化語法代替Ganesh解決方案的變體。

{-# LANGUAGE ExistentialQuantification #-} 
class IsShape a where 
    draw :: a -> String 

data Rectangle = Rectangle Int Int 

instance IsShape Rectangle where 
    draw (Rectangle length width) = "" 

data Circle = Circle Int Int 

instance IsShape Circle where 
    draw (Circle center radius) = "" 

data Shape = forall a. (IsShape a) => Shape a 

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)]