如何将函数转换为函数返回函数返回?

时间:2014-05-13 14:14:41

标签: scala exception-handling either

假设我有一些引发异常的函数。我正在包装它们以返回Either[Throwable, <function return type>]。 (我们假设我需要Either而不是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) }

...

现在我想写一个泛型包装器来摆脱bolierpllate代码。你会建议什么?

5 个答案:

答案 0 :(得分:9)

任何时候你想在arity方面做一些通用的事情,Shapeless很可能拥有你需要的东西。在这种情况下,您可以编写以下内容:

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)
  }
)

现在假设我们有一个不安全的方法,如下所示:

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

我们可以包装它:

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

现在我们不必担心例外情况:

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")

这适用于任何旧的FunctionN

答案 1 :(得分:4)

我会写一些这样的形式:

 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
 }

但这并不构成。 Try非常好。

更新:如果您只想处理正确的投影,那么只需返回正确的投影即可。现在它组成了。

答案 2 :(得分:3)

如果您不想修改初始函数定义:

您是否需要一个适用于任意数量参数的包装器,或者您是否可以使用每个可能数量的参数的通用包装器?在后一种情况下,您可以:

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) }
}

允许您执行eitherify(foo)eitherify(bar)等等。

答案 3 :(得分:3)

由于您显然还没有足够的选择,另一种方法是使用ScalaUtils尝试方法。 ScalaUtils是一个非常小的,集中的库,您可能不介意添加为依赖项。 try方法(位于org.scalautils包对象中)将为您提供一个Or类型,您可以在其上调用toEither。我首先建议使用Try执行此操作,因此您不需要添加任何依赖项,但它似乎没有toEither方法,这让我感到惊讶。所以这就是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)

抱歉,我最初错过了你想要一种方法。我可能只是像noziar建议的那样超载:

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)

如果你想避免过载(和无形),那么另一种选择是magnet pattern。我相信这会让你找到一个安全的方法,但我认为重载会更简单。

答案 4 :(得分:2)

Scalaz有一个右偏Either名为\/,可能会满足您的需求,正如您所描述的那样。您可以在fromTryCatch对象上使用\/来删除所提到的样板文件。由于\/是偏右的,它可以用于理解,偏向右边。

示例用法可能是:

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

上面的结果类型为Throwable \/ A