Why can't I put two readers on the other side?

I get errors like this:

Say I have a monadStack ReaderT A (ReaderT B m) , whenever I use ask or asks , I get this error:

 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' 

Why can't Haskell figure out which instance to use? Also, how can I solve this? Let them say that placing A and B in the same data type is not an option, because I need an instance of MonadReader A m .

+6
source share
2 answers

The MonadReader class MonadReader defined using the FunctionalDependencies extension, which allows declarations of the type

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

This means that for any monad m value of r is uniquely determined. Therefore, you cannot have one monad m , which defines two different types of r . Without this, as a limitation, the compiler will not be able to introduce class usage checks.

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 the (A, B) tuple in your actual implementation:

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

There is also withReaderT for more complex cases.

As an example of why he is not allowed to fold ReaderT s, consider the case

 type App = ReaderT Int (Reader Int) 

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

 type App = ReaderT A (Reader B) 

the compiler should be able to determine what to use, but the problem is that here the ask function will be of type

 ask :: App ??? 

Where ??? may be A or B You can get around this in another way, without using MonadReader directly and defining specific askA and askB :

 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 can have a MonadReader A App , you also cannot have a MonadReader B App . This approach can be called an โ€œexplicit liftโ€, and it makes these functions specific to the App type and, therefore, less compositional.

+13
source

This can be done with some extensions, although it might be nice to have overlapping instances.

 {-# 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 am => MonadReader a (ReaderT bm) 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 

Needless to say, it is much better to use something like ReaderT (A, B) IO if possible.

+3
source

Source: https://habr.com/ru/post/987805/


All Articles