Monad Transformer与MaybeT和RandT叠加(Monad Transformer stacks with MaybeT and RandT)

编程入门 行业动态 更新时间:2024-10-24 14:18:59
Monad Transformer与MaybeT和RandT叠加(Monad Transformer stacks with MaybeT and RandT)

我正在尝试通过重新分解我在第一次学习Haskell时所写的内容来了解​​Monad变形金刚是如何工作的。 它有相当多的组件可以替换为(相当大)的Monad变换器堆栈。

我开始为我的堆栈编写一个类型别名:

type SolverT a = MaybeT (WriterT Leaderboard (ReaderT Problem (StateT SolutionState (Rand StdGen)))) a

快速简要说明:

Rand通过各种随机操作中使用的StdGen线程处理 StateT随着逐步评估而带有解决方案的状态 ReaderT有一个固定的状态问题空间正在被解决 WriterT的排行榜不断更新到目前为止最佳版本的解决方案 MaybeT是必需的,因为问题和解决方案状态都使用Data.Map lookup ,并且它们的配置方式中的任何错误都将导致Data.Map

在原始版本中, Nothing “永远”发生,因为我只使用了Map来有效地查找已知的键/值对(我想我可以重构使用数组)。 在原文中,我通过自由使用fromJust解决了Maybe问题。

根据我的理解,顶部的MaybeT意味着如果在任何SolverT a Nothing任何Nothing ,我不会丢失其他变形金刚中的任何信息,因为它们是从外向内打开的。

边题

[编辑:这是一个问题,因为我没有使用沙盒,所以我有旧的/冲突版本的库导致问题]

当我第一次写堆栈时,我在顶部有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

[编辑:这是一个问题,因为我没有使用沙盒,所以我有旧的/冲突版本的库导致问题]

即使MonadRandom拥有所有内容的实例(MaybeT除外),我仍然必须为每个Transformer编写自己的实例声明:

instance (MonadRandom m) => MonadRandom (MaybeT m) where getRandom = lift getRandom getRandomR = lift . getRandomR getRandoms = lift getRandoms getRandomRs = lift . getRandomRs

我通过复制MonadRandom源代码中的实例为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类型。 堆栈中的所有其他Monads都有(\s -> (a,s))的构造函数,而Maybe Just a | Nothing Just a | Nothing 。 但这应该没有区别, ask的类型应该返回Reader ra ,而lookup km应该给出一个类型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

我可以看到我所有的其他变换器都定义了一个可以通过变压器提升的类型类。 MaybeT似乎没有MonadMaybe类型类。

我知道通过lift我可以将变压器堆栈中的东西提升到MaybeT ,这样我就可以使用MaybeT ma 。 但是,如果我最终得到了Maybe a我认为我可以用<-来绑定它。

问题3

我实际上还有一件事要添加到我的堆栈中,我不确定它应该去哪里。 Solver以固定的周期数运行。 我需要跟踪当前周期与最大周期。 我可以将循环计数添加到解决方案状态,但我想知道是否有可以添加的额外变换器。

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

I'm trying to learn how Monad Transformers work by re-factoring something I wrote when I first learned Haskell. It has quite a few components that could be replaced with a (rather large) stack of Monad Transformers.

I started by writing a type alias for my stack:

type SolverT a = MaybeT (WriterT Leaderboard (ReaderT Problem (StateT SolutionState (Rand StdGen)))) a

A quick rundown:

Rand threads through a StdGen used in various random operations StateT carries the state of the solution as it gets progressively evaluated ReaderT has a fixed state Problem space being solved WriterT has a leaderboard constantly updated by the solution with the best version(s) so far MaybeT is needed because both the problem and solution state use lookup from Data.Map, and any error in how they are configured would lead to a Nothing

In the original version a Nothing "never" happened because I only used a Map for efficient lookups for known key/value pairs (I suppose I could refactor to use an array). In the original I got around the Maybe problem by making a liberal use of fromJust.

From what I understand having MaybeT at the top means that in the event of a Nothing in any SolverT a I don't lose any of the information in my other transformers, as they are unwrapped from outside-in.

Side question

