8

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 ?

arshajii
  • 127,459
  • 24
  • 238
  • 287
Michael
  • 41,026
  • 70
  • 193
  • 341

5 Answers5

9

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Thank you. It looks interesting but pretty advanced. I will play with it on the side but won't push it in the production repo. I would use the `wheaties` solution instead. – Michael May 13 '14 at 15:27
4

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.

wheaties
  • 35,646
  • 15
  • 94
  • 131
  • What do you mean by "this doesn't compose" ? – Michael May 13 '14 at 14:33
  • @Michael There's no real way of doing `map` or `flatMap` on an `Either`. The great thing about `Try` is that it composes so that you can work with several expressions that return `Try` in a single for-comprehension. – wheaties May 13 '14 at 14:35
  • I think he mean that `Try` can be used in for-comprehension so you can write a series of operations that any of can throw an exception and if so the processing is stopped and the raised exception is returned. – almendar May 13 '14 at 14:36
  • @wheaties You are right `Either` does not have `map` and `flatMap` but its projections do. Since the `Right` projections _do_ compose I can use them in a for-comprehension etc. – Michael May 13 '14 at 14:42
  • @Michael If it returns `Either` then you'll be dealing with `Either` and not `Right`. It will not compose. – wheaties May 13 '14 at 14:45
  • 1
    @wheaties I will be dealing with its _right_ projection. For instance, `for {r1 <- wrap(foo(...)).right; r2 <- wrap(bar(...)).right} yield e2` – Michael May 13 '14 at 14:47
3

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.

noziar
  • 1,017
  • 7
  • 11
  • Thanks. I want just _one_ wrapper for all functions with _any_ number of arguments. – Michael May 13 '14 at 14:44
  • 1
    As you can see on the doc for Function (http://www.scala-lang.org/api/2.11.0/index.html#scala.Function$), some wrappers such as tupled, uncurried, and so on, have to be defined separately for each possible number of arguments. So I'm not sure this can be done without any extra library and/or heavier machinery – noziar May 13 '14 at 15:00
3

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.

Bill Venners
  • 3,549
  • 20
  • 15
2

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

gpampara
  • 11,989
  • 3
  • 27
  • 26
  • Thanks for the suggestion. I just need `Either` now. – Michael May 13 '14 at 14:54
  • `\/` is isomorphic to `Either` and has a much better API – gpampara May 13 '14 at 14:55
  • 1
    I agree `\/` looks nicer but I am just adding a little component to the system, which use `Either` rather than `\/` and does not depend on `scalaz` at all. I am not sure I want to add the `scalaz` dependency just for my little component. – Michael May 13 '14 at 14:57