Tomer Ben David (8) [Avatar] Offline
#1
val cust = getCustomer(..) verifyCustomer(cust).map(c => openCheckingAccount(c, date)) .getOrElse(throw new Exception("Verification failed for customer"))

so the side effect is last. usually in my actual work I see side effects also in middle of chain.

enother example from book:

val generateAuditLog: (Account, Amount) => Try[String] = //..
val write: String => Unit
debit(source, amount)
.flatMap(b => generateAuditLog(b, amount))
.foreach(write)

again side effect is last operation. however in real life side effect is also in middle. It sounds simple to me when side effect is last but how to handle it when its in the middle?

for example:

1. receive request
2. audit that received request
3. validate request.
4. send event (side effect) that passed validation.
and so on...
.
.


so side effects are eventually returning Unit and its important that they return Unit because this is a big sign - this is a side effect. but if they are in the middle of the chain how can they return Unit? if we have them not returning unit they will loose the big sign this is a side effect. How to handle these cases while preserving the ordering? I mean ofcourse I can aggregate all the side effects and run them at the end but this would miss the ordering at any point in time I first received the request and only then audit the event i then validated the request and only then sent an event.

So i'm bewildered at how to handle such a scenario, does the book handles it? I only read up to this point (~page 34) and i'm not sure whether i'm referring to a point that is handled in the following book pages or not. and if not then how should I be handling such scenarios.
Debasish Ghosh (113) [Avatar] Offline
#2
Hello -

Actually there are quite a few options. Let me discuss one of them using which you can have a nice composition of pure computation as well as ones that can be side-effecty. The solution is to wrap your computation in a monad. In this use case I am using Try, which is a monad. Try is useful since all of the operations that we are trying to compose together can fail ..

def receiveRequest(body: Int): Try[Request]
def audit(r: Request): Try[Unit]
def validate(r: Request): Try[Boolean]
def sendEvent(r: Request): Try[Unit]

for {
  r <- receiveRequest(100)
  _ <- audit(r)
  _ <- validate(r)
  _ <- sendEvent(r)
} yield (())


Note the side-effecty ones have the signature Try[Unit], Unit indicates the side-effect and the Try indicates that it can fail. There are many other advanced options e.g. using Free Monads that I discuss later in the book. Pls beware that the above code is just a rough one and I don't have a compiler right now. Please feel free to get back if u need more details.

Thanks.
Tomer Ben David (8) [Avatar] Offline
#3
Thanks. It answers what I was looking for.
Tomer Ben David (8) [Avatar] Offline
#4
So I hope that monads and free monads will help me prove the correctness of my code even though it involves intermingled chaining of pure function calls and function calls which have side effects. This would be very cool. So I'll continue to read thanks smilie