[EDIT: This was a problem because I didn't use a sandbox, so I had old/conflicting versions of libraries causing an issue]

When I first wrote the stack I had RandT at the top. I decided to avoid using lift everywhere or writing my own instance declarations for all the other transformers for RandT. So I moved it to the bottom.

I did try writing an instance declaration for MonadReader and this was about as much as I could get to compile:

instance (MonadReader r m,RandomGen g) => MonadReader r (RandT g m) where ask = undefined local = undefined reader = undefined

I just couldn't get any combination of lift, liftRand and liftRandT to work in the definition. It's not particularly important but I am curious about what a valid definition might be?

Problem 1

[EDIT: This was a problem because I didn't use a sandbox, so I had old/conflicting versions of libraries causing an issue]

Even though MonadRandom has instances of everything (except MaybeT) I still had to write my own instance declarations for each Transformer:

instance (MonadRandom m) => MonadRandom (MaybeT m) where getRandom = lift getRandom getRandomR = lift . getRandomR getRandoms = lift getRandoms getRandomRs = lift . getRandomRs

I did this for WriterT, ReaderT and StateT by copying the instances from the MonadRandom source code. Note: for StateT and WriterT they do use qualified imports but not for Reader. If I didn't write my own declarations I got errors like this:

No instance for (MonadRandom (ReaderT Problem (StateT SolutionState (Rand StdGen)))) arising from a use of `getRandomR'

I'm not quite sure why this is happening.

Problem 2

With the above in hand, I re-wrote one of my functions:

randomCity :: SolverT City randomCity = do cits <- asks getCities x <- getRandomR (0,M.size cits -1) --rc <- M.lookup x cits return undefined --rc

The above compiles and I think is how transformers are suppose to be used. In-spite of the tedium of having to write repetitive transformer instances, this is pretty handy. You'll notice that in the above I've commented out two parts. If I remove the comments I get:

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

At first I thought the problem was about the types of Monads that they are. All of the other Monads in the stack have a constructor for (\s -> (a,s)) while Maybe has Just a | Nothing. But that shouldn't make a difference, the type for ask should return Reader r a, while lookup k m should give a type Maybe a.

I thought I would check my assumption, so I went into GHCI and checked these types:

> :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

I can see that all of my other transformers define a type class that can be lifted through a transformer. MaybeT doesn't seem to have a MonadMaybe typeclass.

I know that with lift I can lift something from my transformer stack into MaybeT, so that I can end up with MaybeT m a. But if I end up with Maybe a I assumed that I could bind it in a do block with <-.

Problem 3

I actually have one more thing to add to my stack and I'm not sure where it should go. The Solver operates on a fixed number of cycles. I need to keep track of the current cycle vs the max cycle. I could add the cycle count to the solution state, but I'm wondering if there is an additional transformer I could add.

Further to that, how many transformers is too many? I know this is incredibly subjective but surely there is a performance cost on these transformers? I imagine some amount of fusion can optimise this at compile time so maybe the performance cost is minimal?

最满意答案

问题1

无法重现。 RandT已有这些实例。

问题2

lookup返回Maybe ,但你有一个基于MaybeT的堆栈。 没有MonadMaybe的原因是相应的类型类是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

不同之处在于失败的情况。 前一个堆栈不返回任何内容,而后一个堆栈允许您检索状态和到目前为止计算的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

Problem 1

Can't reproduce. There are already these instances for RandT.

Problem 2

lookup returns Maybe, but you have a stack based on MaybeT. The reason why there is no MonadMaybe is that the corresponding type class is MonadPlus (or more general Alternative) - pure/return correspond to Just and empty/mzero correspond to Nothing. I'd suggest to create a helper

lookupA :: (Alternative f, Ord k) => k -> M.Map k v -> f v lookupA k = maybe empty pure . M.lookup k

and then you can call lookupA wherever you need in your monad stack

As mentioned in the comments, I'd strongly suggest to use RWST, as it's exactly what fits your case, and it's much easier to work with than the stack of StateT/ReaderT/WriterT.

Also think about the difference between

type Solver a = RWST Problem Leaderboard SolutionState (MaybeT (Rand StdGen)) a

and

type Solver a = MaybeT (RWST Problem Leaderboard SolutionState (Rand StdGen)) a

The difference is what happens in the case of a failure. The former stack doesn't return anything, while the latter allows you to retrieve the state and the Leaderboard computed so far.

Problem 3

The easiest way is to add it into the state part. I'd just include it into SolutionState.

Sample code

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

更多推荐

本文发布于:2023-08-03 14:24:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1390124.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Transformer   Monad   MaybeT   stacks   RandT

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!