Ответ 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. - Поскольку вы спрашивали о монадах, вы можете узнать, был ли приведенный мной пример одним. На самом деле, это комбинация нескольких общих монад, хотя какие из них зависят от того, как вы на это смотрите. Однако я сознательно избегал рассматривать его как монаду! Дело в том, что монады захватывают только абстракции, которые в некотором смысле очень мощные, но эта же сила также делает их более устойчивыми в некоторой степени для оптимизации и статического анализа. Главное, что нужно исключить, - это процессоры, которые используют другие процессоры в качестве входных данных и запускают их, что (как вы можете себе представить) может создать сложную логику, которую практически невозможно проанализировать.
Итак, хотя процессоры, вероятно, могут быть монадами, и в некотором логическом смысле по существу есть, может быть более полезно притворяться, что они не являются; навязывая искусственное ограничение, чтобы упростить статический анализ.