Haskell/Monad transformers: различия между версиями
АРГО-67 (обсуждение | вклад) →Реализация lift: орфография |
DannyS712 (обсуждение | вклад) м <source> -> <syntaxhighlight> (phab:T237267) |
||
Строка 11: | Строка 11: | ||
Функция получения пароля от пользователя на Haskell может выглядеть следующим образом: |
Функция получения пароля от пользователя на Haskell может выглядеть следующим образом: |
||
< |
<syntaxhighlight lang="haskell"> |
||
getPassword :: IO (Maybe String) |
getPassword :: IO (Maybe String) |
||
getPassword = do s <- getLine |
getPassword = do s <- getLine |
||
Строка 20: | Строка 20: | ||
isValid :: String -> Bool |
isValid :: String -> Bool |
||
isValid s = length s >= 8 && any isAlpha s && any isNumber s && any isPunctuation s |
isValid s = length s >= 8 && any isAlpha s && any isNumber s && any isPunctuation s |
||
</syntaxhighlight> |
|||
</source> |
|||
В первую очередь, <code>getPassword</code> является <code>IO</code>-функцией, так как ей необходимо получить ввод от пользователя, ну и он не всегда будет возвращаться. Мы также используем <code>Maybe</code>, так как мы намерены возвращать <code>Nothing</code> в случае, если пароль не проходит условие <code>isValid</code>. Заметим, однако, что мы фактически не будем использовать <code>Maybe</code> здесь как монаду: <code>do</code>-блок находится в <code>IO</code>-монаде, и нам просто повезло получить <code>return</code> с <code>Maybe</code>-значением внутри. |
В первую очередь, <code>getPassword</code> является <code>IO</code>-функцией, так как ей необходимо получить ввод от пользователя, ну и он не всегда будет возвращаться. Мы также используем <code>Maybe</code>, так как мы намерены возвращать <code>Nothing</code> в случае, если пароль не проходит условие <code>isValid</code>. Заметим, однако, что мы фактически не будем использовать <code>Maybe</code> здесь как монаду: <code>do</code>-блок находится в <code>IO</code>-монаде, и нам просто повезло получить <code>return</code> с <code>Maybe</code>-значением внутри. |
||
Истинная мотивация для трансформеров монад состоит не только в том, чтобы было проще написать <code>getPassword</code> (что все же происходит), а, скорее, чтобы упростить все куски кода, в которых мы его используем. Наша программа получения пароля может быть продолжена так: |
Истинная мотивация для трансформеров монад состоит не только в том, чтобы было проще написать <code>getPassword</code> (что все же происходит), а, скорее, чтобы упростить все куски кода, в которых мы его используем. Наша программа получения пароля может быть продолжена так: |
||
< |
<syntaxhighlight lang="haskell"> |
||
askPassword :: IO () |
askPassword :: IO () |
||
askPassword = do putStrLn "Insert your new password:" |
askPassword = do putStrLn "Insert your new password:" |
||
Строка 32: | Строка 32: | ||
then do putStrLn "Storing in database..." |
then do putStrLn "Storing in database..." |
||
-- ... other stuff, including 'else' |
-- ... other stuff, including 'else' |
||
</syntaxhighlight> |
|||
</source> |
|||
Нам нужна одна строка для создания <code>maybe_value</code>-переменной, а затем мы должны сделать некоторые дополнительные проверки, чтобы выяснить в порядке наш пароль или нет. |
Нам нужна одна строка для создания <code>maybe_value</code>-переменной, а затем мы должны сделать некоторые дополнительные проверки, чтобы выяснить в порядке наш пароль или нет. |
||
Строка 41: | Строка 41: | ||
<code>MaybeT</code> является оберткой вокруг <code>m (Maybe a)</code>, где <code>m</code> может быть любой монадой (в нашем примере, мы заинтересованы в <code>IO</code>): |
<code>MaybeT</code> является оберткой вокруг <code>m (Maybe a)</code>, где <code>m</code> может быть любой монадой (в нашем примере, мы заинтересованы в <code>IO</code>): |
||
< |
<syntaxhighlight lang="haskell"> |
||
newtype (Monad m) => MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } |
newtype (Monad m) => MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } |
||
</syntaxhighlight> |
|||
</source> |
|||
<code>newtype</code> — это просто более эффективная альтернатива обычным декларациям <code>data</code> на случаи, когда есть только один конструктор. Так, <code>MaybeT</code> является конструктором типа, параметризованного более <code>m</code>, с конструктором данных, который также называется <code>MaybeT</code> и удобной функцией доступа <code>runMaybeT</code>, с помощью которой мы можем получить доступ к базовому (внутреннему) представлению. |
<code>newtype</code> — это просто более эффективная альтернатива обычным декларациям <code>data</code> на случаи, когда есть только один конструктор. Так, <code>MaybeT</code> является конструктором типа, параметризованного более <code>m</code>, с конструктором данных, который также называется <code>MaybeT</code> и удобной функцией доступа <code>runMaybeT</code>, с помощью которой мы можем получить доступ к базовому (внутреннему) представлению. |
||
Весь смысл монадных трансформаторов в том, что ''они сами монады'', и поэтому нам нужно сделать <code>MaybeT m</code> экземпляром класса <code>Monad</code>: |
Весь смысл монадных трансформаторов в том, что ''они сами монады'', и поэтому нам нужно сделать <code>MaybeT m</code> экземпляром класса <code>Monad</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance Monad m => Monad (MaybeT m) where |
instance Monad m => Monad (MaybeT m) where |
||
return = MaybeT . return . Just |
return = MaybeT . return . Just |
||
</syntaxhighlight> |
|||
</source> |
|||
<code>return</code> осуществляется с помощью <code>Just</code>, который вставляет (не указанное из-за частичного применения значение) в монаду <code>Maybe</code>, затем общий <code>return</code>, который в свою очередь вводит (полученное значение) в <code>m</code> (чем бы это ни было) и затем конструктор <code>MaybeT</code>. |
<code>return</code> осуществляется с помощью <code>Just</code>, который вставляет (не указанное из-за частичного применения значение) в монаду <code>Maybe</code>, затем общий <code>return</code>, который в свою очередь вводит (полученное значение) в <code>m</code> (чем бы это ни было) и затем конструктор <code>MaybeT</code>. |
||
Также возможно (хотя менее приятно читается) написать <code>return = MaybeT . return . return</code>. |
Также возможно (хотя менее приятно читается) написать <code>return = MaybeT . return . return</code>. |
||
< |
<syntaxhighlight lang="haskell"> |
||
x >>= f = MaybeT $ do maybe_value <- runMaybeT x |
x >>= f = MaybeT $ do maybe_value <- runMaybeT x |
||
case maybe_value of |
case maybe_value of |
||
Nothing -> return Nothing |
Nothing -> return Nothing |
||
Just value -> runMaybeT $ f value |
Just value -> runMaybeT $ f value |
||
</syntaxhighlight> |
|||
</source> |
|||
Как и у всех монад, оператор связывания <code>bind</code> является сердцем трансформера, и самым важным фрагментом кода для понимания того, как он работает. Как всегда, полезно иметь в виду сигнатуру типов. Типом оператора связывания <code>bind</code> для монады <code>MaybeT</code> будет: |
Как и у всех монад, оператор связывания <code>bind</code> является сердцем трансформера, и самым важным фрагментом кода для понимания того, как он работает. Как всегда, полезно иметь в виду сигнатуру типов. Типом оператора связывания <code>bind</code> для монады <code>MaybeT</code> будет: |
||
< |
<syntaxhighlight lang="haskell"> |
||
-- The signature of (>>=), specialized to MaybeT m |
-- The signature of (>>=), specialized to MaybeT m |
||
(>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b |
(>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b |
||
</syntaxhighlight> |
|||
</source> |
|||
Теперь, давайте рассмотрим, что он делает, шаг за шагом, начиная с первой строки <code>do</code>-блока. |
Теперь, давайте рассмотрим, что он делает, шаг за шагом, начиная с первой строки <code>do</code>-блока. |
||
Строка 75: | Строка 75: | ||
* Наконец, <code>do</code>-блок в целом имеет тип <code>m (Maybe b)</code>; так он обернут конструктором <code>MaybeT</code>. Это может выглядеть сложновато, но все же менее сложно, чем куча оберток и распаковок, реализация делает тоже самое, что и знакомый оператор <code>bind</code> монады <code>Maybe</code>: |
* Наконец, <code>do</code>-блок в целом имеет тип <code>m (Maybe b)</code>; так он обернут конструктором <code>MaybeT</code>. Это может выглядеть сложновато, но все же менее сложно, чем куча оберток и распаковок, реализация делает тоже самое, что и знакомый оператор <code>bind</code> монады <code>Maybe</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
-- (>>=) for the Maybe monad |
-- (>>=) for the Maybe monad |
||
maybe_value >>= f = case maybe_value of |
maybe_value >>= f = case maybe_value of |
||
Nothing -> Nothing |
Nothing -> Nothing |
||
Just value -> f value |
Just value -> f value |
||
</syntaxhighlight> |
|||
</source> |
|||
Вы можете удивиться, почему мы используем конструктор <code>MaybeT</code> перед <code>do</code>-блоком, когда внутри него мы используем функцию доступа <code>runMaybeT</code>: однако, <code>do</code>-блок должен быть в монаде <code>m</code>, а не в <code>MaybeT m</code>, так как для последней мы еще не определили оператор связывания <code>bind</code>. |
Вы можете удивиться, почему мы используем конструктор <code>MaybeT</code> перед <code>do</code>-блоком, когда внутри него мы используем функцию доступа <code>runMaybeT</code>: однако, <code>do</code>-блок должен быть в монаде <code>m</code>, а не в <code>MaybeT m</code>, так как для последней мы еще не определили оператор связывания <code>bind</code>. |
||
Строка 87: | Строка 87: | ||
Технически, это все, что нам надо; однако, будет удобно сделать <code>MaybeT</code> воплощением еще некоторых других классов: |
Технически, это все, что нам надо; однако, будет удобно сделать <code>MaybeT</code> воплощением еще некоторых других классов: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance Monad m => MonadPlus (MaybeT m) where |
instance Monad m => MonadPlus (MaybeT m) where |
||
mzero = MaybeT $ return Nothing |
mzero = MaybeT $ return Nothing |
||
Строка 97: | Строка 97: | ||
instance MonadTrans MaybeT where |
instance MonadTrans MaybeT where |
||
lift = MaybeT . (liftM Just) |
lift = MaybeT . (liftM Just) |
||
</syntaxhighlight> |
|||
</source> |
|||
Последний класс, <code>MonadTrans</code>, реализует (обеспечивает) функцию <code>lift</code>, которая очень полезна для того, чтобы взять функции из монады <code>m</code> и поднять (принести) их внутрь монады <code>MaybeT m</code>, так чтобы мы смогли использовать их в <code>do</code>-блоке внутри монады <code>MaybeT m</code>. |
Последний класс, <code>MonadTrans</code>, реализует (обеспечивает) функцию <code>lift</code>, которая очень полезна для того, чтобы взять функции из монады <code>m</code> и поднять (принести) их внутрь монады <code>MaybeT m</code>, так чтобы мы смогли использовать их в <code>do</code>-блоке внутри монады <code>MaybeT m</code>. |
||
Строка 103: | Строка 103: | ||
=== Применение к парольному примеру === |
=== Применение к парольному примеру === |
||
Со всем сказанным выше, вот то, на что парольное управление будет похоже: |
Со всем сказанным выше, вот то, на что парольное управление будет похоже: |
||
< |
<syntaxhighlight lang="haskell"> |
||
getValidPassword :: MaybeT IO String |
getValidPassword :: MaybeT IO String |
||
getValidPassword = do s <- lift getLine |
getValidPassword = do s <- lift getLine |
||
Строка 113: | Строка 113: | ||
value <- getValidPassword |
value <- getValidPassword |
||
lift $ putStrLn "Storing in database..." |
lift $ putStrLn "Storing in database..." |
||
</syntaxhighlight> |
|||
</source> |
|||
Этот код не прост, особенно в пользовательской функции <code>askPassword</code>. Более важно, что мы не должны вручную проверять, является ли результат <code>Nothing</code> или <code>Just</code>: оператор <code>bind</code> сделает это за нас. |
Этот код не прост, особенно в пользовательской функции <code>askPassword</code>. Более важно, что мы не должны вручную проверять, является ли результат <code>Nothing</code> или <code>Just</code>: оператор <code>bind</code> сделает это за нас. |
||
Строка 119: | Строка 119: | ||
И кстати, с помощью <code>MonadPlus</code> стало очень просто спрашивать у пользователя правильный пароль ''до бесконечности'' |
И кстати, с помощью <code>MonadPlus</code> стало очень просто спрашивать у пользователя правильный пароль ''до бесконечности'' |
||
< |
<syntaxhighlight lang="haskell"> |
||
askPassword :: MaybeT IO () |
askPassword :: MaybeT IO () |
||
askPassword = do lift $ putStrLn "Insert your new password:" |
askPassword = do lift $ putStrLn "Insert your new password:" |
||
value <- msum $ repeat getValidPassword |
value <- msum $ repeat getValidPassword |
||
lift $ putStrLn "Storing in database..." |
lift $ putStrLn "Storing in database..." |
||
</syntaxhighlight> |
|||
</source> |
|||
== Изобилие трансформеров == |
== Изобилие трансформеров == |
||
Строка 133: | Строка 133: | ||
=== Манипуляции с типами === |
=== Манипуляции с типами === |
||
Мы видели, что конструктор типа для <code>MaybeT</code> является оболочкой для значения типа <code>Maybe</code> во внутренней монаде, и поэтому соответствующая функция доступа <code>runMaybeT</code> дает нам значение типа <code>m (Maybe a)</code> — то есть, значение базовой монады возвращенную во внутреннюю монаду. Аналогичным образом, мы имеем |
Мы видели, что конструктор типа для <code>MaybeT</code> является оболочкой для значения типа <code>Maybe</code> во внутренней монаде, и поэтому соответствующая функция доступа <code>runMaybeT</code> дает нам значение типа <code>m (Maybe a)</code> — то есть, значение базовой монады возвращенную во внутреннюю монаду. Аналогичным образом, мы имеем |
||
< |
<syntaxhighlight lang = "haskell"> |
||
runListT :: ListT m a -> m [a] |
runListT :: ListT m a -> m [a] |
||
</syntaxhighlight> |
|||
</source> |
|||
и |
и |
||
< |
<syntaxhighlight lang = "haskell"> |
||
runErrorT :: ErrorT e m a -> m (Either e a) |
runErrorT :: ErrorT e m a -> m (Either e a) |
||
</syntaxhighlight> |
|||
</source> |
|||
для трансформеров списков (list) и ошибок (error). |
для трансформеров списков (list) и ошибок (error). |
||
Строка 168: | Строка 168: | ||
Начнем с того, что строго говоря, она не из темы монадных трансформеров. <code>liftM</code> — черезвычайно полезная функция в стандартной библиотеке со следующей сигнатурой: |
Начнем с того, что строго говоря, она не из темы монадных трансформеров. <code>liftM</code> — черезвычайно полезная функция в стандартной библиотеке со следующей сигнатурой: |
||
< |
<syntaxhighlight lang="haskell"> |
||
liftM :: Monad m => (a1 -> r) -> m a1 -> m r |
liftM :: Monad m => (a1 -> r) -> m a1 -> m r |
||
</syntaxhighlight> |
|||
</source> |
|||
<code>liftM</code> применяет функцию <code>(a1 -> r)</code> к значению в рамках (внутри) монады <code>m</code>. Если вы предпочитаете бесточечную запись, она может превратить обычную функцию в такую, которая действует внутри <code>m</code> — и ''это'' как раз то, что подразумевается под поднятием в монаду. |
<code>liftM</code> применяет функцию <code>(a1 -> r)</code> к значению в рамках (внутри) монады <code>m</code>. Если вы предпочитаете бесточечную запись, она может превратить обычную функцию в такую, которая действует внутри <code>m</code> — и ''это'' как раз то, что подразумевается под поднятием в монаду. |
||
Строка 182: | Строка 182: | ||
|- |
|- |
||
|valign="top"| |
|valign="top"| |
||
< |
<syntaxhighlight lang="haskell"> |
||
do x <- monadicValue |
do x <- monadicValue |
||
return (f x) |
return (f x) |
||
</syntaxhighlight> |
|||
</source> |
|||
|valign="top"| |
|valign="top"| |
||
< |
<syntaxhighlight lang="haskell"> |
||
liftM f monadicValue |
liftM f monadicValue |
||
</syntaxhighlight> |
|||
</source> |
|||
|valign="top"| |
|valign="top"| |
||
< |
<syntaxhighlight lang="haskell"> |
||
f `liftM` monadicValue |
f `liftM` monadicValue |
||
</syntaxhighlight> |
|||
</source> |
|||
|} |
|} |
||
Строка 203: | Строка 203: | ||
|- |
|- |
||
|| |
|| |
||
< |
<syntaxhighlight lang="haskell"> |
||
f $ value |
f $ value |
||
</syntaxhighlight> |
|||
</source> |
|||
|| |
|| |
||
< |
<syntaxhighlight lang="haskell"> |
||
f `liftM` monadicValue |
f `liftM` monadicValue |
||
</syntaxhighlight> |
|||
</source> |
|||
|} |
|} |
||
Строка 221: | Строка 221: | ||
С <code>liftM</code> мы увидели, что сущность поднятия — перефразируя документацию — в продвижении чего-то в монаду. Функция <code>lift</code>, доступная для всех монадных трансформеров, выполняет разный тип поднятия: она продвигает вычисление из внутренней монады в комбинированную монаду. Функция <code>lift</code> определена как единственный метод класса <code>MonadTrans</code> в {{Haskell lib|p=transformers|v=latest|Control|Monad|Trans|Class}}. |
С <code>liftM</code> мы увидели, что сущность поднятия — перефразируя документацию — в продвижении чего-то в монаду. Функция <code>lift</code>, доступная для всех монадных трансформеров, выполняет разный тип поднятия: она продвигает вычисление из внутренней монады в комбинированную монаду. Функция <code>lift</code> определена как единственный метод класса <code>MonadTrans</code> в {{Haskell lib|p=transformers|v=latest|Control|Monad|Trans|Class}}. |
||
< |
<syntaxhighlight lang="haskell"> |
||
class MonadTrans t where |
class MonadTrans t where |
||
lift :: (Monad m) => m a -> t m a |
lift :: (Monad m) => m a -> t m a |
||
</syntaxhighlight> |
|||
</source> |
|||
Имеется вариант <code>lift</code>, специфичный для <code>IO</code> и называемый <code>liftIO</code>, который является единственным методом класса <code>MonadIO</code> в {{Haskell lib|p=transformers|v=latest|Control|Monad|IO|Class}}. |
Имеется вариант <code>lift</code>, специфичный для <code>IO</code> и называемый <code>liftIO</code>, который является единственным методом класса <code>MonadIO</code> в {{Haskell lib|p=transformers|v=latest|Control|Monad|IO|Class}}. |
||
< |
<syntaxhighlight lang="haskell"> |
||
class (Monad m) => MonadIO m where |
class (Monad m) => MonadIO m where |
||
liftIO :: IO a -> m a |
liftIO :: IO a -> m a |
||
</syntaxhighlight> |
|||
</source> |
|||
<code>liftIO</code> может быть удобен, когда у нас множестов трансформеров помещены друг за другом (как в стек) в одну комбинированную монаду. В подобных случаях, <code>IO</code> будет всегда самой внутренней монадой, и таким образом обычно нужен более чем один <code>lift</code>, чтобы поднять <code>IO</code>-значения на вершину стека. <code>liftIO</code>, однако, определен для воплощений таким образом, что позволяет нам поднять <code>IO</code>-значение из произвольной глубины, написав функцию лишь единожды. |
<code>liftIO</code> может быть удобен, когда у нас множестов трансформеров помещены друг за другом (как в стек) в одну комбинированную монаду. В подобных случаях, <code>IO</code> будет всегда самой внутренней монадой, и таким образом обычно нужен более чем один <code>lift</code>, чтобы поднять <code>IO</code>-значения на вершину стека. <code>liftIO</code>, однако, определен для воплощений таким образом, что позволяет нам поднять <code>IO</code>-значение из произвольной глубины, написав функцию лишь единожды. |
||
Строка 238: | Строка 238: | ||
Реализация <code>lift</code> как правило довольно прямолинейна (проста). Рассмотрим трансформер <code>MaybeT</code>: |
Реализация <code>lift</code> как правило довольно прямолинейна (проста). Рассмотрим трансформер <code>MaybeT</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance MonadTrans MaybeT where |
instance MonadTrans MaybeT where |
||
lift m = MaybeT (m >>= return . Just) |
lift m = MaybeT (m >>= return . Just) |
||
</syntaxhighlight> |
|||
</source> |
|||
Мы начинаем с монадическим значением внутренней монады — средним слоем, если вы предпочитаете аналогию с сэндвичем. Используя оператор <code>bind</code> и конструктор типа для базовой монады, мы плавно сдвигаем (скатываем, намазываем ??) нижний слой (базовую монаду) под средний слой. В конце, мы помещаем верхний срез нашего сэндвича с помощью конструктора <code>MaybeT</code>. Таким образом, используя функцию <code>lift</code>, мы трансформировали нижний кусок начинки сэндвича в подлинно трехслойный монадический сэндвич. Отметим, что как в реализации класса <code>Monad</code>, и оператор <code>bind</code>, и общий (основной) оператор <code>return</code> работают в границах внутренней монады. |
Мы начинаем с монадическим значением внутренней монады — средним слоем, если вы предпочитаете аналогию с сэндвичем. Используя оператор <code>bind</code> и конструктор типа для базовой монады, мы плавно сдвигаем (скатываем, намазываем ??) нижний слой (базовую монаду) под средний слой. В конце, мы помещаем верхний срез нашего сэндвича с помощью конструктора <code>MaybeT</code>. Таким образом, используя функцию <code>lift</code>, мы трансформировали нижний кусок начинки сэндвича в подлинно трехслойный монадический сэндвич. Отметим, что как в реализации класса <code>Monad</code>, и оператор <code>bind</code>, и общий (основной) оператор <code>return</code> работают в границах внутренней монады. |
||
Строка 256: | Строка 256: | ||
=== Трансформер List === |
=== Трансформер List === |
||
Также как с трансформером <code>Maybe</code>, мы начнем с создания конструктора типа, который принимает один аргумент: |
Также как с трансформером <code>Maybe</code>, мы начнем с создания конструктора типа, который принимает один аргумент: |
||
< |
<syntaxhighlight lang="haskell"> |
||
newtype ListT m a = ListT { runListT :: m [a] } |
newtype ListT m a = ListT { runListT :: m [a] } |
||
</syntaxhighlight> |
|||
</source> |
|||
Реализация монады <code>ListT m</code> поразительно похожа на свою «кузину», монаду списков. Мы делаем те же самые операции |
Реализация монады <code>ListT m</code> поразительно похожа на свою «кузину», монаду списков. Мы делаем те же самые операции |
||
Строка 268: | Строка 268: | ||
|- |
|- |
||
|valign="top"| |
|valign="top"| |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance Monad [] where |
instance Monad [] where |
||
xs >>= f = |
xs >>= f = |
||
Строка 274: | Строка 274: | ||
let yss = map f xs |
let yss = map f xs |
||
in concat yss |
in concat yss |
||
</syntaxhighlight> |
|||
</source> |
|||
|valign="top"| |
|valign="top"| |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance (Monad m) => Monad (ListT m) where |
instance (Monad m) => Monad (ListT m) where |
||
tm >>= f = ListT $ runListT tm |
tm >>= f = ListT $ runListT tm |
||
>>= \xs -> mapM (runListT . f) xs |
>>= \xs -> mapM (runListT . f) xs |
||
>>= \yss -> return (concat yss) |
>>= \yss -> return (concat yss) |
||
</syntaxhighlight> |
|||
</source> |
|||
|} |
|} |
||
Строка 294: | Строка 294: | ||
Как и монада <code>State</code> могла быть построена определением <code>newtype State s a = State { runState :: (s -> (a,s)) }</code><ref>В версии пакета <tt>mtl</tt> ранее 2.0.0.0, так и ''было'' построено. В настоящее время, однако, <code>State s</code> реализована как синоним типа для <code>StateT s Identity</code>.</ref> Трансформер <code>StateT</code> создан определением |
Как и монада <code>State</code> могла быть построена определением <code>newtype State s a = State { runState :: (s -> (a,s)) }</code><ref>В версии пакета <tt>mtl</tt> ранее 2.0.0.0, так и ''было'' построено. В настоящее время, однако, <code>State s</code> реализована как синоним типа для <code>StateT s Identity</code>.</ref> Трансформер <code>StateT</code> создан определением |
||
< |
<syntaxhighlight lang="haskell"> |
||
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }</ |
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }</syntaxhighlight> |
||
<code>State s</code> является воплощением как класса <code>Monad</code>, так и класса <code>MonadState s</code> (который обеспечивает <code>get</code> и <code>put</code>), так что <code>StateT s m</code> должна быть членом классов <code>Monad</code> и <code>MonadState s</code>. Более того, если <code>m</code> является воплощением <code>MonadPlus</code>, <code>StateT s m</code> также должна быть членом <code>MonadPlus</code>. |
<code>State s</code> является воплощением как класса <code>Monad</code>, так и класса <code>MonadState s</code> (который обеспечивает <code>get</code> и <code>put</code>), так что <code>StateT s m</code> должна быть членом классов <code>Monad</code> и <code>MonadState s</code>. Более того, если <code>m</code> является воплощением <code>MonadPlus</code>, <code>StateT s m</code> также должна быть членом <code>MonadPlus</code>. |
||
Строка 305: | Строка 305: | ||
!|StateT |
!|StateT |
||
|-valign="top" |
|-valign="top" |
||
||< |
||<syntaxhighlight lang="haskell"> |
||
newtype State s a = |
newtype State s a = |
||
State { runState :: (s -> (a,s)) } |
State { runState :: (s -> (a,s)) } |
||
Строка 314: | Строка 314: | ||
let (v,s') = x s |
let (v,s') = x s |
||
in runState (f v) s' |
in runState (f v) s' |
||
</syntaxhighlight> |
|||
</source> |
|||
||< |
||<syntaxhighlight lang="haskell"> |
||
newtype StateT s m a = |
newtype StateT s m a = |
||
StateT { runStateT :: (s -> m (a,s)) } |
StateT { runStateT :: (s -> m (a,s)) } |
||
Строка 324: | Строка 324: | ||
(v,s') <- x s -- get new value and state |
(v,s') <- x s -- get new value and state |
||
runStateT (f v) s' -- pass them to f |
runStateT (f v) s' -- pass them to f |
||
</syntaxhighlight> |
|||
</source> |
|||
|} |
|} |
||
Строка 332: | Строка 332: | ||
класса <code>MonadState</code>, так что мы дадим определения <code>get</code> и <code>put</code>: |
класса <code>MonadState</code>, так что мы дадим определения <code>get</code> и <code>put</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance (Monad m) => MonadState s (StateT s m) where |
instance (Monad m) => MonadState s (StateT s m) where |
||
get = StateT $ \s -> return (s,s) |
get = StateT $ \s -> return (s,s) |
||
put s = StateT $ \_ -> return ((),s) |
put s = StateT $ \_ -> return ((),s) |
||
</syntaxhighlight> |
|||
</source> |
|||
Наконец, мы хотим декларировать все комбинированные монады, в которых используется <code>StateT</code> с воплощением <code>MonadPlus</code>, как воплощения класса <code>MonadPlus</code>: |
Наконец, мы хотим декларировать все комбинированные монады, в которых используется <code>StateT</code> с воплощением <code>MonadPlus</code>, как воплощения класса <code>MonadPlus</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance (MonadPlus m) => MonadPlus (StateT s m) where |
instance (MonadPlus m) => MonadPlus (StateT s m) where |
||
mzero = StateT $ \s -> mzero |
mzero = StateT $ \s -> mzero |
||
(StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s) |
(StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s) |
||
</syntaxhighlight> |
|||
</source> |
|||
Последним шагом сделаем наш монадный трансформер полностью интегрированным с классом монад Haskell’а — для этого сделаем <code>StateT s</code> воплощением класса <code>MonadTrans</code>, обеспечив функцию <code>lift</code>: |
Последним шагом сделаем наш монадный трансформер полностью интегрированным с классом монад Haskell’а — для этого сделаем <code>StateT s</code> воплощением класса <code>MonadTrans</code>, обеспечив функцию <code>lift</code>: |
||
< |
<syntaxhighlight lang="haskell"> |
||
instance MonadTrans (StateT s) where |
instance MonadTrans (StateT s) where |
||
lift c = StateT $ \s -> c >>= (\x -> return (x,s)) |
lift c = StateT $ \s -> c >>= (\x -> return (x,s)) |
||
</syntaxhighlight> |
|||
</source> |
|||
Функция <code>lift</code> создает функцию, изменяющую состояние, <code>StateT</code>, которая связывает вычисление во внутренней монаде с функцией, пакующей результат со входным состоянием. Результат в том, если для воплощения мы применяем |
Функция <code>lift</code> создает функцию, изменяющую состояние, <code>StateT</code>, которая связывает вычисление во внутренней монаде с функцией, пакующей результат со входным состоянием. Результат в том, если для воплощения мы применяем |
Версия от 16:11, 16 апреля 2020
К этому моменту вы должны были предварительно уяснить понятие монады, а также то, что различные монады используются еще для: IO
для «нечистых» функций, Maybe
для значений, которые могут быть или нет, и так далее.
С помощью монад, обеспечивающих такую полезную функциональность общего назначения, очень естественно, что порой мы хотели бы использовать возможности нескольких монад сразу — например, функция, которая использует и IO
, и обработку исключений Maybe
. Конечно, мы можем использовать такой тип как IO (Maybe a)
, но это заставляет нас делать сопоставление с образцом в do
-блоках, чтобы извлечь необходимые значения: однако, фишка монад была также в том, чтобы избавиться от этого.
Итак, рассмотрим монадные трансформеры: специальные типы, которые позволяют нам комбинировать две монады в одной, но разделяющие поведение обоих. Начнем с примера, чтобы проиллюстрировать, почему трансформеры являются полезными и посмотрим простой пример того, как они работают.
Проверка пароля
Рассмотрим обычную проблему из реальной жизни для ИТ-специалистов всего мира: составим программу запроса от пользователей паролей, которые трудно угадать. Типичная стратегия — заставить пользователя ввести пароль не короче минимальной длины, и по крайней мере одну букву, одну цифру (и с другими подобными раздражающими требованиями).
Функция получения пароля от пользователя на Haskell может выглядеть следующим образом:
getPassword :: IO (Maybe String)
getPassword = do s <- getLine
if isValid s then return $ Just s
else return Nothing
-- Тест правильности может быть произвольным по нашему желанию.
isValid :: String -> Bool
isValid s = length s >= 8 && any isAlpha s && any isNumber s && any isPunctuation s
В первую очередь, getPassword
является IO
-функцией, так как ей необходимо получить ввод от пользователя, ну и он не всегда будет возвращаться. Мы также используем Maybe
, так как мы намерены возвращать Nothing
в случае, если пароль не проходит условие isValid
. Заметим, однако, что мы фактически не будем использовать Maybe
здесь как монаду: do
-блок находится в IO
-монаде, и нам просто повезло получить return
с Maybe
-значением внутри.
Истинная мотивация для трансформеров монад состоит не только в том, чтобы было проще написать getPassword
(что все же происходит), а, скорее, чтобы упростить все куски кода, в которых мы его используем. Наша программа получения пароля может быть продолжена так:
askPassword :: IO ()
askPassword = do putStrLn "Insert your new password:"
maybe_value <- getPassword
if isJust maybe_value
then do putStrLn "Storing in database..."
-- ... other stuff, including 'else'
Нам нужна одна строка для создания maybe_value
-переменной, а затем мы должны сделать некоторые дополнительные проверки, чтобы выяснить в порядке наш пароль или нет.
С монадными трансформерами мы сможем извлечь пароль на одном дыхании, без сопоставления с образцом или эквивалентной бюрократией типа isJust
. Выгода для нашего простого примера может показаться небольшой, но ее можно будет расширить для более сложных ситуаций.
Простой монадный трансформер: MaybeT
Для упрощения функции getPassword
и всего кода, который использует ее, мы определим монадный трансформер, который дает IO
-монаде некоторые характеристики монады Maybe
; мы будем называть его MaybeT
, следуя соглашению, что трансформеры монад имеют «T
», добавленное к названию монады, чьи характеристики они обеспечивают.
MaybeT
является оберткой вокруг m (Maybe a)
, где m
может быть любой монадой (в нашем примере, мы заинтересованы в IO
):
newtype (Monad m) => MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
newtype
— это просто более эффективная альтернатива обычным декларациям data
на случаи, когда есть только один конструктор. Так, MaybeT
является конструктором типа, параметризованного более m
, с конструктором данных, который также называется MaybeT
и удобной функцией доступа runMaybeT
, с помощью которой мы можем получить доступ к базовому (внутреннему) представлению.
Весь смысл монадных трансформаторов в том, что они сами монады, и поэтому нам нужно сделать MaybeT m
экземпляром класса Monad
:
instance Monad m => Monad (MaybeT m) where
return = MaybeT . return . Just
return
осуществляется с помощью Just
, который вставляет (не указанное из-за частичного применения значение) в монаду Maybe
, затем общий return
, который в свою очередь вводит (полученное значение) в m
(чем бы это ни было) и затем конструктор MaybeT
.
Также возможно (хотя менее приятно читается) написать return = MaybeT . return . return
.
x >>= f = MaybeT $ do maybe_value <- runMaybeT x
case maybe_value of
Nothing -> return Nothing
Just value -> runMaybeT $ f value
Как и у всех монад, оператор связывания bind
является сердцем трансформера, и самым важным фрагментом кода для понимания того, как он работает. Как всегда, полезно иметь в виду сигнатуру типов. Типом оператора связывания bind
для монады MaybeT
будет:
-- The signature of (>>=), specialized to MaybeT m
(>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b
Теперь, давайте рассмотрим, что он делает, шаг за шагом, начиная с первой строки do
-блока.
- Во-первых, он использует функцию доступа
runMaybeT
, чтобы развернутьx
в монадное вычислениеm (Maybe a)
. Таким образом он разворачивает (разоблачает, выдает)do
-блок находящийся вm
. - И также в первой строке, (операция)
<-
извлекает значениеMaybe a
из развернутого вычисления. - Выражение
case
тестирует значениеmaybe_value
:- если оно равно
Nothing
, возвращаетNothing
внутрь монадыm
; - если оно является
Just
, применяетсяf
к значениюvalue
внутри него. Так как функцияf
имеетMaybeT m b
как тип результата, нам необходимо дополнительно (запускать)runMaybeT
, чтобы поместить результат обратно внутрь монадыm
.
- если оно равно
- Наконец,
do
-блок в целом имеет типm (Maybe b)
; так он обернут конструкторомMaybeT
. Это может выглядеть сложновато, но все же менее сложно, чем куча оберток и распаковок, реализация делает тоже самое, что и знакомый операторbind
монадыMaybe
:
-- (>>=) for the Maybe monad
maybe_value >>= f = case maybe_value of
Nothing -> Nothing
Just value -> f value
Вы можете удивиться, почему мы используем конструктор MaybeT
перед do
-блоком, когда внутри него мы используем функцию доступа runMaybeT
: однако, do
-блок должен быть в монаде m
, а не в MaybeT m
, так как для последней мы еще не определили оператор связывания bind
.
Технически, это все, что нам надо; однако, будет удобно сделать MaybeT
воплощением еще некоторых других классов:
instance Monad m => MonadPlus (MaybeT m) where
mzero = MaybeT $ return Nothing
mplus x y = MaybeT $ do maybe_value <- runMaybeT x
case maybe_value of
Nothing -> runMaybeT y
Just _ -> return maybe_value
instance MonadTrans MaybeT where
lift = MaybeT . (liftM Just)
Последний класс, MonadTrans
, реализует (обеспечивает) функцию lift
, которая очень полезна для того, чтобы взять функции из монады m
и поднять (принести) их внутрь монады MaybeT m
, так чтобы мы смогли использовать их в do
-блоке внутри монады MaybeT m
.
Применение к парольному примеру
Со всем сказанным выше, вот то, на что парольное управление будет похоже:
getValidPassword :: MaybeT IO String
getValidPassword = do s <- lift getLine
guard (isValid s) -- MonadPlus provides guard.
return s
askPassword :: MaybeT IO ()
askPassword = do lift $ putStrLn "Insert your new password:"
value <- getValidPassword
lift $ putStrLn "Storing in database..."
Этот код не прост, особенно в пользовательской функции askPassword
. Более важно, что мы не должны вручную проверять, является ли результат Nothing
или Just
: оператор bind
сделает это за нас.
Отметим как мы используем lift
, чтобы поднять функции getLine
и putStrLn
вовнутрь монады MaybeT IO
. Также, так как MaybeT IO
является воплощением MonadPlus
, проверка правильности пароля может быть осуществлена выражением guard
, который вернет mzero
(то есть IO Nothing
) в случае плохого пароля.
И кстати, с помощью MonadPlus
стало очень просто спрашивать у пользователя правильный пароль до бесконечности
askPassword :: MaybeT IO ()
askPassword = do lift $ putStrLn "Insert your new password:"
value <- msum $ repeat getValidPassword
lift $ putStrLn "Storing in database..."
Изобилие трансформеров
Модуль пакета transformers обеспечивает трансформеры для многих общих монад (MaybeT
, например, может быть найдена в Шаблон:Haskell lib). Они определены в соответствии с их нетрансформерской версией; то есть, реализация в базовом та же самая, только с дополнительными обертками и развертками, необходимыми для ввинчивания в другую монаду.
Выберем произвольный пример, ReaderT Env IO String
-- вычисление, которое вовлечет считываемое значение из некоторого окружения типа Env
(семантика из Reader
, базовой монады) и выполняет некоторое IO
(действие) для того, чтобы получить значение типа String
. Так как операторы bind
и return
отражают семантику базовой монады, do
-блок типа ReaderT Env IO String
будет с внешней стороны похож на do
-блок монады Reader
; главная разница будет в том, что IO
-действия становятся тривиальными для встраивания при использовании функции lift
.
Манипуляции с типами
Мы видели, что конструктор типа для MaybeT
является оболочкой для значения типа Maybe
во внутренней монаде, и поэтому соответствующая функция доступа runMaybeT
дает нам значение типа m (Maybe a)
— то есть, значение базовой монады возвращенную во внутреннюю монаду. Аналогичным образом, мы имеем
runListT :: ListT m a -> m [a]
и
runErrorT :: ErrorT e m a -> m (Either e a)
для трансформеров списков (list) и ошибок (error).
Все же, не все трансформеры связаны с базовой монадой таким образом. Монады Writer
, Reader
, State
и Cont
объединяет то, что в отличие от базовых монады в примерах выше, они не имеют ни нескольких конструкторов, ни конструкторов с несколькими аргументами. По этой причине, у них есть функции run..., которые действуют как простые развертыватели (unwrappers) аналогично соответственным run...T из версий трансформера. В приведенной ниже таблице показаны результаты типов функций run... и run...T в каждом конкретном случае, что может рассматриваться как типы обернутые базовой и трансформированной монадой соответственно.
Base Monad | Transformer | Original Type («wrapped» by base) |
Combined Type («wrapped» by transformed) |
---|---|---|---|
Writer | WriterT | (a, w) |
m (a, w)
|
Reader | ReaderT | r -> a |
r -> m a
|
State | StateT | s -> (a, s) |
s -> m (a, s)
|
Cont | ContT | (a -> r) -> r |
(a -> m r) -> m r
|
Первое, на что нужно обратить внимание, это то, что базовые монады нигде не было видно в комбинированной типов. Это очень естественно, так как без конструкторы интересны (например, те для Может быть, или списки) нет никаких причин, чтобы сохранить базовый тип монады после разворачивания преобразованной монады. . Кроме того, в трех последних случаях у нас есть функция типа завернутые StateT , например, превращает государственные преобразования функций вида с -> (A, S) в государственно-преобразования функций вида с -> M (, с), так что только тип результата функции завернутые идет во внутренний монады. ReaderT аналогична, но не ContT : в связи с семантикой Cont (продолжение монады) результат обоих типов завернутые функции и ее функциональное Аргумент должен быть таким же, так что трансформатор ставит как во внутреннюю монады. Что эти примеры показывают, что в целом нет никакой волшебной формулы для создания трансформатора версия монады-формы каждого трансформера, зависит от того, что имеет смысл в контексте его не-трансформатор типа.
Подъем
Функция lift
, которую мы впервые представили во введении, очень важна для повседневного использования трансформеров; и поэтому мы посвятим несколько слов ей.
liftM
Начнем с того, что строго говоря, она не из темы монадных трансформеров. liftM
— черезвычайно полезная функция в стандартной библиотеке со следующей сигнатурой:
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
liftM
применяет функцию (a1 -> r)
к значению в рамках (внутри) монады m
. Если вы предпочитаете бесточечную запись, она может превратить обычную функцию в такую, которая действует внутри m
— и это как раз то, что подразумевается под поднятием в монаду.
Давайте посомтрим, как liftM
используется. Следующие фрагменты обозначают одно и то же.
do notation | liftM | liftM as an operator |
---|---|---|
do x <- monadicValue
return (f x)
|
liftM f monadicValue
|
f `liftM` monadicValue
|
Третий пример, в котором мы используем liftM
как оператор, предлагает интересную точку зрения на liftM
: это просто монадическая версия ($)
!
non monadic | monadic |
---|---|
f $ value
|
f `liftM` monadicValue
|
Упражнения |
---|
|
lift
Когда мы используем монады, созданные с помощью монадных трансформеров, мы можем избежать явного управления внутренними монадными типами, и в результате получаем более ясный и простой код. Вместо создания дополнительных do
-блоков внутри вычисления для манипуляции значениями во внутренней монаде, мы можем использовать поднятые операции, чтобоы перенести функции из внутренней монады в комбинированную монаду.
С liftM
мы увидели, что сущность поднятия — перефразируя документацию — в продвижении чего-то в монаду. Функция lift
, доступная для всех монадных трансформеров, выполняет разный тип поднятия: она продвигает вычисление из внутренней монады в комбинированную монаду. Функция lift
определена как единственный метод класса MonadTrans
в Шаблон:Haskell lib.
class MonadTrans t where
lift :: (Monad m) => m a -> t m a
Имеется вариант lift
, специфичный для IO
и называемый liftIO
, который является единственным методом класса MonadIO
в Шаблон:Haskell lib.
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
liftIO
может быть удобен, когда у нас множестов трансформеров помещены друг за другом (как в стек) в одну комбинированную монаду. В подобных случаях, IO
будет всегда самой внутренней монадой, и таким образом обычно нужен более чем один lift
, чтобы поднять IO
-значения на вершину стека. liftIO
, однако, определен для воплощений таким образом, что позволяет нам поднять IO
-значение из произвольной глубины, написав функцию лишь единожды.
Реализация lift
Реализация lift
как правило довольно прямолинейна (проста). Рассмотрим трансформер MaybeT
:
instance MonadTrans MaybeT where
lift m = MaybeT (m >>= return . Just)
Мы начинаем с монадическим значением внутренней монады — средним слоем, если вы предпочитаете аналогию с сэндвичем. Используя оператор bind
и конструктор типа для базовой монады, мы плавно сдвигаем (скатываем, намазываем ??) нижний слой (базовую монаду) под средний слой. В конце, мы помещаем верхний срез нашего сэндвича с помощью конструктора MaybeT
. Таким образом, используя функцию lift
, мы трансформировали нижний кусок начинки сэндвича в подлинно трехслойный монадический сэндвич. Отметим, что как в реализации класса Monad
, и оператор bind
, и общий (основной) оператор return
работают в границах внутренней монады.
Упражнения |
---|
|
Реализация трансформеров
Для того, чтобы развить лучшее понимание работы трансформеров, мы обсудим две реализации в стандартных библиотеках.
Трансформер List
Также как с трансформером Maybe
, мы начнем с создания конструктора типа, который принимает один аргумент:
newtype ListT m a = ListT { runListT :: m [a] }
Реализация монады ListT m
поразительно похожа на свою «кузину», монаду списков. Мы делаем те же самые операции
внутри внутренней монады m
, пакуем и распаковываем монадический сэндвич.
List | ListT |
---|---|
instance Monad [] where
xs >>= f =
let yss = map f xs
in concat yss
|
instance (Monad m) => Monad (ListT m) where
tm >>= f = ListT $ runListT tm
>>= \xs -> mapM (runListT . f) xs
>>= \yss -> return (concat yss)
|
Упражнения |
---|
|
Трансформер State
В прошлый раз мы пристально рассмотрели реализацию двух простых монадных трансформеров, MaybeT
и ListT
, совершив обходной путь, чтобы обсудить подъем из (простой) монады в ее вариант-трансформер. Теперь, соединим две идеи вместе, внимательно рассмотрев реализацию одного из наиболее интересных трансформеров в стандартной библиотеке, StateT
. Изучение этого трансформера сотворит озарение в понимании механизма трансформеров, которое вы сможете призвать впоследствии, когда будуте использовать монадные трансформеры в вашем коде. Прежде чем продолжить, вам может быть понадобиться освежить в памяти или просмотреть State monad.
Как и монада State
могла быть построена определением newtype State s a = State { runState :: (s -> (a,s)) }
[1] Трансформер StateT
создан определением
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }
State s
является воплощением как класса Monad
, так и класса MonadState s
(который обеспечивает get
и put
), так что StateT s m
должна быть членом классов Monad
и MonadState s
. Более того, если m
является воплощением MonadPlus
, StateT s m
также должна быть членом MonadPlus
.
Определим StateT s m
как воплощение Monad
:
State | StateT |
---|---|
newtype State s a =
State { runState :: (s -> (a,s)) }
instance Monad (State s) where
return a = State $ \s -> (a,s)
(State x) >>= f = State $ \s ->
let (v,s') = x s
in runState (f v) s'
|
newtype StateT s m a =
StateT { runStateT :: (s -> m (a,s)) }
instance (Monad m) => Monad (StateT s m) where
return a = StateT $ \s -> return (a,s)
(StateT x) >>= f = StateT $ \s -> do
(v,s') <- x s -- get new value and state
runStateT (f v) s' -- pass them to f
|
Наше определение return
использует функцию return
внутренней монады, и оператор связывания использует do-блок, чтобы выполнить вычисление во внутренней монаде.
Мы также хотим декларировать все комбинированные монады, которые используют трансформер StateT
, как воплощения
класса MonadState
, так что мы дадим определения get
и put
:
instance (Monad m) => MonadState s (StateT s m) where
get = StateT $ \s -> return (s,s)
put s = StateT $ \_ -> return ((),s)
Наконец, мы хотим декларировать все комбинированные монады, в которых используется StateT
с воплощением MonadPlus
, как воплощения класса MonadPlus
:
instance (MonadPlus m) => MonadPlus (StateT s m) where
mzero = StateT $ \s -> mzero
(StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s)
Последним шагом сделаем наш монадный трансформер полностью интегрированным с классом монад Haskell’а — для этого сделаем StateT s
воплощением класса MonadTrans
, обеспечив функцию lift
:
instance MonadTrans (StateT s) where
lift c = StateT $ \s -> c >>= (\x -> return (x,s))
Функция lift
создает функцию, изменяющую состояние, StateT
, которая связывает вычисление во внутренней монаде с функцией, пакующей результат со входным состоянием. Результат в том, если для воплощения мы применяем
StateT к монаде the List, функция, которая возвращает список (то есть, вычисление в монаде List) может быть поднято вовнутрь
StateT s []
, где оно станет функцией, которая возвращает StateT (s -> [(a,s)])
. Таким образом, поднятое вычисление производит множественные пары (значение, состояние) из его внутреннего состояния. Эффект выразится в «разветвлении» вычисления в StateT, создавая разные ветви для разных значений в списке, возращенном поднятой функцией. Разумеется, применяя StateT
к разным монадам, получим разную семантику функции lift
.
Благодарности
Этот модуль использует ряд отрывков из All About Monads, с разрешения автора Jeff Newbern.
Примечания
- ↑ В версии пакета mtl ранее 2.0.0.0, так и было построено. В настоящее время, однако,
State s
реализована как синоним типа дляStateT s Identity
.