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

abstract over scala functions without losing types

我正在制作一个接受 lambda 的函数,并在可能的情况下对其使用 .tupled(arity 2+)。对于允许使用它的编译器,它需要知道 lambda 是否真的是 Function2 (~ Function22)。但是,Function2[Any,Any,Any] 的模式匹配意味着我只剩下 (Any, Any) => Any 而不是它的原始类型。甚至不知道数量也无济于事。我尝试像 case f[A <: Any, B <: Any, C <: Any]: scala.Function2[A, B, C] => f.tupled 那样匹配以尝试保留类型,但它实际上不允许在 case 上使用类型参数。

代码:

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>

如果我不需要针对每个可能级别的模式匹配案例,则加分...

如您所料,答案很难看。在函数上使用模式匹配作为 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 不会太难。