2016-07-05 15 views
4

Hier mein Eierpack Fabrik ist:Mit Vielleicht und Writer zusammen

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> Maybe Carton 
add e (Carton c) 
    | c + e <= 12 = Just (Carton $ c + e) 
    | otherwise = Nothing 

main = do 
    print $ pure(Carton 2) >>= add 4 >>= add 4 >>= add 3 

scheint gut zu funktionieren, kann ich gut Kette add Funktionen.

Aber ich möchte ein Protokoll aufnehmen, wie viele Eier bei jedem Schritt hinzugefügt wurden. Also das ich tun:

import Control.Monad.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> Writer [String] (Maybe Carton) 
add e (Carton c) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     return (Just (Carton $ c + e)) 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     return Nothing 

main = do 
    let c = add 4 $ Carton 2 
    print $ fst $ runWriter c 
    mapM_ putStrLn $ snd $ runWriter c 

Das gibt mir, was ich will: Ich kann die resultierende Karton und der Datensatz für 4 Eier sehen hinzugefügt werden.

Aber ich scheine die Fähigkeit zur Kette verloren haben add Funktionen wie ich zuvor:

let c = pure(Carton 2) >>= add 4 -- works 
let c = pure(Carton 2) >>= add 4 >>= add 2 -- does not work 

Wie kann ich meine neue Schreiber-fähigen add Funktionen Kette? Gibt es einen besseren Weg, dies zu tun?

+0

Beachten sie, dass es als 'hinzufügen 4> => add 2 $ Carton Schreiben 2' oder 'add 2 <= => add 2' erlaubt es Ihnen, das Pure wegzulassen. – Gurkenglas

Antwort

1

In dem ersten Beispiel die zweiten >>= in dem Ausdruck für die Instanz von MaybeMonad, während bei dem zweiten Beispiel aus der Monad Instanz Writer ist. Insbesondere erwartet das >>= im ersten Beispiel eine Funktion vom Typ Carton -> Maybe Carton, wie add 2, während im zweiten Beispiel >>= eine Funktion vom Typ Maybe Carton -> Writer [String] (Maybe Carton) erwartet. In beiden Beispielen pure (Carton 2) >> = add 4 funktioniert, weil pure (Carton 2) hat Typ Maybe Carton und add 4 hat Typ Carton -> <something>, so haben Sie keine Probleme. Das Hinzufügen eines weiteren >>= zu dem Ausdruck löst den Fehler aus, da im ersten Beispiel dieser >>= den gleichen Typ wie der erste hat, während er im zweiten Beispiel nicht gleich ist. Eine Lösung kann sein add so zu ändern, dass es Eggs -> Maybe Carton -> Writer [String] (Maybe Carton) Typ hat:

add :: Eggs -> Maybe Carton -> Writer [String] (Maybe Carton) 
add e Nothing = return Nothing 
add e (Just (Carton c)) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     return (Just (Carton $ c + e)) 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     return Nothing 

beachten Sie, dass dies bedeutet, dass Sie nicht mehr pure (Carton 2) verwenden können, aber Sie müssen pure (Just $ Carton 2):

> pure (Just $ Carton 2) >>= add 2 >>= add 5 
WriterT (Identity (Just (Carton 9),["adding 2","adding 5"])) 

Sagte, dass, würde ich Ihnen vorschlagen zu verwenden monad transformersMaybe und Writer zu komponieren, weil dies in einem gemeinsamen Anwendungsfall für sie.Ihr Beispiel könnte neu geschrieben werden als

import Control.Monad.Trans.Maybe 
import Control.Monad.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton 
add e (Carton c) 
    | c + e <= 12 = do 
     lift $ tell ["adding " ++ show e] 
     return (Carton $ c + e) 
    | otherwise = do 
     lift $ tell ["cannot add " ++ show e] 
     mzero 

main = do 
    let c = return (Carton 2) >>= add 4 >>= add 2 
    let result = runWriter $ runMaybeT c 
    print $ fst $ result 
    mapM_ putStrLn $ snd $ result 

