2015-09-04 11 views
5

Ich versuche, den folgenden Stateful Imperativ-Code in Haskell zu konvertieren.Stateful Schleife mit verschiedenen Arten von Brüchen

while (true) { 
    while (get()) { 
    if (put1()) { 
     failImmediately(); 
    } 
    } 
    if (put2()) { 
    succeedImmediately(); 
    } 
} 

Sowohl die put1 und put2 lesen einen Zustand des Systems und ändern. get kann der Einfachheit halber nur den Zustand ablesen. failImmediately sollte aus der Endlosschleife ausbrechen und eine Art Ergebnis anzeigen, succeedImmediately sollte ebenfalls ausbrechen aber ein anderes Ergebnis ergeben.

Was ich versucht war zu verwenden State Env Result wo Env den Zustand der Umwelt dargestellt und Result war so etwas wie Either Failure Success für einige benutzerdefinierte Failure und Success.

Ich kämpfe mit der Anforderung, dass der gesamte resultierende Ausdruck in die Failure/Success einstürzen sollte, sobald einer von ihnen produziert wird (die Schleife zu brechen) und ansonsten weitermachen.

Eine Idee war ich Either Exit() wo data Exit = Success | Failure verwenden hatte und verwenden StateT irgendwie auf Left der Either als ob Either wurde die Monade ist gekettet, das heißt ignoriert alle nachfolgenden Aktionen zu verhalten.

Ich würde wirklich jede Inspiration oder ein Beispiel von Haskell-Code schätzen, der das gleiche Verhalten wie das obige Snippet erreichen würde.

Bearbeiten: verfeinerte Version verschoben, um eine separate Frage "Stateful computation with different types of short-circuit (Maybe, Either)".

+1

Sie sollten schauen Sie in [ 'EitherT (State Env Ergebnis)'] (https://hackage.haskell.org/package/either-4.4.1/docs/Kontrolle-M onad-Trans-Entweder.html). Lassen Sie mich wissen, wenn dieser Hinweis nicht genug ist und Sie weitere Details benötigen :) – Cactus

+0

Ich habe das Gefühl, dass dies das sein könnte, was ich brauche, außer dass ich nicht die geringste Ahnung habe, wie ich es in diesem Szenario verwenden soll :(so nett zu erarbeiten Ich wäre dir so dankbar – jakubdaniel

Antwort

6

das Kit von @ Antwort des chi verwenden, nur hervorheben, dass Sie den direkten die volle Leistung des ContT, nicht brauchen Semantik von EitherT -kurzes Schließen genügt:

import Control.Monad.Trans.Either 

data Result a = Failure | Success a 

foo :: EitherT (Result Int) IO Int 
foo = forever $ do 
    whileM get $ do 
     whenM put1 $ do 
      left Failure 
    whenM put2 $ do 
     left $ Success 42 

run :: (Monad m) => EitherT (Result a) m a -> m (Maybe a) 
run act = do 
    res <- runEitherT act 
    return $ case res of 
     Left Failure -> Nothing 
     Left (Success x) -> Just x 
     Right x -> Just x 

-- whenM/whileM and get/put1/put2 as per @chi's answeer 
+1

Ich stimme zu - 'ContT' ist ein bisschen Overkill dafür. – chi

+0

Danke, das scheint meine ursprüngliche Frage zu lösen (wenn ich IO durch State ersetze). Könnte es nicht einfacher sein, wenn man den "Edit" meines ursprünglichen Posts betrachtet? – jakubdaniel

+0

Es gibt wahrscheinlich eine 'MonadPlus'-basierte Lösung ... vielleicht eine eigene Frage wert (mit dem reduzierten Frageumfang) – Cactus

4

Eine fast wörtliche, nicht elegante, aber effektive Übersetzung.

Wir nutzen die ContT Monade Transformator, um den Effekt von "frühe Rückkehr" zu erreichen. Das heißt, wir wollen unsere Schleifen jederzeit aufbrechen können. Dies wird erreicht, indem callCC $ \exit -> ... verwendet wird, was ungefähr exit unsere magische Funktion macht, die uns sofort aus den inneren Blöcken entkommen lässt.

import Control.Monad.Cont 

action :: IO String 
action = flip runContT return $ callCC $ \exit -> 
    forever $ do     -- while (true) 
     let loop = do 
      r1 <- lift $ get  -- if (get()) 
      when r1 $ do 
       r2 <- lift $ put1 
       when r2 $   -- if (put1()) 
        exit "failImmediately" 
       loop    -- "repeat while" 
     loop 
     r3 <- lift $ put2 
     when r3 $ 
     exit "succeedImmediately" 

get :: IO Bool 
get = readLn 

put1 :: IO Bool 
put1 = putStrLn "put1 here" >> readLn 

put2 :: IO Bool 
put2 = putStrLn "put2 here" >> readLn 

main :: IO() 
main = action >>= putStrLn 

Wir haben auch einige benutzerdefinierte Helfer definieren können, um den Code zu beschönigen:

action2 :: IO String 
action2 = flip runContT return $ callCC $ \exit -> 
    forever $ do    -- while (true) 
     whileM get $    -- while(get()) 
     whenM put1 $   -- if (put1()) 
      exit "failImmediately" 
     whenM put2 $    -- if (put2()) 
     exit "succeedImmediately" 

whenM :: (MonadTrans t, Monad m, Monad (t m)) => m Bool -> t m() -> t m() 
whenM condition a = do 
    r <- lift condition 
    when r a 

whileM :: (MonadTrans t, Monad m, Monad (t m)) => m Bool -> t m() -> t m() 
whileM condition a = whenM condition (a >> whileM condition a) 
+0

Danke, ich hoffte, dass es einen Weg gab, all das Kontrollflussartige Zeug (wann) könnte in einer Abstraktion versteckt sein (wie vielleicht beide Monaden verstecken) auch die beiden Ergebnisse sind andere Typen, dieses Beispiel nimmt an, dass beide Ergebnisse IO-String sind Ich wünsche, dass das Ganze entweder Erfolg oder Fehler zurückgibt oder nie endet. – jakubdaniel

+0

Wenn die Ergebnisse von verschiedenen Typen sind, können Sie entweder 'A B 'anstelle von' String 'verwenden, so dass Sie diesen nach einigen' Links/Rechts'-Umschlägen als allgemeinen Typ verwenden können. Das ist dir wahrscheinlich schon bekannt. – chi

+0

Es könnte eine elegantere Art geben, alles mit einem cleveren Looping-Kombinator aus einer Bibliothek zu abstrahieren. Der Code ist jedoch ziemlich zwingend und statusbehaftet: Jede Zeile beeinflusst den Zustand irgendwie. Das Imperativ-Snippet ist auch ziemlich einfach - ich weiß nicht, ob wir das in Bezug auf Klarheit übertreffen können. Vielleicht kommt jemand anderes mit einer besseren Lösung. – chi