Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing this OO concept in Haskell

Tags:

haskell

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

  1. Am I doing this correctly?
  2. If not, how would you correct it?
  3. How can I use the above (or your corrected way) to take a collection of many cars, trucks, and other vehicles, drive each of them 10 times, and get the corresponding [Double] list of the total fuel used? (In OO this would be a simple matter of throwing the vehicles into a list, calling the method on each of them 10 times, and putting the total fuel used into a new list.)
like image 254
stackoverflowuser Avatar asked Dec 26 '22 09:12

stackoverflowuser


1 Answers

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.

like image 163
duplode Avatar answered Jan 09 '23 15:01

duplode