2015-07-25 12 views
6

Um zu verstehen, wie man Monad-Transformatoren verwendet, schrieb ich den folgenden Code ohne einen. Es liest die Standardeingabe Zeile für Zeile und zeigt jede Zeile umgekehrt an, bis eine leere Zeile gefunden wird. Es zählt auch die Zeilen mit State und zeigt am Ende die Gesamtzahl an.Verwenden Sie zwei Monaden ohne Transformator

import Control.Monad.State 

main = print =<< fmap (`evalState` 0) go where 
    go :: IO (State Int Int) 
    go = do 
     l <- getLine 
     if null l 
     then return get 
     else do 
      putStrLn (reverse l) 
      -- another possibility: fmap (modify (+1) >>) go 
      rest <- go 
      return $ do 
       modify (+1) 
       rest 

Ich wollte die aktuelle Zeilennummer vor jeder Zeile hinzufügen. Ich war in der Lage, es zu tun mit StateT:

import Control.Monad.State 

main = print =<< evalStateT go 0 where 
    go :: StateT Int IO Int 
    go = do 
     l <- lift getLine 
     if null l 
     then get 
     else do 
      n <- get 
      lift (putStrLn (show n ++ ' ' : reverse l)) 
      modify (+1) 
      go 

Meine Frage ist: wie man das gleiche in der Version ohne Monade Transformatoren zu tun?

Antwort

2

Sie müssten nur die akkumulierte Zustandsberechnung in jeder Zeile ausführen. Das ist O (n²) Zeit, aber da Ihr erstes Programm bereits O (n) Raum verwendet, ist das nicht zu schrecklich. Natürlich ist der StateT Ansatz in fast jeder Hinsicht überlegen! Wenn Sie es wirklich "von Hand" machen und keinen Effizienzpreis zahlen wollen, verwalten Sie den Staat einfach per Hand, anstatt einen staatlichen Transformator zu bauen. Sie erhalten wirklich keinen Vorteil, indem Sie State anstelle von Int im ersten Programm verwenden.

+2

Ich weiß, dass. Ich suche aus Effizienzgründen nicht nach einer Version ohne Monade-Transformator, ich möchte nur sehen, wie es aussehen würde, und etwas lernen, indem ich die beiden vergleiche und hoffentlich ein besseres Verständnis für die Notwendigkeit von Monad-Transformatoren erlange. – ByteEater

+0

Auch das Ausführen der akkumulierten Zustandsberechnung in jeder Zeile ist etwas, das ich aus genau diesem Grund in Betracht gezogen und abgelehnt habe: Es scheint nicht der richtige Weg zu sein, Monaden zu verwenden, ein einfaches "Int" wäre eine bessere Wahl. Mit der Inkrementierung macht es keinen Unterschied, aber es wäre konzeptionell falsch, da die 'State'-Berechnung durch Voranstellen von' modify (+1) '-Aktionen aufgebaut wird, so dass, wenn ich z. 'modify (+ length l)', das würde nicht so funktionieren, wie es sollte. – ByteEater

+0

@ByteEater, ist die Art, es ohne einen Monade-Transformator zu tun, nur die 'Int' herumgeben von Hand (ärgerlich) oder verwenden Sie eine' IORef' (begrenzt auf 'IO'-ähnliche Dinge und möglicherweise ineffizient, aber in Ordnung, wenn die Box ist unvermeidlich oder Updates sind selten). Ich weiß nicht, was du sonst noch suchst. – dfeuer

1

Vielleicht suchen Sie das?

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return (st >>= \_ -> get) 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (st >>= \_ -> modify (+1) >>= \_ -> get) 

Die Idee hier ist go Schwanz rekursiv zu machen, Ihren Zustand Berechnung Aufbau, die Sie bei jedem Schritt dann auswerten können.

EDIT

Diese Version wird die Größe des Staates Berechnung auf eine konstante Größe gebunden, obwohl unter lazy evaluation, wenn der vorherige Zustand Berechnung gezwungen wird, sollten wir in der Lage sein, es wieder zu verwenden, ohne neu zu bewerten es, so vermute ich, dass diese im wesentlichen die gleichen ...

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return st 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (modify (\s -> s+ln+1) >>= \_ -> get) 
+0

In der Tat erlaubt dies, dass der aktuelle Wert, der in 'State Int' berechnet wird, auf Kosten einer wiederholten Neuberechnung verwendet wird (obwohl mit einer referentiell transparenten Sprache und einem intelligenten Compiler diese Kosten vermieden werden könnten). Ich hatte darüber nachgedacht, war aber nicht davon überzeugt, dass dies das kanonische Äquivalent zu "State" - und "IO" -Monaden meines zweiten Programms ist, das einen Transformator verwendet. – ByteEater

+0

Ich bezweifle, dass dies mehr "kanonisch" als mein erster Versuch ist, aber meine zweite Bearbeitung kann die Zustandsberechnung auf eine konstante Größe begrenzen, indem die vorherige Zustandsberechnung weggeworfen und der aktuelle Zustand sofort auf das letzte Ergebnis plus gesetzt wird 1. Irgendwelche besseren? – Matt

+0

Besser, so wie du es beschrieben hast. Aber es ist immer noch nicht so einfach mit der Version zu vergleichen, die StateT benutzt, um klar zu sehen, welche Verbesserung im Code (Refactoring zu einer höheren Form) tatsächlich mit einem Monadetransformator erreicht werden kann. – ByteEater

10

das Problem, das Sie ist sind mit, dass die Hand-Abrollen des StateT s IO as -> IO (s, a) ist, nicht IO (s -> (s, a))! Sobald Sie diesen Einblick haben, ist es ziemlich einfach zu sehen, wie es geht: