2010-09-03 13 views
26

nehme ich einen Zustand Monade haben wie:Kombinieren Zustand mit IO Aktionen

data Registers = Reg {...} 

data ST = ST {registers :: Registers, 
       memory :: Array Int Int} 

newtype Op a = Op {runOp :: ST -> (ST, a)} 

instance Monad Op where 
return a = Op $ \st -> (st, a) 
(>>=) stf f = Op $ \st -> let (st1, a1) = runOp stf st 
           (st2, a2) = runOp (f a1) st1 
          in (st2, a2) 

mit Funktionen wie

getState :: (ST -> a) -> Op a 
getState g = Op (\st -> (st, g st) 

updState :: (ST -> ST) -> Op() 
updState g = Op (\st -> (g st,())) 

und so weiter. Ich möchte verschiedene Operationen in dieser Monade mit IO-Aktionen kombinieren. So konnte ich entweder eine Auswertung Schleife schreiben, in dem Operationen in diesem Monade durchgeführt wurden und eine IO-Aktion mit dem Ergebnis ausgeführt, oder, glaube ich, sollte ich in der Lage sein, etwas zu tun, wie folgt aus:

newtype Op a = Op {runOp :: ST -> IO (ST, a)} 

Printing Funktionen hätten den Typ Op() und andere Funktionen hätten den Typ Op a, zB könnte ich mit einer Funktion vom Typ IO Char ein Zeichen vom Terminal lesen. Ich bin mir jedoch nicht sicher, wie eine solche Funktion aussehen würde, da beispielsweise das Folgende nicht gültig ist.

seit getLine hat Typ IO Char, aber dieser Ausdruck hätte Typ Op Char. Umreiß, wie würde ich das tun?

Antwort

25

Der grundlegende Ansatz wäre, Ihre Op Monade als Monade-Transformator neu zu schreiben. Dies würde es Ihnen ermöglichen, es in einem "Stapel" von Monaden zu verwenden, dessen Unterseite IO sein könnte.

Hier ist ein Beispiel dafür, was das aussehen könnte:

  • Die Verwendung der MonadTrans Klasse
  • Die Verwendung der wirkenden lift Funktion auf:

    import Data.Array 
    import Control.Monad.Trans 
    
    data Registers = Reg { foo :: Int } 
    
    data ST = ST {registers :: Registers, 
           memory :: Array Int Int} 
    
    newtype Op m a = Op {runOp :: ST -> m (ST, a)} 
    
    instance Monad m => Monad (Op m) where 
    return a = Op $ \st -> return (st, a) 
    (>>=) stf f = Op $ \st -> do (st1, a1) <- runOp stf st 
               (st2, a2) <- runOp (f a1) st1 
               return (st2, a2) 
    
    instance MonadTrans Op where 
        lift m = Op $ \st -> do a <- m 
              return (st, a) 
    
    getState :: Monad m => (ST -> a) -> Op m a 
    getState g = Op $ \st -> return (st, g st) 
    
    updState :: Monad m => (ST -> ST) -> Op m() 
    updState g = Op $ \st -> return (g st,()) 
    
    testOpIO :: Op IO String 
    testOpIO = do x <- lift getLine 
           return x 
    
    test = runOp testOpIO 
    

    die wichtigsten Dinge zu beachten getLine, die verwendet wird, um die getline Funktion von der IO Monade und in die Op IO Monade zu bringen.

Übrigens, wenn Sie nicht die IO Monade wollen immer anwesend sein, können Sie es mit dem Identity Monade in Control.Monad.Identity ersetzen. Die Op Identity Monade verhält sich genau so wie Ihre ursprüngliche Op Monade.

+3

Ich erkenne (jetzt), dass die vorgestellte Lösung Standard ist, aber das ist eine sehr interessante (und elegante) Lösung. Es war sehr hilfreich, dass Sie meinen Code mit einem Monad-Transformer neu geschrieben haben, da er ein konkretes Beispiel lieferte. Vielen Dank! – danportin

+0

Eine schöne pädagogische Ergänzung zu Martijns pragmatischer Lösung. –

26

Verwenden liftIO

Sie sind schon sehr nahe! Ihr Vorschlag

newtype Op a = Op {runOp :: ST -> IO (ST, a)} 

ist ausgezeichnet und der Weg zu gehen.

Um getLine in einem Op Kontext auszuführen, müssen Sie ‚Aufzug‘ der IO Betrieb in den Op Monade.Sie können dies tun, indem Sie eine Funktion schreiben liftIO:

liftIO :: IO a -> Op a 
liftIO io = Op $ \st -> do 
    x <- io 
    return (st, x) 

Sie können jetzt schreiben:

Gebrauchsklasse MonadIO

nun das Muster eines IO-Aktion in einem benutzerdefinierten Monade von Hebe ist so üblich, dass es eine Standardtypklasse dafür gibt:

import Control.Monad.Trans 

class Monad m => MonadIO m where 
    liftIO :: IO a -> m a 

Damit Ihre Version von liftIO wird eine Instanz von MonadIO statt:

instance MonadIO Op where 
    liftIO = ... 

Verwenden StateT

Sie derzeit Ihre eigene Version des Staates Monade, spezialisiert auf staatliche ST geschrieben habe. Warum benutzt du nicht die Standardzustandsmonade? Es erspart Ihnen, Ihre eigene Monad Instanz zu schreiben, die immer dieselbe für die Zustands-Monade ist.

type Op = StateT ST IO 

StateT bereits eine Monad Instanz und eine MonadIO Instanz, so können Sie diese sofort ab.

Monad Transformatoren

StateT ist eine sogenannte monadisch Transformator. Sie wollen nur IO Aktionen in Ihrem Op Monad, also habe ich es bereits mit dem IO Monad für Sie spezialisiert (siehe die Definition von type Op). Aber Monade-Transformatoren ermöglichen es Ihnen, beliebige Monaden zu stapeln. Davon spricht der Introverflow. Sie können mehr über sie here und here lesen.

+2

Ihre Vorschläge sind alle exzellent, und mir ist klar, dass meine 'op' Monade eine spezialisierte Version der Staatsmonade ist. Aber in diesem Stadium schreibe ich lieber den Code selbst, da er die Struktur und die Konzepte in meinem Kopf zementiert. Vielen Dank, dass Sie mir MonadIO, StateT usw. zur Kenntnis gebracht haben. – danportin