Monad Transformer 与 MaybeT 和 RandT 叠加
Monad Transformer stacks with MaybeT and RandT
我正在尝试通过重构我第一次学习 Haskell 时写的东西来了解 Monad Transformers 的工作原理。它有很多组件可以用(相当大的)Monad Transformers 堆栈替换。
我首先为我的堆栈写了一个类型别名:
type SolverT a = MaybeT
(WriterT Leaderboard
(ReaderT Problem
(StateT SolutionState
(Rand StdGen)))) a
简要说明:
Rand
threads through a StdGen
used in various random operations
StateT
承载解决方案的状态,因为它得到逐步评估
ReaderT
有固定状态问题 space 正在解决
WriterT
有一个排行榜,根据迄今为止最佳版本的解决方案不断更新
MaybeT
是必需的,因为问题和解决方案状态都使用 Data.Map
中的 lookup
,并且它们的配置方式中的任何错误都会导致 Nothing
在原始版本中发生了 Nothing
"never" 因为我只使用 Map
来有效查找已知的 key/value 对(我想我可以重构使用数组)。在最初,我通过自由使用 fromJust
.
解决了 Maybe
问题
据我所知,MaybeT
位于顶部意味着如果 Nothing
在任何 SolverT a
中,我不会丢失其他转换器中的任何信息,因为它们是从外向内展开的。
边题
[编辑:这是一个问题,因为我没有使用沙箱,所以我有 old/conflicting 个版本的库导致了问题]
当我第一次写堆栈时,我在顶部有 RandT
。我决定避免在任何地方使用 lift
或为 RandT
的所有其他转换器编写我自己的实例声明。所以我把它移到了底部。
我确实尝试为 MonadReader
写了一个实例声明,这已经是我能编译的了:
instance (MonadReader r m,RandomGen g) => MonadReader r (RandT g m) where
ask = undefined
local = undefined
reader = undefined
我无法让 lift
、liftRand
和 liftRandT
的任何组合在定义中起作用。这不是特别重要,但我很好奇有效的定义是什么?
问题 1
[编辑:这是一个问题,因为我没有使用沙箱,所以我有 old/conflicting 个版本的库导致了问题]
即使 MonadRandom 拥有所有实例(MaybeT 除外),我仍然必须为每个 Transformer 编写自己的实例声明:
instance (MonadRandom m) => MonadRandom (MaybeT m) where
getRandom = lift getRandom
getRandomR = lift . getRandomR
getRandoms = lift getRandoms
getRandomRs = lift . getRandomRs
我通过从 MonadRandom source code 复制实例来为 WriterT
、ReaderT
和 StateT
执行此操作。注意:对于 StateT
和 WriterT
,他们确实使用合格的导入,但不用于 Reader。如果我没有写自己的声明,我会得到这样的错误:
No instance for (MonadRandom (ReaderT Problem (StateT SolutionState (Rand StdGen))))
arising from a use of `getRandomR'
我不太清楚为什么会这样。
问题2
有了上面的内容,我重写了一个函数:
randomCity :: SolverT City
randomCity = do
cits <- asks getCities
x <- getRandomR (0,M.size cits -1)
--rc <- M.lookup x cits
return undefined --rc
以上编译,我认为应该如何使用转换器。尽管必须编写重复的转换器实例很乏味,但这非常方便。你会注意到在上面我已经注释掉了两个部分。如果我删除我得到的评论:
Couldn't match type `Maybe'
with `MaybeT
(WriterT
Leaderboard
(ReaderT Problem (StateT SolutionState (Rand StdGen))))'
Expected type: MaybeT
(WriterT
Leaderboard (ReaderT Problem (StateT SolutionState (Rand StdGen))))
City
Actual type: Maybe City
起初我以为问题出在它们的 Monad 类型上。堆栈中的所有其他 Monad 都有 (\s -> (a,s))
的构造函数,而 Maybe 有 Just a | Nothing
。但这应该没什么区别,ask
的类型应该是 return Reader r a
,而 lookup k m
的类型应该是 Maybe a
.
我想我会检查我的假设,所以我进入 GHCI 并检查了这些类型:
> :t ask
ask :: MonadReader r m => m r
> :t (Just 5)
(Just 5) :: Num a => Maybe a
> :t MaybeT 5
MaybeT 5 :: Num (m (Maybe a)) => MaybeT m a
我可以看到我所有的其他转换器都定义了一个类型 class 可以通过转换器提升。 MaybeT
似乎没有 MonadMaybe
类型 class.
我知道使用 lift
我可以将一些东西从我的变压器堆栈中提取到 MaybeT
,这样我就可以得到 MaybeT m a
。但是,如果我以 Maybe a
结束,我假设我可以将它绑定到 do
块中 <-
。
问题3
我实际上还有一件事要添加到我的堆栈中,但我不确定它应该放在哪里。 Solver
以固定的周期数运行。我需要跟踪当前周期与最大周期。我可以将循环计数添加到解决方案状态,但我想知道是否可以添加额外的转换器。
此外,多少个变压器太多了?我知道这是非常主观的,但这些变压器肯定会有性能成本吗?我想一定数量的融合可以在编译时优化它,所以性能成本可能是最小的?
问题 1
无法重现。 RandT
.
已经有这些实例
问题 2
lookup
returns Maybe
,但你有一个基于 MaybeT
的堆栈。之所以没有MonadMaybe
是因为对应的类型class是MonadPlus
(或者更一般的Alternative
)——pure
/return
对应Just
和empty
/mzero
对应Nothing
。我建议创建一个助手
lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v
lookupA k = maybe empty pure . M.lookup k
然后你可以在你的 monad 栈中任何你需要的地方调用 lookupA
如评论中所述,我强烈建议使用 RWST
,因为它正是适合您的情况,并且比 StateT/ReaderT/WriterT 的堆栈更容易使用。
也想想区别
type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a
和
type Solver a = MaybeT (RWST Problem Leaderboard SolutionState (Rand StdGen)) a
不同之处在于失败时会发生什么。前者堆栈没有 return 任何东西,而后者允许您检索状态和 Leaderboard
目前计算的结果。
问题 3
最简单的方法是将其添加到状态部分。我只是将它包含在 SolutionState
.
中
示例代码
import Control.Applicative
import Control.Monad.Random
import Control.Monad.Random.Class
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Control.Monad.RWS
import qualified Data.Map as M
import Data.Monoid
import System.Random
-- Dummy data types to satisfy the compiler
data Problem = Problem
data Leaderboard = Leaderboard
data SolutionState = SolutionState
data City = City
instance Monoid Leaderboard where
mempty = Leaderboard
mappend _ _ = Leaderboard
-- dummy function
getCities :: Problem -> M.Map Int City
getCities _ = M.singleton 0 City
-- the actual sample code
type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a
lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v
lookupA k = maybe empty pure . M.lookup k
randomCity :: Solver City
randomCity = do
cits <- asks getCities
x <- getRandomR (0, M.size cits - 1)
lookupA x cits
我正在尝试通过重构我第一次学习 Haskell 时写的东西来了解 Monad Transformers 的工作原理。它有很多组件可以用(相当大的)Monad Transformers 堆栈替换。
我首先为我的堆栈写了一个类型别名:
type SolverT a = MaybeT
(WriterT Leaderboard
(ReaderT Problem
(StateT SolutionState
(Rand StdGen)))) a
简要说明:
Rand
threads through aStdGen
used in various random operationsStateT
承载解决方案的状态,因为它得到逐步评估ReaderT
有固定状态问题 space 正在解决WriterT
有一个排行榜,根据迄今为止最佳版本的解决方案不断更新MaybeT
是必需的,因为问题和解决方案状态都使用Data.Map
中的lookup
,并且它们的配置方式中的任何错误都会导致Nothing
在原始版本中发生了 Nothing
"never" 因为我只使用 Map
来有效查找已知的 key/value 对(我想我可以重构使用数组)。在最初,我通过自由使用 fromJust
.
Maybe
问题
据我所知,MaybeT
位于顶部意味着如果 Nothing
在任何 SolverT a
中,我不会丢失其他转换器中的任何信息,因为它们是从外向内展开的。
边题
[编辑:这是一个问题,因为我没有使用沙箱,所以我有 old/conflicting 个版本的库导致了问题]
当我第一次写堆栈时,我在顶部有 RandT
。我决定避免在任何地方使用 lift
或为 RandT
的所有其他转换器编写我自己的实例声明。所以我把它移到了底部。
我确实尝试为 MonadReader
写了一个实例声明,这已经是我能编译的了:
instance (MonadReader r m,RandomGen g) => MonadReader r (RandT g m) where
ask = undefined
local = undefined
reader = undefined
我无法让 lift
、liftRand
和 liftRandT
的任何组合在定义中起作用。这不是特别重要,但我很好奇有效的定义是什么?
问题 1
[编辑:这是一个问题,因为我没有使用沙箱,所以我有 old/conflicting 个版本的库导致了问题]
即使 MonadRandom 拥有所有实例(MaybeT 除外),我仍然必须为每个 Transformer 编写自己的实例声明:
instance (MonadRandom m) => MonadRandom (MaybeT m) where
getRandom = lift getRandom
getRandomR = lift . getRandomR
getRandoms = lift getRandoms
getRandomRs = lift . getRandomRs
我通过从 MonadRandom source code 复制实例来为 WriterT
、ReaderT
和 StateT
执行此操作。注意:对于 StateT
和 WriterT
,他们确实使用合格的导入,但不用于 Reader。如果我没有写自己的声明,我会得到这样的错误:
No instance for (MonadRandom (ReaderT Problem (StateT SolutionState (Rand StdGen))))
arising from a use of `getRandomR'
我不太清楚为什么会这样。
问题2
有了上面的内容,我重写了一个函数:
randomCity :: SolverT City
randomCity = do
cits <- asks getCities
x <- getRandomR (0,M.size cits -1)
--rc <- M.lookup x cits
return undefined --rc
以上编译,我认为应该如何使用转换器。尽管必须编写重复的转换器实例很乏味,但这非常方便。你会注意到在上面我已经注释掉了两个部分。如果我删除我得到的评论:
Couldn't match type `Maybe'
with `MaybeT
(WriterT
Leaderboard
(ReaderT Problem (StateT SolutionState (Rand StdGen))))'
Expected type: MaybeT
(WriterT
Leaderboard (ReaderT Problem (StateT SolutionState (Rand StdGen))))
City
Actual type: Maybe City
起初我以为问题出在它们的 Monad 类型上。堆栈中的所有其他 Monad 都有 (\s -> (a,s))
的构造函数,而 Maybe 有 Just a | Nothing
。但这应该没什么区别,ask
的类型应该是 return Reader r a
,而 lookup k m
的类型应该是 Maybe a
.
我想我会检查我的假设,所以我进入 GHCI 并检查了这些类型:
> :t ask
ask :: MonadReader r m => m r
> :t (Just 5)
(Just 5) :: Num a => Maybe a
> :t MaybeT 5
MaybeT 5 :: Num (m (Maybe a)) => MaybeT m a
我可以看到我所有的其他转换器都定义了一个类型 class 可以通过转换器提升。 MaybeT
似乎没有 MonadMaybe
类型 class.
我知道使用 lift
我可以将一些东西从我的变压器堆栈中提取到 MaybeT
,这样我就可以得到 MaybeT m a
。但是,如果我以 Maybe a
结束,我假设我可以将它绑定到 do
块中 <-
。
问题3
我实际上还有一件事要添加到我的堆栈中,但我不确定它应该放在哪里。 Solver
以固定的周期数运行。我需要跟踪当前周期与最大周期。我可以将循环计数添加到解决方案状态,但我想知道是否可以添加额外的转换器。
此外,多少个变压器太多了?我知道这是非常主观的,但这些变压器肯定会有性能成本吗?我想一定数量的融合可以在编译时优化它,所以性能成本可能是最小的?
问题 1
无法重现。 RandT
.
问题 2
lookup
returns Maybe
,但你有一个基于 MaybeT
的堆栈。之所以没有MonadMaybe
是因为对应的类型class是MonadPlus
(或者更一般的Alternative
)——pure
/return
对应Just
和empty
/mzero
对应Nothing
。我建议创建一个助手
lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v
lookupA k = maybe empty pure . M.lookup k
然后你可以在你的 monad 栈中任何你需要的地方调用 lookupA
如评论中所述,我强烈建议使用 RWST
,因为它正是适合您的情况,并且比 StateT/ReaderT/WriterT 的堆栈更容易使用。
也想想区别
type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a
和
type Solver a = MaybeT (RWST Problem Leaderboard SolutionState (Rand StdGen)) a
不同之处在于失败时会发生什么。前者堆栈没有 return 任何东西,而后者允许您检索状态和 Leaderboard
目前计算的结果。
问题 3
最简单的方法是将其添加到状态部分。我只是将它包含在 SolutionState
.
示例代码
import Control.Applicative
import Control.Monad.Random
import Control.Monad.Random.Class
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Control.Monad.RWS
import qualified Data.Map as M
import Data.Monoid
import System.Random
-- Dummy data types to satisfy the compiler
data Problem = Problem
data Leaderboard = Leaderboard
data SolutionState = SolutionState
data City = City
instance Monoid Leaderboard where
mempty = Leaderboard
mappend _ _ = Leaderboard
-- dummy function
getCities :: Problem -> M.Map Int City
getCities _ = M.singleton 0 City
-- the actual sample code
type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a
lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v
lookupA k = maybe empty pure . M.lookup k
randomCity :: Solver City
randomCity = do
cits <- asks getCities
x <- getRandomR (0, M.size cits - 1)
lookupA x cits