我来自OOP背景,所以我无法理解在Haskell中如何做到这一点。
在OOP中,假设我们有Shape -> Circle, Rectangle, Square
层次结构。我可以轻松编写这个伪代码:
Shape[] shapes = [create_circle(), create_rect(), create_square()]
foreach(Shape s: Shapes) draw(s)
如果它没有为子类型实现,它将为圆形,矩形,方形(或形状)调用draw
方法。
如何在Haskell中实现这一目标?
答案 0 :(得分:4)
您可以使用数据类型封装所有形状,也可以使用存在量化。两种选择都有优点和缺点。你正面对"expression problem"。选择正确的选项取决于您的架构,因此我只是详细介绍两者。
我们假设您对形状有类似的定义:
data Circle = Circle { circleCenter :: Point, circleRadius :: Float }
data Rectangle = Rectangle { rectTopLeft :: Point, rectSize :: Size }
data Square = Square { squareTopLeft :: Point, squareSize :: Float }
...以及一些功能drawCircle
,drawRectangle
和drawSquare
。
data Shape
= Circle Cirle
| Rectangle Rectangle
| Square Square
draw :: Shape -> IO ()
draw (Circle c) = drawCircle c
draw (Rectangle r) = drawRectangle r
draw (Square s) = drawSquare s
此模式可让您轻松添加新功能(例如shapeArea
,shiftShape
等...),但添加新形状很困难,尤其是对于您图书馆的用户
{-# LANGUAGE ExistentialQuantification #-}
-- Instead of using a datatype, we use a typeclass
class Shape s where
draw :: s -> IO ()
instance Shape Circle where
draw = drawCircle
instance Shape Rectangle where
draw = drawRectangle
instance Shape Square where
draw = drawSquare
-- Can't use newtype with ExistentialQuantification
data Sh = forall s. Shape s => Sh s
instance Shape Sh where
draw (Sh s) = draw s
使用此解决方案,您或用户将能够轻松添加新形状,但添加新功能可能会稍微复杂一些。
你也可以"降级"对于数据类型的类型类,以及记录字段的类型类成员,如this article中所述,正如左下方提到的那样。
我无法帮助您,因为我不太了解您的代码。如果您仍然需要帮助,请在评论中告诉我们:)
答案 1 :(得分:4)
您可以使用typed final style。例如:
class Circle s where
circle :: Point -> Radius -> s
class Rectangle s where
rectangle :: Point -> Point -> s
class Square s where
square :: Point -> Side -> s
newtype Draw a = Draw { runDraw :: IO a }
deriving (Functor, Applicative, Monad)
draw :: Draw () -> Draw ()
draw = id
instance Circle (Draw ()) where
circle = ... -- draw circle here
instance Rectangle (Draw ()) where
rectangle = ... -- draw rectangle here
instance Square (Draw ()) where
square = ... -- draw square here
main = runDraw $ forM_ shapes draw
where
shapes = [circle ..., rectangle ..., square ...]
答案 2 :(得分:3)
你可能想要
data Circle = Circle { {- e.g. center- and radius fields -} }
data Rectangle = Rectangle { ... }
| SquareRectangle Square
data Square = Square { ... }
然后
data Shape = CircleShape Circle
| RectangleShape Rectangle
现在,您可以获得Shapes
的列表,其中可能包含任何圆圈,矩形和正方形。
与您的OOP代码的主要区别在于Shape
“类”(它不是Haskell类而是ADT)已关闭:如果sombody想要添加新的替代形状,他们需要为此定义一个新类型,或者更改旧定义的实际源代码。
对于许多真实世界的应用程序来说,这实际上是一件好事,因为这意味着编译器每次都会在视图中拥有所有可能的选项,并且可以告诉您是否编写的代码不包含某些可能的选项。 / p>
或者,如果你的意图要留下形状抽象并通过它们的绘制方式定义 ,只需使用
type Shape = WhateverTypeYourDrawingCanvasHas
答案 3 :(得分:1)
我认为将构造函数Circle
,Rectangle
和Square
分组在名为Shape
的相同类型下会很好,甚至我们可以发明Shapes
有一些常用的方法如下。
class Shapes a where
area :: a -> Float
circumference :: a -> Float
draw :: a -> IO ()
data Shape = Circle {pos :: (Float, Float), radius :: Float} |
Rect {pos :: (Float, Float), width :: Float, height :: Float} |
Square {pos :: (Float, Float), width :: Float}
instance Shapes Shape where
area (Circle _ radius) = pi * radius ^ 2
area (Rect _ width height) = width * height
area (Square _ width) = width ^ 2
circumference (Circle _ radius) = 2 * pi * radius
circumference (Rect _ width height) = 2 * (width + height)
circumference (Square _ width) = 4 * width
draw (Circle pos radius) = putStrLn (" Circle drawn @ " ++ show pos ++ " with radius " ++ show radius)
draw (Rect pos width height) = putStrLn (" Rectangle drawn @ " ++ show pos ++ " with (w,h) " ++ show (width,height))
draw (Square pos width) = putStrLn (" Square drawn @ " ++ show pos ++ " with width " ++ show width)
instance Show Shape where
show (Circle pos radius) = "Circle with radius " ++ show radius ++ " @ " ++ show pos
show (Rect pos width height) = "Rect (w,h)" ++ show (width, height) ++ " @ " ++ show pos
show (Square pos width) = "Square with edge " ++ show width ++ " @ " ++ show pos
*Main> let c1 = Circle (20,20) 5
*Main> draw c1
Circle drawn @ (20.0,20.0) with radius 5.0
*Main> let c2 = c1 {pos = (10,10)}
*Main> draw c2
Circle drawn @ (10.0,10.0) with radius 5.0
*Main> draw c1 -- c1 is immutable
Circle drawn @ (20.0,20.0) with radius 5.0
*Main> area c1
78.53982
但是,如果您想创建一个可扩展的数据类型,那么最好定义一个Shape
类,并将您的形状定义为从Shape
类继承方法的单个数据类型。好的阅读可能是SO answer。