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.
Puisque vous ne cherchez pas explicitement une solution 'Applicative', j'ajoute ceci dans les commentaires à la place: https://gist.github.com/f8e5d1ecf20ea09a8b36 –