将面向对象的类结构转换为Haskell

时间:2015-02-14 06:56:23

标签: haskell typeclass

由于实例重叠,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中有一个像下面这样的结构,允许我在不使用层次结构的情况下调用泛型函数:

  1. 保持同步重复的实例集。
  2. 要求在呼叫站点添加额外的“标记”参数。
  3. 话虽这么说,我不需要CarPlaneVehicle成为clases,FordFerrari等也不需要定义它们的方式,只要我可以调用go (some vehicle)并得到相同的结果,并且客户可以添加新的汽车,飞机和车辆类型而不重复自己。

    每种类型的车辆都需要向用户开放以添加自己的车辆。车辆类型本身可以关闭,但如果不是这样的话,我更愿意。

3 个答案:

答案 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中做一些O​​OP的常见尝试是通过存在类型。 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"