Я собираюсь ответить на ваш второй вопрос первым.На самом деле существует много способов обработки изменяемого состояния в Haskell (и других языках FP).Прежде всего, Haskell поддерживает изменяемое состояние в IO через конструкции IORef
и mvar
.Их использование будет очень знакомо программистам из императивных языков.Существуют также специализированные версии, такие как STRef
и TMVar
, а также изменяемые массивы, указатели и различные другие изменяемые данные.Самый большой недостаток заключается в том, что они обычно доступны только в IO или в более специализированной монаде.
Наиболее распространенный способ симуляции состояния в функциональном языке - это явная передача состояния в качестве аргумента функции и возвращаемого значения.Например:
randomGen :: Seed -> (Int, Seed)
Здесь randomGen
принимает параметр начального числа и возвращает новое начальное число.Каждый раз, когда вы вызываете его, вам нужно отслеживать начальное число для следующей итерации.Этот метод всегда доступен для передачи состояний, но он быстро становится утомительным.
Вероятно, наиболее распространенный подход Haskell заключается в использовании монады для инкапсуляции передачи этого состояния.Мы можем заменить randomGen
следующим:
-- a Random monad is simply a Seed value as state
type Random a = State Seed a
randomGen2 :: Random Int
randomGen2 = do
seed <- get
let (x,seed') = randomGen seed
put seed'
return x
Теперь любые функции, которым требуется PRNG, могут запускаться в монаде Random для запроса их по мере необходимости.Вам просто нужно указать начальное состояние и вычисление.
runRandomComputation :: Random a -> Seed -> a
runRandomComputation = evalState
(обратите внимание, что есть функции, которые значительно сокращают определение randomGen2; я выбрал наиболее явную версию).
Если вашдля случайных вычислений также требуется доступ к IO
, затем вы используете версию State для преобразователя монад, StateT
.
. Особо следует отметить монаду ST
, которая, по сути, предоставляет механизм для инкапсуляции IO-специфичныхмутации от остальной части IO.Монада ST предоставляет STRef, которые являются изменяемой ссылкой на данные, а также изменяемые массивы.Используя ST, можно определить такие вещи:
randomList :: Seed -> [Int]
, где [Int] - это бесконечный список случайных чисел (в конечном итоге он будет циклически изменяться в зависимости от вашего PSRG) из начального начального числа, которое вы ему дадите.
Наконец, есть Функциональное реактивное программирование .Вероятно, в настоящее время наиболее известными библиотеками для этого являются Yampa и Reactive , но другие также заслуживают внимания.Существует несколько подходов к изменчивому состоянию в различных реализациях FRP;из-за их небольшого использования они часто кажутся похожими по концепции на сигнальную структуру, как в QT или Gtk + (например, добавление слушателей для событий).
Теперь по первому вопросу.Для меня самым большим преимуществом является то, что изменяемое состояние отделено от другого кода на уровне типа.Это означает, что код не может случайно изменить состояние, если это явно не указано в сигнатуре типа.Это также дает очень хороший контроль над состоянием «только чтение» и изменяемым состоянием («Монада чтения» и «Монада состояния»).Я считаю очень полезным структурировать мой код таким образом, и полезно иметь возможность просто по сигнатуре типа определить, может ли функция неожиданно изменять состояние.
У меня лично нет никаких оговорокоб использовании изменяемого состояния в Haskell.Самая большая трудность заключается в том, что добавлять состояние к тому, что ранее не требовалось, может быть утомительно, но то же самое было бы утомительно в других языках, которые я использовал для подобных задач (C #, Python).