In main
I can read my config file, and supply it as runReader (somefunc) myEnv
just fine. But somefunc
doesn't need access to the myEnv
the reader supplies, nor do the next couple in the chain. The function that needs something from myEnv is a tiny leaf function.
How do I get access to the environment in a function without tagging all the intervening functions as (Reader Env)
? That can't be right because otherwise you'd just pass myEnv around in the first place. And passing unused parameters through multiple levels of functions is just ugly (isn't it?).
There are plenty of examples I can find on the net but they all seem to have only one level between runReader and accessing the environment.
I'm accepting Chris Taylor's because it's the most thorough and I can see it being useful to others. Thanks too to Heatsink who was the only one who attempted to actually directly answer my question.
For the test app in question I'll probably just ditch the Reader altogether and pass the environment around. It doesn't buy me anything.
I must say I'm still puzzled by the idea that providing static data to function h changes not only its type signature but also those of g which calls it and f which calls g. All this even though the actual types and computations involved are unchanged. It seems like implementation details are leaking all over the code for no real benefit.
Environmental functions are defined as the possible uses of our non-human made physical surroundings, on which humanity is dependent, whether they be producing, consuming, breathing or recreating.
The parent environment of a function is the environment in which the function was created. If a function was created in the execution environment (for example, in the global environment), then the environment in which the function was called will be the same as the environment in which the function was created.
R Programming Environment The top level environment available to us at the R command prompt is the global environment called R_GlobalEnv . Global environment can be referred to as . GlobalEnv in R codes as well. We can use the ls() function to show what variables and functions are defined in the current environment.
C++ getenv()The getenv() function in C++ returns a pointer to a C string containing the value of the environment variable passed as argument. If the environment variable passed to the getenv() function is not in the environment list, it returns a null pointer.
You do give everything the return type of Reader Env a
, although this isn't as bad as you think. The reason that everything needs this tag is that if f
depends on the environment:
type Env = Int
f :: Int -> Reader Int Int
f x = do
env <- ask
return (x + env)
and g
calls f
:
g x = do
y <- f x
return (x + y)
then g
also depends on the environment - the value bound in the line y <- f x
can be different, depending on what environment is passed in, so the appropriate type for g
is
g :: Int -> Reader Int Int
This is actually a good thing! The type system is forcing you to explicitly recognise the places where your functions depend on the global environment. You can save yourself some typing pain by defining a shortcut for the phrase Reader Int
:
type Global = Reader Int
so that now your type annotations are:
f, g :: Int -> Global Int
which is a little more readable.
The alternative to this is to explicitly pass the environment around to all of your functions:
f :: Env -> Int -> Int
f env x = x + env
g :: Env -> Int -> Int
g x = x + (f env x)
This can work, and in fact syntax-wise it's not any worse than using the Reader
monad. The difficulty comes when you want to extend the semantics. Say you also depend on having an updatable state of type Int
which counts function applications. Now you have to change your functions to:
type Counter = Int
f :: Env -> Counter -> Int -> (Int, Counter)
f env counter x = (x + env, counter + 1)
g :: Env -> Counter -> Int -> (Int, Counter)
g env counter x = let (y, newcounter) = f env counter x
in (x + y, newcounter + 1)
which is decidedly less pleasant. On the other hand, if we are taking the monadic approach, we simply redefine
type Global = ReaderT Env (State Counter)
The old definitions of f
and g
continue to work without any trouble. To update them to have application-counting semantics, we simply change them to
f :: Int -> Global Int
f x = do
modify (+1)
env <- ask
return (x + env)
g :: Int -> Global Int
g x = do
modify(+1)
y <- f x
return (x + y)
and they now work perfectly. Compare the two methods:
Explicitly passing the environment and state required a complete rewrite when we wanted to add new functionality to our program.
Using a monadic interface required a change of three lines - and the program continued to work even after we had changed the first line, meaning that we could do the refactoring incrementally (and test it after each change) which reduces the likelihood that the refactor introduces new bugs.
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