2012-08-29 5 views
8

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.

+0

Dato che non stai cercando esplicitamente una soluzione 'Applicativa', aggiungo invece questo nei commenti: https://gist.github.com/f8e5d1ecf20ea09a8b36 –

risposta

2

WARNING

Preferibilmente non utilizzare Funzione Variadica per questo tipo di lavoro. Hai solo un numero finito di costruttori, quindi i costruttori intelligenti non sembrano essere un grosso problema. Le ~ 10-20 linee di cui avresti bisogno sono molto più semplici e più manutenibili di una soluzione variadica. Anche una soluzione applicativa è molto meno lavoro.

WARNING

La monade/applicativa in combinazione con le funzioni variadic è il problema. Il "problema" è il passo di aggiunta argomento utilizzato per la classe variadica. La classe di base sarebbe simile

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

dove lo fai variadic avendo le istanze della forma

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

che avrebbe spezzato quando si desidera iniziare ad utilizzare monadi. Aggiungere la monade nella definizione della classe impedirebbe l'espansione degli argomenti (si otterrebbe :: M (arg -> f), per alcuni monad M). Aggiungerlo al caso base impedirebbe di usare la monade nell'espansione, poiché non è possibile (per quanto ne so) aggiungere il vincolo monadico all'istanza di espansione. Per un suggerimento su una soluzione complessa vedere il P.S ..

La direzione della soluzione per l'utilizzo di una funzione che risulta in (Env -> Foo) è più promettente. Il codice seguente richiede ancora un vincolo di tipo :: Foo e utilizza per brevità una versione semplificata di Env/ID.

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

L'estensione famiglie di caratteri viene utilizzata per rendere il confronto più rigoroso/migliore. Ora la classe della funzione 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. 

L'ambiente Env è espressamente passato, in sostanza monade Reader disimballato impedisce i problemi tra monadi e funzioni variadic (per monade la funzione State determinazione deve restituire un nuovo ambiente). Il test con i risultati app Env Foo1 ID :: Foo nell'aspettato Foo1 'a'.

P.S. È possibile ottenere funzioni variadiche monadiche per funzionare (in una certa misura) ma richiede di piegare le funzioni (e la mente) in alcuni modi molto strani. Il modo in cui riesco a far funzionare queste cose è "ripiegare" tutti gli argomenti variadici in una lista eterogenea. Lo scucolamento può quindi essere fatto in modo monadico. Anche se ho fatto alcune cose del genere, vi scoraggio vivamente dall'usare queste cose nel codice (usato) attuale, poiché diventa rapidamente incomprensibile e non gestibile (per non parlare degli errori di tipo che otterreste).

+0

Grazie per i tuoi approfondimenti. La tua classe 'MApp' e le sue istanze sono essenzialmente identiche al mio' VarC' non monadico? – Deewiant

+0

@Deewiant, infatti lo è. Penso che sia il meglio che si possa fare in questo caso.Ma dubito che sia possibile fare una funzione variadica monadica senza fare brutti scherzi, su cui potrei pubblicare una risposta se lo desideri. – Laar

+0

Si prega di fare! Non ho intenzione di usarlo, ma sono interessato a quale tipo di trucco richiederebbe. – Deewiant