I need to build records with multiple fields.
The record definition is like:
class Record(
f1: Int,
f2: String,
...
err: String
)
The logic is like:
record.f1 = get_f1()
record.f2 = get_f2()
...
The calls to get_f1 etc may throw exceptions. When this happen, we set record.err to the exception message.
Right now, my hacky solution is to use a mutable Record instance and patch the fields incrementally. When exception happens, I'll catch it from an outer function and set the err field.
The question is how can I implement this in a more functional way?
CodePudding user response:
This is a (mostly) functional way of doing this:
import scala.util.{Try, Success, Failure}
Try {
Record(getf1(), getf2(), "")
} match {
case Success(r) => r
case Failure(e) => Record(0, "", e.getMessage)
}
If the code inside the Try
runs OK then it will return Success
and the match
will extract the value and return it.
If the code throws an exception then it will be caught by the Try
and it will return Failure
. The match
will catch the error code and then create a value of Return
with err
set to the error message from the exception.
As others have mentioned, the Either
type is a better way of handling the error case as it removes the error information from the Record
class itself:
Try {
Record(getf1(), getf2())
}.toEither
The result will be Right(<record>)
on success or Left(<exception>)
on error. You can use match
to determine which answer was given.
CodePudding user response:
The question is how can I implement this in a more functional way?
By properly modeling this in a functional style.
First, let's remove the err
field from the case class
, and rather use Either
Second, let's properly handle the nulls
using Option
Finally, let's use a functional library like cats.
The end result would be something like this:
import cats.data.EitherNec
import cats.syntax.all._
type Error = String // You may use a better data type.
final case class Record(
f1: Int,
f2: String,
f3: Boolean
)
object Record {
def fromRawData(f1: Option[Int], f2: Option[String], f3: Option[Boolean]): EitherNec[Error, Record] =
(
f1.toRightNec(left = "F1 was empty"),
f2.toRightNec(left = "F2 was empty"),
f3.toRightNec(left = "F3 was empty")
).parMapN(Record.apply)
}
The
EitherNec[E, A]
is just an alias forEither[NonEmptyChain[E], A]
Meaning that if it is aLeft
it will hold all the errors; it also implies that there must be at least one error.
Feel free to ask any follow-up questions you may have.
If you don't want to use cats you can replace the parMapN
with a for
; but this will stop at the first failure.
You can see the code running here.