2009-06-15 28 views
16

と仮定私は、次のしている:Haskellの多型とリスト

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)] 

私はオブジェクト指向の方法で考え、Haskellのにそれを適用しようとしている知っている、それはないかもしれない:リストの要素はすべて同じタイプではありませんので、次のコードはコンパイルされません最良のアプローチ。さまざまな種類のオブジェクトのコレクションを扱う必要があるプログラムにとって、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)] 

(私はそうでない場合は、データ型と名前の衝突があるだろうとして、あなたのクラスの名前を変更し、ネーミングにこれを持ちます一生懸命に見える)。

コンストラクタが異なる単一のデータ型に関連するこのソリューションの利点は、オープンです。任意の場所に新しいインスタンスIsShapeを定義することができます。もう一つの答えの利点は、それがより機能的であること、そしてクライアントが何を期待するかを正確に知ることを意味するので、場合によっては閉鎖性が利点であるということです。

+1

私はあなたの例をコンパイルすることができませんでした。しかし、あなたが参照するwikiページは私の質問に完全に答えます。 –

+0

今すぐお試しください - 私は 'Shape'のデータコンストラクタの署名に' IsShape'を持っていたはずの 'Shape'を持っていました。 –

+2

今後の読者にとって、このアプローチの欠点は、 'Shape'コンテナから円と四角形を取得するコードは、' IsShape'インスタンスで与えられたもの以外の*プロパティを使用できないことです。あなたは座標を得ることができず、それが 'Circle'か' Rectangle'かどうかを知ることができず、形状ではなく円や矩形に特化して動作する他の関数を呼び出すことはできません。これは、Ganeshが話しているオープン性の根本的な結果です(そして、強制的に型定義されたオブジェクト指向プログラミングでは、 "強制的にダウンキャスト"していると感じます)。 – Ben

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

これは動作しますが、私は図形のリストを処理するコードは、形状のすべての種類を意識する必要がないように、新しいデータ型は、他の場所で定義されることを可能にするソリューションを探していました。 –

5

Ganeshが言っていたように、実際にGADTを使用して型の安全性を高めることができます。しかし、あなたが望んでいない(または必要としない)場合は、ここに私の取るこの:

あなたが知っているように、リストのすべての要素は同じタイプである必要があります。異なる型の要素のリストを持つことは、あまり有用ではありません。なぜなら、型情報を放棄するからです。

しかし、この場合、型情報(値の描画可能部分のみに興味があります)を捨てたいので、値の型を描画可能なものに変更することをお勧めします。

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を変換値をShapeResult値に変換するには、Shapeクラスのすべての関数を呼び出します。怠惰のため、必要な値が実際に計算されるまで値は計算されません。

正に、私は実際にこのような構成を使用するとは思わない。上記のDrawableの方法を使用するか、より一般的な解決策が必要な場合は、GADTを使用してください。つまり、これは楽しい運動です。それを行うには

+0

描画が本当にあなたが望むものなら、これは素晴らしいことです - もしあなたが 'Shape'クラスとは違ったことをしたいと思うなら、これはすべてをうまくスケールしません - 私の解決策は自然ですこのアプローチをクラスディクショナリ全体を渡りながら各値で拡張し、必要に応じて適用することができます。 –

+0

もちろん、これは 'Shape'に' draw'以上しかない場合にのみ有効です。これを書いてみると、単一の 'IO()'フィールドと 'toDrawable ::(Shape a)=> a - > Drawable'関数を持つ' Drawable'データ型がありました。これは、(Shapeの各関数の)より多くのフィールドに拡張可能であり、もちろん手作業で構築されたクラス辞書にすぎません... –

+0

Ganeshのコメントもこれに対する私の反応でした。これは、ハスケルの怠惰な評価の本当に面白い使用を示しています。それは一種のクールです。ハスケルにとっては初めてのことなので、怠惰な評価を利用するのに慣れるのには少し時間がかかると認めなければなりません。 –

5

一つの方法は、vtableをして次のようになります。

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

ここでの問題は、Shapeのデータ型がすべての可能な図形のすべてのフィールドの和集合でなければならないことです。それは私の(考案された)ケースでうまくいくかもしれませんが、もっと複雑なケースでは厄介なアプローチになります。 –

+1

興味深い答えは、このようなことは考えていませんでした。 @Clint:ここのShapeデータ型はすべての可能なフィールドの和集合ではないと思います。 'Shape'クラスのすべての関数を記述します。各コンストラクタ関数(rectangle、circle)は、インスタンスの場合と同様に、関数の一意の実装を提供することによってシェイプを構築します。 –

+1

これは、基本的に明示的な型クラス辞書で動作します。それは動作しますが、暗黙の辞書のほとんど単純です。私が公開した存在ベースの解決策は、(少なくともGHCのような、型クラスを実装するために辞書を使用するコンパイラを使って)これと同じになるでしょう。 –

1

がどのように異種Haskellでは形状のリストに対処する - 型クラスと抽象多型: http://pastebin.com/hL9ME7qP

@pastebin CODE経由:

{-# 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)]