Ich versuche, eine Variadic-Funktion mit einem monadischen Rückgabetyp zu machen, deren Parameter auch den monadischen Kontext erfordern. (Ich bin nicht sicher, wie man diesen zweiten Punkt beschreiben: z. B. printf
IO()
zurückkehren können, aber es ist anders, dass seine Parameter gleich behandelt werden, ob es am Ende IO()
oder String
ist)Haskell: Wie schreibe ich eine monadische Variadic-Funktion, mit Parametern mit dem monadischen Kontext
Im Grunde habe ich eine bekam Datenkonstruktor, der beispielsweise zwei Char
Parameter annimmt. Ich möchte stattdessen zwei Zeigerarsenal ID Char
Argumente zur Verfügung stellen, die automatisch von einer umschließenden State
Monade über eine Typklasseninstanz decodiert werden können. Also, anstatt get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2))
zu tun, möchte ich tun.
Was folgt ist, was ich bisher habe, Literacy Haskell Stil für den Fall, dass jemand es kopieren und damit durcheinander bringen will.
Zunächst wird die grundlegende Umgebung:
> {-# LANGUAGE FlexibleContexts #-}
> {-# LANGUAGE FlexibleInstances #-}
> {-# LANGUAGE MultiParamTypeClasses #-}
> import Control.Monad.Trans.State
> data Foo = Foo0
> | Foo1 Char
> | Foo2 Bool Char
> | Foo3 Char Bool Char
> deriving Show
> type Env = (String,[Bool])
> newtype ID a = ID {unID :: Int}
> deriving Show
> class InEnv a where envGet :: Env -> ID a -> a
> instance InEnv Char where envGet (s,_) i = s !! unID i
> instance InEnv Bool where envGet (_,b) i = b !! unID i
Einige Testdaten für die Bequemlichkeit:
> cid :: ID Char
> cid = ID 1
> bid :: ID Bool
> bid = ID 2
> env :: Env
> env = ("xy", map (==1) [0,0,1])
ich diese nicht-monadische Version haben, die einfach die Umwelt als erste nimmt Parameter. Das funktioniert gut, aber es ist nicht das, wonach ich suche. Beispiele:
$ mkFoo env Foo0 :: Foo
Foo0
$ mkFoo env Foo3 cid bid cid :: Foo
Foo3 'y' True 'y'
(I funktionale Abhängigkeiten verwenden könnte oder Familien geben, um loszuwerden, die Notwendigkeit für die :: Foo
Typenannotationen Vorerst bin ich nicht viel Aufhebens darüber, da dies nicht das, was ich bin interessiert. jedenfalls.)
> mkFoo :: VarC a b => Env -> a -> b
> mkFoo = variadic
>
> class VarC r1 r2 where
> variadic :: Env -> r1 -> r2
>
> -- Take the partially applied constructor, turn it into one that takes an ID
> -- by using the given state.
> instance (InEnv a, VarC r1 r2) => VarC (a -> r1) (ID a -> r2) where
> variadic e f = \aid -> variadic e (f (envGet e aid))
>
> instance VarC Foo Foo where
> variadic _ = id
Jetzt möchte ich eine variadic Funktion, die in der folgenden Monade läuft.
> type MyState = State Env
Und im Grunde habe ich keine Ahnung, wie ich vorgehen sollte. Ich habe versucht, die Typklasse auf verschiedene Arten auszudrücken (variadicM :: r1 -> r2
und variadicM :: r1 -> MyState r2
), aber es ist mir nicht gelungen, die Instanzen zu schreiben. Ich habe auch versucht, die nicht-monadische Lösung oben so anzupassen, dass ich irgendwie mit einem Env -> Foo
"ende", den ich dann leicht in einen MyState Foo
verwandeln konnte, aber kein Glück dort auch.
Was folgt, ist mein bisher bester Versuch.
> mkFooM :: VarMC r1 r2 => r1 -> r2
> mkFooM = variadicM
>
> class VarMC r1 r2 where
> variadicM :: r1 -> r2
>
> -- I don't like this instance because it requires doing a "get" at each
> -- stage. I'd like to do it only once, at the start of the whole computation
> -- chain (ideally in mkFooM), but I don't know how to tie it all together.
> instance (InEnv a, VarMC r1 r2) => VarMC (a -> r1) (ID a -> MyState r2) where
> variadicM f = \aid -> get >>= \e -> return$ variadicM (f (envGet e aid))
>
> instance VarMC Foo Foo where
> variadicM = id
>
> instance VarMC Foo (MyState Foo) where
> variadicM = return
Es ist für Foo0 und Foo1 funktioniert, aber nicht darüber hinaus:
$ flip evalState env (variadicM Foo1 cid :: MyState Foo)
Foo1 'y'
$ flip evalState env (variadicM Foo2 cid bid :: MyState Foo)
No instance for (VarMC (Bool -> Char -> Foo)
(ID Bool -> ID Char -> MyState Foo))
(Hier möchte ich loswerden der Notwendigkeit für die Anmerkung bekommen, aber die Tatsache, dass diese Formulierung benötigt zwei Instanzen für Foo
macht das problematisch.)
Ich verstehe die Beschwerde: Ich habe nur eine Instanz, die von Bool -> Char -> Foo
bis ID Bool -> MyState (ID Char -> Foo)
geht. Aber ich kann nicht die Instanz, die es will, weil ich MyState
dort irgendwo brauche, so dass ich die ID Bool
in eine Bool
verwandeln kann.
Ich weiß nicht, ob ich völlig aus der Spur oder was bin. Ich weiß, dass ich mein grundlegendes Problem (ich möchte nicht meinen Code mit den idGet s
Äquivalenten überall auf der Welt) auf verschiedene Arten lösen, wie zum Beispiel liftA
/liftM
-Style-Funktionen für verschiedene Nummern von ID-Parametern, mit Typen wie (a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret
, aber ich habe zu viel Zeit damit verbracht, darüber nachzudenken. :-) Ich möchte wissen, wie diese variadische Lösung aussehen sollte.
Da Sie explizit nicht nach einer 'Applicative' Lösung suchen, füge ich dies stattdessen in den Kommentaren hinzu: https://gist.github.com/f8e5d1ecf20ea09a8b36 –