2016-05-03 16 views
0

Im Moment sieht mein Code wie folgt aus:Haskell binden mit mehreren Monaden

postUser :: ServerPart Response 
postUser = do 
    -- parseBody :: ServerPart (Maybe User) 
    parsedUser <- parseBody 
    case parsedUser of 
    Just user -> do 
     -- saveUser :: User -> ServerPart (Maybe (Key User)) 
     savedUser <- saveUser user 
     case savedUser of 
     Just user -> successResponse 
     Nothing -> errorResponse "user already exists" 
    Nothing -> errorResponse "couldn't parse user" 

Welche funktioniert, aber ich weiß, gibt es eine Möglichkeit, die verschachtelte Pattern-Matching zu vermeiden. Ich dachte, das war es, was binden würde für mich tun, also habe ich versucht

parseUser :: ServerPart (Maybe User) 
addUser :: User -> ServerPart (Maybe()) 
case parseUser >>= saveUser of 
    Just _ -> success 
    Nothing -> error 

und

savedUser <- (parseUser >>= saveUser) 
case savedUser of 
    Just _ -> success 
    Nothing -> error 

Aber ich erhalte den folgenden Fehler:

Couldn't match type ‘Maybe a0’ with ‘User’ 
    Expected type: Maybe a0 -> ServerPartT IO (Maybe (Key User)) 
     Actual type: User -> ServerPart (Maybe (Key User)) 
    In the second argument of ‘(>>=)’, namely ‘saveUser’ 
    In the expression: parseBody >>= saveUser 

, die ich nehmen zu bedeuten >>= ist Anwendung saveUser auf die Maybe User anstelle der User, die ich brauche, und ich bin mir nicht sicher, wie die Typen zu finagle passen. Wie kann ich dies umschreiben, um die Verschachtelung von verschachtelten Mustern zu vermeiden?

Antwort

2

Während ich argumentieren würde, dass die ursprüngliche Art, wie Sie es geschrieben haben, der lesbarste Ansatz ist, dies als eine Übung zu nehmen, ist der MaybeT Monade Transformator, was Sie suchen.

Das Problem, auf das Sie stoßen, ist, dass Sie versuchen, zwischen der ServerPart-Monade und der Maybe-Monade zu springen. Deshalb können Sie parseBody und saveUser nicht direkt binden. Mit Monad-Transformatoren können Sie Monaden kombinieren, um dieses Problem zu vermeiden.

import Control.Monad.Trans.Maybe 

postUser :: ServerPart Response 
postUser = do 
    -- parseBody :: MaybeT ServerPart User 
    -- saveUser :: User -> MaybeT ServerPart (Key User) 
    user <- runMaybeT $ parseBody >>= saveUser 
    case user of 
    Just _ -> successResponse 
    Nothing -> errorResponse "Error saving user" 

Sie müssen Ihre parseBody- und saveUser-Funktionen umgestalten, um die MaybeT-Monade zu verwenden. Da ich diese Funktionen nicht sehen kann, kann ich Ihnen nicht helfen, aber es kann normalerweise einfach mit lift von Control.Applicative getan werden.

Nützliche Links Monade Transformatoren:

https://en.wikibooks.org/wiki/Haskell/Monad_transformers https://www.schoolofhaskell.com/user/commercial/content/monad-transformers

EDIT: MaybeT für parseBody

parseBody :: FromJSON a => MaybeT ServerPart a 
parseBody = MaybeT $ fmap A.decode getBody 

Und nur als allgemeine Tipp: bar >>= (return . f) entspricht fmap f bar. Letzteres ist sauberer und allgemeiner, da es die Monadeninstanz nicht erfordert.

+0

das hat total funktioniert, jetzt wie du erwähntest, ich versuche, 'parseBody' zu konvertieren, um MaybeT zu verwenden, mit dem ich auch Probleme habe. Ich fühle mich, als ob ich nah dran bin, aber immer noch keine passenden Typen finden kann. https://gist.github.com/feigter09/be2afa1fb6cb810f9e1c24d79882caf8 – Austin

+0

Warum hast du 'ServerPart' zu' ServerPartT IO' gewechselt? –

+0

'ServerPart' ist ein Alias ​​für' ServerPartT IO', und als ich es als 'parseBody :: FromJSON a => MaybeT ServerPart a' hatte, bekam ich den Fehler:' Type Synonym 'ServerPart' sollte 1 Argument haben, aber wurde keine gegeben https://hackage.haskell.org/package/happstack-server-7.4.6.1/docs/Happstack-Server-Internal-Monads.html#t:ServerPart – Austin