Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I stack two readers ontop of eachother?

I get errors like this:

Let's say I have a monadStack ReaderT A (ReaderT B m), whenever I use ask or asks, I get an error like this:

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’

How come Haskell can't figure out which instance to use? Also, how can I solve this? Let's say putting A and B in the same datatype is not an option, because I need a MonadReader A m instance.

like image 569
Syd Kerckhove Avatar asked May 23 '15 21:05

Syd Kerckhove


1 Answers

The MonadReader class is defined using the FunctionalDependencies extension, which allows declarations like

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

This means that for any monad m, the r is uniquely determined by it. Therefore, you can't have a single monad m that determines two different r types. Without this as a restriction the compiler wouldn't be able to type check uses of the class.

The solution to this is to write your functions like

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

Then just use a tuple (A, B) in your actual implementation:

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

There's also a withReaderT for more complex cases.

As an example for why it's not allowed to stack ReaderTs, consider the case

type App = ReaderT Int (Reader Int)

When you call ask, which Int are you referring to? It may seem obvious that for cases like

type App = ReaderT A (Reader B)

the compiler should be able to figure out which to use, but the problem is that the ask function here would have the type

ask :: App ???

Where ??? could be A or B. You can get around this another way, by not using MonadReader directly and defining specific askA and askB functions:

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)

But you will only be able to have MonadReader A App, you can't also have MonadReader B App. This approach could be called "explicit lifting", and it makes those functions specific to the App type, and therefore less composable.

like image 63
bheklilr Avatar answered Oct 27 '22 02:10

bheklilr