2015-05-23 24 views
6

dostaję błędy tak:Dlaczego nie mogę ułożyć na sobie dwóch czytników?

Powiedzmy mam monadStack ReaderT A (ReaderT B m), gdy używam ask lub asks, pojawia się błąd jak poniżej:

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’ 

Czemu Haskell nie może dowiedzieć się, który wystąpienie do użycia? Ponadto, jak mogę to rozwiązać? Powiedzmy, że wstawianie A i B w tym samym typie danych nie jest opcją, ponieważ potrzebuję instancji MonadReader A m.

+0

Oh, 'instancja (MonadReader r m) => MonadReader r (ReaderT s m)" nawet nie istnieje ?! Dlaczego? –

Odpowiedz

13

Klasa MonadReader określona jest rozszerzenie FunctionalDependencies, co pozwala zgłoszeń jak

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

Oznacza to, że dla każdego monadzie m The r jest jednoznacznie określony przez nią. Dlatego nie możesz mieć pojedynczej monady m, która określa dwa różne typy r. Bez tego, jako ograniczenia, kompilator nie będzie w stanie wpisać sprawdzających zastosowań klasy.

Rozwiązaniem tego problemu jest, aby napisać swoje funkcje jak

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 

Następnie wystarczy użyć krotki (A, B) w swojej rzeczywistej realizacji:

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

Jest też withReaderT dla bardziej skomplikowanych przypadków.

Jako przykład, dlaczego nie wolno układać ReaderT s, należy rozważyć przypadek

type App = ReaderT Int (Reader Int) 

Po wywołaniu ask, który Int pan myśli? To może wydawać się oczywiste, że w przypadkach takich jak

type App = ReaderT A (Reader B) 

kompilator powinien być w stanie dowiedzieć się, które w użyciu, ale problemem jest to, że ask funkcja tutaj musiałby typ

ask :: App ??? 

Gdzie ??? mogli być A lub B. Można obejść ten inny sposób, nie używając MonadReader bezpośrednio i definiowanie konkretnych askA i askB funkcje:

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) 

Ale będziesz tylko mógł mieć MonadReader A App, nie można też mieć MonadReader B App. Takie podejście można nazwać "wyraźnym podnoszeniem", a to sprawia, że ​​funkcje te są specyficzne dla typu App, a zatem są mniej kompozycyjne.

+0

Dzięki za odpowiedź, dla tego ostatniego komentarza, jak wybrałeś jedną z 'MonadReader A App' lub' MonadReader B App', którą otrzymałeś, ponieważ to faktycznie rozwiązałoby mój problem. –

+1

@SydKerckhove Niezależnie od tego, który jest na górze. – bheklilr

+2

@SydKerckhove Więc jeśli masz 'ReaderT B (Reader A)', będziesz miał instancję 'MonadReader B'. – bheklilr

3

Można to zrobić za pomocą niektórych rozszerzeń, choć może nie być dobrym pomysłem nakładanie się nakładek.

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

trzeba dodawać, że w miarę możliwości jest znacznie lepiej użyć coś jak ReaderT (A, B) IO.

+0

Właśnie to rozgryzłem i to działa! Miałem tylko nadzieję na bardziej przejrzyste (bezpieczniejsze) rozwiązanie. –