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.
Como explícitamente no está buscando una solución 'Applicative', la agrego en los comentarios: https://gist.github.com/f8e5d1ecf20ea09a8b36 –