问题
When learning the Reader Monad, I find that it is defined as:
newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return a = Reader $ \_ -> a
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
I want to known why using function as constructor parameter instead of something else such as a tuple:
newtype Reader r a = Reader { runReader :: (r, a) }
instance Monad (Reader r) where
-- Here I cannot get r when defining return function,
-- so does that's the reason that must using a function whose input is an "r"?
return a = Reader (r_unknown, a)
m >>= k = Reader (fst $ runReader m) (f (snd $ runReader m))
According to the Reader definition, we need a "environment" which we can use to generate a "value". I think a Reader type should contain the information of "environment" and "value", so the tuple seems perfect.
回答1:
You didn't mention it in the question, but I guess you thought specifically of using a pair for defining Reader
because it also makes sense to think of that as a way of providing a fixed environment. Let's say we have an earlier result in the Reader
monad:
return 2 :: Reader Integer Integer
We can use this result to do further calculations with the fixed environment (and the Monad
methods guarantee it remains fixed throughout the chain of (>>=)
):
GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3
5
(If you substitute the definitions of return
, (>>=)
and runReader
in the expression above and simplify it, you will see exactly how it reduces to 2 + 3
.)
Now, let's follow your suggestion and define:
newtype Env r a = Env { runEnv :: (r, a) }
If we have an environment of type r
and a previous result of type a
, we can make an Env r a
out of them...
Env (3, 2) :: Env Integer Integer
... and we can also get a new result from that:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2)
5
The question, then, is whether we can capture this pattern through the Monad
interface. The answer is no. While there is a Monad
instance for pairs, it does something quite different:
newtype Writer r a = Writer { Writer :: (r, a) }
instance Monoid r => Monad (Writer r) where
return x = (mempty, x)
m >>= f = Writer
. (\(r, x) -> (\(s, y) -> (mappend r s, y)) $ f x)
$ runWriter m
The Monoid
constraint is needed so that we can use mempty
(which solves the problem that you noticed of having to create a r_unknown
out of nowhere) and mappend
(which makes it possible to combine the first elements of the pair in a way that doesn't violate the monad laws). This Monad
instance, however, does something very different than what the Reader
one does. The first element of the pair isn't fixed (it is subject to change, as we mappend
other generated values to it) and we don't use it to compute the second element of the pair (in the definition above, y
does not depend neither on r
nor on s
). Writer
is a logger; the r
values here are output, not input.
There is one way, however, in which your intuition is justified: we can't make a reader-like monad using a pair, but we can make a reader-like comonad. To put it very loosely, Comonad is what you get when you turn the Monad
interface upside down:
-- This is slightly different than what you'll find in Control.Comonad,
-- but it boils down to the same thing.
class Comonad w where
extract :: w a -> a -- compare with return
(=>>) :: w a -> (w a -> b) -> w b -- compare with (>>=)
We can give the Env
we had abandoned a Comonad
instance:
newtype Env r a = Env { runEnv :: (r, a) }
instance Comonad (Env r) where
extract (Env (_, x)) = x
w@(Env (r, _)) =>> f = Env (r, f w)
That allows us to write the 2 + 3
example from the beginning in terms of (=>>)
:
GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv)
(3,5)
One way to see why this works is noting that an a -> Reader r b
function (i.e. what you give to Reader
's (>>=)
) is essentially the same thing that an Env r a -> b
one (i.e. what you give to Env
's (=>>)
):
a -> Reader r b
a -> (r -> b) -- Unwrap the Reader result
r -> (a -> b) -- Flip the function
(r, a) -> b -- Uncurry the function
Env r a -> b -- Wrap the argument pair
As further evidence of that, here is a function that changes one into the other:
GHCi> :t \f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
\f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
:: (t -> Reader r a) -> Env r t -> a
GHCi> -- Or, equivalently:
GHCi> :t \f -> uncurry (flip (runReader . f)) . runEnv
\f -> uncurry (flip (runReader . f)) . runEnv
:: (a -> Reader r c) -> Env r a -> c
To wrap things up, here is a slightly longer example, with Reader
and Env
versions side-by-side:
GHCi> :{
GHCi| flip runReader 3 $
GHCi| return 2 >>= \x ->
GHCi| Reader (\r -> x ^ r) >>= \y ->
GHCi| Reader (\r -> y - r)
GHCi| :}
5
GHCi> :{
GHCi| extract $
GHCi| Env (3, 2) =>> (\w ->
GHCi| (\(r, x) -> x ^ r) $ runEnv w) =>> (\z ->
GHCi| (\(r, x) -> x - r) $ runEnv z)
GHCi| :}
5
回答2:
First of all note that your bind function is wrong and would not compile.
If the Reader
were defined as you describe with a tuple, there would be problems:
The monad laws would be violated, e.g. left identity, which states that:
return a >>= f == f a
or the right identity:
m >>= return == m
would be broken, depending on the implmentation of >>=
because >>=
would forget either the first tuple element of the first argument or the second, i.e. if the implmentation would be:
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (mr, fv)
then we would always lose the reader value that comes out of f
(aka fr
) and otherwise if >>=
would be
(Reader (mr, mv)) >>= f =
let (Reader (fr, fv)) = f mv
in Reader (fr, fv)
-- ^^^ tiny difference here ;)
we would always lose mr
.
- A
Reader
is some action, that mayask
for a constant value, which cannot be changed by another monadic action, which is read-only.
But when defined with a tuple, we could super-easy overwrite the reader value, e.g. whith this function:
tell :: x -> BadReader x ()
tell x = BadReader (x, ())
If a reader is instead defined with a function, this is impossible (try it)
- Also, that enviroment is actually not required before converting a
Reader
to a pure value (aka running the Reader), so from this alone it makes sense to use a function instead of a tuple.
When using a tuple, we would have to provide the Reader
value even before we actually run an action.
You can see that in your return
definition, you even point out the problem, where the r_unknown
comes from ...
To get a btter intuition, let's assume a Reader
action that returns the Person
s with a certain age
from the Addressbook
:
data Person = MkPerson {name :: String, age :: Int}
type Addressbook = [Person]
personsWithThisAge :: Int -> Reader Addressbook [Person]
personsWithThisAge a = do
addressbook <- ask
return (filter (\p -> age p == a) addressbook)
This personsWithAge
function returns a Reader
action and since it only ask
s for the Addressbook
, it is like a function that accepts an addressbook and gives back a [Person]
list,
so it is natural to define a reader as just that, a function from some input to a result.
We could rewrite this Reader
action to be a function of the Addressbook
like this:
personsWithThisAgeFun :: Int -> Addressbook -> [Person]
personsWithThisAgeFun a addressbook =
filter (\p -> age p == a) addressbook
But why invent Reader
??
The real value of Reader
shows when combining several functions like e.g. personsWithThisAge
, that all depend on (the same) one constant Addressbook
.
Using a Reader
we don't have to explicitly pass some Addressbook
around, individual Reader
actions don't even have any way at all to modify the Addressbook
- Reader
guarantees us, that every action uses the same, unmodified Addressbook
, and all a Reader
action can ever to with the environment is ask
for it.
The only way to implement this, with these guarantees is with a function.
Also if you look at the monad instances that are included in the standard library, you will see that (r ->)
is a monad; actually it is identical to the Reader
monad apart from some technical differences.
Now the structure you describe with the tuple is actually pretty close to a Writer
monad, what is write-only , but that's out of scope.
来源:https://stackoverflow.com/questions/42310931/why-to-define-the-constructor-parameter-of-reader-as-a-function