Ein paar Dinge aus Ihrem Beispiel geändert haben:

  1. MaybeT m a ist der Monade Transformator. In diesem Beispiel ist mWriter [String] und a ist Carton. Um alles zu starten, geben wir zuerst runMaybeT ein, was Ihnen eine Writer [String] (Maybe Carton) gibt, und dann rufen wir runWriter darauf an, wie Sie es in Ihrem Beispiel getan haben.
  2. Um Writer Funktionen in MaybeT (Writer [String]) zu verwenden, müssen wir lift ihnen. Zum Beispiel lift $ tell ["something"]
  3. return carton wird verwendet, um eine Rückkehr Just Carton während mzero wird verwendet, um das Rück Nothing

Eine letzte Sache: in diesem Beispiel können wir Maybe und Writer andersrum nicht komponieren, mit WriterT [String] Maybe Carton, denn wenn es sind mehr als 12 Eier runWriterTNothing und unterdrücken die Geschichte zurückkehren würde:

import Control.Monad 
import Control.Monad.Trans 
import Control.Applicative 
import Control.Monad.Trans.Maybe 
import Control.Monad.Trans.Writer 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

add :: Eggs -> Carton -> WriterT [String] Maybe Carton 
add e (Carton c) 
    | c + e <= 12 = do 
     tell ["adding " ++ show e] 
     lift $ Just $ Carton $ c + e 
    | otherwise = do 
     tell ["cannot add " ++ show e] 
     lift Nothing 

main = do 
    let c = return (Carton 2) >>= add 4 >>= add 20 
    case runWriterT c of 
     Nothing -> 
     print "nothing to print" 
     Just (carton, history) -> do 
     print carton 
     mapM_ putStrLn $ history 
+0

Dies ist die genaue Antwort, die ich erwartet habe. Könnten Sie ein Beispiel mit Monade-Transformatoren geben? – zoran119

+0

Ausgezeichnetes Beispiel. Vielen Dank dafür. Ich kann Ihr 'MaybeT (Writer [String]) Carton' Beispiel ohne' lift' ausführen. – zoran119

+0

'tell' funktioniert auf einer allgemeineren Version von' Writer' namens ['MonadWriter'] (https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Writer-Class.html). 'MaybeT' hat eine Instanz von' MonadWriter', was bedeutet, dass Sie 'tell' direkt für' MaybeT' verwenden können, anstatt die 'Writer' Version zu" heben ". Innerhalb der Instanz selbst ist ['tell' definiert als' lift. sag '] (https://hackage.haskell.org/package/mtl-2.2.1/docs/src/Control-Monad-Writer-Class.html#line-151), was ich hier benutzt habe. Ich denke, die explizite Komposition hilft, das Beispiel zu verstehen. – mariop

5

komponieren Gerade add mit MaybeT:

import Control.Trans.Monad.Maybe 

test = pure (Carton 2) >>= MaybeT . add 3 
         >>= MaybeT . add 4 
         >>= MaybeT . add 5 

runTest = do 
    print $ fst $ runWriter (runMaybeT test) 

Voll Beispiel an: http://lpaste.net/169070

5

I add & c ändern würde verwenden MaybeT (Writer [String]):

import Control.Monad.Writer 
import Control.Monad.Trans.Maybe 

type Eggs = Int 
data Carton = Carton Eggs deriving Show 

main = do 
    let c = add 4 $ Carton 2 
     (result, log) = runWriter $ runMaybeT c 
    print result 
    mapM_ putStrLn log 

add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton 
add e (Carton c) 
    | c + e <= 12 = do 
      tell ["adding " ++ show e] 
      return $ Carton $ c + e 
    | otherwise = do 
      tell ["cannot add " ++ show e] 
      mzero 

dies wird Ihrem ursprünglichen Code ermöglichen von

pure (Carton 2) >>= add 4 >>= add 2 

funktioniert wie erwartet.