在不丢失类型的情况下对 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 不会太难。
我正在制作一个接受 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 不会太难。