在Haskell中实现这个OO概念

时间:2014-05-07 16:01:22

标签: haskell

我需要帮助将OO概念转换为Haskell。

想象一下Vehicle类以及CarTruck子类,使用driveOneMile方法返回表示所用燃油总量的双精度型。每次拨打driveOneMile也会改变车辆的内部状态。

这是我迄今为止在Haskell中所做的事情(因为在Haskell中没有实例变量,似乎我必须创建自己的"状态"类型):

type CarState = (Double,Double)
initialCarState = (0,0)

driveCarOneMile :: CarState -> (Double,CarState) --Double: total fuel used
driveCarOneMile s = ...


--the internal state of trucks is more complex. needs three Doubles
type TruckState = (Double,Double,Double)
initialTruckState = (0,0,0)

driveTruckOneMile :: TruckState -> (Double,TruckState)
driveTruckOneMile s = ...

以类似的方式,我可以建造其他车辆和他们的驾驶"功能

这就是我开车两次的方式:

fuelFor2Miles = fst $ driveCarOneMile $ snd $ driveCarOneMile initialCarState

  1. 我这样做是否正确?
  2. 如果没有,你会如何纠正它?
  3. 我如何使用上述(或您的纠正方式)收集许多汽车,卡车和其他车辆的集合,每次驾驶10次,并获得相应的[双]燃料总量列表? (在OO中,将车辆放入列表,将每种方法调用10次,并将使用的总燃料放入新列表中是一件简单的事情。)

4 个答案:

答案 0 :(得分:8)

Haskell中的类与OO类完全不同,并且使用它们就像在大多数情况下编写OO代码一样,使事情变得比它们应该更复杂。特别是,一旦你开始考虑" [采取]许多汽车,卡车和其他车辆的集合"在OO术语中,你直接沿着兔子洞(正如Haskell Antipattern: Existential Typeclass所指出的那样)。

您可能根本不需要上课来模拟不同类型的车辆。你可以定义

data Vehicle = Car CarState | Truck TruckState

然后

driveOneMile :: Vehicle -> (Double, Vehicle)

使用模式匹配来区分不同的车辆。

即使您真的需要更像类的东西(例如,您正在编写图书馆并希望用户提供自己的车辆),这并不一定意味着您需要异构集合。您可以为Vehicle提供一个构造函数,并为其添加与类方法相对应的字段,以便该类成为一个类型,并且类实例成为值(这是反模式文章所倡导的方法)。您可能还有一个Vehicle类,其toGeneralVehicle :: a -> GeneralVehicle方法,并且具有根据GeneralVehicle类型定义的常见行为。

P.S。:TruckState -> (Double,TruckState)等签名背后的想法是合理的。事实上,我说你不小心发现了State类型! State只是一个方便的抽象,使状态传递管道隐含。如果您感到好奇,请查找有关State monad的问题和教程。

答案 1 :(得分:2)

我对你的问题的最初直觉反应是使用状态monad实现这一点,duplode在他的回答中提示。这是一种在代码有些必要的地方完成你想要的东西的方法。

正如duplode建议的那样,我也会定义

data Vehicle = Car CarState | Truck TruckState

然后,您可以将driveOneMile定义为

driveOneMile :: State Vehicle Double
driveOneMile = do
    vehicleState <- get
    case vehicleState of
         Car carState -> do
             ...
             return totalFuelUsed
         Truck truckState -> do
             ...
             return totalFuelUsed

现在,在你的例子中,你驾驶两英里,难道你不想总结每英里使用的燃料量吗?你拥有它的方式,它只返回第二英里使用的燃料量。

使用州monad,driveTwoMile甚至driveNMiles n非常简单:

driveTwoMile :: State Vehicle Double
driveTwoMile = do
    firstMileFuelUsage <- driveOneMile
    secondMileFuelUsage <- driveOneMile
    return (firstMileFuelUsage + secondMileFuelUsage)

driveNMiles :: Int -> State Vehicle Double
driveNMiles n = do
    fuelUsages <- replicateM n driveOneMile
    return (sum fuelUsages)

至于车辆列表,我不知道最好的方法是什么,但以下是这样做的一种方式(我在这里使用列表作为monad):

type DrivingDistance = Int

driveManyVehicles :: [Vehicle] -> [DrivingDistance] -> [(Double, Vehicle)]
driveManyVehicles v d = do
    (currentVehicle, drivingDistance) <- zip v d
    return (runState (driveNMiles drivingDistance) currentVehicle)

答案 2 :(得分:0)

如果你真的想要一堂课,那就想这样:

class Vehicle a where
  driveOneMile :: a -> Double

data Truck = Truck -- Fill in the details...
data Car   = Car   -- Fill in the details...

instance Vehicle Truck
instance Vehicle Car

但正如前面回答的作者所说的那样 - 将它们作为Vehicle数据类型的不同构造函数实现可能更好。

答案 3 :(得分:0)

这是我为你的场景建模的方式:

我不会把汽车和卡车放在一起,以适应一种总和类型。求和类型并不是为了描述类型之间数据的某些共性(而是一种不相交的案例联合)您可以通过类型类捕获数据的通用性(尽管它不会为两种类型节省很多)。然后我会将您的车辆集合定义为每种类型的列表。

type Miles = Double
type Gallons = Double

data Truck = Truck { tid :: Int, truckTankGallons :: Gallons, tmpg :: Double  } deriving (Show)
data Car   = Car   { cid :: Int, carTankGallons   :: Gallons, cmpg :: Double  } deriving (Show)

class GasVehicle a where
  mpg :: a -> Double
  gasGallons :: a -> Gallons
  changeGasAmt :: Gallons -> a -> a

instance GasVehicle Car where
  mpg = cmpg
  gasGallons = carTankGallons
  changeGasAmt g c = Car (cid c) g (cmpg c)

instance GasVehicle Truck where
  mpg = tmpg
  gasGallons = truckTankGallons
  changeGasAmt g c = Truck (tid c) g (tmpg c)

milesToGallons :: GasVehicle a => a -> Miles -> Gallons
milesToGallons a m = m / (mpg a)

driveMiles :: GasVehicle a => Miles -> a -> (a, Gallons)
driveMiles m a = (a', dg) where
  dg = (milesToGallons a m)
  a' = changeGasAmt (gasGallons a - dg) a

data Vehicles = Vehicles { cars :: [Car], trucks :: [Truck] } deriving (Show)

driveVehicles :: Miles -> Vehicles -> (Vehicles, Gallons)
driveVehicles m v = (Vehicles cars' trucks', gasUsed) where
  gasUsed = (sum carGallons) + (sum truckGallons)
  (cars', carGallons)     = unzip $ map (driveMiles m) $ cars v
  (trucks', truckGallons) = unzip $ map (driveMiles m) $ trucks v