Sto provando a fare una funzione variadica con un tipo di ritorno monadico, i cui parametri richiedono anche il contesto monadico. (Non sono sicuro di come descrivere questo secondo punto:. Es printf
può tornare IO()
ma è diverso in quanto i suoi parametri vengono trattati allo stesso modo se si finisce per essere IO()
o String
)Haskell: come scrivere una funzione variadica monadica, con parametri che usano il contesto monadico
Fondamentalmente, ho un costruttore di dati che prende, ad esempio, due parametri Char
. Voglio fornire invece due argomenti di stile puntatore ID Char
, che possono essere decodificati automaticamente da una monade allegata State
tramite un'istanza di classe tipo. Quindi, invece di fare get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2))
, voglio fare fooVariadic Constructor id1 id2
.
Quello che segue è quello che ho ottenuto fino ad ora, stile letterario Haskell nel caso qualcuno volesse copiarlo e pasticciarlo.
In primo luogo, l'ambiente di 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
Alcuni dati di test per convenienza:
> cid :: ID Char
> cid = ID 1
> bid :: ID Bool
> bid = ID 2
> env :: Env
> env = ("xy", map (==1) [0,0,1])
Ho questa versione non monadica, che prende semplicemente l'ambiente come la prima parametro. Funziona bene ma non è esattamente quello che sto cercando. Esempi:
$ mkFoo env Foo0 :: Foo
Foo0
$ mkFoo env Foo3 cid bid cid :: Foo
Foo3 'y' True 'y'
(potrei usare dipendenze funzionali o il tipo di famiglie di sbarazzarsi della necessità per le annotazioni di tipo :: Foo
Per ora non sono dedicati circa esso, dal momento che questo non è quello che mi interessa. in ogni caso.)
> 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
Ora, voglio una funzione variadic che viene eseguita nella seguente monade.
> type MyState = State Env
E in sostanza, non ho idea di come procedere. Ho provato ad esprimere la classe del tipo in modi diversi (variadicM :: r1 -> r2
e variadicM :: r1 -> MyState r2
) ma non sono riuscito a scrivere le istanze. Ho anche provato ad adattare la soluzione non monadica sopra in modo che io "finisca" in qualche modo con uno Env -> Foo
che potrei poi facilmente trasformare in un MyState Foo
, ma senza fortuna neanche lì.
Quello che segue è il mio miglior tentativo finora.
> 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
Funziona per Foo0 e foo1, ma non al di là di quanto segue:
$ 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))
(Qui vorrei per sbarazzarsi della necessità per l'annotazione, ma il fatto che questa formulazione ha bisogno di due istanze per Foo
rende problematico.)
Ho letto il reclamo: ho solo un'istanza che va da Bool -> Char -> Foo
a ID Bool -> MyState (ID Char -> Foo)
. Ma non posso rendere l'istanza desiderata perché ho bisogno di MyState
da qualche parte in modo che io possa trasformare lo ID Bool
in uno Bool
.
Non so se sono completamente fuori pista o cosa. So che potrei risolvere il mio problema di base (non voglio inquinare il mio codice con gli equivalenti idGet s
dappertutto) in diversi modi, come la creazione di funzioni stile liftA
/liftM
per diversi numeri di parametri ID, con tipi come (a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret
, ma ho passato troppo tempo a pensarci. :-) Voglio sapere come dovrebbe apparire questa soluzione variadica.
Dato che non stai cercando esplicitamente una soluzione 'Applicativa', aggiungo invece questo nei commenti: https://gist.github.com/f8e5d1ecf20ea09a8b36 –