2017-03-28 3 views
4

Ich habe Probleme mit Elms Mangel an Monaden. Eine Bibliothek, die die staatliche Monade für Elm implementiert (http://package.elm-lang.org/packages/folkertdev/elm-state/latest/State), hat mir ziemlich geholfen.Wie kombiniere ich Ergebnis und Status in Elm?

Das Problem ist, dass ich jetzt in eine Situation geraten bin, wo ich abwechselnd geschachtelt habe Ergebnis-und State-Typen, wenn ich nur eine von jedem haben will.

Ich habe versucht, eine Funktion mit der folgenden Signatur zu schreiben, aber es scheint unmöglich, weil das innere Ergebnis nur bekannt ist, sobald der äußere Zustand ausgewertet wird.

join : Result a (State s (Result a (State s x))) -> Result a (State s x) 

Vielleicht, es würde funktionieren, wenn ich das Ergebnis innerhalb des Staates, in dem Rückgabewert gesetzt, aber das wäre einen Dummy-Staat im Fall des Außen Ergebnis Err ist zu erzeugen.

Ich denke, die richtige Idee wäre, etwas zu machen, das sowohl Ergebnis als auch Zustand ist. Kann jemand, der mit Haskell-Monade-Transformatoren vertraut ist, erklären, wie sie diese Art von Problem lösen oder eine alternative Lösung vorschlagen?

ist hier eine grobe Version von einem Ort, wo das Problem entsteht:

generateConstraints environment value 
    |> Result.map (State.map (\(value, valueC) -> 
    Result.map 
     (State.map2 (\this (body, bodyC) -> 
     (this 
     , valueC++ bodyC++ [(this, body)] 
     )) 
     freshTypevar) 
     (generateConstraints (extend environment name value) body)) 
) 
+1

Monade-Transformatoren sind eine Art von Schmerzen, wenn Sie die gleiche Art von Transformator mischen. Ich verwende normalerweise monad typeclasses, aber diese scheitern, wenn der Typ mehrmals erscheint. Eine Lösung dafür ist, die bestimmten Kontexte neu zu tippen, die Sie benötigen. –

+0

Ich brauche nicht wirklich die gleiche Monade mehrmals. Das Code-Snippet, das ich eingefügt habe, ist ein Fall von 'generateConstraints', der 'Result String (State Int (Typ, Listenbeschränkung))' zurückgibt. Rekursion funktioniert gut, aber die Constraint-Generierung für Let Bindings, die die Monaden durcheinander bringen, ist anders. – Joonazan

+0

Ich weiß immer noch nicht, wie ich den Zustand kombinieren und möglicherweise falsch sein kann, aber das besondere Beispiel, das ich mir gegeben habe, löste sich auf wundersame Weise, weil ich in der Lage war, alle außergewöhnlichen Ergebnisse daraus zu entfernen. – Joonazan

Antwort

2

Ich schrieb eine Monade, die beides ist. Ich musste die Fähigkeit zum Scheitern opfern, bevor der Staat berührt wurde, denn ich muss danach scheitern können.

type alias Infer a = State Int (Result String a) 

infer : a -> Infer a 
infer x = 
    State.state (Ok x) 

map : (a -> value) -> Infer a -> Infer value 
map f x = 
    State.map (Result.map f) x 

andThen : (a -> Infer b) -> Infer a -> Infer b 
andThen f x = 
    State.andThen 
    (\r -> case r of 
     Ok v -> f v 
     Err e -> State.state <| Err e 
    ) 
    x 

andMap : Infer y -> Infer (y -> z) -> Infer z 
andMap y = 
    andThen (\g -> map g y) 

map2 : (a -> b -> c) -> Infer a -> Infer b -> Infer c 
map2 f x y = 
    map f x 
    |> andMap y 

map3 : (a -> b -> c -> d) -> Infer a -> Infer b -> Infer c -> Infer d 
map3 f a b c = 
    map2 f a b 
    |> andMap c 

map4 : (a -> b -> c -> d -> e) -> Infer a -> Infer b -> Infer c -> Infer d -> Infer e 
map4 f a b c d = 
    map3 f a b c 
    |> andMap d 
3

Kann jemand, der mit Haskell Monade Transformatoren vertraut ist zu erklären, wie sie diese Art von Problem oder vorschlagen, eine alternative Lösung zu lösen?

Nun, ich kann es zumindest versuchen. Dies ist, wie Ihre Art sieht Haskell direkt übersetzt:

type EffM a s x = Either a (State s x) 

Eine recht offensichtliche Beobachtung ist, dass es nicht ein Monade Transformator. Das ist, wie ein Transformator würde wie folgt aussehen:

type TransM a s x = EitherT a (State s) x 

Wie Sie sehen können, ist die einzige Veränderung die T und die Tatsache, dass x außerhalb der Pars ist. Der letzte Teil ist wesentlich für das Verständnis des Transformator-Ansatzes.

Die Kernidee ist, dass die State ein Teil der Ergebnisses Produktion ist unabhängig davon, ob die Either Ergebnisse in „Erfolg“ oder „nicht bestanden“, während in Ihrem Fall „nicht bestanden“ produzieren bedeutet, dass der Betrieb niemals berührt wird. Ich müsste mir genauer überlegen, was das in der Praxis bedeutet, aber intuitiv ist der Transformator-Ansatz genau das, was man bei der Arbeit mit typischem, imperativem Code im Hinterkopf hat.

Jetzt, wenn Sie einen solchen Transformator verwenden, wird join tatsächlich kostenlos zur Verfügung gestellt, als Folge der Anpassung in die Monad-Schnittstelle.

import Control.Monad.State 
import Control.Monad.Trans.Either 
import Control.Monad 

type Eff e s a = EitherT e (State s) a 

-- type the following in REPL 

λ :t join 
join :: Monad m => m (m a) -> m a 

λ :t join :: Eff e s (Eff e s a) -> Eff e s a 
join :: Eff e s (Eff e s a) -> Eff e s a 
    :: Eff e s (Eff e s a) -> Eff e s a 

-- the slightly cryptic output above means that it typechecks correctly 

Das ist also, wie Haskell sie löst. Nun ist es natürlich möglich, einen spezialisierten EitherState Typ auf diese Weise zu schreiben (obwohl ich diese beiden in allen Beispielen persönlich in StateEither umdrehen würde - fühlt sich natürlicher an) und spiegelt, was die Implementierung von join für die entsprechenden Transformatoren tun würde. Ich weiß nicht, ob das Schreiben von EitherT speziell in Elm möglich ist.


Ein möglicher Ansatz. Es gibt andere, Rekursionssysteme/Free, die wahrscheinlich in den kommenden Jahren zu sehen sein werden. Die inhärente Reihenfolge der Effekte-Stapelung erweist sich als problematischer als es zunächst scheint.

Es ist auch keine Monad, zumindest in dem Sinne, dass die x nicht die direkte Art in der Monade Instanz sein kann (weil Either offensichtlich als eine im Fach Fall handeln kann).

+1

Sie waren ein bisschen zu langsam; Ich habe das Problem bereits selbst gelöst. Würde in meinem Fall ein Staat arbeiten? Zumindest so, wie ich es geschrieben habe, funktioniert es nur umgekehrt. – Joonazan

+0

'Join' kommt kostenlos, weil es ziemlich genau so ist wie' >> = ', oder? Auch in meinem andThen gibt es dann den sehr hässlichen 'Err e -> State.state <| Err e'. Ich denke zumindest in Elm muss es so gemacht werden, weil 'Err e -> x 'nicht tippt. – Joonazan

+1

@Joonazan Ich habe deinen Kommentar gesehen, aber ich dachte, ich könnte trotzdem antworten. Ich denke, es würde in Ihrem Fall funktionieren; Die Reihenfolge dieser Transformatoren sollte keine Rolle spielen. Und ja, "Join" ist einfach "Join x = x >> = id". Die "Hässlichkeit" Ihrer Implementierung ergibt sich hauptsächlich aus der Tatsache, dass Sie beide Ebenen gleichzeitig auspacken müssen, während Transformatoren sie aufteilen und später die Komposition ermöglichen können. –

Verwandte Themen