Monad Transformer与MaybeT和RandT叠加

时间:2015-01-06 00:35:18

标签: haskell random monad-transformers maybe

我正在尝试通过重新分解我在第一次学习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

我无法将liftRandliftRandTinstance (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中的实例为ReaderTStateTStateT执行了此操作。注意:对于WriterTNo 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}}以固定数量的周期运行。我需要跟踪当前周期与最大周期。我可以将循环计数添加到解决方案状态,但我想知道是否有一个额外的变换器我可以添加。

除此之外,有多少变压器太多了?我知道这是非常主观的,但这些变压器肯定会有性能成本吗?我想有一些融合可以在编译时优化它,所以可能性能成本很低?

1 个答案:

答案 0 :(得分:3)

问题1

无法重现。 RandT已经存在这些实例。

问题2

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

问题3

最简单的方法是将其添加到状态部分。我只是将它包含在Leaderboard

示例代码

SolutionState