Scala Function.tupled 和 Function.untupled 等价于变量元数,或者,用元组调用变量元数函数

Scala Function.tupled and Function.untupled equivalent for variable arity, or, calling variable arity function with tuple

昨晚我试图做一些关于接受和调用通用函数的事情(即类型在调用站点是已知的,但可能在调用站点之间有所不同,因此定义应该是通用的)。

例如,假设我有一个函数f: (A, B, C, ...) => Z。 (这样的f其实有很多,我事先不知道,所以无法确定A, B, C, ..., Z的类型和数量。)

我正在努力实现以下目标。

  1. 如何使用 (A, B, C, ...) 的实例调用 f?如果事先知道 f 的签名,那么我可以做一些涉及 Function.tupled f 或等效的事情。

  2. 如何定义与f具有相同签名的另一个函数或方法(例如,某些objectapply方法)?也就是说,我如何定义一个 g 当且仅当 f(a, b, c, ...) 类型检查时 g(a, b, c, ...) 类型检查?为此,我正在研究 Shapeless 的 HList。到目前为止,据我所知,HList 至少解决了 "representing an arbitrary arity args list" 问题,而且,Shapeless 将解决与元组之间的转换问题。但是,我仍然不确定我是否理解这将如何适应通用 arity 函数(如果有的话)。

  3. 如何定义另一个具有与 f 相关的类型签名的函数或方法?现在想到的最大的例子就是一些h: (A, B, C, ...) => SomeErrorThing[Z] \/ Z.

我记得前段时间看过一个关于 Shapeless 的会议演讲。虽然演示者没有明确展示这些东西,但他们展示的东西(围绕 abstracting/genericizing 元组与 HLists 的各种技术)会让我相信使用相同的工具可以实现与上述类似的事情。

提前致谢!

是的,Shapeless 绝对可以帮助您。例如,假设我们想要采用任意数量的函数并将其转换为相同数量的函数,但 return 类型包含在 Option 中(我认为这会触及你的所有三个点问题)。

为简单起见,我只说 Option 始终是 Some。这需要相当密集的四行:

import shapeless._, ops.function._

def wrap[F, I <: HList, O](f: F)(implicit
  ftp: FnToProduct.Aux[F, I => O],
  ffp: FnFromProduct[I => Option[O]]
): ffp.Out = ffp(i => Some(ftp(f)(i)))

我们可以证明它有效:

scala> wrap((i: Int) => i + 1)
res0: Int => Option[Int] = <function1>

scala> wrap((i: Int, s: String, t: String) => (s * i) + t)
res1: (Int, String, String) => Option[String] = <function3>

scala> res1(3, "foo", "bar")
res2: Option[String] = Some(foofoofoobar)

注意适当的静态 return 类型。现在如何工作:

FnToProduct 类型 class 提供证据表明某些类型 F 是可以转换为函数的 FunctionN(对于某些 N)从一些 HList 到原始输出类型。 HList函数(准确地说是Function1)是实例的Out类型成员,或者是FnToProduct.Aux助手的第二个类型参数。

FnFromProduct 做相反的事情——它证明一些 F 是一个 Function1HList 到一些可以转换成一些函数的输出类型该输出类型的 arity。

在我们的 wrap 方法中,我们使用 FnToProduct.Aux 来约束 FFnToProduct 实例的 Out,这样我们就可以在我们的 FnFromProduct 实例的类型中参考 HList 参数列表和 O 结果类型。实现非常简单——我们只需在适当的地方应用实例。

这一切看起来可能非常复杂,但是一旦您在 Scala 中使用这种泛型编程一段时间后,它就会或多或少地变得直观,我们当然很乐意回答更具体的问题你的用例。