Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oop Haskell:如何在不同但相关类型的列表上实现处理?_Oop_Haskell_Subtyping - Fatal编程技术网

Oop Haskell:如何在不同但相关类型的列表上实现处理?

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

我有OOP的背景,所以我无法理解Haskell是如何做到这一点的

在OOP中,假设我们有
形状->圆形、矩形、正方形
层次结构。我可以轻松编写以下伪代码:

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,就像我说的,如果你想暂时保持这个开放的话