I need help translating an OO concept into Haskell.
Imagine a Vehicle
class and Car
and Truck
subclasses, with a driveOneMile
method that returns a double representing the total fuel used. Each call to driveOneMile
also changes the internal state of the vehicle.
This is what I've done so far in Haskell (since there are no instance variables in Haskell, it seems I have to create my own "state" types):
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 = ...
In a similar way I could construct other vehicles and their "drive" functions.
This is how I would drive a car twice:
fuelFor2Miles = fst $ driveCarOneMile $ snd $ driveCarOneMile initialCarState
Classes in Haskell are quite different from OO classes, and using them as if you were writing OO code in most cases makes things more complicated than they should be. In particular, as soon as you begin thinking about "[taking] a collection of many cars, trucks, and other vehicles" in OO terms you go straight down the rabbit hole (as pointed out by Haskell Antipattern: Existential Typeclass).
It is likely that you don't really need a class at all to model your different types of vehicles. You might define
data Vehicle = Car CarState | Truck TruckState
and then
driveOneMile :: Vehicle -> (Double, Vehicle)
using pattern matching to distinguish between different vehicles.
Even if you really need something more class-like (e.g. you are writing a library and want users to supply their own vehicles) that doesn't necessarily mean you need heterogeneous collections. You can give Vehicle
a single constructor and add fields to it corresponding to the class methods, so that the class becomes a type and the class instances become values (that is the approach advocated by the antipattern article). You might also have a Vehicle
class with a toGeneralVehicle :: a -> GeneralVehicle
method, and have the common behaviour defined in terms of the GeneralVehicle
type.
P.S.: The idea behind signatures such as TruckState -> (Double,TruckState)
is sound. In fact, I'd say that you have accidentally discovered the State
type! State
is just a convenient abstraction to make the state-passing plumbing implicit. If you are curious, look for questions and tutorials about the State
monad.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With