0

So I'm told (->) r is an instance of the Reader monad, but I can't seem to find any concrete examples of how this is supposed to work. I want to use this without having to explicitly wrap some of my code in a Reader

import Control.Monad.Reader

testOne :: Reader String String
testOne = do
  env <- ask
  return $ "Hello, " ++ env

testTwo :: String -> String
testTwo = do
  env <- ask
  return $ "G'day, " ++ env

Running runReader testOne "there" works fine, but running runReader testTwo "mate" fails spectacularly with the following message:

Couldn't match type ‘String -> String’
                   with ‘ReaderT [Char] Data.Functor.Identity.Identity a’
    Expected type: Reader [Char] a
      Actual type: String -> String

So what am I missing here?

Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
  • 1
    Why do you **want** `runReader testTwo "mate"` to be a valid syntax for producing anything, as distinct from, say, `id testTwo "mate"`? Note that `(->) r` is not an "instance of the Reader monad" but rather that it is a "monad which does the exact same thing as the Reader monad." But we need to know in order to help you: are you interfacing with something which will take a `Reader r x` as an argument? Or do you need code which is polymorphic and will both accept a `Reader r x` and an `r -> x` function? Or what? – CR Drost Dec 27 '15 at 18:56
  • @CRDrost it was more of a "in case I have something that uses both things" kinda question, having one unified runner would be useful. But I guess since the entire question is guided by a misleading statement, I don't really need it? – Electric Coffee Dec 27 '15 at 20:19

2 Answers2

3

The type of runReader is runReader :: ReaderT r Identity -> r -> a, if you expand out the newtype Reader = ReaderT r Identity. I think you want something very generic, along the lines of this:

foo :: (MonadReader r m) => m a -> r -> a

So that you could evaluate both foo testOne "there" and foo testTwo "mate".

Unfortunately, no such function exists. The mtl library's job is to abstract the choice of the underlying concrete type. Both (->) String and Reader String = ReaderT String Identity are concrete types that obey the Monad and MonadReader laws, but that only guarantees you an interface of return, >>=, ask, reader, and local (and <$>, <*>, pure).

This is both limiting and useful!

Limiting: In order to "run" the computation represented by either type, you need to use the appropriate type-specific API. For (->) String, that's simply calling the function (the invisible function application operator); for Reader String, that's runReader.

Useful: you can expose, with a library, values constrained by MonadReader, knowing that users will only be able to use them with the MonadReader interface. This is nice, as you can use this trick to make sure that users aren't doing anything untoward, like doing early runs of your values with their own environments (r's).

hao
  • 10,138
  • 1
  • 35
  • 50
2

runReader :: Reader r a -> r -> a is specifically for Reader newtype, which you want to avoid. Since testTwo is just a function, you simply use testTwo "mate".

If you want a generic way to run MonadReader, you could define your own type class for this. Approximately like this (untested):

class MonadReader r m => RunReader r m | m -> r where
  type Output m a :: *
  runReader' :: m a -> r -> Output m a

instance RunReader r ((->) r) where
  type Output ((->) r) a = a
  runReader' = ($)

instance Monad m => RunReader r (ReaderT r m) where
  type Output (ReaderT r m) a = m a
  runReader' = runReaderT

instance RunReader r m => RunReader r (MaybeT m) where
  type Output (MaybeT m) a = Output m (Maybe a)
  runReader' = runMaybeT . runReader'

-- any other instances

and then runReader' testOne and runReader' testTwo will work. See "Associated data and type families" for an explanation of use of type here.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • using it as a function defeats the purpose of what I'm trying to accomplish though – Electric Coffee Dec 27 '15 at 16:26
  • 2
    Probably whoever told you "(->) r is an instance of the Reader monad" meant that it is an instance of `MonadReader` (alternately, they could have meant it's isomorphic to the `Reader` type). So you can write `testAbstract :: MonadReader m => m String String` with the same definition. But there is no general way to "run" a `MonadReader`. – Alexey Romanov Dec 27 '15 at 16:36
  • I've expanded the answer to show how you could _create_ such an abstraction. – Alexey Romanov Dec 27 '15 at 17:13
  • Will those `Output` instances really be accepted? Don't they take too many indices? Or am I confused? – dfeuer Dec 27 '15 at 17:54
  • @dfeuer Note the kind of `Output m`. You could also write is as `type Output m a :: *`. There is a similar example with `data GMap k` in the linked documentation. – Alexey Romanov Dec 27 '15 at 18:45
  • @dfeuer You are right about the edit, though; thanks! – Alexey Romanov Dec 27 '15 at 18:47
  • I haven't tried compiling it yet, but I'm surprised. A free-standing type family `type family Foo a :: * -> *` will, I believe, only accept instances that reduce to first-class types of that kind. So `type instance Foo Int = Either Bool`, but not `type instance Foo Int a = a`. – dfeuer Dec 27 '15 at 19:26
  • @dfeuer I may be confused myself, but I can't test it today. If it doesn't work, I think `type Output m a :: *` should? – Alexey Romanov Dec 27 '15 at 20:12
  • I tried compiling it; no dice. A partially applied data family is "first class". A partially applied type family, on he other hand, is second-class like a partially applied type synonym. – dfeuer Dec 29 '15 at 02:54