2013-04-17 11 views
6

Ich habe den folgenden Code Monade:Vielleicht innen Stapel von Transformatoren

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 

Wenn ich do Notation innen in Maybe Monade arbeiten es funktioniert nicht. Aus dem Fehler, den es gibt, sieht es aus wie Typ-Signatur für diese do nicht übereinstimmt. Ich habe jedoch keine Ahnung, wie ich es beheben kann. Ich habe versucht, einige lift Kombinationen, aber keiner von ihnen hat funktioniert und ich möchte nicht mehr raten.

+0

Haben Sie den Code in der inneren 'do' wollen nur im' Maybe' Monade laufen, oder es muss Zugriff auf die ' StateT Int' und 'IO' auch? – pat

+0

Nur 'Maybe' Monade. – Adrian

Antwort

8

Das Problem ist, dass Maybe nicht Teil Ihres Transformatorstacks ist. Wenn Ihr Transformator nur über StateT Int und IO weiß, weiß es nichts über das Heben von Maybe.

Sie können dieses Problem beheben, indem Sie Ihre Art T etwas wie das Ändern:

type T = StateT Int (MaybeT IO) Int 

(Sie werden Control.Monad.Trans.Maybe importieren.)

Sie müssen auch Ihre innere do ändern, mit zu arbeiten MaybeT anstatt Maybe. Dies bedeutet, rohe Maybe a Werte mit MaybeT . return Verpackung:

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

Dies ist ein wenig umständlich, so dass Sie wahrscheinlich eine Funktion wie liftMaybe schreiben wollen:

liftMaybe = MaybeT . return 

Wenn Sie verwendet liftIO a Werte in anderen zu heben Teile Ihres Codes, dies wird jetzt brechen, weil Sie jetzt drei Ebenen in Ihrem Transformator-Stack haben. Sie erhalten eine Fehlermeldung erhalten, die wie folgt aussieht:

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

Um dies zu beheben, sollten Sie liftIO für alle rohen IO a Werte verwenden. Dies verwendet eine Typenklasse zum Leben IO Aktionen durch eine beliebige Anzahl von Transformatorschichten.

Als Antwort auf Ihren Kommentar: Wenn Sie nur ein Stück Code auf Maybe je haben, wäre es einfacher, nur das Ergebnis der do Notation in eine Variable und Spiel gegen das setzen:

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

Dies bedeutet, dass der Code Maybe keinen IO ausführen kann. Sie können natürlich auch eine Funktion wie fromMaybe anstelle von case verwenden.

+0

Ich habe eine Reihe von Funktionen, die in dieser Monade funktionieren, aber in nur einer von ihnen muss ich einige Werte aus 'Maybe' entpacken und die' do' -Notation würde mir erlauben, verschachtelte 'case'-Ausdrücke loszuwerden. Ist das Definieren des Typs die einzige Möglichkeit, dies zu tun? – Adrian

+0

Es ist natürlich auch möglich, 'runMaybeT' für den Block zu verwenden, der den' MaybeT'-Transformator verwendet. – dflemstr

+0

@Adrian: Sie können einfach die Do-Notation in ihre eigene Funktion extrahieren und dann einen einzelnen Case-Ausdruck haben, der darauf abgestimmt ist. In der Tat ist das wahrscheinlich der beste Ansatz; Allerdings solltest du mit meiner Version ein wenig herumspielen, um Monod Transformer mehr zu verstehen. –

3

Wenn Sie den Code in der inneren do rein im Maybe Monade ausführen möchten, werden Sie keinen Zugriff auf die haben StateT Int oder IO Monaden (was eine gute Sache sein könnte). Dadurch wird wieder so ein Maybe Wert, die Sie prüfen müssen:

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