Reputation: 1356
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
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
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