help with reader monad
Asked Answered
J

3

14

I am new at haskell, I have to write a program context-aware,so I thought I can use the Reader Monad for keeping the context read from a file, I know how to read the file puting the content in a list of tuplessomething like [([Char],[Char])], but I do not know how to implement the Reader Monad for making the environment available to all the components of my program without using imperative style, In particular I do not know how to set and use the environment, as far as I understood I should give it as parameter to all the functions that need the environment with runReader function env, but I am very confused, can somebody give me some indications or a good tutorial? thanks in advance

Jacintha answered 10/8, 2010 at 17:11 Comment(4)
Are you sure you need Reader in the first place? "Making the environment available to all components" usually isn't the best way to write code in Haskell. Can you describe the task you're working on in more detail?Mccray
@Travis Brown: It can make sense if you have a large chunk of essentially static data, needed as-is in many places throughout the program, which is only available at runtime, e.g., by loading data files. Imagine a program where all the text is localized and loaded from a resource file when the program starts, for instance.Ihram
In fact, if anything sounds dubious to me it's the type [([Char], [Char])]. Knowing that it's an environment, it sounds suspiciously like a string dictionary, which ought to at least be a Data.Map.Map String String instead, if not something even more charming like a lovely bytestring trie.Ihram
Answering to Travis:I am working on a cache server, that operates in different ways in function of the context, in my case the context is a file.txt that can be modified while the server is working. Having read something about reader monad and writer monad I thought, that the reader monad could be good for managing the environment and the writer monad for the cache, but I am not sure, thus I asked advice to the forum, now I will check what is the bytestring-trieJacintha
A
6

I think it's easiest if you look at how you would solve this problem without using Reader, then compare the translated version. Here's a trimmed-down example from a program I'm working on where the environment is a set of callback functions to update the display. It's slightly more complicated because it uses ReaderT instead of Reader, but everything works in basically the same way.

runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
  newstate <- processAction state action
  let ufunc = mainUFunc env              -- get the callback to update the display
  ufunc newstate                         -- update the display
  return newstate

Now I'll change it to use the Reader monad to pass along the environment. Since the code was already in IO, it's necessary to use the monad transformer version, ReaderT.

runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
  newstate <- lift $ processAction state action
  env <- ask                              -- get the environment from the reader
  liftIO $ (mainUFunc env) newstate       -- updating is in IO; it needs to be lifted
  return newstate

At this point, the program's main loop will essentially be:

loop :: State -> ReaderT Env IO ()
loop = do
  action <- liftIO getAction
  if action == EndLoop
    then return ()
    else do
      st' <- processActionR st action
      loop st'

mainLoop :: IO ()
mainLoop = do
  env <- setUpCallbacks
  let st = initState
  runReaderT $ loop st

So that's how you can use Reader. Every function that used to take an environment parameter no longer needs to. Functions that don't take the environment can be used directly or lifted if they're monadic.

Araucania answered 10/8, 2010 at 19:38 Comment(0)
I
8

The basic scheme for using any "normal" monad[0] is pretty much the same across the board. Essentially:

  • Write functions that return a value of monadic type, using do notation if you like, just like you'd write an IO function like main.
  • Make use of any specific functions for the monad you're working with.
  • Call these functions from each other, using the standard rule:
    • Bind a value from the same monad using a <- to get at the value "inside", causing the other value to be "run".
    • Bind any other value using let, leaving it independent of the monadic structure.
  • Use a particular monad's specialized "run" function to evaluate the monadic computation and get the final result.

Do that, and all the messy details of the extra functionality described by the monad (in this case, passing an extra environment parameter around) are handled automatically.

Now, the usual Reader operations are ask and local:

  • ask is a monadic value holding the environment; in a do block you use it the same way you'd use something like getLine in the IO monad.
  • local takes a function that provides a new environment and a computation in the Reader monad, runs the latter in an environment modified by the former, then takes the result and puts it into the current function. In other words, it runs a sub-computation with a locally modified environemnt.

The "run" function is the creatively-named runReader, which simply takes a computation in the Reader monad and an environment value, runs the former using the latter, and returns the final result outside of the monad.

As an example, here's some functions doing some meaningless calculation in a Reader monad, where the environment is a "maximum value" that says when to stop:

import Control.Monad.Reader

computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do 
    maxVal <- ask
    let y = f x
    if y > maxVal
        then return []
        else do zs <- local (subtract y) (computeUpToMax f y)
                z <- frob y
                return (z:zs)

frob :: Int -> Reader Int (Maybe Int)
frob y = do
    maxVal <- ask
    let z = maxVal - y
    if z == y 
        then return Nothing
        else return $ Just z

To run it, you'd use something like this:

> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]

...where 9 is the initial environment.

Almost exactly the same structure can be used with other monads, such as State, Maybe, or [], though in the latter two cases you'd typically just use the final monadic result value instead of using a "run" function.

[0]: Where normal means not involving compiler magic, the most obvious "abnormal" monad of course being IO.

Ihram answered 10/8, 2010 at 18:25 Comment(2)
Where does the bind >>= come into play?Orlon
@CMCDragonkai: It's used in translating do blocks. A line like x <- foo becomes a bind foo >>= \x ->, where the body of the lambda is (the translated version of) the rest of the do block.Ihram
A
6

I think it's easiest if you look at how you would solve this problem without using Reader, then compare the translated version. Here's a trimmed-down example from a program I'm working on where the environment is a set of callback functions to update the display. It's slightly more complicated because it uses ReaderT instead of Reader, but everything works in basically the same way.

runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
  newstate <- processAction state action
  let ufunc = mainUFunc env              -- get the callback to update the display
  ufunc newstate                         -- update the display
  return newstate

Now I'll change it to use the Reader monad to pass along the environment. Since the code was already in IO, it's necessary to use the monad transformer version, ReaderT.

runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
  newstate <- lift $ processAction state action
  env <- ask                              -- get the environment from the reader
  liftIO $ (mainUFunc env) newstate       -- updating is in IO; it needs to be lifted
  return newstate

At this point, the program's main loop will essentially be:

loop :: State -> ReaderT Env IO ()
loop = do
  action <- liftIO getAction
  if action == EndLoop
    then return ()
    else do
      st' <- processActionR st action
      loop st'

mainLoop :: IO ()
mainLoop = do
  env <- setUpCallbacks
  let st = initState
  runReaderT $ loop st

So that's how you can use Reader. Every function that used to take an environment parameter no longer needs to. Functions that don't take the environment can be used directly or lifted if they're monadic.

Araucania answered 10/8, 2010 at 19:38 Comment(0)
P
0

This is IMHO best monad resource - All About Monads, and here's part for Reader monad.

Padraig answered 10/8, 2010 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.