2011-11-10 10 views
7

Ho una domanda generale e una domanda caso più specifica.Combinazione di monadi (IEnumerable e Maybe a titolo di esempio)

Come si combinano le diverse monadi in generale? Alcune combinazioni di operatori monad consentono una composizione facile? O bisogna scrivere metodi ad-hoc per combinare ogni possibile coppia di monadi?

Come esempio specifico, ho scritto una Forse monade. Come si potrebbe utilizzare un IEnumerable<IMaybe<T>>? Oltre a scavare manualmente nella monade Maybe all'interno delle estensioni LINQ (come ad esempio: if(maybe.HasValue) ... all'interno delle clausole select), esiste un modo "monadico" di combinare le due con le rispettive operazioni monad Bind, ecc.?

Altrimenti, se devo scrivere metodi di combinazione specifici, è qualcosa del genere il modo giusto per farlo?

public static IEnumerable<B> SelectMany<A, B>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func) 
    { 
     return from item in sequence 
       let result = func(item) 
       where result.HasValue 
       select result.Value; 
    } 


    public static IEnumerable<C> SelectMany<A, B, C>(this IEnumerable<A> sequence, Func<A, IMaybe<B>> func, Func<A, B, C> selector) 
    { 
     return from item in sequence 
       let value = item 
       let maybe = func(item) 
       where maybe.HasValue 
       select selector(value, maybe.Value); 
    } 
+0

ho trovato un riferimento davvero interessante di monadi, che conciliano: http://heinrichapfelmus.github.com/operational/Documentation.html#alternatives-to-monad-transformers Sostiene un modo generale di combinare monadi. Dovresti anche leggere questo documento, descrive come combinare i tipi di dati in modo generale ed estensibile: http://www.cs.ru.nl/~wouters/Publications/DataTypesALaCarte.pdf –

risposta

-1

In questo caso specifico, è possibile implementare IEnumerable<T> in MayBe<T>, in modo che sia restituisce 0 o 1 Valore.

1

Ottima domanda!

A trasformatore monad è un tipo che aggiunge alcune funzionalità a una monade di base arbitraria, preservando la monadeità. Purtroppo, i trasformatori monad sono inesprimibili in C# perché fanno un uso essenziale dei tipi più alti. Quindi, lavorando in Haskell,

class MonadTrans (t :: (* -> *) -> (* -> *)) where 
    lift :: Monad m => m a -> t m a 
    transform :: Monad m :- Monad (t m) 

Esaminiamo questa linea per linea. La prima riga dichiara che un trasformatore monad è un tipo t, che accetta un argomento di tipo * -> * (ovvero un tipo che prevede un argomento) e lo trasforma in un altro tipo di tipo * -> *. Quando ti rendi conto che tutte le monadi hanno il tipo * -> * puoi vedere che l'intenzione è che lo t trasforma le monadi in altre monadi.

La riga successiva dice che tutti i trasformatori monade devono supportare un'operazione lift, che richiede una monade arbitrario m e ascensori in mondo del trasformatore t m.

Infine, il metodo transform dice che per qualsiasi monad m, t m deve essere anche una monade. Sto utilizzando l'agente :- da the constraints package.


Questo avrà più senso con un esempio. Ecco un trasformatore monad che aggiunge Maybe -ness a una base arbitraria monad m. L'operatore nothing ci consente di interrompere il calcolo.

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } 

nothing :: Monad m => MaybeT m a 
nothing = MaybeT (return Nothing) 

Affinché MaybeT per essere un trasformatore Monade, deve essere una monade ogniqualvolta suo argomento è una monade.

instance Monad m => Monad (MaybeT m) where 
    return = MaybeT . return . Just 
    MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f) 

Ora per scrivere l'implementazione MonadTrans. L'implementazione di lift avvolge il valore di ritorno del monad di base in un Just.L'implementazione di transform non è interessante; dice semplicemente al risolutore di vincoli di GHC di verificare che MaybeT sia effettivamente una monade ogni volta che il suo argomento è.

instance MonadTrans MaybeT where 
    lift = MaybeT . fmap Just 
    transform = Sub Dict 

Ora possiamo scrivere un calcolo monadica che usa MaybeT aggiungere mancato, ad esempio, il State monade. lift ci consente di utilizzare i metodi standard Stateget e put, ma abbiamo anche accesso a nothing se abbiamo bisogno di fallire il calcolo. (Ho pensato di usare il tuo esempio di IEnumerable (aka []), ma c'è qualcosa di perverso sull'aggiunta mancata una monade che supporta già esso.)

example :: MaybeT (State Int)() 
example = do 
    x <- lift get 
    if x < 0 
    then nothing 
    else lift $ put (x - 1) 

Ciò che rende trasformatori monade veramente utile è la loro impilabilità. Questo ti permette di comporre grandi monadi con molte capacità di molte piccole monadi con una capacità ciascuna. Ad esempio, potrebbe essere necessario eseguire una determinata applicazione IO, leggere le variabili di configurazione e generare eccezioni; questo sarebbe essere codificato con un tipo come

type Application = ExceptT AppError (ReaderT AppConfig IO) 

Ci sono strumenti in the mtl package che la aiutano ad astratta sulla raccolta e l'ordine di trasformatori monade precisa in una determinata pila, che consente di elidere le chiamate a lift.