gpt4 book ai didi

oop - Haskell 中的面向对象多态性

转载 作者:行者123 更新时间:2023-12-05 01:02:07 25 4
gpt4 key购买 nike

所以我看到了一些问题,问你如何在 Haskell 中进行面向对象编程,比如 this例如。答案是“类型类就像接口(interface)但不完全一样”。特别是类型类不允许构建所有这些类型的列表。例如。我们做不到map show [1, 1.4, "hello"]尽管有一个合乎逻辑的结果。

有一段时间我想知道是否不可能做得更好。所以我尝试为一个简单的 Shape 类编写多态性,可以在下面找到它(如果你喜欢理智可能最好现在停止阅读,并为它这么长而道歉)。

module Shapes (
Shape(..)
, Point
, Circle(..)
, Triangle(..)
, Square(..)
, location
, area
) where

data Point = Point {
xcoord :: Float
, ycoord :: Float
} deriving (Read, Show)

data Shape = CircleT Circle | PolygonT Polygon deriving (Read, Show)

data Circle = Circle {
cLocation :: Point
, cRadius :: Float
} deriving (Read, Show)

data Polygon = SquareT Square | TriangleT Triangle deriving (Read, Show)

data Square = Square {
sLocation :: Point
, sLength :: Float
} deriving (Read, Show)

-- only right angled triangles for ease of implementation!
data Triangle = Triangle {
tLocation :: Point
, tSide1 :: Float
, tSide2 :: Float
} deriving (Read, Show)

class ShapeIf a where
location :: a -> Point
area :: a -> Float

instance ShapeIf Shape where
location (CircleT a) = location a
location (PolygonT a) = location a
area (CircleT a) = area a
area (PolygonT a) = area a

instance ShapeIf Polygon where
location (SquareT a) = location a
location (TriangleT a) = location a
area (SquareT a) = area a
area (TriangleT a) = area a

instance ShapeIf Square where
location = sLocation
area a = (sLength a) ^ 2

instance ShapeIf Circle where
location = cLocation
area a = pi * (cRadius a) ^ 2

instance ShapeIf Triangle where
location = tLocation
area a = 0.5 * (tSide1 a) * (tSide2 a)

尽管这很疯狂,但最终还是有一些非常好的属性:我可以有一个形状列表,我可以在它们上映射有意义的函数(比如位置和区域)。但如果我有一个特定的形状(比如三角形),那么我也可以在上面调用 area 。但这太可怕了。我根本不喜欢这段代码(事实上,我确信它在任何面向对象的编程语言中都会短得多)。

那么我哪里出错了?这怎么能变得更好?说“不要考虑对象”很好,但这似乎有几个应用程序(例如角色扮演游戏中的角色列表......具有一些共享属性但不同的能力,或者对象倾向于的 GUI 编程有意义)。

最佳答案

您可以为此目的使用简单的数据类型,而无需求助于类型类。如果您确实想使用类型类,最好使用它来描述对基本类型的转换,而不是让它包含所有实现细节:

data Point = Point
{ xcoord :: Float
, ycoord :: Float
} deriving (Eq, Read, Show)

data Shape = Shape
{ shapeLocation :: Point
, shapeArea :: Float
} deriving (Eq, Show)

这可能是您唯一需要的两种类型,具体取决于您的应用程序,因为您可以编写函数
circle :: Point -> Float -> Shape
circle loc radius = Shape loc $ pi * r * r

square :: Point -> Float -> Shape
square loc sLength = Shape loc $ sLength * sLength

triangle :: Point -> Float -> Float -> Shape
triangle loc base height = Shape loc $ 0.5 * base * height

但也许你想保留这些论点。在这种情况下,为每个写一个数据类型
data Circle = Circle
{ cLocation :: Point
, cRadius :: Float
} deriving (Eq, Show)

data Square = Square
{ sLocation :: Point
, sLength :: Float
} deriving (Eq, Show)

data Triangle = Triangle
{ tLocation :: Point
, tBase :: Float
, tHeight :: Float
} deriving (Eq, Show)

然后为方便起见,我将在这里使用类型类来定义 toShape :
class IsShape s where
toShape :: s -> Shape

instance IsShape Shape where
toShape = id

instance IsShape Circle where
toShape (Circle loc radius) = Shape loc $ pi * radius * radius

instance IsShape Square where
toShape (Square loc sideLength) = Shape loc $ sideLength * sideLength

instance IsShape Triangle where
toShape (Triangle loc base height) = Shape loc $ 0.5 * base * height

但是现在有一个问题,你必须将每种类型转换为 Shape为了以更通用的方式获取其区域或位置,除了您可以添加功能
location :: IsShape s => s -> Point
location = shapeLocation . toShape

area :: IsShape s => s -> Float
area = shapeArea . toShape

我会将这些排除在 IsShape 之外类,这样它们就不能被重新实现,这类似于 replicateM 之类的函数适用于所有 Monad s,但不属于 Monad类型类。现在您可以编写如下代码
twiceArea :: IsShape s => s -> Float
twiceArea = (2 *) . area

当您只对单个形状参数进行操作时,这很好。如果要对它们的集合进行操作:
totalArea :: IsShape s => [s] -> Float
totalArea = sum . map area

这样您就不必依赖存在主义来构建它们的集合,而是可以拥有
> let p = Point 0 0
> totalArea [toShape $ Circle p 5, toShape $ Square p 10, toShape $ Triangle p 10 20]
278.53983
> totalArea $ map (Square p) [1..10]
385.0

这使您可以灵活地处理不同类型的对象列表,或者使用相同功能且绝对没有语言扩展的仅单一类型的列表。

请记住,这仍在尝试以严格的函数式语言实现一种对象模型,这并不完全理想,但考虑到这一点,您可以拥有
  • 多个“接口(interface)”(转换为不同类型)
  • 泛型 ( totalArea :: IsShape s => [s] -> Float )
  • 如果要对 Shape 使用智能构造函数,则密封方法并向其添加更多方法,然后使用 area 给它们起别名和 location
  • 如果您只允许智能构造函数设置未密封的方法
  • public 和 private 由模块导出
  • 设置

    可能还有其他一些 OOP 范例,所有代码都比 Java 或 C# 中的代码少,唯一的区别是代码没有全部组合在一起。这有其优点和缺点,例如能够更自由地定义新的实例和数据类型,但使代码更难以导航。

    关于oop - Haskell 中的面向对象多态性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27005419/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com