Как Вы управляете графом объектов в Haskell?

Я пытаюсь повторно изучить системный анализ. У меня есть много объектно-ориентированных взглядов, для которых я не могу найти эквиваленты в Haskell, все же.

Вымышленная система состоит из Станций Машины скорой помощи, Машин скорой помощи и Команды. (Это уже получает объект-y.) Все это состояние может быть обернуто в большом типе SystemState. SystemState [Станции] [Машины скорой помощи] [Команда]. Я могу затем создать функции, которые берут SystemState и возвращают новый 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)

Вот ghci сессия, где я создаю некоторые данные.

*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}]

Можно уже видеть несколько проблем здесь:

  1. Я не мог создать последовательный SystemState - некоторые значения являются 'старыми' значениями, такими как s или s', а не s''.
  2. много ссылок на 'те же' данные имеет отдельные копии.

Я мог теперь создать функцию, которая берет SystemState и имя Члена команды, которое возвращает новый SystemState, где тот член команды является 'резервным'.

Моя проблема состоит в том, что я должен найти и изменить члена команды в Машине скорой помощи и (идентичная копия) член команды в SystemState.

Это возможно для маленьких систем, но реальные системы имеют намного больше связей. Это похоже на n-squared проблему.

Я очень знаю, что думаю о системе объектно-ориентированным способом.

Как такая система была бы правильно создана в Haskell?

Править: Благодаря всем для Ваших ответов и ответов на reddit также http://www.reddit.com/r/haskell/comments/b87sc/how_do_you_manage_an_object_graph_in_haskell/

Мое понимание теперь, кажется, что я могу сделать вещи, которые я хочу в Haskell. На оборотной стороне это, кажется, что графики объекта/записи/структуры не являются объектами 'первого класса' в Haskell (как они находятся в C/Java/etc.), из-за необходимого отсутствия ссылок. Существует только компромисс - некоторые задачи синтаксически более просты в Haskell, некоторые более просты (и более небезопасны) в C.

15
задан fadedbee 2 March 2010 в 18:59
поделиться

7 ответов

Небольшой совет: если вы используете рекурсивные let или where (в файле . hs файле, я не думаю, что это работает в ghci), вы можете по крайней мере установить начальный граф более легко следующим образом:

ambSys = SystemState [s] [a] [c] where
    s = Station "London" [a]
    a = Ambulance "ABC" s [c]
    c = Crew "John Smith" a False

Это приведет вас к состоянию, которое, как я думаю, вы пытаетесь достичь, но не пытайтесь использовать производные Show экземпляры :-) Обновление таких состояний - это еще один вариант; я подумаю над этим и посмотрю, что получится.

EDIT: Я подумал об этом еще немного, и вот что я, вероятно, сделаю:

Я бы разорвал циклы в графе объектов, используя ключи. Что-то вроде этого (я использовал похожий подход при построении реальных графов):

import qualified Data.Map as M

version = "0.0.1"

newtype StationKey = StationKey String deriving (Eq,Ord,Show)
newtype AmbulanceKey = AmbulanceKey String deriving (Eq,Ord,Show)
newtype CrewKey = CrewKey String deriving (Eq,Ord,Show)

data SystemState = SystemState (M.Map StationKey Station) (M.Map AmbulanceKey Ambulance) (M.Map CrewKey Crew) deriving (Show)

data Station = Station { stName :: StationKey
                       , stAmbulances :: [AmbulanceKey]
                       } deriving (Show)

data Ambulance = Ambulance { amCallSign :: AmbulanceKey
                           , amStation :: StationKey
                           , amCrew :: [CrewKey]
                           } deriving (Show)

data Crew = Crew { crName :: CrewKey
                 , crAmbulance :: AmbulanceKey
                 , crOnDuty :: Bool
                 } deriving (Show)

ambSys = SystemState (M.fromList [(londonKey, london)]) (M.fromList [(abcKey, abc)]) (M.fromList [(johnSmithKey, johnSmith)]) where
    londonKey = StationKey "London"
    london = Station londonKey [abcKey]
    abcKey = AmbulanceKey "ABC"
    abc = Ambulance abcKey londonKey [johnSmithKey]
    johnSmithKey = CrewKey "John Smith"
    johnSmith = Crew johnSmithKey abcKey False

А затем вы можете начать определять свои собственные комбинаторы, изменяющие состояние. Как вы можете видеть, построение состояний теперь более многословное, но showing снова работает хорошо!

Также я, возможно, создам типовой класс, чтобы сделать связь между типами Station и StationKey и т.д. более явной, если это станет слишком громоздким. Я не делал этого в коде графа, так как там у меня было только два типа ключей, которые также были разными, поэтому новые типы были не нужны.

8
ответ дан 1 December 2019 в 03:34
поделиться

Haskell - отличный выбор для моделирования системы, которую вы описываете.

Однако, как и в любом языке программирования, способ моделирования вашей системы в значительной степени зависит от того, какие операции вы хотите с ней выполнять. И язык функционального программирования, такой как Haskell, поможет вам сосредоточьтесь на этом. Моделирование данных - это хорошо, но где же функции?

Ваши типы для скорой помощи, станции и бригады просты и понятны. Я не знаю, почему вы затем хотите объединить их в один большой SystemState. Такая конструкция действительно полезна в определенных ситуациях. Неудивительно, что она немного усложняет вам задачу, потому что это что-то вроде ad hoc пюре -вверх. Нужна она или нет полностью зависит от того, какие функции вы будете писать.

