2012-08-29 6 views
8

J'essaie de faire une fonction variadique avec un type de retour monadique, dont les paramètres requièrent également le contexte monadique. (Je ne sais pas comment décrire ce second point:. Par exemple printf peut retourner IO() mais il est différent en ce que ses paramètres sont traités de la même si elle finit par être IO() ou String)Haskell: comment écrire une fonction variadique monadique, avec des paramètres utilisant le contexte monadique

Fondamentalement, j'ai un constructeur de données qui prend, disons, deux Char paramètres. Je souhaite fournir deux arguments de type pointeur ID Char à la place, qui peuvent être décodés automagiquement à partir d'une monade State englobante via une instance de classe de type. Donc, au lieu de faire get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2)), je veux faire fooVariadic Constructor id1 id2. Ce qui suit est ce que j'ai jusqu'ici, le style Literate Haskell au cas où quelqu'un voudrait le copier et le désordre.

En premier lieu, l'environnement de base:

> {-# 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 

Certaines données de test pour plus de commodité:

> cid :: ID Char 
> cid = ID 1 
> bid :: ID Bool 
> bid = ID 2 
> env :: Env 
> env = ("xy", map (==1) [0,0,1]) 

J'ai cette version non-monadique, qui prend simplement l'environnement comme le premier paramètre. Cela fonctionne bien mais ce n'est pas tout à fait ce que je cherche. Exemples:

$ mkFoo env Foo0 :: Foo 
Foo0 
$ mkFoo env Foo3 cid bid cid :: Foo 
Foo3 'y' True 'y' 

(je pourrais utiliser des dépendances fonctionnelles ou d'un type de familles pour se débarrasser de la nécessité pour les :: Foo annotations de type Pour l'instant je ne suis pas embêté à ce sujet, puisque ce n'est pas ce que je suis intéressé. en tout cas.)

> 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 

Maintenant, je veux une fonction variadique qui s'exécute dans la monade suivante.

> type MyState = State Env 

Et fondamentalement, je n'ai aucune idée de comment je devrais procéder. J'ai essayé d'exprimer la classe de type de différentes manières (variadicM :: r1 -> r2 et variadicM :: r1 -> MyState r2) mais je n'ai pas réussi à écrire les instances. J'ai aussi essayé d'adapter la solution non-monadique ci-dessus pour que je finisse d'une manière ou d'une autre avec un Env -> Foo que je pourrais facilement transformer en MyState Foo, mais pas de chance là non plus.

Ce qui suit est ma meilleure tentative jusqu'à présent.

> 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 

Il travaille pour foo0 et Foo1, mais pas au-delà:

$ 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)) 

(Ici, je voudrais vous débarrasser de la nécessité pour l'annotation, mais le fait que cette formulation a besoin de deux instances pour Foo rend ce problème.)

Je comprends la plainte: J'ai seulement une instance qui va de Bool -> Char -> Foo à ID Bool -> MyState (ID Char -> Foo). Mais je ne peux pas faire l'instance qu'il veut parce que j'ai besoin de MyState là quelque part afin que je puisse transformer le ID Bool en Bool.

Je ne sais pas si je suis complètement hors piste ou quoi. Je sais que je pourrais résoudre mon problème de base (je ne veux pas polluer mon code avec les équivalents idGet s partout) de différentes manières, telles que la création de liftA/liftM fonctions de style pour différents nombres de paramètres d'ID, avec des types comme (a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret, mais j'ai passé trop de temps à y penser. :-) Je veux savoir à quoi devrait ressembler cette solution variée.

+0

Puisque vous ne cherchez pas explicitement une solution 'Applicative', j'ajoute ceci dans les commentaires à la place: https://gist.github.com/f8e5d1ecf20ea09a8b36 –

Répondre

2

AVERTISSEMENT

De préférence ne pas utiliser les fonctions variadique pour ce type de travail. Vous n'avez qu'un nombre limité de constructeurs, donc les constructeurs intelligents ne semblent pas être un gros problème. Les ~ 10-20 lignes dont vous auriez besoin sont beaucoup plus simples et plus faciles à maintenir qu'une solution variadique. Aussi une solution applicative est beaucoup moins de travail.

AVERTISSEMENT

Le monade/en combinaison avec applicative fonction variadique est le problème. Le 'problème' est l'étape d'addition d'argument utilisée pour la classe variadique. La classe de base ressemblerait

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

où vous le faites variadique en ayant des cas de la forme

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

qui briserait lorsque vous commencez à utiliser monades. L'ajout de la monade dans la définition de la classe empêcherait l'expansion des arguments (vous obtiendriez :: M (arg -> f), pour une monade M). L'ajouter au cas de base empêcherait d'utiliser la monade dans l'expansion, car il n'est pas possible (pour autant que je sache) d'ajouter la contrainte monadique à l'instance d'expansion. Pour un indice à une solution complexe, voir le P.S ..

La direction de la solution d'utiliser une fonction qui aboutit à (Env -> Foo) est plus prometteuse. Le code suivant requiert toujours une contrainte de type :: Foo et utilise une version simplifiée de l'Env/ID pour des raisons de concision. L'extension Type familles est utilisée pour rendre la correspondance plus stricte/meilleure. Maintenant, la classe de fonction variadique.

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. 

L'environnement Env est explicitement adopté, en substance la Reader monade est décompressé empêche les problèmes entre les monades et les fonctions variadique (pour la State monade la fonction de résolution doit retourner un nouvel environnement). Test avec app Env Foo1 ID :: Foo résultats dans le prévu Foo1 'a'.

P.S. Vous pouvez obtenir des fonctions variadiques monadiques (dans une certaine mesure), mais cela nécessite de plier vos fonctions (et votre esprit) de façon très étrange. La façon dont j'ai de telles choses à travailler est de «plier» tous les arguments variadiques dans une liste hétérogène. Le déballage peut alors être fait monadic-allié. Bien que j'aie fait certaines choses comme ça, je vous déconseille vivement d'utiliser de telles choses dans le code actuel (utilisé) car cela devient rapidement incompréhensible et impossible à maintenir (sans parler des erreurs de type que vous obtiendriez).

+0

Merci pour vos idées. Cependant, votre classe 'MApp' et ses instances ne sont-elles pas essentiellement identiques à mon' VarC 'non monadique? – Deewiant

+0

@Deewiant, en effet est. Je pense que c'est le meilleur qui puisse être fait dans ce cas.Mais je doute qu'il soit possible de faire une fonction variadique monadique sans faire quelques mauvais tours, que je pourrais poster (une tentative de) une réponse si vous voulez. – Laar

+0

S'il vous plaît faire! Je ne prévois pas l'utiliser, mais je m'intéresse au genre de supercherie dont il aurait besoin. – Deewiant

Questions connexes