Управление системой вычисления состояния в Haskell

Итак, у меня есть система процессоров с поддержкой состояния, которые соединены вместе. Например, процессор может выводить среднее из последних 10 входов. Это требует состояния для вычисления этого среднего значения.

Я хотел бы представить значения в систему и получить выходные данные. Я также хотел бы вернуться назад и восстановить состояние в любое время в прошлом. То есть. Я запускаю 1000 значений через систему. Теперь я хочу "переместить" систему обратно так же, как и после того, как я отправил 500-е значение. Затем я хочу снова "переиграть" систему с этой точки.

Мне также нужно иметь возможность сохранить историческое состояние на диске, чтобы я мог восстановить все это снова в будущем (и все еще есть функции возврата и воспроизведения). И, конечно, мне нужно сделать это с гигабайтами данных, и это будет очень быстро:)

Я приближался к нему, используя закрытие, чтобы удерживать состояние. Но мне интересно, имеет ли смысл использовать монаду. Я прочитал только 3 или 4 аналогии для монад, поэтому не понимаю их еще хорошо, поэтому не стесняйтесь обучать меня.

Если каждый процессор изменяет свое состояние в монаде таким образом, что его история сохраняется и привязана к идентификатору для каждого этапа обработки. И тогда как-то монада может переключить свое состояние на id предыдущего шага и запустить систему с монадой в этом состоянии. И у монады был бы некоторый механизм для (де) сериализации для хранения.

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

Существует ли уже существующая библиотека/механизм/подход/концепция, которая уже была выполнена, чтобы выполнить или помочь в достижении того, что я пытаюсь сделать?

Ответы

Ответ 1

Итак, у меня есть система процессоров с поддержкой состояния, которые соединены вместе. Например, процессор может выводить среднее из последних 10 входов. Это требует состояния для вычисления этого среднего значения.

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

Я хотел бы представить значения в систему и получить выходные данные. Я также хотел бы вернуться назад и восстановить состояние в любое время в прошлом. То есть. Я запускаю 1000 значений через систему. Теперь я хочу "переместить" систему обратно так же, как и после того, как я отправил 500-е значение. Затем я хочу снова "переиграть" систему с этой точки.

Простейший подход здесь, конечно, состоит в том, чтобы просто вести журнал всех предыдущих состояний. Но поскольку это звучит так, как будто у вас много данных, необходимое хранилище может легко стать непомерно высоким. Я бы рекомендовал подумать о том, как вы могли бы построить свои процессоры таким образом, чтобы это можно было избежать, например:

  • Если состояние процессора можно легко восстановить из состояний его соседей за несколько шагов до этого, вы можете не записывать его напрямую.
  • Если в некоторых ситуациях процессор легко обратим, вам не нужно немедленно регистрировать их; перемотка может быть рассчитана напрямую, а регистрация может выполняться как периодические снимки.
  • Если вы можете прибить процессор до очень небольшого количества состояний, обязательно сделайте это.
  • Если процессор работает с очень предсказуемыми способами для определенных типов входных данных, вы можете записать это как таковое - например, если он простаивает на числовом входе ниже некоторого отсечения, вместо того, чтобы регистрировать каждое значение, просто записывайте "на холостом ходу для N шагов",.

Мне также нужно иметь возможность сохранить историческое состояние на диске, чтобы я мог восстановить все это снова в будущем (и все еще есть функции возврата и воспроизведения). И, конечно, мне нужно сделать это с гигабайтами данных, и это будет очень быстро:)

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

Существует ли существующая библиотека/механизм/подход/концепция, которая уже была сделана для выполнения того, что я пытаюсь сделать? Имеет ли смысл монада смысл? Существуют ли другие более эффективные/специальные подходы, которые помогли бы ему сделать это эффективно, особенно учитывая огромное количество данных, которые мне нужно управлять?

Если большинство ваших процессоров похожи на преобразователи конечного состояния, и вам нужно иметь процессоры, которые вносят различные типы и производят разные типы выходов, то, что вы, вероятно, хотите, на самом деле что-то с структура, основанная на Arrow s, которая дает вам абстракцию для вещей, которые в некотором смысле составляют "похожие функции", например, подключение входа одного процессора к выходу другого.

Кроме того, если вы избежите класса ArrowApply и убедитесь, что модель вашего конечного автомата возвращает только выходное значение и новое состояние, вам гарантировано избегать неявного состояния, потому что (в отличие от функций) Arrow не являются автоматически более высокими.

Учитывая размер данных... это действительно не должно быть даже все в памяти, что означало бы, что монада должна была бы отображаться на диск, кэшироваться и т.д.

Учитывая статическое представление вашей сети процессора, не должно быть слишком сложно также обеспечить инкрементную систему ввода-вывода, которая будет считывать данные, сериализовать/десериализовать состояние и написать любой вывод.


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

data Transducer s a b = Transducer { curState :: s
                                   , runStep  :: s -> a -> (s, b)
                                   }

runTransducer :: Transducer s a b -> [a] -> [b]
runTransducer t [] = (t, [])
runTransducer t (x:xs) = let (s, y) = runStep t (curState t) x
                             (t', ys) = runTransducer (t { curState = s }) xs
                         in (t', y:ys)

Это простой, общий процессор с явным внутренним состоянием типа s, ввода типа a и вывода типа b. Функция runTransducer запускает список входов через, вручную обновляет значение состояния и собирает список выходов.


P.S. - Поскольку вы спрашивали о монадах, вы можете узнать, был ли приведенный мной пример одним. На самом деле, это комбинация нескольких общих монад, хотя какие из них зависят от того, как вы на это смотрите. Однако я сознательно избегал рассматривать его как монаду! Дело в том, что монады захватывают только абстракции, которые в некотором смысле очень мощные, но эта же сила также делает их более устойчивыми в некоторой степени для оптимизации и статического анализа. Главное, что нужно исключить, - это процессоры, которые используют другие процессоры в качестве входных данных и запускают их, что (как вы можете себе представить) может создать сложную логику, которую практически невозможно проанализировать.

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