Mark Elston (110) [Avatar] Offline
#1
I think I understand what is happening here in the map function. It is simply returning a new Reader with a run member that calls the 'original' run with the provided parameter (r) and then calls f on the result. The end result of this is a little unclear as I am not quite clear on the relationship between the 'original' run and the one being passed in. In other words, how is this supposed to be used in practice? It seems there is a 'stack' of evaluations that must eventually be resolved and I don't see this actually happening anywhere.

However, I do not understand the flatMap function at all and you don't explain it. Why is run being called twice? What does this function do for us?

Mark
Mark Elston (110) [Avatar] Offline
#2
Let me see if I can answer my own question...

Since f produces a Reader[R, B], if we could just apply f to r then we could return the result right away and be done with it. However, a Reader is not an immediate application to the environment, R. Instead, it is a deferred evaluation.

So we do not actually have an R to apply to f yet so we cannot generate the resulting Reader. And we cannot just return r => f(run(r)) either since that is not a Reader. It is a function that generates a Reader. Neither can we wrap r => f(run(r)) in a Reader constructor since the return type of the resulting run function will be wrong.

So, the only real alternative is to 'tack on' the second run call as this actually returns the proper B value anyway, and wrap the whole thing, including both calls to run, in a Reader constructor.

How is that? Is it close to the right answer, anyone?
Debasish Ghosh (113) [Avatar] Offline
#3
Thanks for your explanation and apologize for the late response ..

The best way to understand flatMap is to start backwards.

We need to produce a Reader[R, B]. So we start with a Reader(r => ..), where r is of type R. Note that the abstraction takes a run function which takes an R and generates an A. So we apply this r to run and run(r) gives me a A. Now f: A => Reader[R, B] - hence we apply run(r), which gives me an A, to f. Now I get a Reader[R, B], which is an abstraction that has not yet been evaluated. This Reader[R, B] takes the r through the second run and generates a B, which is, by definition a Reader[R, B].
Mark Elston (110) [Avatar] Offline
#4
Thanks for the reply. Your response almost answers the question, I think. My problem is really with the second run call. I have less of a problem with that call in this instance. However, I am really struggling with it in the case of the State Monad. I have a question on StackOverflow about it and can't quite seem to grasp the whole thing.

This scenario is not quite the same but is similar enough that I still have trouble seeing it.

One comment I would have with what you describe is that it sounds like we can do just about anything that gets the types right without worrying about exactly what it is we are doing. That can't be correct as there are a number of things that keep the types correct without providing the right answers. I am looking to be able to reason through a problem and justify what I am doing. I don't really follow the logic of your explanation in terms of following a calculation from start to finish and being able to validate the result. I thought that was one of the values of FP. I can't see just 'throwing in' an extra run call just to make the types work right. Maybe I missed something somewhere.
225929 (2) [Avatar] Offline
#5
Since the intention of the Reader[R, A] is to represent the result of an operation that depends on a repository in such a way that we can sequence the application of many operations that also depend on the same repository; the flatMap function, which is used to bind the operations, should return exactly the result of the operation it receives.
For example, if we have two operations worked to return a Reader[Repo, X] instead of X:
def op1(a:A):Reader[Repo,B]
def op2(b:B):Reader[Repo,C]

We want Reader works such way that binding the two operations with flatMap like this
val composedOperation:Reader[Repo, C] = op1(someA).flatMap { (b:B) => op2(b) }
val result: c = composedOperation.run(repo)

both operations are ever executed, the order of execution is op1 before op2, letting op2 have access to b (where b = op1(someA).run(repo)), and the result of the whole expression be the result of op2. Note that there should be no effect other than the ability to have access to the repository. Unlike Option, the op2 should ever be executed, independently of the result of op1.

So, op1(someA).flatMap(op2) should ever return the result of op2(b)
val composedOperation: Reader[Repo, C] = op2(b)
where b is the result of applying the function
op1(someA).run
to the environemnt repo.
val b: B = op1(someA).run(repo)

So we have to return the result of the expression
val composedOperation:Reader[Repo, C] = op2(op1(someA).run(repo))

But this expression (the composed operation) depends on an unknown variable: repo. How can we get rid of repo without modifying the meaning of the resulting composed operation expression? It would be impossible if the expression represented a value, because having a dependence on something forces it be a function. But since the type of the expression is
Reader[Repo,C], which is a lifted function, we can make rid of the variable repo if we are able to make repo be the independent variable of the lifted function. Let's try.
To manipulate in the function domain we have to unlift the expression from the Reader codomain to the function domain.
val composedOperationUnlifted: Repo => C = x => op2(op1(someA).run(repo)).run(x)

Here we could have chosen any other name for the variable x. Even repo, because they have the same type.
val composedOperationUnlifted: Repo => C = repo => op2(op1(someA).run(repo)).run(repo)

Lifting back to the Reader codomain we get
val composedOperation: Reader[Repo, C] = Reader { repo => op2(op1(someA).run(repo)).run(repo) }

which doesn't depend on any unknown. So we are ready. Replacing op1(someA) with this we have
this.flatMap { (b:B) => op(b) } == Reader { repo => op(this.run(repo)).run(repo) }
Debasish Ghosh (113) [Avatar] Offline
#6
Yes .. your explanation is spot on!

Thanks.