2017-02-14 14 views
1

Я занимаюсь текстовым приложением как способ узнать haskell. В основной части моего приложения я использую комбинацию состояния, рандов, и IO, как это:Как вы вызываете функцию, использующую подмножество текущего стека монад?

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

Я уже писал некоторые функции, которые я теперь хочу, чтобы сшить вместе, но эти функции были определены только монады (ей), которые им нужны, против полного стека. Я бы хотел избежать использования полного набора повсюду, если я могу помочь ему.

Вот пример, который показывает различные случаи я пытаюсь работать:

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 

Googling высадился меня в модуле Control.Monad.Morph, который, кажется, чтобы сделать то, что я хочу, но Я не смог получить комбинацию, которая работает до сих пор.

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

Любые предложения приветствуются!

+0

Вы пробовали использовать классы MonadState и аналогичного типа? – bheklilr

+0

Не так далеко, но пример от Майкла ниже дает мне место для начала, спасибо за ответ. Как я упоминал ниже, мне нужно сделать некоторое чтение, чтобы лучше понять, что это такое и как оно сравнивается с тем, что я делал. – Chris

ответ

3

Очевидное решение здесь не использовать конкретные типы трансформаторов, как те, из StateT от transformers и RandT от MonadRandom до конца, но для создания деталей с использованием «mtl -Style» классов. Ваш 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

@ Крис первый - это естественное решение, когда вы используете состояние, читатель, писатель, случайный и несколько других «эффектов». С такими вещами, как 'conduit' или' pipes', вы имеете дело с трансформатором монады, который не может быть так легко представлен классом «mtl style», а затем прямой подход «поднять» и «подъем» становится более естественным. Последний подход имеет тенденцию давать более четкие сообщения об ошибках, поскольку он не использует классы типов, но маршрут 'mtl' менее подробен. – Michael

0

два варианта, плюс бонус один:

  1. Основное решение будет обобщая свои типы из 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, которые работать из коробки для всех монадов, построенных с StateT и RandT соответственно. При использовании somethingInRandom, хотя, вы должны проскользнуть в lift продвигать его в StateT -transformed монады:

    test :: StateT MyState (RandT StdGen IO)() 
    test = do 
        x <- lift somethingInRandom 
        _ <- somethingThatModifiesState x 
        _ <- somethingInStateAndRand 
        -- etc. 
    
  2. Существует более хороший альтернатива, хотя: С тех пор, как я уже упоминал выше, ваши реализации в термины MonadState и MonadRandom, вы можете, как предлагает bheklilr, обобщать типы в терминах этих классов. Это имеет два преимущества перед первым решением: вам больше не нужен lift для somethingInRandom, и ваш код не нуждается в каких-либо дополнительных изменениях, если вы, например, уложите еще один слой трансформатора на StateT или переключитесь на экземпляр MonadRandom, отличный от RandT :

    somethingInRandom :: MonadRandom m => m Int 
    somethingThatModifiesState :: MonadState MyState m => m() 
    somethingInStateAndRand :: (MonadRandom m, MonadState MyState m) => m Int 
    
  3. Поскольку вы упомянули mmorph: если изменение подписи не вариант по какой-либо причине (например, если вы используете чужой код), вам придется распаковывать State/Rand вычисления, проскользните в return . runIdentity, чтобы изменить их внутреннюю монаду и переделать их. Поскольку return . runIdentity монада морфизм, есть более удобный способ сделать это в случае StateT, который имеет MFunctor экземпляр:

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

    generalize от Control.Monad.Morph буквально return . runIdentity. Далее обсуждается этот трюк в mmorph's documentation.

Michael's answer демонстрирует решения № 2 и № 3.

+1

Большое спасибо! – Chris