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.
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 ReaderT
s, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With