2016-04-13 7 views
2

Ich habe mehrere State Monadenaktionen. Einige der Aktionen treffen Entscheidungen basierend auf dem aktuellen Zustand und andere Eingaben erzeugen optional das Ergebnis. Die zwei Arten von Aktionen rufen einander auf.Komponieren von Status- und Statustransformatoraktionen

Ich habe diese beiden Aktionstypen mit State und StateT Maybe modelliert. Das folgende (konstruierte) Beispiel zeigt meinen derzeitigen Ansatz.

{-# LANGUAGE MultiWayIf #-} 

import Control.Monad (guard) 
import Control.Monad.Identity (runIdentity) 
import Control.Monad.Trans.State 

type Producer  = Int -> State [Int] Int 
type MaybeProducer = Int -> StateT [Int] Maybe Int 

produce :: Producer 
produce n 
    | n <= 0 = return 0 

    | otherwise = do accum <- get 
        let mRes = runStateT (maybeProduce n) accum 

        if | Just res <- mRes -> StateT $ const (return res) 
         | otherwise  -> do res <- produce (n - 1) 
               return $ res + n 

maybeProduce :: MaybeProducer 
maybeProduce n = do guard $ odd n 
        modify (n:) 

        mapStateT (return . runIdentity) $ 
         do res <- produce (n - 1) 
          return $ res + n 

Ich habe herausgerechnet, die Kontrollen von den Aktionen zu trennen (so sie in einfache staatlichen Aktionen verwandeln), weil die Prüfung selbst sehr beteiligt ist (80% der Arbeit) und stelle die Bindungen in der Aktion benötigt. Ich möchte nicht die State Aktionen zu StateT Maybe entweder fördern, weil es ein ungenaues Modell darstellt.

Gibt es einen besseren oder eleganeren Weg, den ich vermisse? Insbesondere mag ich nicht die mapStateT/runStateT Duo, aber es scheint notwendig.

PS: Ich weiß, dass das Beispiel ist eigentlich ein Writer, aber ich verwendet State besser, um den realen Fall

+2

'mapStateT' ist völlig in Ordnung, IMO. Ich würde nur "Nur" verwenden. RunIdentity' als Argument. – arrowd

+0

Etwa die gleiche Frage (obwohl mehr theoretisch): http://StackOverflow.com/Questions/4138671/Combining-Statet-and-State-Monads – gcnew

Antwort

1

Ich will nicht spiegeln die State Aktionen StateT Maybe entweder fördern, weil es einen ungenauen stellt Modell.

Was meinen Sie mit "fördern"? Ich kann nicht, welche davon sagen Sie vielleicht:

    die Definitionen der State Aktionen Rewrite
  1. , so dass ihre Art StateT Maybe ist jetzt, auch wenn sie auf Maybe überhaupt nicht verlasse; Verwenden Sie eine Adapterfunktion, die State s a in StateT s Maybe a umwandelt.

Ich bin damit einverstanden (1) mit Ablehnung, aber für mich, das entweder bedeuten:

  • Go für (2). Ein nützliches Werkzeug hierfür ist die Verwendung der mmorph library (blog entry).
  • Schreiben Sie die Aktionen von State s a um Monad m => StateT s m a zu verwenden.

Im zweiten Fall ist der Typ kompatibel mit jedem m Monad erlaubt aber nicht der Code eine bestimmte Base Monade zu übernehmen, so dass Sie erhalten die gleiche Reinheit wie State s a.

Ich würde geben mmorph eine Aufnahme. Beachten Sie, dass:

  • State s a = StateT s Identity a;
  • hoist generalize :: (MFunctor t, Monad m) => t Identity a -> t m a;
  • Und das ist spezialisiert auf hoist generalize :: State s a -> StateT s Maybe a.

EDIT: Es lohnt sich nichts, es ist ein Isomorphismus zwischen den State s a und forall m. StateT s m a Typen, die durch diese Umkehrfunktionen gegeben:

{-# LANGUAGE RankNTypes #-} 

import Control.Monad.Morph 
import Control.Monad.Trans 
import Control.Monad.Trans.State 
import Control.Monad.Identity 

fwd :: (MFunctor t, Monad m) => t Identity a -> t m a 
fwd = hoist generalize 

-- The `forall` in the signature forbids callers from demanding any 
-- specific choice of type for `m`, which allows *us* to choose 
-- `Identity` for `m` here. 
bck :: MFunctor t => (forall m. t m a) -> t Identity a 
bck = hoist generalize 

So sind die Monad m => StateT s m a und mmorph Lösungen sind, effektiv , das Gleiche. Ich bevorzuge hier jedoch mmorph.

+0

Ja, durch "fördern" ich bezog sich auf (1). Das "Monad m => StateT s m a" ist ein netter Trick, aber nicht obszön und ich fühle mich vom API-Standpunkt etwas irreführend. Natürlich macht es total Sinn - "Ich bin nicht abhängig von der Art der inneren Monade". 'mmorph' sieht aus wie das richtige Werkzeug für den Job, danke. – gcnew

+0

"Die' Monade m => StateT s m a 'ist ein netter Trick, aber nicht obszön und ich fühle mich vom API Standpunkt etwas irreführend. " Ich stimme zu, dass es weniger klar ist, aber ich würde sagen, es ist streng genommen nicht irreführend, weil der Typ isomorph zu State s a wäre. Siehe meine Bearbeitung zur Antwort. –