2013-04-17 9 views
6

Mam następujący kod:Może monada wewnątrz stosu transformatorów

import Control.Monad 
import Control.Monad.Trans 
import Control.Monad.Trans.State 

type T = StateT Int IO Int 

someMaybe = Just 3 

f :: T 
f = do 
    x <- get 
    val <- lift $ do 
     val <- someMaybe 
     -- more code in Maybe monad 
     -- return 4 
    return 3 

Gdy używam do notacji wewnątrz pracować w Maybe monady nie powiedzie. Z błędu wynika, że ​​wygląda jak podpis typu dla tego do nie pasuje. Jednak nie mam pojęcia, jak to naprawić. Próbowałem kombinacji lift, ale żaden z nich nie działał i nie chcę już więcej zgadywać.

+0

Chcesz kod w wewnętrznej 'do' uruchomić tylko w' Maybe' monady, czy też potrzebują dostępu do ' StateT Int' i 'IO' też? – pat

+0

Tylko moneta "Maybe". – Adrian

Odpowiedz

8

Problem polega na tym, że Maybe nie jest częścią twojego stosu transformatora. Jeśli twój transformator wie tylko o StateT Int i IO, nie wie nic o tym, jak podnieść Maybe.

Można to naprawić zmieniając typ T do czegoś podobnego:

type T = StateT Int (MaybeT IO) Int 

(Będziesz musiał importować Control.Monad.Trans.Maybe).

Potrzebny będzie również zmienić swoją wewnętrzną do pracować MaybeT zamiast Maybe. Oznacza to, owijając surowe Maybe a wartości z MaybeT . return:

f :: T 
f = do 
    x <- get 
    val <- lift $ do 
     val <- MaybeT $ return someMaybe 
     -- more code in Maybe monad 
     return 4 
    return 3 

Jest to trochę niewygodne, więc prawdopodobnie chcesz napisać funkcję jak liftMaybe:

liftMaybe = MaybeT . return 

Jeśli użyto lift podnieść IO a wartości w innych części twojego kodu, teraz to się zepsuje, ponieważ masz teraz trzy poziomy w stosie transformatora. Dostaniesz błąd, który wygląda tak:

Couldn't match expected type `MaybeT IO t0' 
      with actual type `IO String' 

Aby rozwiązać ten problem, należy użyć liftIO dla wszystkich surowców IO a wartości. Wykorzystuje to działania typeklass do życia przez dowolną liczbę warstw transformatora.

W odpowiedzi na Twój komentarz: jeśli masz tylko kawałek kodu w zależności od Maybe, łatwiej byłoby po prostu umieścić wynik notacji do do zmiennej i meczu przeciwko że:

let maybeVal = do val <- someMaybe 
        -- more Maybe code 
        return 4 
case maybeVal of 
    Just res -> ... 
    Nothing -> ... 

Oznacza to, że kod Maybe nie będzie w stanie wykonać operacji wejścia/wyjścia. Oczywiście możesz także użyć funkcji takiej jak fromMaybe zamiast case.

+0

Mam kilka funkcji, które działają w tej monadzie, ale tylko w jednym z nich muszę rozpakować niektóre wartości z 'Maybe', a użycie notacji' do' pozwoli mi pozbyć się zagnieżdżonego 'case 'expresion. Czy zdefiniowanie typu jest jedynym sposobem na zrobienie tego? – Adrian

+0

Oczywiście możliwe jest również użycie 'runMaybeT' dla bloku korzystającego z transformatora' MaybeT'. – dflemstr

+0

@Adrian: Możesz po prostu wyodrębnić notację 'do' do swojej własnej funkcji, a następnie użyć pojedynczego wyrażenia przypadku dopasowanego do tego. W rzeczywistości jest to prawdopodobnie najlepsze podejście; jednak powinieneś trochę pograć z moją wersją, żeby lepiej zrozumieć transformatory Monady. –

3

Jeśli chcesz uruchomić kod w wewnętrznej do czysto w Maybe monady, nie będziesz mieć dostępu do StateT Int lub IO monad (co może być dobrą rzeczą). W przeciwnym razie zwróci wartość Maybe, które trzeba będzie przeanalizować:

import Control.Monad 
import Control.Monad.Trans 
import Control.Monad.Trans.State 

type T = StateT Int IO Int 

someMaybe = Just 3 

f :: T 
f = do 
    x <- get 
    -- no need to use bind 
    let mval = do 
     -- this code is purely in the Maybe monad 
     val <- someMaybe 
     -- more code in Maybe monad 
     return 4 
    -- scrutinize the resulting Maybe value now we are back in the StateT monad 
    case mval of 
     Just val -> liftIO . putStrLn $ "I got " ++ show val 
     Nothing -> liftIO . putStrLn $ "I got a rock" 
    return 3 
Powiązane problemy