如何调用使用当前monad堆栈子集的函数?

时间:2017-02-14 16:18:10

标签: haskell

我一直在尝试使用基于文本的应用程序作为学习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模块,该模块似乎可以完成我想要的那些事情但是我还没有能够获得迄今为止有效的组合。

我也一直在研究这些类型以获得一些提示,但它有点超出了我目前的理解。

非常感谢任何建议!

2 个答案:

答案 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)

两个选项,加上奖金一个:

  1. 基本解决方案是将您的类型从StateRand推广到StateTRandT。请注意,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
    

    您无需更改计算的正文,因为您已根据MonadStateMonadRandom的方法实现这些计算,这些方法对于使用{构建的所有monad都开箱即用分别为{1}}和StateT。但是,在使用RandT时,您需要使用somethingInRandom将其推广到lift - 已转换的monad:

    StateT
  2. 但是有一个更好的选择:正如我上面提到的那样,你的实现是test :: StateT MyState (RandT StdGen IO) () test = do x <- lift somethingInRandom _ <- somethingThatModifiesState x _ <- somethingInStateAndRand -- etc. MonadState,你可以像bheklilr建议的那样推广类型在这些课程方面。与第一个解决方案相比,这有两个优点:您不再需要MonadRandom lift,而且如果您在例如{{}上堆叠另一个变换器层,则您的代码将不需要进一步更改。 1}}或切换到somethingInRandom以外的StateT个实例:

    MonadRandom
  3. 由于你提到 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中进一步讨论了这个技巧。

  4. Michael's answer演示了解决方案#2和#3。