Один из вариантов, который здесь предоставили другие, - это возможность использовать отдельный тип ключа и искать значения, к которым вы, возможно, располагаете круговые ссылки ключом, на карте возможных членов экипажа, станций или скорой помощи.
Конечно, существует более прямое кодирование с использованием ссылок, которое больше похоже на то, к чему вы привыкли:
data Station = Station { stName :: String
, stAmbulances :: [IORef Ambulance]
} deriving (Show)
data Ambulance = Ambulance { amCallSign :: String
, amStation :: IORef Station
, amCrew :: [IORef Crew]
} deriving (Show)
data Crew = Crew { crName :: String
, crAmbulance :: IORef Ambulance
, crOnDuty :: Bool
} deriving (Show)
Это приводит к сильному побочному стилю программирования. По сути, вы только начинаете писать C / C ++ на Haskell, используя монаду ввода-вывода.
Есть два подхода, подобных Haskell, чтобы решить эту проблему.
Один из них - завязать узел и сохранить циклические ссылки, но тогда обновление становится проблематичным.
Другой способ убить циклические ссылки:
data Station = Station { stName :: String
, stAmbulances :: [Ambulance]
} deriving (Show)
data Ambulance = Ambulance { amCallSign :: String
, amCrew :: [Crew]
} deriving (Show)
data Crew = Crew { crName :: String
, crOnDuty :: Bool
} deriving (Show)
Вы можете получить доступ к экипажу со станции:
stCrew :: Station -> [Crew]
stCrew = stAmbulances >>= amCrew
В зависимости от того, какие виды доступа вам требуются, для доступа к члену экипажа может потребоваться довольно медленный путь.
Но, еще лучшая кодировка могла бы заключаться в том, чтобы почти полностью исключить объекты из вашего мышления и охватить карту, которую вы будете использовать для поиска ключа, как части самой структуры. Я прошу прощения за грубую природу этого кода, я пишу его прямо сейчас.
import Control.Monad ((>=>))
import Data.Map (Map)
import qualified Data.Map as Map
type Name = String
newtype CrewTraits = CrewTraits { onDuty :: Bool }
type Crew = (Name, CrewTraits)
type CallSign = String
type AmbulanceTraits = Map Name AssignmentTraits
type Amulance = (CallSign, AmbulanceTraits)
type StationName = String
type StationTraits = Map CallSign AmbulanceTraits
type Station = (StationName,StationTraits)
type Fleet = Map StationName StationTraits
crew :: Name -> Bool -> Crew
crew name isOnDuty = (name, CrewTraits isOnDuty)
ambulance :: CallSign -> [Crew] -> Ambulance
ambulance sign crew = (sign, Map.fromList crew)
station :: StationName -> [Ambulance] -> Station
station name ambulances = (name, Map.fromList ambulances)
fleet :: [Station] -> Fleet
fleet = Map.fromList
Теперь вы можете изменить станцию, просто используя встроенную функциональность из Data.Map:
updateStationTraits :: (StationName -> StationTraits -> Maybe StationTraits) ->
StationName -> Fleet -> Fleet
updateStationTraits = Map.updateWithKey
, который вы могли бы сделать более естественным, объединив Name и StationTraits:
updateStation :: (Station -> Maybe StationTraits) ->
StationName -> Fleet -> Fleet
updateStation = Map.updateWithKey . curry
addAmbulanceToFleet :: Ambulance -> StationName -> Fleet -> Fleet
addAmbulanceToFleet (k,v) = Map.adjust (Map.insert k v)
Теперь вы можете объединить понятие пути в этой структуре с более ранним понятием ключа:
type CrewPath = (StationName,CallSign,Name)
type AmbulancePath = (StationName, CallSign)
type StationPath = StationName
lookupCrewTraits :: CrewKey -> Fleet -> Maybe CrewTraits
lookupCrewTraits (s,c,n) = lookup s >=> lookup c >=> lookup n
lookupCrew :: CrewKey -> Fleet -> Maybe Crew
lookupCrew scn@(_,_,n) = (,) n `fmap` lookupCrewTraits scn