David H
David H

Reputation: 1356

Where to handle monad fails?

I want to know what is the best way in order to write code using monads(Option,Try,Either). My first impression was that those monads should let me write the logic and ignore errors, then based on the type result I would better know what happened. I will give an example

val a:Option[Something] = list.get("key")

now I can operate on my value like I have it with map and flatMap
at the end of the code, if I have None it means the list doesnt have the "key". This workflow works only when you have small logic. but how should I write code if I have a lot of logic i.e :

val server:Option[Server] = serverList.get("serverId")
val value:Option[Try[Value]] = serverList.map(serverId=>getDataFromServer(serverId))
val processedValue:Option[Try[Some[OtherValue]]] = value.map(server => server.map( value=> processValue(value))

now when handling error I will do something like:

processedValueOption match { 
   case None=> .... // server is not identified
   case Some(Failure(e)) =>  //error to get value from server
   case .......
}

Actually my types let me know what is the error at the end of my code. But it's becoming very complex types when you have big logic. How do you think I should write the code? Should I handle errors when I write the logic in order to get simple types?

Upvotes: 1

Views: 202

Answers (2)

dfeuer
dfeuer

Reputation: 48631

In Haskell, this is generally done with Either, EitherT, or ExceptT (EitherT and ExceptT are monad transformers and are almost identical). You can use do notation with any of these to string together actions that may fail and get out either a final result or information about the failure. In some situations, it can be nice use Applicative operators instead. For example,

f <$> e1 <*> e2 <*> e3

will calculate e1, e2, and e3, stopping if any fail, and if all succeeded will combine the results with f. You can even bring Alternative into the mix:

f <$> e1 <*> (e2 <|> e3)

Upvotes: 0

sarveshseri
sarveshseri

Reputation: 13985

Either can be one of the better solutions for such cases,

// Explicit errors
object MyErrors {
    trait MyError
    object ServerNotIdentifiedError extends MyError
    object CanNotGetValueFromServerError extends MyError
    object ValueProcessingFailedError extends MyError 
}

val server: Either[ Server, MyError ] = serverList.get("serverId").match {
  case Some( server ) => Left( server )
  case None => Right( ServerNotIdentifiedError )
}

// assuming getDataFromServer returns Try[ Value ]
val value:Either[ Value, MyError ] = getDataFromServer( serverId ) match {
  case Success( value ) => Left( value ),
  case Failure( ex ) => {
    ex.printStackTrace();
    Right( CanNotGetValueFromServerError )
  }  
}

// assuming processValue returns Try[ OtherValue ]
val processedValue: Either[ OtherValue, MyError ] = value match {
  case Left( value ) => processValue(value) match {
    case Success( otherValue ) => Left( otherValue )
    case Failure( ex ) => {
      ex.printStackTrace()
      Right( ValueProcessingFailedError )
    }
  }
  case _ => value
}

Upvotes: 1

Related Questions