在不丢失类型的情况下对Scala函数进行抽象

时间:2015-09-07 08:57:01

标签: scala functional-programming arity

我正在创建一个带有lambda的函数,如果可能的话,使用.tupled(arity 2+)。为了让编译器允许使用它,它需要知道lambda是否真的是一个Function2(〜Function22)。但是,Function2[Any,Any,Any]的模式匹配意味着我留下了(Any, Any) => Any而不是原始类型。甚至不知道arity也没有帮助。我尝试匹配case f[A <: Any, B <: Any, C <: Any]: scala.Function2[A, B, C] => f.tupled以试图保留类型,但它并不真正允许案例的类型参数。

代码:

val add = (a: Int, b: Int) => a + b
add: (Int, Int) => Int = <function2>

val tpl = (fn: Any) => {
  fn match {
    case f: Function0[Any] => f
    case f: Function1[Any,Any] => f
    case f: Function2[Any,Any,Any] => f.tupled
//  case f: Function3[Any,Any,Any,Any] => f.tupled
//  ...
//  case _ => { throw new Exception("huh") }
  }
}

// actual result:
tpl(add)
res0: Any = <function1>

// desired result is like this one:
scala> add.tupled
res3: ((Int, Int)) => Int = <function1>

如果我不需要每个可能的arity级别的模式匹配案例,那么奖励积分......

1 个答案:

答案 0 :(得分:1)

答案很难看,正如你所预料的那样。在函数上使用模式匹配作为val将不起作用。当您从Any开始时,您已经丢失了大量的类型信息。并且标准库也没有真正帮助,因为没有功能的抽象。这意味着我们甚至不能真正使用反射来尝试获取类型参数,因为我们甚至不知道有多少参数。你可以弄清楚你有FunctionN,但不是它包含的类型,因为它们已经丢失了。

另一种可能性是使tpl成为一种方法,并为每个FunctionN重载一次。

def tpl[A](f: Function0[A]): Function0[A] = f
def tpl[A, R](f: Function1[A, R]): Function1[A, R] = f
def tpl[A1, A2, R](f: Function2[A1, A2, R]): Function1[(A1, A2), R] = f.tupled
def tpl[A1, A2, A3, R](f: Function3[A1, A2, A3, R]): Function1[(A1, A2, A3), R] = f.tupled
// ... and so on

scala> val add = (a: Int, b: Int) => a + b
add: (Int, Int) => Int = <function2>

scala> tpl(add)
res0: ((Int, Int)) => Int = <function1>

它不漂亮,但它至少是类型安全的。我认为创建一个宏来生成重载1-22并不太难。