2017-01-18 2 views
3

Попытка ознакомления с монадных трансформаторами, я написал следующий фрагмент кода:Haskell - не выход из в StateT монады

data GlobalState = GlobalState { 
     rng :: StdGen 
} 

foo :: IO() 
foo = 
     evalStateT (StateT $ 
        \s -> 
        let (v, newrng) = roll $ rng s in 
         return (putStrLn $ show v, 
           s { rng = newrng }) 
     ) zeroState >> 
     putStrLn "See you soon." 

zeroState :: GlobalState 
zeroState = GlobalState { 
     rng = mkStdGen 0 
} 

roll :: StdGen -> (Int, StdGen) 
roll gen = randomR (1, 6) gen 

Идея заключалась в том, чтобы инициализировать состояние, использовать его для действия IO и вернуться обратно до равного IO. Что-то, однако, идет не так, и печатается только «До свидания», putStrLn $ show v не производит выход.

Итак, вопрос в том, как это исправить и, самое главное, Почему не печатает ничего?

Редактировать: спасибо всем за ваши ответы, они очень помогают.

+1

Я думаю, что если вы назовете имя и присвоите его значение 'StateT', это может сделать проблему более ясной. 'return (putStrLn ...)' возвращает действие, которое * при оценке * будет печатать что-то, но затем вы выбрасываете его, когда используете '>>'. – ryachza

+0

То есть 'evalStateT ... zeroState' имеет тип' Monad m => m (IO()) '; он должен иметь тип 'IO()'. – chepner

ответ

3

По моему скромному мнению, монада трансформаторы легче работать, если вы используете MTL классы типов. Это также приводит к более общему коду, поскольку он не привязывает вас к какому-либо конкретному типу данных. Кроме того, я бы рекомендовал, как правило, отделять часть «что делать» от части «run». Я переписан код, мы надеемся продемонстрировать, что я имею в виду:

import System.Random 
import Control.Monad.State 

data GlobalState = GS { rng :: StdGen } 

zeroState = GS { rng = mkStdGen 0 } 

roll :: StdGen -> (Int,StdGen) 
roll = randomR (1,6) 

-- works with any concrete monad m that supplies IO and GlobalState state 
foo :: (MonadIO m, MonadState GlobalState m) => m() 
foo = do 
    (v,gen) <- gets (roll . rng) 
    liftIO $ print v 
    put $ GS gen 

-- commit to StateT here 
evalFoo :: IO() 
evalFoo = evalStateT foo zeroState 

Преимущество заключается в том, что foo теперь повторно. Например, если мы хотели несколько раз перематывать:

evalFooN :: Int -> IO() 
evalFooN n = evalStateT (replicateM_ n foo) zeroState 
1

Самое простое исправление использовать join для уменьшения значения IO (IO()) возвращенное evalStateT к значению IO().

foo :: IO() 
foo = 
     (join (evalStateT (StateT $ 
        \s -> 
        let (v, newrng) = roll $ rng s in 
         return (putStrLn $ show v, 
           s { rng = newrng }) 
     ) zeroState) >> 
     putStrLn "See you soon." 

Более сложный, но чиста, исправление, чтобы убедиться, что вы не вводить новую IO действия в самом государстве, а просто включить строку, вы укутаться.

foo :: IO() 
foo = 
     evalStateT (StateT $ 
        \s -> 
        let (v, newrng) = roll $ rng s in 
         return (show v, s { rng = newrng }) 
     ) zeroState >>= putStrLn >> 
     putStrLn "See you soon." 
+0

Я не думаю, что это то, что намеревалось сделать ОП. Он хочет использовать 'IO' из' StateT s IO a', а не возвращать значение «IO», а затем выполнять его. – jpath

+0

Отказ в сторону, я не уверен, что вижу разницу. Думаю, он мог бы использовать 'Debug.Trace.trace' вместо' putStrLn'? – chepner

+0

Разница в том, что OP пытается понять трансформаторы Monad, и ваше решение даже не использует часть трансформатора. Вы могли бы просто заменить 'StateT s IO' на' State s' в вашем коде. И 'trace' - это не то, что вы хотите использовать вне отладки (если вы серьезно относитесь к этому). – jpath

3

Ваша проблема в том, что вы, кажется, ошибочный тип StateT для (m a, s) -> StateT s m a, когда на самом деле это m (a, s) -> StateT s m a. То есть m находится снаружи, а не внутри кортежа. Вот почему вам пришлось использовать return, чтобы вернуть что-то типа IO (a,GlobalState). Вместо этого вы должны сделать что-то вроде строк putStrLn (show v) >> return ((),s {rng = newrng}). Как это:

import Control.Monad.Trans.State 
import System.Random 

data GlobalState = GlobalState { 
        rng :: StdGen 
       } 

foo :: IO() 
foo = evalStateT (StateT $ 
     \s -> let (v, newrng) = roll $ rng s in 
      putStrLn (show v) >> 
      return ((),s { rng = newrng }) 
    ) zeroState >> 
     putStrLn "See you soon." 

zeroState :: GlobalState 
zeroState = GlobalState { 
      rng = mkStdGen 0 
      } 

roll :: StdGen -> (Int, StdGen) 
roll gen = randomR (1, 6) gen 
Смежные вопросы