2012-04-19 11 views
60

Monaden können viele erstaunliche, verrückte Dinge tun. Sie können Variablen erstellen, die eine Überlagerung von Werten enthalten. Mit ihnen können Sie auf Daten aus der Zukunft zugreifen, bevor Sie sie berechnen. Sie können Ihnen erlauben, destruktive Updates zu schreiben, aber nicht wirklich. Und dann die Fortsetzung Monade können Sie Menschen die Köpfe brechen! Normalerweise Ihre eigenen. ;-)Die Pause monad

Aber hier ist eine Herausforderung: Können Sie eine Monade machen, die pausiert sein kann?

 
data Pause s x 
instance Monad (Pause s) 
mutate :: (s -> s) -> Pause s() 
yield :: Pause s() 
step :: s -> Pause s() -> (s, Maybe (Pause s())) 

Die Pause monadisch ist eine Art von Zustand monadisch (daher mutate, mit der offensichtlichen Semantik). Normalerweise hat eine solche Monade eine Art "run" -Funktion, die die Berechnung ausführt und Ihnen den letzten Zustand zurückgibt. Aber Pause ist anders: Es bietet eine step-Funktion, die die Berechnung ausführt, bis es die magische yield-Funktion aufruft. Hier wird die Berechnung angehalten, wobei dem Aufrufer genügend Informationen zurückgegeben werden, um die Berechnung später fortzusetzen.

Für zusätzliche Ehrfurcht: Erlauben Sie dem Anrufer, den Zustand zwischen step Anrufe zu ändern. (Die Art Signaturen oben sollte dies ermöglichen, zum Beispiel.)


Anwendungsfall: Es ist oft einfach Code zu schreiben, etwas komplex macht, aber insgesamt PITA es zu transformieren, um auch Ausgang die Zwischenzustände in seiner Operation. Wenn Sie wollen, dass der Benutzer in der Lage ist, Änderung in der Mitte der Ausführung,, werden die Dinge wirklich schnell komplex.

Implementierung Ideen:

  • Offensichtlich kann es mit einem Gewinde, Schlösser und IO erfolgen. Aber können wir es besser machen? ;-)

  • Etwas verrückt mit einer Fortsetzung Monade?

  • Vielleicht eine Art von Writer Monad, wo yield nur den aktuellen Zustand protokolliert, und dann können wir "step es vortäuschen" durch Iterieren über die Zustände im Protokoll. (Offensichtlich schließt dies das Verändern des Zustands zwischen den Schritten aus, da wir jetzt nichts "pausieren".)

+3

nicht mehr verrückt als jede andere 'Cont' Beispiel würde ich denken; stochern bei 'callCC'. – geekosaur

+2

In der ersten Instanz würde ich versuchen, die freie Monade auf der Signatur zu konstruieren {muate :: (s -> s) ->(); Ausbeute ::() ->()}. – pigworker

+2

GHC hatte eine Monade, die Sie könnten _resume_ (ResumeT), aber aus irgendeinem Grund verschwand es um Version 6.8, denke ich. –

Antwort

53

Sicher; Entweder Sie lassen nur jede Berechnung mit einem Ergebnis beenden oder auszusetzen selbst, eine Aktion geben auf Wiederaufnahme verwendet werden, zusammen mit dem Zustand zum Zeitpunkt:

data Pause s a = Pause { runPause :: s -> (PauseResult s a, s) } 

data PauseResult s a 
    = Done a 
    | Suspend (Pause s a) 

