由于实例重叠,Vehicle
的以下实例是非法的:
data Ford = Ford
data Ferrari = Ferrari
class Car c where
accelerate :: c -> String
instance Car Ford where
accelerate _ = "Broom broom I'm a Ford"
instance Car Ferrari where
accelerate _ = "Rrrr I'm a fast red Ferrari!"
data Bowing747 = Bowing747
data JetFighter = JetFighter
class Plane p where
takeoff :: p -> String
instance Plane Bowing747 where
takeoff _ = "My passengers and I are taking off!"
instance Plane JetFighter where
takeoff _ = "RAWW I'm a loud jet fighter!"
class Vehicle v where
go :: v -> String
instance (Car c) => Vehicle c where
go = accelerate
instance (Plane p) => Vehicle p where
go = takeoff
main = return ()
我显然是以面向对象的方式接近这一点,也许这就是我出错的地方。
那么如何在Haskell中有一个像下面这样的结构,允许我在不使用层次结构的情况下调用泛型函数:
话虽这么说,我不需要Car
,Plane
或Vehicle
成为clases,Ford
,Ferrari
等也不需要定义它们的方式,只要我可以调用go (some vehicle)
并得到相同的结果,并且客户可以添加新的汽车,飞机和车辆类型而不重复自己。
每种类型的车辆都需要向用户开放以添加自己的车辆。车辆类型本身可以关闭,但如果不是这样的话,我更愿意。
答案 0 :(得分:2)
在Haskell中,通常不使用类型类来模拟OOP。相反,您可以只编写包含函数的纯数据类型。在您的代码中,车辆是具有go
方法的任何方法,在给定车辆的情况下返回String
。所以让我们定义一个表达这个的数据类型:
data Vehicle = Vehicle { go :: String }
现在制造新车非常容易。例如,要将Plane
转换为Vehicle
,您可以编写函数plane
:
data Plane = Plane { takeoff :: String }
plane :: Plane -> Vehicle
plane p = Vehicle { go = takeoff p }
或者您可以制作Car
类型:
data Car = Car { accelerate :: String }
car :: Car -> Vehicle
car c = Vehicle { go = accelerate c }
如果需要,您甚至可以制作ToVehicle
类型类,以便在转换Car -> Vehicle
时使用相同的函数转换Plane -> Vehicle
,并且函数可以是通用的他们接受的Vehicle
类型(不强制用户先转换为Vehicle
)
class IsVehicle v where
toVehicle :: v -> Vehicle
instance IsVehicle Car where
toVehicle = car
instance IsVehicle Plane where
toVehicle = plane
但是如果可以的话,我建议避免使用类型类。如果你想在Haskell中这样做,最好重新考虑你的设计。
这种方法并不完全是OOP,因为你没有真正的子类型(如果不使用类型类,你不能传递Car
Vehicle
,所以你必须首先手动转换),但如果你的设计适应这种风格,我发现在实践中已经足够了。所以我建议不要尝试将基于OOP的结构转换为Haskell,而是考虑以Haskell的方式解决这个问题。
如果您可以修复Vehicles
的类型,您甚至可以使用Haskell的和类型来表达它更简单:
data Vehicle = Car String | Plane String
这种方法与您原来的方法完全不同,因为您不再使用单独的类型,但如果您稍微采用您的架构,它可能适合您的用例。
答案 1 :(得分:1)
首先要做的是:每种语言在编程时都有不同的方法。将位于语言X核心的概念翻译成语言Y通常会导致设计非常糟糕。这是因为在X中没有出现的Y的优点被忽略了,同时试图将方形钉子放入圆孔中。
Haskell中的OOP根本不适合。
而且,为了公平起见,Haskell的许多功能都不适合大多数其他语言。
话虽这么说,在Haskell中做一些OOP的常见尝试是通过存在类型。 E.g。
data Car where
Car :: state -> (state -> String) -> Car
-- ^^^^^ the internal representation
-- ^^^^^^^^^^^^^^^^^ a method
然而,在许多情况下,使用存在性编码OOP只是一种反模式。 Luke Palmer在着名的blog post中描述了这一点。 (虽然我不完全同意某些观点,但我发现一般论证是固定的。)例如,上面的类型与更简单的类型是同构的
data Car where
Car :: String -> Car
-- ^^^^^^ the method result
由于懒惰,上述作品与原作一样好。
在Haskell中不存在子类型,你必须将你的类型向上转换为Car
,向上转换意味着转换"对象"进入Car
形式。类型类可以缓解这种情况,但通常向上转换并不令人讨厌。
最后,让我承认我发现OOP是一种不错但却被高估的建模方法。仅仅因为一些"大"语言以OO为中心,并不意味着OO无疑是建模一切的最佳方式。
答案 2 :(得分:-1)
data Ford = Ford
data Ferrari = Ferrari
class Vehicle v where
go :: v -> String
class (Vehicle v) => Car v where
accelerate :: v->String
instance Vehicle Ford where
go = accelerate
instance Car Ford where
accelerate _ = "Vrrom I am ford"
同样适用于飞机
class (Vehicle v) => Plane v where
takeoff :: v-> String
instance Vehicle Bowing747 where
go = takeoff
instance Plane Bowing747 where
takeoff _ = "taking off from plane"