我看到有人问如何在Haskell中进行面向对象编程,例如这个问题。答案大致是“类型类像接口但又不完全相同”。 特别地,类型类不允许构建所有这些类型的列表。例如,我们不能执行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)
尽管这样做有些疯狂,但它具有一些不错的特性:我可以有一个形状列表,并且可以对它们进行有意义的函数映射(如位置和面积)。此外,如果我有一个特定的形状(比如三角形),那么我也可以直接调用它的面积。但是这个代码非常可怕。我完全不喜欢它(事实上,我相信在任何面向对象编程语言中都会更短)。
那么我哪里做错了?如何让它更好看?说“不要考虑对象”的话很好,但这似乎有几个应用场景(例如角色扮演游戏中的角色列表...他们具有一些共同属性但不同的能力,或者 GUI 编程中对象通常是有意义的)。
data Shape = Shape { location :: Point, area :: Float }
类型和class IsShape s where toShape :: s -> Shape
类型类有什么问题呢?然后,您可以为想要的每种形状制作不同的数据类型,然后只需编写IsShape
的实例,以便可以在其上调用toShape
。如果需要计算位置和面积的值,则由于惰性,它们将在需要时再进行计算。您必须手动执行类型转换,但这对面向对象编程并不新鲜。然后,任何使用Shape
的函数都具有与ShapeIf
相同的所有信息。 - bheklilrdisplay :: Shape -> IO()
将非常困难。 - davedata Shape = Shape { location :: Point, area :: Float, display :: IO () }
。是的,当您添加函数时,您的代码将出现编译错误,但这与在Java中添加抽象方法相同。在这两种情况下,编译错误都是有帮助的。 - FrankyShape
类型添加另一个函数来实现。我会将我的回答打成答案,以帮助更清晰地表达。 - bheklilr