3

独自の型と型クラスの作成」では、次のコードを提供します。

data Point = Point Float Float deriving (Show)  
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

surface :: Shape -> Float  
surface (Circle _ r) = pi * r ^ 2  
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)  

nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r  
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))

main = do
    print (surface (Circle (Point 0 0) 24))
    print (nudge (Circle (Point 34 34) 10) 5 10)

現状では、コンストラクターに対するパターンマッチングは、その時点でかなり雑然としています。

nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = ....

Shapeタイプを次のように定義しましたか?

data Shape = Circle Point Float | Rectangle Float Float Float Float deriving (Show)

次に、タイプの性質が少しわかりにくくなりますが、以下に示すように、パッテンのマッチングはあまり乱雑に見えません。

data Point = Point Float Float deriving (Show)  
data Shape = Circle Point Float | Rectangle Float Float Float Float deriving (Show)

surface :: Shape -> Float  
surface (Circle _ r) = pi * r ^ 2  
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)  

nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r  
nudge (Rectangle x1 y1 x2 y2) a b = Rectangle (x1+a) (y1+b) (x2+a) (y2+b)

main = do
    print (surface (Circle (Point 0 0) 24))
    print (nudge (Circle (Point 34 34) 10) 5 10)

私の質問は、両方を持つことが可能かどうかです

Rectangle Point Point

Rectangle Float Float Float Float

同じコード内(つまり、値コンストラクターの一種の「オーバーロード」)で、次のようなことができます。

...
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)  

...
nudge (Rectangle x1 y1 x2 y2) a b = Rectangle (x1+a) (y1+b) (x2+a) (y2+b)

ここで、「...」は上記のコードと同じことを示します。また、「ナッジ(長方形....」ポイントで表記をもう少しコンパクトにするためのトリックは他にありますか?ありがとうございます)。

4

3 に答える 3

4

1つのオプションは、ビューパターンを使用することです。簡単な例を挙げましょう。

{-# LANGUAGE ViewPatterns #-}

data Point = Point Float Float
data Shape = Circle Point Float | Rectangle Point Point

rectangleAsPoints :: Shape -> Maybe (Point,Point)
rectangleAsPoints (Rectangle a b) = Just (a,b)
rectangleAsPoints _               = Nothing

rectangleFromPoints :: Point -> Point -> Shape
rectangleFromPoints = Rectangle

rectangleAsCoords :: Shape -> Maybe (Float,Float,Float,Float)
rectangleAsCoords (Rectangle (Point x y) (Point a b)) = Just (x,y,a,b)
rectangleAsCoords _                                   = Nothing

rectangleFromCoords :: Float -> Float -> Float -> Float -> Shape
rectangleFromCoords a b c d = Rectangle (Point a b) (Point c d)

surface (rectangleAsPoints -> Just (Point x1 y1, Point x2 y2)) =
  (abs $ x2 - x1) * (abs $ y2 - y1)
surface (Circle _ r) = pi * r ^ 2

nudge (rectangleAsCoords -> Just (x1,y1,x2,y2)) a b =
  rectangleFromCoords (x1+a) (y1+b) (x2+a) (y2+b)
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r

一貫性を保つために、長方形の両方のビューを関数として実装しました。このようにして、Rectangleタイプの実際の実装を非表示のままにすることができます。

通常のパターンマッチングとビューパターンを組み合わせる方法に注意してください。

于 2012-11-11T21:27:01.170 に答える
4

Point -> Point -> Rectangle型クラスを使用して、関数をとの両方として動作させることもできますがFloat -> Float -> Float -> Float -> Rectangle、私はそれを推奨しません。それは利益のために多くの問題になるでしょう。とにかく、そのようなオーバーロードされた名前をパターンマッチングで使用できるようにすることはできないと思います。

私の見方では、Point値を分解して生のFloat値を操作することによってのみ値を使用する場合は、実際にはそれほど多くの値を取得していないので、取り除くことで問題を解決できます。それは完全に。

しかし、ポイントを直接調整する関数を実装する絶好の機会を逃しています!

手始めに、私はあなたと価値観Offsetを保持するタイプを作ります。次に、結合を行う関数を作成します。そして、あなたはその仕事をするためにの内部構造を理解する必要さえありません!abadjust :: Offset -> Point -> PointnudgePoint

例(免責事項:私は実際にこれをコンパイルしていません)1

data Point = Point Float Float deriving (Show)
data Offset = Offset Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

adjust :: Point -> Offset -> Point
adjust (Point x y) (Offset ox oy) = Point (x + ox) (y + oy)

nudge :: Shape -> Offset -> Shape
nudge (Circle c r) o = Circle (adjust c o) r  
nudge (Rectangle p1 p2) o = Rectangle (adjust p1 o) (adjust p2 o)

同様に、との操作のファミリー全体が存在する可能性がPointありOffsetます。たとえばoffsetFrom :: Point -> Point -> Offset、関数で役立つ可能性がありますsurface。私はかつて船外に出て、型クラスを使用して、さまざまなものを組み合わせることができる演算子のファミリー(、、などIIRC)を実装しました|+||*|たとえば、aPointとaOffsetをどちらかの順序で追加して、を取得したり、 sPointを加算および減算したりできます) sではOffsetなく、 sにスカラーPointを掛けることはできますが、 sを掛けることはできません。最終的にそれが価値があるかどうかはわかりませんが、それによって私のコードはもう少し数学のように見えました!OffsetPoint