instance Monad (Pause s) where 
    return a = Pause (\s -> (Done a, s)) 
    m >>= k = Pause $ \s -> 
     case runPause m s of 
      (Done a, s') -> runPause (k a) s' 
      (Suspend m', s') -> (Suspend (m' >>= k), s') 

get :: Pause s s 
get = Pause (\s -> (Done s, s)) 

put :: s -> Pause s() 
put s = Pause (\_ -> (Done(), s)) 

yield :: Pause s() 
yield = Pause (\s -> (Suspend (return()), s)) 

step :: Pause s() -> s -> (Maybe (Pause s()), s) 
step m s = 
    case runPause m s of 
     (Done _, s') -> (Nothing, s') 
     (Suspend m', s') -> (Just m', s') 

Das Monad Beispiel Sequenzen nur Dinge in der normalen Art und Weise, Übergeben des Endergebnisses an die k Fortsetzung oder Hinzufügen des Rests der Berechnung, die bei der Aussetzung durchzuführen ist.

+0

Punkte, die 'get' und' put' enthalten und somit sowohl den Geist als auch den Buchstaben der ursprünglichen Frage erfüllen :) –

+0

Es ist leicht zu erkennen, dass "step" auf die Typensignatur 'Pause sx 'erweitert werden konnte -> s -> (Entweder x (Pause sx), s) '. Ändern Sie einfach die Zeile '(Done x, s ') -> (Links x, s')' und ändern Sie in der folgenden Zeile 'Just' in 'Right'. In der Tat, ParseResult s a === Entweder a (Pause s a) –

+0

Sehr schön gemacht. Ich habe vergessen, dass ich "holen" muss. (Ich brauche eigentlich nicht 'put', nur' mutate', aber das ist einfach.) Das ist eine sehr nette Antwort. – MathematicalOrchid

8
{-# LANGUAGE TupleSections #-} 
newtype Pause s x = Pause (s -> (s, Either x (Pause s x))) 

instance Monad (Pause s) where 
    return x = Pause (, Left x) 

    Pause k >>= f = Pause $ \s -> let (s', v) = k s in 
           case v of 
            Left x -> step (f x) s' 
            Right x -> (s', Right (x >>= f)) 

mutate :: (s -> s) -> Pause s() 
mutate f = Pause (\s -> (f s, Left())) 

yield :: Pause s() 
yield = Pause (, Right (return())) 

step :: Pause s x -> s -> (s, Either x (Pause s x)) 
step (Pause x) = x 

Das ist, wie ich es geschrieben. Ich gab step eine etwas allgemeinere Definition, es könnte auch runPause genannt werden. In der Tat, über Typ von step denkend, führe mich zur Definition von Pause.

Im Paket monad-coroutine finden Sie einen allgemeinen Monadetrafo. Die Pause s Monade ist die gleiche wie Coroutine (State s) Id. Sie können Coroutinen mit anderen Monaden kombinieren.

Verwandte: die Prompt Monade in http://themonadreader.files.wordpress.com/2010/01/issue15.pdf

+0

Der Code ist nicht groß, aber +1 für die Prompt-Monad-Referenz. – MathematicalOrchid

60

Hinweis: dass Sie sich keinen direkten Zugriff auf den aktuellen Stand s in dieser Monade zur Verfügung gestellt.

Pause s ist nur eine kostenlose Monade über die mutate und yield Operationen. Umgesetzt direkt erhalten Sie:

data Pause s a 
    = Return a 
    | Mutate (s -> s) (Pause s a) 
    | Yield (Pause s a) 

instance Monad (Pause s) where 
    return = Return 
    Return a >>= k = k a 
    Mutate f p >>= k = Mutate f (p >>= k) 
    Yield p >>= k = Yield (p >>= k) 

mit ein paar intelligenten Konstrukteurs Sie die gewünschte API zu geben:

mutate :: (s -> s) -> Pause s() 
mutate f = Mutate f (return()) 

yield :: Pause s() 
yield = Yield (return()) 

und die Sprungfunktion es

step :: s -> Pause s() -> (s, Maybe (Pause s())) 
step s (Mutate f k) = step (f s) k 
step s (Return()) = (s, Nothing) 
step s (Yield k) = (s, Just k) 

Sie könnten auch fahren definieren dies direkt unter Verwendung

data Free f a = Pure a | Free (f (Free f a)) 

(aus meinem Paket 'frei') mit

data Op s a = Mutate (s -> s) a | Yield a 

dann haben wir bereits eine Monade für Pause

type Pause s = Free (Op s) 

und müssen nur den Smart-Konstrukteure und Stepper definieren.

Es schneller machen.

Nun sind diese Implementierungen leicht zu verstehen, aber sie haben nicht das schnellste Betriebsmodell. Insbesondere ergeben Linksverwendungen von (>> =) asymptotisch langsameren Code.

Um zu umgehen, dass Sie die Codensity Monad auf Ihre vorhandene kostenlose Monade anwenden können, oder einfach die 'Church free' Monade direkt verwenden, von denen ich ausführlich auf meinem Blog beschreibe.

http://comonad.com/reader/2011/free-monads-for-less/

http://comonad.com/reader/2011/free-monads-for-less-2/

http://comonad.com/reader/2011/free-monads-for-less-3/

Das Ergebnis der Kirche codierte Version des Frei Monade Anwendung ist, dass Sie eine einfache Vernunft über Modell für den Datentyp, und Sie immer noch erhalten ein schnelles Bewertungsmodell.

+1

Es gibt eine Menge Großartigkeit in dieser Antwort. Wo kann ich mehr über freie Monaden lesen? (Ich weiß, es gibt Sachen in "Datentypen à la carte", aber ich bin auf der Suche nach mehr) –

+0

Sehr schön. Obwohl mir ehird besser gefallen hat. Ich werde später die Blog-Referenzen überprüfen ... – MathematicalOrchid

+1

http://www.haskell.org/haskellwiki/Free_structure führt kostenlose Monaden recht gut ein. Ich rede auch auf meinem Blog ziemlich oft über sie, aber der relevante Inhalt ist diffuser. –

31

So würde ich gehen, mit frei Monaden. Äh, ähm, was sind sie? Sie sind Bäume mit Aktionen an den Knoten und Werten an den Blättern, wobei wie Baumtransplantation wirkt.

data f :^* x 
    = Ret x 
    | Do (f (f :^* x)) 

Es ist nicht ungewöhnlich F * X für so etwas in der Mathematik, daher meine verschroben Infix Typnamen zu schreiben.Um eine Instanz zu erstellen, brauchen Sie nur f, um etwas zu sein, das Sie überlisten können: alle Functor werden tun.

instance Functor f => Monad ((:^*) f) where 
    return = Ret 
    Ret x >>= k = k x 
    Do ffx >>= k = Do (fmap (>>= k) ffx) 

das ist nur „gelten k bei allen Blättern und Transplantat in den resultierenden Bäume“. Diese Can Trees repräsentieren Strategien für interaktive Berechnungen: Der gesamte Baum deckt jede mögliche Interaktion mit der Umgebung ab, und die Umgebung wählt aus, welchen Pfad in dem Baum zu folgen. Warum sind sie frei? Sie sind nur Bäume, und es gibt keine interessante Gleichungstheorie darüber, welche Strategien zu welchen anderen Strategien äquivalent sind.

Jetzt haben wir ein Kit für die Erstellung von Functors, die einer Reihe von Befehlen entsprechen, die wir ausführen können. Dieses Ding

data (:>>:) s t x = s :? (t -> x) 

instance Functor (s :>>: t) where 
    fmap k (s :? f) = s :? (k . f) 

fängt die Idee, einen Wert in x nach dem Aufstehen ein Befehl mit Eingabetyp s und Ausgabetyp t. Dazu müssen Sie eine Eingabe in s auswählen und erklären, wie Sie den Wert in x mit der Ausgabe des Befehls in t fortsetzen können. Um eine Funktion über so etwas abzubilden, klebst du sie auf die Fortsetzung. Bisher Standardausstattung. Für unser Problem, können wir jetzt zwei functors definieren:

type Modify s = (s -> s) :>>:() 
type Yield  =() :>>:() 

Es ist wie habe ich nur die Werttypen abgewertet für die Befehle, die wir in der Lage sein zu wollen!

Jetzt stellen wir sicher, dass wir eine Wahl zwischen diesen Befehlen anbieten können. Wir können zeigen, dass eine Auswahl zwischen Funktoren einen Funktor ergibt. Weitere Standardausrüstung.

So, Modify s :+: Yield repräsentiert die Wahl zwischen Modifizieren und Ergeben. Jede Signatur einfacher Befehle (die mit der Welt in Bezug auf Werte kommunizieren anstatt die Berechnungen zu manipulieren) kann auf diese Weise in einen Funktor umgewandelt werden. Es ist ein Ärger, dass ich es von Hand machen muss!

Das gibt mir deine Monade: die freie Monade über die Signatur von modifizieren und ergeben.

Ich kann die Modify und Yield-Befehle als one-do-then-return definieren. Abgesehen davon, dass der Dummy-Eingang für yield ausgehandelt wird, ist das nur mechanisch.

mutate :: (s -> s) -> Pause s() 
mutate f = Do (L (f :? Ret)) 

yield :: Pause s() 
yield = Do (R (() :? Ret)) 

Die step Funktion gibt dann eine Bedeutung für die Strategie Bäume. Es ist ein Steueroperator, eine Berechnung (vielleicht) von einer anderen konstruierend.

step :: s -> Pause s() -> (s, Maybe (Pause s())) 
step s (Ret())   = (s, Nothing) 
step s (Do (L (f :? k))) = step (f s) (k()) 
step s (Do (R (() :? k))) = (s, Just (k())) 

step Die Funktion läuft die Strategie, bis es entweder mit einem Ret beendet ist, oder es ergibt sich, Mutieren der Zustand, wie es geht.

Das allgemeine Verfahren geht wie folgt: trennen die Befehle (in Bezug auf die Interaktion Werte) von den Steuer Operatoren (Manipulieren Berechnungen); Baue die freie Monade von "Strategy Trees" über die Signatur von Befehlen (kurbele den Griff); Implementieren der Steueroperatoren durch Rekursion über die Strategiebäume.

+3

Die Auszeichnung für die meisten abstrakten Antwort geht an ... – luqui

+2

Ich dachte, es könnte sein nützlich, um das Muster freizulegen und das Kit zu erstellen, mit dem Sie es einfach instanziieren können. Natürlich ist es ziemlich unangenehm mit (Do (L (f:? K))) Mustern zu arbeiten. Das mache ich normalerweise mit "Muster-Synonymen" besser lesbar. Nach diesem Muster (oder seiner schnelleren Ausarbeitung) sollte weniger Arbeit sein. Vielleicht werde ich es so machen. – pigworker

+1

Definitiv das abstrakteste. Persönlich hatte ich Mühe, ihr zu folgen, aber ich bin mir sicher, dass es irgendjemand irgendwo faszinierend finden wird. – MathematicalOrchid

11

Hat Ihr Typ Signaturen nicht genau übereinstimmen, aber sicherlich einfach:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} 
import Control.Monad.State 

newtype ContinuableT m a = Continuable { runContinuable :: m (Either a (ContinuableT m a)) } 
instance Monad m => Monad (ContinuableT m) where 
    return = Continuable . return . Left 
    Continuable m >>= f = Continuable $ do 
     v <- m 
     case v of 
      Left a -> runContinuable (f a) 
      Right b -> return (Right (b >>= f)) 

instance MonadTrans ContinuableT where 
    lift m = Continuable (liftM Left m) 

instance MonadState s m => MonadState s (ContinuableT m) where 
    get = lift get 
    put = lift . put 

yield :: Monad m => ContinuableT m a -> ContinuableT m a 
yield = Continuable . return . Right 

step :: ContinuableT (State s) a -> s -> (Either a (ContinuableT (State s) a), s) 
step = runState . runContinuable 

-- mutate unnecessary, just use modify 
8

Hinweis: Diese Antwort available als gebildete Haskell-Datei an Gist ist.

Ich habe diese Übung sehr genossen. Ich habe versucht, es zu tun, ohne mir die Antworten anzusehen, und es hat sich gelohnt. Es hat viel Zeit gekostet, aber das Ergebnis ist überraschend nahe zwei der anderen Antworten, sowie monad-coroutine Bibliothek. Also ich denke, das ist eine natürliche Lösung für dieses Problem. Ohne diese Übung würde ich nicht verstehen, wie monad-coroutine wirklich funktioniert.

Um zusätzlichen Wert hinzuzufügen, erkläre ich die Schritte, die mich schließlich zur Lösung geführt haben.

den Zustand Monade erkennen

Da wir mit Staaten zu tun haben, es wir nach Mustern suchen, die effektiv durch den Staat Monade beschrieben werden kann. Insbesondere ist s - s isomorph zu s -> (s,()), so dass es durch State s() ersetzt werden könnte. Und Funktion des Typs s -> x -> (s, y) kann zu x -> (s -> (s, y)) umgedreht werden, das ist eigentlich x -> State s y. Dies führt uns zu aktualisierten Signaturen

mutate :: State s() - Pause s() 
step :: Pause s() - State s (Maybe (Pause s())) 

Generalisierung

Unsere Pause Monade derzeit vom Staat parametrisiert. Aber jetzt sehen wir, dass wir den Staat für nichts wirklich brauchen, noch verwenden wir irgendwelche Spezifika der Staatsmonade. So konnten wir versuchen, eine allgemeine Lösung zu machen, die von jedem Monade parametrisiert:

mutate :: (Monad m) = m() -> Pause m() 
yield :: (Monad m) = Pause m() 
step :: (Monad m) = Pause m() -> m (Maybe (Pause m())) 

Auch wir könnten versuchen, indem sie jede Art von Wert mutate und step allgemeinere zu machen, nicht nur (). Und durch die Erkenntnis, dass Maybe a isomorph ist zu Either a() können wir endlich unsere Unterschriften verallgemeinern zu

mutate :: (Monad m) = m a -> Pause m a 
yield :: (Monad m) = Pause m() 
step :: (Monad m) = Pause m a -> m (Either (Pause m a) a) 

so dass step kehrt der Zwischenwert der Berechnung.

Monad Transformator

Jetzt sehen wir, dass wir tatsächlich eine Monade von einem Monade zu machen versuchen - fügen Sie einige zusätzliche Funktionen. Dies wird normalerweise monad transformer genannt. Darüber hinaus ist die Signatur mutate genau gleich lift von MonadTrans. Höchstwahrscheinlich sind wir auf dem richtigen Weg.

Der letzte Monade

Die step Funktion scheint der wichtigste Teil unserer Monade zu sein, ist es genau das, was wir brauchen, definiert. Vielleicht könnte dies die neue Datenstruktur sein? Lassen Sie uns versuchen:

import Control.Monad 
import Control.Monad.Cont 
import Control.Monad.State 
import Control.Monad.Trans 

data Pause m a 
    = Pause { step :: m (Either (Pause m a) a) } 

Wenn der Either Teil Right ist, es ist nur ein monadischen Wert, ohne Suspensionen. Dies führt uns, wie die easist Sache zu implementieren - die lift Funktion von MonadTrans:

instance MonadTrans Pause where 
    lift k = Pause (liftM Right k) 

und mutate ist einfach eine Spezialisierung:

mutate :: (Monad m) => m() -> Pause m() 
mutate = lift 

Wenn der Either Teil Left ist, stellt es die fortgesetzte Berechnung nach einer Suspension. Lassen Sie sich also eine Funktion für das Erstellen:

suspend :: (Monad m) => Pause m a -> Pause m a 
suspend = Pause . return . Left 

Jetzt yield ing eine Berechnung einfach ist, wir nur mit einer leeren Berechnung auszusetzen:

yield :: (Monad m) => Pause m() 
yield = suspend (return()) 

Dennoch sind wir den wichtigsten Teil fehlen. Die Monad Instanz. Lassen Sie uns es beheben. return Die Implementierung ist einfach, wir die innere Monade nur heben. Implementieren >>= ist ein bisschen schwieriger. Wenn der ursprüngliche Pause Wert nur ein einfacher Wert (Right y) war, dann wickeln wir nur f y als das Ergebnis. Wenn es sich um eine pausierte Berechnung ist, die fortgesetzt werden kann (Left p), steigen wir rekursiv hinein.

instance (Monad m) => Monad (Pause m) where 
    return x = lift (return x) -- Pause (return (Right x)) 
    (Pause s) >>= f 
     = Pause $ s >>= \x -> case x of 
      Right y  -> step (f y) 
      Left p  -> return (Left (p >>= f)) 

Testing

Lasst uns versuchen, einige Modellfunktion zu machen, und Updates Zustand verwendet, wodurch man während in der Berechnung:

test1 :: Int -> Pause (State Int) Int 
test1 y = do 
      x <- lift get 
      lift $ put (x * 2) 
      yield 
      return (y + x) 

Und eine Hilfsfunktion, die die Monade debuggt - druckt seine Zwischenschritte die Konsole:

debug :: Show s => s -> Pause (State s) a -> IO (s, a) 
debug s p = case runState (step p) s of 
       (Left next, s')  -> print s' >> debug s' next 
       (Right r, s')  -> return (s', r)  

main :: IO() 
main = do 
    debug 1000 (test1 1 >>= test1 >>= test1) >>= print 

Das Ergebnis ist

2000 
4000 
8000 
(8000,7001) 

wie erwartet.

Koroutinen und Monade-Koroutine

Was wir implementiert haben, ist eine ganz allgemeine monadischen Lösung, die Coroutines implementiert. Vielleicht nicht überraschend, hatte jemand die Idee vor :-), und schaffte das monad-coroutine Paket. Weniger überraschend, es ist ziemlich ähnlich zu dem, was wir geschaffen haben.

Das Paket verallgemeinert die Idee noch weiter. Die fortlaufende Berechnung wird in einem beliebigen Funktor gespeichert. Dies ermöglicht suspend viele Variationen wie mit suspendierten Berechnungen zu arbeiten. Zum Beispiel, um pass a value an den Aufrufer von resume (die wir step genannt) oder wait for a value vorgesehen werden, um fortzufahren usw.