문제

Suppose I have a few functions that raise exceptions. I am wrapping them to return Either[Throwable, <function return type>]. (Let's assume I need Either rather than Try).

def fooWrapper(arg1: FooArg1, arg2: FooArg2) =
  try Right(foo(arg1, arg2)) catch { case NonFatal(e) => Left(e) }

def barWrapper(arg1: BarArg1, arg2: BarArg2, a3: BarArg3) =
  try Right(bar(arg1, arg2, artg3)) catch { case NonFatal(e) => Left(e) }

...

Now I would like to write a generic wrapper to get rid of the bolierpllate code. What would you suggest ?

도움이 되었습니까?

해결책 2

I would write something of the form like this:

 def wrap[Value](f: => Value): Either[Value, Exception] = try{
   Right(f).right
 }
 catch{
   case ex: Exception => Left(ex).right
 }

 def foo(arg1: FooArg1, arg2: FooArg2) = wrap{
   //anything I'd have written before in foo
 }

but this doesn't compose. Try is so much nicer.

Updated: If you only ever want to deal with the right projection, then just return the right projection. Now it composes.

다른 팁

Any time you want to make something generic with respect to arity, Shapeless is pretty likely to have what you need. In this case you can write the following:

import scala.util.control.NonFatal
import shapeless._, ops.function._

def safeify[F, A <: HList, R, O](f: F)(implicit
  ftp: FnToProduct.Aux[F, A => R],
  ffp: FnFromProduct[A => Either[Throwable, R]]
) = ffp((a: A) =>
  try Right(ftp(f)(a)) catch {
    case NonFatal(ex) => Left(ex)
  }
)

Now suppose we have an unsafe method like the following:

def bad(s: String, i: Int) = s.toInt / i

We can wrap it:

scala> val better = safeify(bad _)
better: (String, Int) => Either[Throwable,Int] = <function2>

And now we don't have to worry about exceptions:

scala> better("1", 0)
res0: Either[Throwable,Int] = Left(ArithmeticException: / by zero)

scala> better("a", 1)
res1: Either[Throwable,Int] = Left(NumberFormatException: For input string: "a")

This will work for any old FunctionN.

In case you don't want to modify your initial function definition:

Do you need one wrapper that works for any number of arguments, or are you fine with a generic wrapper for each possible number of arguments? In the latter case you can have:

def eitherify[A, B, C](f: Function2[A, B, C])(a: A, b: B) = {
    try Right(f(a, b)) catch { case NonFatal(e) => Left(e) }
}
def eitherify[A, B, C, D](f: Function3[A, B, C, D])(a: A, b: B, c: C) = {
    try Right(f(a, b, c)) catch { case NonFatal(e) => Left(e) }
}

which allows you to do eitherify(foo), eitherify(bar) and so on.

Since you clearly don't have enough choices yet, one other way is to use ScalaUtils attempt method. ScalaUtils is a very tiny, focused library that you may not mind adding as a dependency. The attempt method (which is in the org.scalautils package object) will give you an Or type on which you can invoke toEither. I was first going to suggest doing this with Try so you wouldn't need to add any dependencies, but it doesn't appear to have the toEither method, which surprised me. So here's what it would look like with Or:

scala> import org.scalautils._
import org.scalautils._

scala> def foo(i: Int, s: String): String = { require(i >= 0); s * i }
foo: (i: Int, s: String)String

scala> def bar(b: Boolean, i: Int, s: String): Int = { require(i >= 0); if (b) s.length else i }
bar: (b: Boolean, i: Int, s: String)Int

scala> def foo2(i: Int, s: String) = attempt(foo(i, s)).toEither
foo2: (i: Int, s: String)Either[Throwable,String]

scala> def bar2(b: Boolean, i: Int, s: String) = attempt(bar(b, i, s)).toEither
bar2: (b: Boolean, i: Int, s: String)Either[Throwable,Int]

scala> foo2(2, "ho")
res10: Either[Throwable,String] = Right(hoho)

scala> foo2(-2, "ho")
res11: Either[Throwable,String] = Left(java.lang.IllegalArgumentException: requirement failed)

scala> bar2(true, 3, "ho")
res12: Either[Throwable,Int] = Right(2)

scala> bar2(false, 3, "ho")
res13: Either[Throwable,Int] = Right(3)

scala> bar2(false, -3, "ho")
res14: Either[Throwable,Int] = Left(java.lang.IllegalArgumentException: requirement failed)

Sorry, I initially missed that you wanted one method. I would probably just overload like noziar suggested:

scala> :paste
// Entering paste mode (ctrl-D to finish)

def safely[A, B, C](f: (A, B) => C): (A, B) => Either[Throwable, C] = (a: A, b: B) => attempt(f(a, b)).toEither
def safely[A, B, C, D](f: (A, B, C) => D): (A, B, C) => Either[Throwable, D] = (a: A, b: B, c: C) => attempt(f(a, b, c)).toEither


// Exiting paste mode, now interpreting.

safely: [A, B, C](f: (A, B) => C)(A, B) => Either[Throwable,C] <and> [A, B, C, D](f: (A, B, C) => D)(A, B, C) => Either[Throwable,D]
safely: [A, B, C](f: (A, B) => C)(A, B) => Either[Throwable,C] <and> [A, B, C, D](f: (A, B, C) => D)(A, B, C) => Either[Throwable,D]

scala> val foo3 = safely { foo _ }
foo3: (Int, String) => Either[Throwable,String] = <function2>

scala> val bar3 = safely { bar _ }    
bar3: (Boolean, Int, String) => Either[Throwable,Int] = <function3>

scala> foo3(2, "ho")
res5: Either[Throwable,String] = Right(hoho)

scala> foo3(-2, "ho")
res6: Either[Throwable,String] = Left(java.lang.IllegalArgumentException: requirement failed)

scala> bar3(true, 3, "ho")
res7: Either[Throwable,Int] = Right(2)

scala> bar3(false, 3, "ho")
res8: Either[Throwable,Int] = Right(3)

scala> bar3(false, -3, "ho")
res9: Either[Throwable,Int] = Left(java.lang.IllegalArgumentException: requirement failed)

If you want to avoid overloading (and shapeless), then the other alternative is the magnet pattern. I believe that would get you down to one safely method, but I think overloading would be simpler.

Scalaz has a right-biased Either called \/ which might cater to your needs, as you have described them. You can use fromTryCatch on the \/ object to remove the boilerplate mentioned. As \/ is right-biased, it can be used in for comprehensions, biased to the right.

Sample usage could be:

for {
  r1 <- \/.fromTryCatch(something1)
  r2 <- \/.fromTryCatch(something2)
} yield something3(r1, r2)

The resulting type above would be Throwable \/ A

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top