現在のコードを使用すると、Point必要になるたびにすべての操作を効果的に再度実装できます(の同じ方程式で、同じ調整操作を2回行うnudgeことも含まれます。これが、非常に見栄えが悪い理由です)。


1のような関数を作成するために行われるべき特定の議論があり、操作されている「主要な」ものが最後に来る署名adjustを持っています。これは、部分的に適用するとタイプが「ポイントトランスフォーマー」になり、同様に部分的に適用してタイプが「シェイプトランスフォーマー」になるため便利です。nudgeadjust :: Offset -> Point -> Pointnudge :: Offset -> Shape -> ShapeadjustPoint -> PointnudgeShape -> Shape

これは、ポイントまたはシェイプのコレクションがあり、それらすべてに同じ変換を適用する場合に役立ちます。次に例を示します。

data Shape = Polygon [Point]

adjust :: Offset -> Point -> Point
adjust (Offset ox oy) (Point x y) = Point (x + ox) (y + oy)

nudge :: Offset -> Shape -> Shape
nudge o (Polygon ps) = Polygon (map (adjust o) ps)

そして、一般的に、型を持つ「トランスフォーマー」はSomething -> Something、メインのデータ構造に持つのに役立つものです。したがって、いくつかの補助データをaと組み合わせてSomething新しいを生成する関数がある場合は常に、最後の引数としてasを指定Somethingすると便利なことがよくあるので、トランスフォーマー関数の別の簡単なソースがあります。Something

于 2012-11-11T21:36:13.303 に答える
2

あなたが望むのは可能ではありません。パターンマッチングの目的でViewPatterns、複数のコンストラクターの貧弱な代替として使用し、単一の関数を作成して構築を容易にすることができます。

{-# LANGUAGE ViewPatterns #-}

-- Your pattern match aid
unRectangle :: Shape -> Maybe (Float, Float, Float, Float)
unRectangle (Rectangle (Point x1 y1) (Point x2 y2)) = Just (x1,y1,x2,y2)
unRectangle _ = Nothing

-- your construction aid
rectangle :: Float -> Float -> Float -> Float -> Shape
rectangle x y u v = Rectangle (Point x y) (Point u v)

surface (unRectangle -> Just (x1,y1,x2,y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
...
nudge (unRectangle -> Just (x1,y1,x2,y2)) = rectangle (x1+a) (y1+b) (x2+a) (y2+b)
于 2012-11-11T21:35:50.687 に答える