以相同的功能打印到控制台并更新 Monad 状态
Print to console and update Monad state in same function
我想定义一个需要 Int
的函数,根据数字 (x) 在控制台中打印错误,然后更新 State
与 Nothing
.
如何将这些命令加入一个函数中?
这是我得到的:
type Env = [(Variable,Int)]
newtype StateError a = StateError { runStateError :: Env -> Maybe (a, Env) }
class Monad m => MonadError m where
throw :: Monad m => a -> m a
instance MonadError StateError where
throw x = StateError (\s -> Nothing)
但我不知道如何在同一函数定义中执行 IO
副作用和 然后 状态更新
否
状态 monad 中的函数,例如 a -> State s b
,是一个纯函数(无 IO),它恰好有一个额外的函数参数 s
通过一些方便的管道隐藏。
您不能从状态 monad 打印到控制台。
不过,是的!
但是!您可以使用 monad transformer 来获取 State 和一些底层 monad,例如 IO.
我将提供一个使用 transformers
而不是自定义 monad 和 mtl
的示例,因为它看起来像您正在使用的那样。使用 mtl
,您可以像 MonadError
一样使用 类 来利用 throw
,它可以与使用 mtl 类 的其他库一起很好地工作。另一方面,如果你是这个变压器的最终消费者,那么它就不那么重要了。
首先,我们将导入为我们提供 MonadIO、StateT、MaybeT 的模块,并使用新类型派生,这样我们就不必输入 monad 实例样板:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import qualified Control.Monad.Trans.State as S
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans
为了完整起见,我们将详细说明对您的抽象有用的类型:
type Variable = String
type Env = [(Variable,Int)]
现在我们可以进入有趣的部分 - 用于管道的 Monad 定义和函数。 monad 栈是 StateT MaybeT IO:
newtype StateError a = StateError { unStateError :: S.StateT Env (MaybeT IO) a }
deriving (Monad, Applicative, Functor)
我们可以 运行 它首先解开新类型,然后 运行 状态,最后 MaybeT:
run :: StateError a -> IO (Maybe (a, Env))
run = runMaybeT . flip S.runStateT [] . unStateError
通常您会编写一大堆函数来提供您的 monad 抽象。对于这个问题,它只是 "update the state" 和 "print to stdout":
modify :: (Env -> Env) -> StateError ()
modify = StateError . S.modify
emit :: Show a => a -> StateError ()
emit = StateError . liftIO . print . show
有了我们的 Monad of Power,我们可以做一些奇特的事情,比如更新状态 and 发出 IO 消息 and 跟踪失败或成功:
updateAndPrint :: Variable -> Int -> StateError ()
updateAndPrint v i =
do emit (v,i)
modify ((v,i):)
哦,失败非常简单 - 只需在我们的 MaybeT
monad 中失败:
throw :: a -> StateError b
throw _ = fail "" -- same as 'MaybeT (pure Nothing)'
我们可以按预期使用这个 monad:
> run $ updateAndPrint "var" 1
"(\"var\",1)"
Just (() -- ^ return value of `updateAndPrint`
,[("var",1)]) -- ^ resulting state
我想定义一个需要 Int
的函数,根据数字 (x) 在控制台中打印错误,然后更新 State
与 Nothing
.
如何将这些命令加入一个函数中?
这是我得到的:
type Env = [(Variable,Int)]
newtype StateError a = StateError { runStateError :: Env -> Maybe (a, Env) }
class Monad m => MonadError m where
throw :: Monad m => a -> m a
instance MonadError StateError where
throw x = StateError (\s -> Nothing)
但我不知道如何在同一函数定义中执行 IO
副作用和 然后 状态更新
否
状态 monad 中的函数,例如 a -> State s b
,是一个纯函数(无 IO),它恰好有一个额外的函数参数 s
通过一些方便的管道隐藏。
您不能从状态 monad 打印到控制台。
不过,是的!
但是!您可以使用 monad transformer 来获取 State 和一些底层 monad,例如 IO.
我将提供一个使用 transformers
而不是自定义 monad 和 mtl
的示例,因为它看起来像您正在使用的那样。使用 mtl
,您可以像 MonadError
一样使用 类 来利用 throw
,它可以与使用 mtl 类 的其他库一起很好地工作。另一方面,如果你是这个变压器的最终消费者,那么它就不那么重要了。
首先,我们将导入为我们提供 MonadIO、StateT、MaybeT 的模块,并使用新类型派生,这样我们就不必输入 monad 实例样板:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import qualified Control.Monad.Trans.State as S
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans
为了完整起见,我们将详细说明对您的抽象有用的类型:
type Variable = String
type Env = [(Variable,Int)]
现在我们可以进入有趣的部分 - 用于管道的 Monad 定义和函数。 monad 栈是 StateT MaybeT IO:
newtype StateError a = StateError { unStateError :: S.StateT Env (MaybeT IO) a }
deriving (Monad, Applicative, Functor)
我们可以 运行 它首先解开新类型,然后 运行 状态,最后 MaybeT:
run :: StateError a -> IO (Maybe (a, Env))
run = runMaybeT . flip S.runStateT [] . unStateError
通常您会编写一大堆函数来提供您的 monad 抽象。对于这个问题,它只是 "update the state" 和 "print to stdout":
modify :: (Env -> Env) -> StateError ()
modify = StateError . S.modify
emit :: Show a => a -> StateError ()
emit = StateError . liftIO . print . show
有了我们的 Monad of Power,我们可以做一些奇特的事情,比如更新状态 and 发出 IO 消息 and 跟踪失败或成功:
updateAndPrint :: Variable -> Int -> StateError ()
updateAndPrint v i =
do emit (v,i)
modify ((v,i):)
哦,失败非常简单 - 只需在我们的 MaybeT
monad 中失败:
throw :: a -> StateError b
throw _ = fail "" -- same as 'MaybeT (pure Nothing)'
我们可以按预期使用这个 monad:
> run $ updateAndPrint "var" 1
"(\"var\",1)"
Just (() -- ^ return value of `updateAndPrint`
,[("var",1)]) -- ^ resulting state