我正在尝试通过重新分解我在第一次学习Haskell时所写的内容来了解Monad变形金刚是如何工作的。它有很多组件可以用(相当大的)Monad Transformers堆栈替换。
我开始为我的堆栈写一个类型别名:
type SolverT a = MaybeT
(WriterT Leaderboard
(ReaderT Problem
(StateT SolutionState
(Rand StdGen)))) a
快速简要说明:
Rand
线程通过各种随机操作中使用的StdGen
StateT
承载解决方案的状态,因为它会逐步评估ReaderT
有一个固定状态问题空间正在解决WriterT
有一个排行榜,不断更新解决方案到目前为止最好的版本MaybeT
是必需的,因为问题和解决方案状态都使用lookup
中的Data.Map
,并且如何配置它们会导致Nothing
在原始版本中,Nothing
“从未”发生,因为我只使用Map
来有效地查找已知的键/值对(我想我可以重构使用数组)。在原文中,我通过自由使用Maybe
来解决fromJust
问题。
据我所知,MaybeT
位于顶部意味着如果Nothing
SolverT a
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
和instance (MonadRandom m) => MonadRandom (MaybeT m) where
getRandom = lift getRandom
getRandomR = lift . getRandomR
getRandoms = lift getRandoms
getRandomRs = lift . getRandomRs
的任意组合用于定义。这不是特别重要,但我对可能的有效定义感到好奇吗?
问题1
[编辑:这是一个问题因为我没有使用沙箱,所以我有旧的/冲突版本的库导致问题]
即使MonadRandom拥有所有内容的实例(MaybeT除外),我仍然必须为每个Transformer编写自己的实例声明:
WriterT
我通过复制MonadRandom source code中的实例为ReaderT
,StateT
和StateT
执行了此操作。注意:对于WriterT
和No instance for (MonadRandom (ReaderT Problem (StateT SolutionState (Rand StdGen))))
arising from a use of `getRandomR'
,他们使用合格的导入,但不使用Reader。如果我没有写自己的声明,我会遇到这样的错误:
randomCity :: SolverT City
randomCity = do
cits <- asks getCities
x <- getRandomR (0,M.size cits -1)
--rc <- M.lookup x cits
return undefined --rc
我不太清楚为什么会这样。
问题2
有了上述内容,我重写了我的一个功能:
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
以上编译,我认为变形金刚应该如何使用。尽管必须编写重复的变换器实例,但这非常方便。你会注意到,在上面我已经评论了两个部分。如果我删除了我得到的评论:
(\s -> (a,s))
起初我认为这个问题与他们的Monads类型有关。堆栈中的所有其他Monads都有Just a | Nothing
的构造函数,而Maybe有ask
。但这不应该有所不同,Reader r a
的类型应该返回lookup k m
,而Maybe a
应该提供类型> :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
。
我以为我会检查我的假设,所以我进入了GHCI并检查了这些类型:
MaybeT
我可以看到我的所有其他变换器都定义了一个可以通过变换器提升的类型类。 MonadMaybe
似乎没有lift
类型类。
我知道MaybeT
我可以将变压器堆栈中的某些内容提升到MaybeT m a
,这样我最终可以使用Maybe a
。但如果我最终得到do
,我认为我可以将<-
块与Solver
绑定在一起。
问题3
我实际上还有一件事要添加到我的堆栈中,我不确定它应该去哪里。 {{1}}以固定数量的周期运行。我需要跟踪当前周期与最大周期。我可以将循环计数添加到解决方案状态,但我想知道是否有一个额外的变换器我可以添加。
除此之外,有多少变压器太多了?我知道这是非常主观的,但这些变压器肯定会有性能成本吗?我想有一些融合可以在编译时优化它,所以可能性能成本很低?
答案 0 :(得分:3)
无法重现。 RandT
已经存在这些实例。
lookup
会返回Maybe
,但您有一个基于MaybeT
的堆栈。没有MonadMaybe
的原因是相应的类型类是MonadPlus
(或更通用Alternative
) - pure
/ return
对应{{1 }和Just
/ empty
对应mzero
。我建议创建一个帮手
Nothing
然后你可以在monad堆栈中的任何地方调用lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v
lookupA k = maybe empty pure . M.lookup k
正如评论中所提到的,我强烈建议使用lookupA
,因为它恰好适合您的情况,并且比StateT / ReaderT / WriterT的堆栈更容易使用。
还要考虑
之间的区别RWST
和
type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a
不同之处在于失败的情况。前一个堆栈不会返回任何内容,而后一个堆栈允许您检索状态和到目前为止计算的type Solver a = MaybeT (RWST Problem Leaderboard SolutionState (Rand StdGen)) a
。
最简单的方法是将其添加到状态部分。我只是将它包含在Leaderboard
。
SolutionState