2012-08-29 14 views
8

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. printfIO() 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.

+0

Da Sie explizit nicht nach einer 'Applicative' Lösung suchen, füge ich dies stattdessen in den Kommentaren hinzu: https://gist.github.com/f8e5d1ecf20ea09a8b36 –

Antwort

2

WARNUNG

Vorzugsweise variadische Funktionen nicht für diese Art von Arbeit. Sie haben nur eine endliche Anzahl von Konstruktoren, also scheinen intelligente Konstruktoren keine große Sache zu sein. Die ~ 10-20 Zeilen, die Sie benötigen, sind viel einfacher und wartungsfreundlicher als eine variadische Lösung. Auch eine anwendungsorientierte Lösung ist viel weniger Arbeit.

WARNUNG

Die Monade/applicative in Kombination mit variadische Funktionen ist das Problem. Das 'Problem' ist der Argumentadditionsschritt, der für die variadische Klasse verwendet wird. Die Basisklasse würde aussehen wie

class Variadic f where 
    func :: f 
    -- possibly with extra stuff 

, wo Sie es variadische machen durch Instanzen des Formulars

instance Variadic BaseType where ... 
instance Variadic f => Variadic (arg -> f) where ... 

, welche brechen würde, wenn Sie Monaden zu verwenden beginnen würde. Das Hinzufügen der Monade in der Klassendefinition würde die Erweiterung von Argumenten verhindern (Sie würden :: M (arg -> f) für einige Monaden M erhalten). Das Hinzufügen zum Basisfall würde die Verwendung der Monade in der Erweiterung verhindern, da es (soweit ich weiß) nicht möglich ist, der Erweiterungsinstanz die monadische Einschränkung hinzuzufügen. Für einen Hinweis auf eine komplexe Lösung siehe P.S.

Die Lösungsrichtung der Verwendung einer Funktion, die (Env -> Foo) ergibt, ist vielversprechender. Der folgende Code erfordert weiterhin eine :: Foo Typ-Einschränkung und verwendet der Kürze halber eine vereinfachte Version von Env/ID.

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-} 

module Test where 

data Env = Env 
data ID a = ID 
data Foo 
    = Foo0 
    | Foo1 Char 
    | Foo2 Char Bool 
    | Foo3 Char Bool Char 
    deriving (Eq, Ord, Show) 

class InEnv a where 
    resolve :: Env -> ID a -> a 
instance InEnv Char where 
    resolve _ _ = 'a' 
instance InEnv Bool where 
    resolve _ _ = True 

Die Typenfamilien-Erweiterung wird verwendet, um die Übereinstimmung strenger/besser zu machen. Jetzt die Variadic-Funktionsklasse.

class MApp f r where 
    app :: Env -> f -> r 

instance MApp Foo Foo where 
    app _ = id 
instance (MApp r' r, InEnv a, a ~ b) => MApp (a -> r') (ID b -> r) where 
    app env f i = app env . f $ resolve env i 
    -- using a ~ b makes this instance to match more easily and 
    -- then forces a and b to be the same. This prevents ambiguous 
    -- ID instances when not specifying there type. When using type 
    -- signatures on all the ID's you can use 
    -- (MApp r' r, InEnv a) => MApp (a -> r') (ID a -> r) 
    -- as constraint. 

Die Umwelt Env ist ausdrücklich im Wesentlichen übergeben, die Reader Monade die Probleme zwischen Monaden und variadische Funktionen ausgepackt zu verhindern (für die State Monade der Entschlossenheit Funktion eine neue Umgebung zurückkehren sollte). Das Testen mit app Env Foo1 ID :: Foo führt zu dem erwarteten Foo1 'a'.

P.S. Sie können monadische Variadic-Funktionen (teilweise) erhalten, aber es erfordert, dass Sie Ihre Funktionen (und Ihren Geist) auf eine sehr seltsame Art und Weise verbiegen. Die Art, wie ich solche Dinge zum Laufen bringe, besteht darin, alle variadischen Argumente in eine heterogene Liste zu "falten". Das Auspacken kann dann monadisch erfolgen. Obwohl ich einige Dinge wie diese gemacht habe, rate ich dringend davon ab, solche Dinge in tatsächlichem (gebrauchtem) Code zu verwenden, da es schnell unverständlich und unwartbar wird (ganz zu schweigen von den Typfehlern, die man bekommen würde).

+0

Vielen Dank für Ihre Einsichten. Sind Ihre "MApp" -Klasse und ihre Instanzen nicht im Wesentlichen identisch mit meiner nicht-monadischen 'VarC'? – Deewiant

+0

@Deewiant, ist in der Tat. Ich denke, es ist das Beste, was in diesem Fall getan werden kann.Aber ich bezweifle, dass es möglich ist, eine monadische variadic Funktion zu machen, ohne einige fiese Tricks zu machen, auf die ich eine Antwort schreiben könnte, wenn Sie wollen. – Laar

+0

Bitte tun! Ich plane nicht, es zu benutzen, aber ich bin daran interessiert, welche Art von Tricks es erfordern würde. – Deewiant