Но главная проблема здесь в том, как эффективно использовать GHCi.

Что именно вы пытаетесь делать в GHCi? Я трачу много времени на приглашение GHCi. Я могу разделить это время на три категории: изучение функций , чтобы лучше понять их, тестирование и отладка функций, чтобы убедиться, что они работают, и выполнение разовых вычислений с использованием функций, которые я уже понимаю {{ 1}} и уже знаю, работают. Не думаю, что я очень много использовал GHCi для просто набирать структуры данных и заставлять GHCi выплевывать их мне в ответ.

Тем не менее, для каждого из этих трех применений мне нужны структуры данных. Обычно те, которые мне нужны, достаточно простые, чтобы я мог набрать все за один раз.На самом деле они не должны быть очень простыми для этого - не забывайте, что вы можете ввести несколько взаимно-рекурсивных определений в одном операторе let , разделив их с ';' и , что GHCi поддерживает многострочные операторы с командами ": {" и ":}".

Если структура данных, которая мне нужна, достаточно сложна, я хочу {{1} } для постепенного наращивания, как вы это делали, есть несколько простых способов сделать это.

Чтобы получить изменяемую переменную, которую вы неоднократно изменяете для построения своей структуры, аналогично как вы бы сделали это в командной строке для императивного языка, посмотрите на модуль Data.IORef. Если вы новичок в Haskell, я бы рекомендовал избегать Data.IORef похоже на чуму в вашем программировании - он всегда будет искушать вас, и это почти всегда неправильно . Но в приглашении GHCi все в порядке.

По правде говоря, Я почти никогда этого не делаю. Из-за лени я просто использую стрелку вверх и другие средства редактирования командной строки. ключи для постепенного преобразования всего этого в одну команду GHCi.

И, конечно же, если набираемая вами структура данных на самом деле значима, а не является одноразовым примером, вы захотите ввести ее в файл в вашем любимом редакторе Haskell. а не в подсказке. Затем вы воспользуетесь интеграцией GHCi вашего редактора, или командой GHCi ": r", чтобы поддерживать актуальную версию вашей структуры , доступную в GHCi.

2
ответ дан 1 December 2019 в 03:34
поделиться

Есть несколько способов обойти это. Один простой способ - думать о ваших данных как о базе данных SQL. То есть все ваши станции, машины скорой помощи и экипаж - это таблицы со связанными с ними спутниковыми данными. Другой вариант - определить его как базу данных графов с библиотекой графов.

1
ответ дан 1 December 2019 в 03:34
поделиться

Объектно-ориентированный подход не работает, пока вы не начнете говорить о наследовании и полиморфизме подтипов. Программы содержали структуры данных, называемые «скорая помощь» и «станция», задолго до появления объектно-ориентированного программирования; OO не имеет монополии на абстракцию и инкапсуляцию данных. Дизайн FP также будет «определяться предметной областью», как и императивное программирование.

Проблема, с которой вы столкнулись, заключается в том, как управлять состоянием, и это хроническая проблема в Haskell (фактически, в любой системе программирования, см. Раздел 3.1.3 SICP (Структура и интерпретация компьютерных программ Абельсона и Сассмана ) http://mitpress.mit.edu/sicp/ (Не пугайтесь ни громких академических слов, ни имени домена, оно очень читабельно - их пример - банковский счет).

Ваш проблема в том, что ur ссылается на старое, устаревшее состояние и сохраняет его. Я бы посоветовал вам написать функции, которые принимают текущее состояние, изменяют его и возвращают новое состояние. Что-то вроде:

addStation state station = 
     let (SystemState stations ambs crews) = state
     in SystemState (station:stations) ambs crews)

И если ur использует ghci интерпретатора, будет полезно узнать о переменной it, которая содержит результат последнего вычисления.

В конечном итоге вы попадете в Монаду состояний, но похоже, что это позже ....

5
ответ дан 1 December 2019 в 03:34
поделиться

Я тоже пытался сделать что-то подобное и пришел к выводу, что Haskell (для меня), вероятно, не является подходящим инструментом для этой работы.

Ваш вопрос 2 попадает в точку:

множество ссылок на "одни и те же" данные имеют отдельные копии.

Haskell, как язык, специально разработан для того, чтобы затруднить "совместное использование экземпляров" или создание "отдельных копий". Поскольку все переменные хранят неизменяемые значения, нет ссылок на объекты для сравнения на идентичность.

Тем не менее, существуют некоторые приемы.

Один из методов - использовать изменяемые объекты для структур. Однако это заставит весь ваш код работать с монадой.

Вы также можете посмотреть эту статью Type-Safe Observable Sharing, которая показывает, как использовать некоторые новые возможности языка, поддерживающие низкоуровневые ссылки при создании графа. Их пример - цифровая схема, но я думаю, что он обобщается.

1
ответ дан 1 December 2019 в 03:34
поделиться

Если вам действительно, действительно нужно, чтобы данные были такими рекурсивными, используйте подходящую библиотеку графов, такую ​​как fgl.

1
ответ дан 1 December 2019 в 03:34
поделиться

Один из вариантов, который здесь приводили другие, это возможность использовать отдельный тип Key, и искать значения, на которые вы держите возможно круговые ссылки, по ключу в карте возможных членов экипажа, станций или машин скорой помощи.

Конечно, существует более прямое кодирование с использованием ссылок, которое ведет себя более похоже на то, к чему вы привыкли:

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, используя монаду IO.

Есть два подхода к решению этой проблемы в стиле 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
3
ответ дан 1 December 2019 в 03:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: