Oop Haskell:如何在不同但相关类型的列表上实现处理?
我有OOP的背景,所以我无法理解Haskell是如何做到这一点的 在OOP中,假设我们有Oop Haskell:如何在不同但相关类型的列表上实现处理?,oop,haskell,subtyping,Oop,Haskell,Subtyping,我有OOP的背景,所以我无法理解Haskell是如何做到这一点的 在OOP中,假设我们有形状->圆形、矩形、正方形层次结构。我可以轻松编写以下伪代码: Shape[] shapes = [create_circle(), create_rect(), create_square()] foreach(Shape s: Shapes) draw(s) 它将为圆、矩形、正方形(如果没有为子类型实现,则为Shape)调用draw方法 如何在Haskell中实现这一点?您可能希望 data Circl
形状->圆形、矩形、正方形层次结构。我可以轻松编写以下伪代码:
Shape[] shapes = [create_circle(), create_rect(), create_square()]
foreach(Shape s: Shapes) draw(s)
它将为圆、矩形、正方形(如果没有为子类型实现,则为Shape)调用draw
方法
如何在Haskell中实现这一点?您可能希望
data Circle = Circle { {- e.g. center- and radius fields -} }
data Rectangle = Rectangle { ... }
| SquareRectangle Square
data Square = Square { ... }
然后
现在您可以有一个形状列表
,其中可能包含任何圆、矩形和正方形
OOP代码的主要区别在于形状
“类”(它不是Haskell类,而是ADT)是关闭的:如果sombody想要添加新的替代形状,他们需要为此定义一个新类型,或者更改旧定义的实际源代码
对于许多实际应用程序来说,这实际上是一件好事,因为这意味着编译器在任何时候都会看到所有可能的选项,并且可以告诉您是否编写的代码没有包含某些可能的选项
或者,如果您的意图是让形状保持抽象,并根据它们的绘制方式进行定义,只需使用
type Shape = WhateverTypeYourDrawingCanvasHas
你可能想要
data Circle = Circle { {- e.g. center- and radius fields -} }
data Rectangle = Rectangle { ... }
| SquareRectangle Square
data Square = Square { ... }
然后
现在您可以有一个形状列表
,其中可能包含任何圆、矩形和正方形
OOP代码的主要区别在于形状
“类”(它不是Haskell类,而是ADT)是关闭的:如果sombody想要添加新的替代形状,他们需要为此定义一个新类型,或者更改旧定义的实际源代码
对于许多实际应用程序来说,这实际上是一件好事,因为这意味着编译器在任何时候都会看到所有可能的选项,并且可以告诉您是否编写的代码没有包含某些可能的选项
或者,如果您的意图是让形状保持抽象,并根据它们的绘制方式进行定义,只需使用
type Shape = WhateverTypeYourDrawingCanvasHas
您可以使用数据类型封装所有形状,也可以使用存在量化。这两种选择都有优点和缺点。你正面临着挑战。选择正确的选项取决于您的体系结构,因此我将详细介绍两者
我们假设您对形状有类似的定义:
data Circle = Circle { circleCenter :: Point, circleRadius :: Float }
data Rectangle = Rectangle { rectTopLeft :: Point, rectSize :: Size }
data Square = Square { squareTopLeft :: Point, squareSize :: Float }
。。。以及一些函数drawCircle
、drawRectangle
和drawSquare
使用数据类型
此模式允许您轻松添加新函数(如shapeArea
,shiftShape
等),但添加新形状很困难,尤其是对于库的用户
使用存在量化
使用此解决方案,您或用户将能够轻松添加新形状,但添加新功能可能会稍微复杂一些
您还可以将typeclass“降级”为数据类型,将typeclass成员“降级”为记录字段,如中所述,正如chi在leftaroundabout的回答中提到的
我无法进一步帮助您,因为我不知道您的详细代码。如果您仍然需要帮助来选择,请在注释中告诉我:)您可以使用数据类型来封装所有形状,也可以使用存在量化。这两种选择都有优点和缺点。你正面临着挑战。选择正确的选项取决于您的体系结构,因此我将详细介绍两者
我们假设您对形状有类似的定义:
data Circle = Circle { circleCenter :: Point, circleRadius :: Float }
data Rectangle = Rectangle { rectTopLeft :: Point, rectSize :: Size }
data Square = Square { squareTopLeft :: Point, squareSize :: Float }
。。。以及一些函数drawCircle
、drawRectangle
和drawSquare
使用数据类型
此模式允许您轻松添加新函数(如shapeArea
,shiftShape
等),但添加新形状很困难,尤其是对于库的用户
使用存在量化
使用此解决方案,您或用户将能够轻松添加新形状,但添加新功能可能会稍微复杂一些
您还可以将typeclass“降级”为数据类型,将typeclass成员“降级”为记录字段,如中所述,正如chi在leftaroundabout的回答中提到的
我无法进一步帮助您,因为我不知道您的详细代码。如果您仍然需要帮助来选择,请在评论中告诉我:)您可以使用。例如:
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 ...]
你可以用。例如:
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 ...]
我认为将构造函数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
类继承方法的单个数据类型。一个好的读物可能是这样的。我认为最好将构造函数圆形
,矩形
和正方形
组合在名为形状
的同一类型下,甚至我们也可以发明一个形状
类来拥有以下一些常用方法
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
类继承方法的单个数据类型。一个好的读物可能是这个。关于表达式问题呢?如果以后我想添加一个新形状?@mahdix这是双重问题w.r.t.OOP。在OOP中,您可以轻松添加新形状,但不能添加新操作(您需要修改所有形状)。在(闭合)ADT中,您可以轻松添加新操作,但不能添加新形状(您需要修改这些操作)。如果你已经读过关于表达式问题的书,你可能应该读过关于这种二元性的书。@mahdix有时候,一个带有类型类的存在类型可以让你更接近OOP,但这很容易导致一个已知的反模式@mahdix,就像我说的,如果你想暂时保持这个开放的话