I’m trying to re-learn systems analysis. I’ve got a lot of object-oriented thinking for which I’m not able to find equivalents in Haskell, yet.
A fictional system consists of Ambulance Stations, Ambulances and Crew. (It’s getting object-y already.) All this state can be wrapped up in a big SystemState type. SystemState [Stations] [Ambulances] [Crew]. I can then create functions which take a SystemState, and return a new SystemState.
module AmbSys
( version
, SystemState
, Station
, Ambulance
, Crew
) where
version = "0.0.1"
data SystemState = SystemState [Station] [Ambulance] [Crew] deriving (Show)
data Station = Station { stName :: String
, stAmbulances :: [Ambulance]
} deriving (Show)
data Ambulance = Ambulance { amCallSign :: String
, amStation :: Station
, amCrew :: [Crew]
} deriving (Show)
data Crew = Crew { crName :: String
, crAmbulance :: Ambulance
, crOnDuty :: Bool
} deriving (Show)
Here’s a ghci session where I create some data.
*AmbSys> :load AmbSys
[1 of 1] Compiling AmbSys ( AmbSys.hs, interpreted )
Ok, modules loaded: AmbSys.
*AmbSys> let s = Station "London" []
*AmbSys> let a = Ambulance "ABC" s []
*AmbSys> let s' = Station "London" [a]
*AmbSys> let c = Crew "John Smith" a False
*AmbSys> let a' = Ambulance "ABC" s [c]
*AmbSys> let s'' = Station "London" [a']
*AmbSys> let system_state = SystemState [s''] [a'] [c]
*AmbSys> system_state
SystemState [Station {stName = "London", stAmbulances = [Ambulance {amCallSign = "ABC",
amStation = Station {stName = "London", stAmbulances = []}, amCrew = [Crew
{crName = "John Smith", crAmbulance = Ambulance {amCallSign = "ABC",
amStation = Station {stName = "London", stAmbulances = []}, amCrew = []},
crOnDuty = False}]}]}] [Ambulance {amCallSign = "ABC", amStation = Station {
stName = "London", stAmbulances = []}, amCrew = [Crew {crName = "John Smith",
crAmbulance = Ambulance {amCallSign = "ABC", amStation = Station {stName = "London",
stAmbulances = []}, amCrew = []}, crOnDuty = False}]}] [Crew {crName = "John Smith",
crAmbulance = Ambulance {amCallSign = "ABC", amStation = Station {stName = "London",
stAmbulances = []}, amCrew = []}, crOnDuty = False}]
You can already see a couple of problems here:
- I have been unable to create a consistent SystemState – some of the values are ‘old’ values, such as s or s’, rather than s”.
- lots of references to the ‘same’ data have separate copies.
I could now create a function which takes a SystemState and a Crew member’s name which returns a new SystemState where that crew member is ‘off-duty’.
My problem is that I have to find and change the crew member in the Ambulance and the (identical copy of the) crew member in the SystemState.
This is possible for small systems, but real systems have many more linkages. It looks like a n-squared problem.
I am very aware that I am thinking about the system in an object-oriented way.
How would such a system be correctly created in Haskell?
Edit: Thanks to everyone for your answers, and those on reddit too http://www.reddit.com/r/haskell/comments/b87sc/how_do_you_manage_an_object_graph_in_haskell/
My understanding now seems to be that I can do the things I want in Haskell. On the downside, it seems to be that object/record/struct graphs aren’t ‘first class’ objects in Haskell (as they are in C/Java/etc.), because of the necessary lack of references. There’s just a trade off – some tasks are syntactically simpler in Haskell, some are simpler (and more unsafe) in C.
Small tip: If you use a recursive
letorwhere(in a .hs file, I don’t think it works in ghci) you can at least set up the initial graph more easily as follows:This will get you to the state I think you’re trying to reach, but don’t try to use the derived
Showinstances 🙂 Updating states like these is another can of beans; I’ll give that some thought and see what I come up with.EDIT: I’ve thought about it some more, and here’s what I’d probably do:
I would break the cycles in the object graph by using keys. Something like this would work (I used a similar approach when building real graphs):
And then you can start defining your own state-modifying combinators. As you can see, the building of states is more verbose now, but
showing works nicely again!Also I would probably set up a typeclass to make the link between
StationandStationKeyetcetera types more explicit, if that becomes too cumbersome. I didn’t do that in my graph code, as there I had only two key types, which also were distinct, so the newtypes weren’t necessary.