pjan (4) [Avatar] Offline
#1
Hi Debasish,

p133 of the Meap v5:
You mention that op(“noNotExists”) run repo will result in immediate Failure and break the chain. However, the monadic composition you are doing is over the Reader monad, and not the Try monad. Hence, I believe it will just run over all of the steps and not break the monadic bind like you.

You likely need monad transformers here? Or am I missing something crucial here?

Kindest regards
Debasish Ghosh (111) [Avatar] Offline
#2
Hi pjan -

When I do op(“noNotExists”) run repo for a non-existing account, I run the Reader monad. When u run the Reader monad, the for comprehension will get executed and the Failure will result. Note that when we do the comprehension, we get a composed Reader over the entire sequence (Reader[AccountRepository, Try[Balance]]), which gets executed in the run. To see the example in action, get the source code from the Github repo (link given in Manning book page) and try executing as per the gist https://gist.github.com/debasishg/5d6869b10c0d8c514c6a. Note the return types in each step and the final Failure that we get for a non-existing account.

Hope it clarifies. Let me know if you need more help.

Thanks.
pjan (4) [Avatar] Offline
#3
Hi Debasish,

Thanks for the quick reply, and for the gist to reason on.

What I was trying to say is that, as opposed to what I read in the book – and I might be misreading it – it would fail quickly and break the chain. In which case it would have to return the error associated with the first operation (i.e. the credit(no, BigDecimal(100))). However, the Reader monad will attempt each single operation, and not fail fast. In your gist/example, it is obvious because of the error message returned being the one from the balance operation (not that of the credit one).

So, unlike what I read in the text, the execution does not depend on the Success or Failure of the previous step in the chain, at least, not when you wrap it in the Reader monad. In case the service would return a Try, rather than a Reader[AccountRepository, Try[Account]], it would indeed be the case and fail fast.

Hope this clarifies?
Happy to continue the discussion or review it if I would be wrong.

Kindest regards,
@pjan
Debasish Ghosh (111) [Avatar] Offline
#4
Let me try to explain again ..

When you are within the Reader monad, things are handled by the Reader. After the for comprehension, if you invoke op("noNotExists"), you get a Reader - no failure till now since all we have is a computation, which has not yet been evaluated.

The moment you do op("noNotExists") run repo, you are out of the reader monad and the run combinator asks for the evaluation of the computation, which the Reader was holding on till now. If we think of the Reader as just a Function1, it's actually a function AccountRepository => Try[Balance]. So the moment you run it by giving the argument "repo", it starts the evaluation of the for comprehension.

And in the first step, the "credit" method fails since what credit first tries to do is look up for the account no. It fails and since Try is a monad, it's a fail-fast computation. Hence the entire operation fails and by the signature of the entire computation, which is AccountRepository => Try[Balance] (the de-sugared version of Reader[AccountRepository, Try[Balance]]), we have a Failure reported with an exception.

Thanks.
pjan (4) [Avatar] Offline
#5
Let me use your code and gist to explain ...

If it would fail fast, then it would fail in the first credit step, and running the Reader would result in the following Failure:
Failure(java.lang.Exception: Account not found with no a-143)

However, as per your gist, it returns the following Failure (which is the failure associated with the balance step:
Failure(java.lang.Exception: No account exists with no a-143)

This shows that it is not failing fast.


Other way of showing this: if you would add an always fail step at the beginning of op:

def fail: Reader[AccountRepository, Try[Account]] = 
  Reader { c =>
    Failure(new Exception(s"boom"))
  }

def op(no: String) = for {
  _ <- fail
  _ <- credit(no, BigDecimal(100))
  _ <- credit(no, BigDecimal(300))
  _ <- debit(no, BigDecimal(160))
  b <- balance(no)
} yield b 


then, in case of a fail fast, it would always result in a Failure whatever account number you throw it, but it doesn't for the one that is in the repo

...
Debasish Ghosh (111) [Avatar] Offline
#6
Hi pjan -

I am not sure what I was thinking. I should not have replied from my phone while traveling with a kid smilie .. You are absolutely correct. The bind is over the Reader, which doesn't fail and hence we get the failure message of the last call to balance.

Thanks for pointing this out. I will correct it in the next MEAP - change the example to one w/o the Reader.

Thanks.
- Debasish