Сначала давайте проясним используемый алгоритм Фибоначчи. Идея состоит в том, чтобы начать с кортежа (0, 1)
, а затем найти следующий (1, 0 + 1)
, следующий как (1, 1 + 1)
, (2, 2 + 1)
, (3, 3 + 2)
и так далее. Как правило, шаг \(a, b) -> (b, a + b)
. Вы можете видеть, что в этих кортежах есть числа Фибоначчи.
Что происходит внутри первого утверждения внутренних дел, то есть то, что делает (а, б) < -get привести к?
У Haskell нет утверждений, только выражений.
y <- x
не является полным выражением. Он похож на x >>= \y ->
.
y <- x
m
Является полным выражением и эквивалентно x >>= \y -> m
. Линия n
, не относящаяся к форме y <- n
, эквивалентна _ <- n
(за исключением let
линий и, возможно, некоторых других я забываю).
Используя это, мы можем обозначить десурацию.
fib n =
flip evalState (0, 1)
(forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
>>= (\_ -> get >>= (\(a, b) -> return a)))
)
Сейчас речь идет просто о понимании >>=
, return
, get
, put
, и так далее.
Состояние на самом деле просто функции типа s -> (s, a)
. Они берут начальное состояние и дают следующее состояние плюс другое значение.
m >>= n
a.k.a. "bind" имеет тип Monad m => m a -> (a -> m b) -> m b
. Тогда, если наша Монада State s
, это то же самое, как:
m >>= n ::
( s -> (s, a))
-> (a -> s -> (s, b))
-> ( s -> (s, b))
a
возвращаемый m
должен быть передан n
. Что еще мы можем догадаться? Мы ожидаем, что государство также пройдет, поэтому государство, возвращенное m
, должно быть передано также n
. Функция m >>= n
должна вернуть состояние и значение, которое возвращает n
. Затем мы знаем, как осуществить привязку:
m >>= n = uncurry (flip n) . m
return :: Monad m => a -> m a
, который затем эквивалентно return :: a -> s -> (s, a)
:
return = flip (,)
get :: State s s
эквивалентно get :: s -> (s, s)
:
get = join (,)
put :: s -> State s()
или put :: s -> s -> (s,())
:
put s _ = (s,())
evalState :: s -> State s a -> a
или evalState :: s -> (s -> (s, a)) -> a
:
evalState s f = snd (f s)
можно развернуть все определения и увидеть то, что происходит в примере. Однако достаточно интуиции.
forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
Мы не заботимся о том, число 0
к n - 1
поэтому первый аргумент отбрасывается. get
извлекает текущее состояние, затем put
записывает новое состояние. Мы делаем это n
раз.
>>= (\_ -> get >>= (\(a, b) -> return a)))
Мы не заботимся о накопленном значении (которое является единицей), и поэтому первый параметр отбрасывается. Затем мы получаем текущее состояние и проектируем только первый элемент пары. Это окончательный ответ, который мы ищем.
flip evalState (0, 1) …
Наконец Бежим, начиная с начального состояния (0, 1)
.
Есть некоторые очистки, которые мы можем сделать для этой реализации. Во-первых, нас не волнует диапазон [0..(n-1)]
, нам просто нужно повторить действие n
раз.Более прямой способ сделать это состоит в следующем:
replicateM n (get >>= \(a, b) -> put (b, a + b))
Результатом является список блока, который не используется, так что более эффективная версия:
replicateM_ n (get >>= \(a, b) -> put (b, a + b))
Существует уже функция для общий шаблон get
, за которым следует put
с именем modify
, который определяется как \f -> get >>= put . f
. Поэтому:
replicateM_ n (modify (\(a, b) -> (b, a + b)))
Тогда есть часть:
>>= (\_ -> get >>= (\(a, b) -> return a)))
Каждый раз, когда мы не заботимся о предыдущем результате мы можем использовать >>
.
>> get >>= (\(a, b) -> return a))
Это:
>> get >>= return . fst
m >>= return . f
упрощается fmap f m
:
>> fmap fst get
Теперь у нас есть, в общей сложности:
fib n =
evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> fmap fst get
)
(0, 1)
Мы могли бы также использовать для сравнения :
fib n =
fst
(evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> get
)
(0, 1)
)
А потом, потому что я глупый:
fib =
fst
. flip evalState (0, 1)
. (>> get)
. flip replicateM_ (modify (snd &&& uncurry (+)))
Почему вы хотите использовать государственную монады здесь?
Вы бы этого не сделали. Это понятно, потому что мы используем только значение состояния; другое значение всегда является единицей и отбрасывается. Другими словами, нам нужно только n
(то есть номер Фибоначчи, который нужно найти) в начале, а затем нам нужен только накопленный кортеж.
Иногда вы считаете, что есть такая композиция, как h . g . f
, но вы хотите отправить два аргумента вместо одного. То есть, когда может применяться State
.
Если некоторые функции читаются, а некоторые записывают состояние (второй аргумент) или делают оба, то State
подходит для счета. Если есть только читатели, то используйте Reader
, и если есть только авторы, используйте Writer
.
Мы можем изменить пример, чтобы лучше использовать государственную Монаду. Я заставлю кортеж исчезнуть!
fib =
flip evalState 0
. foldr (=<<) (return 1)
. flip replicate (\x -> get >>= \y -> put x $> x + y)
Что касается 2) идет, вы ... не могли бы, в реальном коде, это просто игрушечный пример. –