2015-05-23 19 views
6

ottengo gli errori come questo:Perché non posso impilare due lettori uno sull'altro?

Diciamo che ho un monadStack ReaderT A (ReaderT B m), ogni volta che uso ask o asks, ottengo un errore come questo:

Types.hs:21:10: 
    Couldn't match type ‘A’ with ‘B’ 
    arising from a functional dependency between: 
     constraint ‘MonadReader B m’ 
     arising from the instance declaration 
     instance ‘MonadReader A m2’ at Types.hs:21:10-63 
    In the instance declaration for ‘MonadReader A m’ 

Come mai Haskell non può capire quale istanza da usare? Inoltre, come posso risolvere questo? Diciamo che mettere lo A e lo B nello stesso tipo di dati non è un'opzione, perché ho bisogno di un'istanza MonadReader A m.

+0

Oh, 'istanza (MonadReader r m) => MonadReader r (ReaderT s m)' non esiste nemmeno ?! Perché? –

risposta

13

La classe MonadReader è definita secondo la FunctionalDependencies estensione, che permette dichiarazioni come

class Monad m => MonadReader r m | m -> r where 
    ... 

Ciò significa che per ogni monad m, il r è univocamente determinata da esso. Pertanto, non è possibile avere una singola monad m che determina due diversi tipi di r. Senza questo come una restrizione il compilatore non sarebbe in grado di digitare gli usi di controllo della classe.

La soluzione a questo è quello di scrivere le funzioni come

getA'sInt :: A -> Int 
getA'sInt = undefined 

getB'sString :: B -> String 
getB'sString = undefined 

foo :: (MonadReader A m) => m Int 
foo = do 
    a <- asks getA'sInt 
    return $ a + 1 

bar :: (MonadReader B m) => m String 
bar = do 
    b <- asks getB'sString 
    return $ map toUpper b 

Poi basta usare una tupla (A, B) nell'implementazione attuale:

baz :: Reader (A, B) (Int, String) 
baz = do 
    a <- withReader fst foo 
    b <- withReader snd bar 
    return (a, b) 

C'è anche un withReaderT per i casi più complessi.

Come esempio del motivo per cui non è permesso di impilare ReaderT s, si consideri il caso

type App = ReaderT Int (Reader Int) 

Quando si chiama ask, che Int ti riferisci? Può sembrare ovvio che in casi come

type App = ReaderT A (Reader B) 

il compilatore dovrebbe essere in grado di capire quale usare, ma il problema è che la funzione ask qui avrebbe il tipo

ask :: App ??? 

Dove ??? potrebbe essere A o B. È possibile ottenere intorno a questo un altro modo, non usando MonadReader direttamente e la definizione di specifiche askA e askB funzioni:

type App = ReaderT A (Reader B) 

askA :: App A 
askA = ask 

askB :: App B 
askB = lift ask 

baz :: App (Int, String) 
baz = do 
    a <- askA 
    b <- askB 
    return (getA'sInt a, getB'sString b) 

Ma sarà solo in grado di avere MonadReader A App, non si può avere anche MonadReader B App. Questo approccio potrebbe essere chiamato "sollevamento esplicito" e rende tali funzioni specifiche per il tipo App e quindi meno componibili.

+0

Grazie per la risposta, per l'ultimo commento, come si sceglie quale di 'MonadReader A App' o' App MonadReader B 'si ottiene, perché in realtà sarebbe risolvere il mio problema. –

+1

@SydKerckhove Qualunque sia in cima. – bheklilr

+2

@SydKerckhove Quindi se tu avessi 'ReaderT B (Reader A)' allora avresti l'istanza 'MonadReader B'. – bheklilr

3

Può essere eseguito utilizzando alcune estensioni, anche se potrebbe non essere una buona idea avere istanze sovrapposte.

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} 
{-# LANGUAGE OverlappingInstances #-} 

import Control.Monad.Reader 

import Data.Functor.Identity 

data A = A deriving Show 
data B = B deriving Show 

type SomeMonad a = ReaderT A (ReaderT B Identity) a 

instance MonadReader a m => MonadReader a (ReaderT b m) where 
    ask = lift ask 
    local f mx = do 
    b <- ask 
    lift $ local f $ runReaderT mx b 

main :: IO() 
main = do 
    let res = runIdentity $ flip runReaderT B $ flip runReaderT A $ do 
       a <- ask :: SomeMonad A 
       b <- ask :: SomeMonad B 
       return (a, b) 
    print res 

Inutile dire che, quando possibile, è molto meglio usare qualcosa come ReaderT (A, B) IO.

+0

Questo è quello che avevo appena capito e funziona! Speravo solo in una soluzione più chiara (più sicura). –