我一直在尝试使用基于文本的应用程序作为学习haskell的方法。在我的应用程序的主要部分中,我使用了状态,rand和IO的组合,如下所示:
test :: StateT MyState (RandT StdGen IO) ()
我已经编写了一些我现在想要拼接在一起的函数,但是这些函数只是用他们需要的monad来完成。如果我能提供帮助,我想避免在任何地方使用全套装置。
这是一个示例,显示了我尝试解决的不同情况:
module Test.State where
import Control.Monad
import Control.Monad.Identity
import Control.Monad.Morph
import Control.Monad.Trans.Class
import Control.Monad.State
import Control.Monad.Random
import Data.Functor.Identity
import Data.Monoid
import System.Random
type MyState = Int
somethingThatModifiesState :: Int -> State MyState ()
somethingThatModifiesState x = do
put x
return ()
somethingThatUsesIO :: Int -> IO ()
somethingThatUsesIO x = print x
somethingInRandom :: Rand StdGen Int
somethingInRandom = getRandomR (0,10)
somethingInStateAndRand :: StateT MyState (Rand StdGen) Int
somethingInStateAndRand = do
y <- getRandomR (0,10)
put y
return y
test :: StateT MyState (RandT StdGen IO) ()
test = do
x <- somethingInRandom -- fail :(
_ <- somethingThatModifiesState x -- fail :(
_ <- somethingInStateAndRand -- fail :(
s <- get -- ok!
liftIO $ somethingThatUsesIO s -- ok!
return()
myState :: Int
myState = 17
run = do
g <- getStdGen
runRandT (runStateT test myState) g
谷歌搜索让我进入了Control.Monad.Morph模块,该模块似乎可以完成我想要的那些事情但是我还没有能够获得迄今为止有效的组合。
我也一直在研究这些类型以获得一些提示,但它有点超出了我目前的理解。
非常感谢任何建议!
答案 0 :(得分:3)
这里显而易见的解决方案是不使用来自StateT
的{{1}}和transformers
的{{1}}直到最后的混凝土变压器类型,而是建立零件使用“RandT
- 样式”类。你的MonadRandom
就是一个教科书案例:
mtl
编辑已添加:
在这种情况下,您可以使用test
直接使用变换器。很遗憾,module Test.State where
import Control.Monad
import Control.Monad.State
import Control.Monad.Random
import System.Random
type MyState = Int
somethingThatModifiesState :: MonadState MyState m => Int -> m ()
somethingThatModifiesState x = do
put x
return ()
somethingThatUsesIO :: MonadIO m => Int -> m ()
somethingThatUsesIO x = liftIO $ print x
somethingInRandom :: (MonadIO m , MonadRandom m) => m Int
somethingInRandom = getRandomR (0,10)
somethingInStateAndRand :: (MonadState MyState m, MonadRandom m) => m Int
somethingInStateAndRand = do
y <- getRandomR (0,10)
put y
return y
-- this type is inferred
test :: (MonadRandom m, MonadIO m, MonadState MyState m) => m ()
test = do
x <- somethingInRandom -- fail :(
_ <- somethingThatModifiesState x -- fail :(
_ <- somethingInStateAndRand -- fail :(
s <- get -- ok!
somethingThatUsesIO s -- ok!
return()
myState :: Int
myState = 17
run = do
g <- getStdGen
runRandT (runStateT test myState) g
-- >>> run
-- 4
-- (((),4),787162639 1655838864)
没有hoist
个实例。 RandT
确实会导出可以完成工作的MFunctor
。我在下面定义Control.Monad.Trans.Random
,因此其类型是统一的mapRandT
将具有(这需要hoistRandT
)。
hoist
主要业务发生在RankNTypes
,我会仔细调整每个'动作',以便它卡入到位。这实际上非常简单并且具有一定的魅力,但需要一点时间习惯。 {-#LANGUAGE RankNTypes #-}
module Test.State where
import Control.Monad
import Control.Monad.Identity
import Control.Monad.Morph
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Control.Monad.Random
import Control.Monad.Trans.Random
import Data.Functor.Identity
import Data.Monoid
import System.Random
hoistRandT :: (forall r . m r -> n r) -> RandT s m a -> RandT s n a
hoistRandT = mapRandT
type MyState = Int
somethingThatModifiesState :: Int -> State MyState ()
somethingThatModifiesState x = do
put x
return ()
somethingThatUsesIO :: Int -> IO ()
somethingThatUsesIO x = print x
somethingInRandom :: RandT StdGen Identity Int
somethingInRandom = getRandomR (0,10)
somethingInStateAndRand :: StateT MyState (RandT StdGen Identity) Int
somethingInStateAndRand = do
y <- getRandomR (0,10)
put y
return y
test :: StateT MyState (RandT StdGen IO) ()
test = do
x <- lift $ hoistRandT generalize somethingInRandom
_ <- hoist generalize $ somethingThatModifiesState x
_ <- hoist (hoistRandT generalize) somethingInStateAndRand
s <- get
liftIO $ somethingThatUsesIO s
return()
myState :: Int
myState = 17
run = do
g <- getStdGen
runRandT (runStateT test myState) g
只是
test
请注意,第二个模块中没有签名使用类约束。
答案 1 :(得分:0)
两个选项,加上奖金一个:
基本解决方案是将您的类型从State
和Rand
推广到StateT
和RandT
。请注意,State s
只是StateT s Identity
的同义词,类似于Rand
:
somethingInRandom :: Monad m => RandT StdGen m Int
somethingThatModifiesState :: Monad m => Int -> StateT MyState m ()
somethingInStateAndRand :: Monad m => StateT MyState (RandT StdGen m) Int
您无需更改计算的正文,因为您已根据MonadState
和MonadRandom
的方法实现这些计算,这些方法对于使用{构建的所有monad都开箱即用分别为{1}}和StateT
。但是,在使用RandT
时,您需要使用somethingInRandom
将其推广到lift
- 已转换的monad:
StateT
但是有一个更好的选择:正如我上面提到的那样,你的实现是test :: StateT MyState (RandT StdGen IO) ()
test = do
x <- lift somethingInRandom
_ <- somethingThatModifiesState x
_ <- somethingInStateAndRand
-- etc.
和MonadState
,你可以像bheklilr建议的那样推广类型在这些课程方面。与第一个解决方案相比,这有两个优点:您不再需要MonadRandom
lift
,而且如果您在例如{{}上堆叠另一个变换器层,则您的代码将不需要进一步更改。 1}}或切换到somethingInRandom
以外的StateT
个实例:
MonadRandom
由于你提到 mmorph :如果因任何原因改变签名不是一个选项(例如,如果你使用别人的代码),你将不得不打开{{1 } / RandT
计算,滑入somethingInRandom :: MonadRandom m => m Int
somethingThatModifiesState :: MonadState MyState m => m ()
somethingInStateAndRand :: (MonadRandom m, MonadState MyState m) => m Int
以更改其内部monad并重新包装它们。由于State
是monad态射,因此在Rand
的情况下有一种更方便的方法,它具有MFunctor
实例:
return . runIdentity
来自return . runIdentity
的 StateT
字面上是test = do
-- etc.
_ <- hoist generalize (somethingThatModifiesState x)
-- etc.
。在mmorph's documentation中进一步讨论了这个技巧。
Michael's answer演示了解决方案#2和#3。