2017-02-14 15 views
1

我一直在用基於文本的應用程序來學習haskell。在我的應用程序的主要部分,我使用狀態,蘭特和IO的組合是這樣的:如何調用一個使用當前monad堆棧子集的函數?

test :: StateT MyState (RandT StdGen IO)() 

我已經寫了一些功能,我現在要合併,但這些功能被定義只他們需要的monad(s)反對整個堆棧。如果我可以幫忙的話,我想避免使用全套。

這裏是顯示了不同的情況下,我試圖找出一個例子:

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

你有沒有嘗試過使用'MonadState'和類似的類? – bheklilr

+0

不是那麼遠,但下面Michael的例子給了我一個開始的地方,謝謝你的回覆。正如我在下面提到的,我需要做一些閱讀以更好地理解這是什麼以及它與我一直在做的事情的比較。 – Chris

回答

3

這裏顯而易見的解決辦法是不使用混凝土變壓器類型,如那些來自StateTMonadRandomtransformersRandT直到結束,而是要建立使用「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發生的事情,我仔細地調整每個「動作」,使其卡入到位。這其實很簡單,有一定的魅力,但需要一點習慣。 generalize只是

return . runIdentity :: Monad m => Identity a -> m a 

請注意,第二個模塊中沒有簽名使用類約束。

+0

非常感謝,這正是我所希望的。我需要做一些閱讀以更好地理解差異。 – Chris

+0

@Chris我按照你最初的意圖添加了一個使用'提升''升降機'等的版本,這樣你就可以看到它是如何工作的。這是一種不同的方法,只是使用變換器類型,沒有類(它們是以mtl爲例)。不幸的是,這裏不是一個真正的選項,因爲'RandT'沒有'MFunctor'實例。 – Michael

+0

@Chris當你使用狀態,讀者,作家,隨機和其他一些'效果'時,第一個是自然的解決方案。有了諸如「管道」或「管道」之類的東西,你正在處理一個monad變壓器,這種變壓器不能被「mtl風格」課程輕鬆地表現出來,然後直接的「lift」和「hoist」方法變得更加自然。後一種方法傾向於給出更明確的錯誤消息,因爲它不使用類型類,但是'mtl'路由不那麼冗長。 – Michael

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方法,術語加以執行其爲所有使用StateTRandT構建的monads開箱即用。當使用somethingInRandom,不過,你需要在一個lift將下滑至其提升到您的StateT - 轉化的單子:

    test :: StateT MyState (RandT StdGen IO)() 
    test = do 
        x <- lift somethingInRandom 
        _ <- somethingThatModifiesState x 
        _ <- somethingInStateAndRand 
        -- etc. 
    
  2. 有一個更好的選擇,但:既然,如我上面提到的,你的實現是在如MonadStateMonadRandom的條款,您可以像bheklilr所建議的那樣根據這些類來推廣這些類型。這與第一種解決方案相比具有兩個優點:如果您不需要lift for somethingInRandom,並且您的代碼將不需要進一步更改,例如,如果您將另一個變壓器層堆疊在StateT之上或切換到以外的MonadRandom實例:

    somethingInRandom :: MonadRandom m => m Int 
    somethingThatModifiesState :: MonadState MyState m => m() 
    somethingInStateAndRand :: (MonadRandom m, MonadState MyState m) => m Int 
    
  3. 既然你提到mmorph:如果更改簽名不是爲了任何原因的選項(例如,如果你使用別人的代碼),你就必須解開的State/Rand計算,滑入return . runIdentity以改變其內部monad並重新包裝它們。由於return . runIdentity是一個單子射,有做的是在StateT的情況下,它有一個MFunctor實例更方便的方法:

    test = do 
        -- etc. 
        _ <- hoist generalize (somethingThatModifiesState x) 
        -- etc. 
    

    generalizeControl.Monad.Morph簡直是return . runIdentity。這個技巧在mmorph's documentation中有進一步的討論。

Michael's answer演示解決方案#2和#3。

+1

非常感謝! – Chris

相關問題