2015-01-29 6 views
9

Ich habe zwei Funktionen, eine, die versucht, ein Token von einem Webservice zu bekommen und kann fehlschlagen, und eine, die versucht, dieses Token zu verwenden, um den Benutzernamen zu erhalten und kann fehlschlagen.Wie bindet man verschachtelte Monaden?

getToken :: IO (Maybe Token) 
getUsername :: Token -> IO (Maybe String) 

Ich möchte das Ergebnis von getToken nehmen und es zu getUsername füttern. Wenn es nur IO oder Maybe gäbe, könnte ich einfach bind verwenden, aber da es verschachtelte Monaden gibt, kann ich nicht. Wie kann ich etwas schreiben, das zu getToken >>= getUsername :: IO (Maybe String) äquivalent ist?

Allgemeiner, welche Funktion hat Typ m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b?

Bonus Frage: Wie würde ich das tun mit der Do-Notation in einem IO Kontext?

type Token = String 

getToken :: IO (Maybe Token) 
getToken = undefined 

getUsername :: Token -> IO (Maybe String) 
getUsername = undefined 

useToken :: IO (Maybe String) 
useToken = do 
    token <- getToken 
    case token of 
    Just x -> getUsername x 
    Nothing -> return Nothing 

Wenn Sie nicht do Notation verwenden möchten, dann können Sie verwenden:

+2

Die allgemeine Art und Weise Monaden zu kombinieren, ist Monade Transformatoren zu verwenden. In Ihrem Fall, "MaybeT" (http://hackage.haskell.org/package/transformers-0.4.2.0/docs/Control-Monad-Trans-Maybe.html#v:MaybeT) –

+0

MaybeT ist in der Tat die beste Lösung hier . –

+0

Es gibt im Allgemeinen keine Funktion vom Typ '(Monad m1, Monad m2) => m1 (m2a) -> (a -> m1 (m2b)) -> m1 (m2b)'. Das ist gemeint, wenn Leute sagen "Monaden komponieren nicht". Siehe auch [Konkretes Beispiel, das zeigt, dass Monaden in der Zusammensetzung nicht geschlossen sind] (http://stackoverflow.com/q/13034229/791604). –

Antwort

10

ich eine Funktion useToken, um Ihr Anwendungsfall definiert haben

useToken2 :: IO (Maybe String) 
useToken2 = getToken >>= \token -> maybe (return Nothing) getUsername token 

Oder mit Monade Transformatoren, Ihr Code wird einfacher:

import Control.Monad.Trans.Maybe 
type Token = String 

getToken :: MaybeT IO Token 
getToken = undefined 

getUsername :: Token -> MaybeT IO String 
getUsername = undefined 

useToken :: MaybeT IO String 
useToken = do 
    token <- getToken 
    getUsername token 

Nein Damit können Sie auch IO-Operationen innerhalb des Monadetransformators direkt anheben. Wie @Robedino weist darauf hin, jetzt wird der Code prägnanter ohne Notation tun:

useToken :: MaybeT IO String 
useToken = getToken >>= getUsername 
+0

Das ist in der Tat der Code, den ich geschrieben habe, um das zu tun. Denken Sie, dass es eine allgemeinere Lösung dafür gibt? Oder sollte ich mit dieser Formulierung zufrieden sein? – madjar

+0

@madjar Ich habe einen Code ohne Do Notation hinzugefügt. Ich weiß nicht, ob es eine allgemeinere Lösung gibt. – Sibi

+0

Und anstatt die Do-Notation in useToken zu verwenden, können Sie auch: '' 'getToken >> = getUsername''' mit der' '' MaybeT''' Lösung verwenden! – RobertDiep

3

Da die Menschen in den Kommentaren vorschlagen, sollten Sie nur Monade Transformatoren verwenden.

Sie können dies jedoch in Ihrem Fall vermeiden.Monaden pendeln in der Regel nicht, so kann man nicht eine Funktion mit dieser Signatur

bind' :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b) 

Aber alles ist in Ordnung, wenn die innere Monade ist eine Instanz der Klasse Traversable schreiben:

import Data.Traversable as T 
import Control.Monad 

joinT :: (Monad m, Traversable t, Monad t) => m (t (m (t a))) -> m (t a) 
joinT = (>>= liftM join . T.sequence) 

liftMM :: (Monad m, Monad n) => (a -> b) -> m (n a) -> m (n b) 
liftMM = liftM . liftM 

bindT :: (Monad m, Traversable t, Monad t) => m (t a) -> (a -> m (t b)) -> m (t b) 
bindT x f = joinT (liftMM f x) 

und die Maybe Monade ist; daher

type Token = String 

getToken :: IO (Maybe Token) 
getToken = undefined 

getUsername :: Token -> IO (Maybe String) 
getUsername = undefined 

useToken :: IO (Maybe String) 
useToken = getToken `bindT` getUsername 

Auch mit der {-# LANGUAGE RebindableSyntax #-} können Sie

(>>=) = bindT 

useToken :: IO (Maybe String) 
useToken = do 
    x <- getToken 
    getUsername x 

aktualisieren

Mit dem Typ-Ebene schreiben komponieren

newtype (f :. g) a = Nested { runNested :: f (g a) } 

Sie eine Monade Instanz definieren können verschachtelte Monaden:

instance (Monad m, Traversable t, Monad t) => Monad (m :. t) where 
    return = Nested . return . return 
    x >>= f = Nested $ runNested x `bindT` (runNested . f) 

Ihr Beispiel dann ist

type Token = String 

getToken :: IO (Maybe Token) 
getToken = undefined 

getUsername :: Token -> IO (Maybe String) 
getUsername = undefined 

useToken :: IO (Maybe String) 
useToken = runNested $ Nested getToken >>= Nested . getUsername 

Oder wie Sie mit dem MaybeT Transformator tun würde:

type Nested = (:.) 

type Token = String 

getToken :: Nested IO Maybe Token 
getToken = undefined 

getUsername :: Token -> Nested IO Maybe String 
getUsername = undefined 

useToken :: Nested IO Maybe String 
useToken = getToken >>= getUsername 

runUseToken :: IO (Maybe String) 
runUseToken = runNested useToken