2012-08-29 38 views
8

Estoy tratando de hacer una función variadica con un tipo de retorno monádico, cuyos parámetros también requieren el contexto monádico. (No estoy seguro de cómo describir ese segundo punto:. Ej printf puede volver IO() pero es diferente, ya que sus parámetros son tratados de la misma si se termina siendo IO() o String)Haskell: cómo escribir una función variada monádica, con parámetros usando el contexto monádico

Básicamente, tengo una constructor de datos que toma, digamos, dos parámetros Char. En su lugar, deseo proporcionar dos argumentos de estilo de puntero ID Char, que se pueden decodificar automágicamente a partir de una mónada State adjunta a través de una instancia de clase de tipo. Entonces, en lugar de hacer get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2)), quiero hacer fooVariadic Constructor id1 id2.

Lo que sigue es lo que tengo hasta ahora, estilo Literate Haskell en caso de que alguien quiera copiarlo y meterse con él.

En primer lugar, el entorno básico:

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

algunos datos de prueba para mayor comodidad:

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

Tengo esta versión no monádico, que simplemente toma el medio ambiente como la primera parámetro. Esto funciona bien, pero no es lo que busco. Ejemplos:

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

(Podría utilizar las dependencias funcionales o escribe familias para deshacerse de la necesidad de que las anotaciones de tipo :: Foo Por el momento no soy muy exigente en cuanto ella, ya que esto no es lo que me interesa. de todos modos.)

> 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 

Ahora, quiero una función variadic que se ejecute en la siguiente mónada.

> type MyState = State Env 

Y básicamente, no tengo idea de cómo debo proceder. Intenté expresar la clase de tipos de diferentes maneras (variadicM :: r1 -> r2 y variadicM :: r1 -> MyState r2), pero no he logrado escribir las instancias. También intenté adaptar la solución no monádica anterior para que de alguna manera "termine" con un Env -> Foo que luego podría convertir fácilmente en un MyState Foo, pero tampoco tuve suerte allí.

Lo que sigue es mi mejor intento hasta el momento.

> 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 

Funciona para Foo0 y foo1, pero no más allá de eso:

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

(Aquí me gustaría deshacerse de la necesidad de la anotación, pero el hecho de que esta formulación necesita dos casos para Foo hace que eso sea problemático.)

Entiendo la queja: solo tengo una instancia que va desde Bool -> Char -> Foo hasta ID Bool -> MyState (ID Char -> Foo). Pero no puedo hacer la instancia que quiere porque necesito MyState allí en algún lugar para que pueda convertir el ID Bool en un Bool.

No sé si estoy completamente desviado o qué. Sé que podría resolver mi problema básico (no quiero contaminar mi código con los equivalentes idGet s en todas partes) de diferentes maneras, como crear liftA/liftM -style funciones de estilo para diferentes números de parámetros de ID, con tipos como (a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret, pero he pasado demasiado tiempo pensando en esto. :-) Quiero saber cómo debería ser esta solución variadica.

+0

Como explícitamente no está buscando una solución 'Applicative', la agrego en los comentarios: https://gist.github.com/f8e5d1ecf20ea09a8b36 –

Respuesta

2

ADVERTENCIA

Preferiblemente no utilizan funciones variadic para este tipo de trabajo. Solo tienes un número finito de constructores, por lo que los constructores inteligentes no parecen ser un gran problema. Las ~ 10-20 líneas que necesitaría son mucho más simples y más fáciles de mantener que una solución variadica. También una solución aplicativa es mucho menos trabajo.

ADVERTENCIA

La mónada/aplicativo en combinación con funciones variadic es el problema. El "problema" es el paso de adición de argumento utilizado para la clase variadic. La clase básica se vería

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

en el que hacer que variadic por tener casos de la forma

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

lo que podría romper cuando se empezaría a usar mónadas. Agregar la mónada en la definición de clase evitaría la expansión de argumentos (obtendría :: M (arg -> f), por alguna mónada M). Agregarlo al caso base evitaría el uso de la mónada en la expansión, ya que no es posible (hasta donde sé) agregar la restricción monádica a la instancia de expansión. Para obtener una pista sobre una solución compleja, consulte la P.S ..

La dirección de la solución para usar una función que da como resultado (Env -> Foo) es más prometedora. El siguiente código aún requiere una restricción de tipo :: Foo y utiliza una versión simplificada del Env/ID para abreviar.

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

La extensión de tipo Familias se utiliza para hacer el ajuste más estricto/mejor. Ahora la clase de función variadica.

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. 

El entorno Env se pasa de forma explícita, en esencia, el Reader mónada es desempaquetado impidiendo que los problemas entre las mónadas y funciones variadic (por la mónada la función de determinación State debe devolver un nuevo entorno). Probar con app Env Foo1 ID :: Foo da como resultado el Foo1 'a' esperado.

P.S. Puede hacer que las funciones variadas monádicas funcionen (hasta cierto punto) pero requiere doblar sus funciones (y su mente) de maneras muy extrañas. La forma en que tengo tales cosas para trabajar es 'doblar' todos los argumentos variados en una lista heterogénea. El desenvolvimiento puede hacerse monádicamente. A pesar de que he hecho algunas cosas así, te recomiendo encarecidamente que no uses tales cosas en el código real (usado) ya que rápidamente se vuelve incomprensible y no se puede mantener (por no hablar de los errores de tipo que obtendrías).

+0

Gracias por su conocimiento. ¿No es su clase 'MApp' y sus instancias esencialmente idénticas a mi' VarC' no monádica? – Deewiant

+0

@Deewiant, de hecho lo es. Creo que es lo mejor que se puede hacer en este caso.Pero dudo que sea posible hacer una función variada monádica sin hacer algunos trucos desagradables, sobre los que podría publicar (un intento de) una respuesta si lo desea. – Laar

+0

¡Por favor hazlo! No estoy planeando usarlo, pero estoy interesado en qué clase de engaño requeriría. – Deewiant

Cuestiones relacionadas