使用 shapeless 对 arity 进行抽象

Abstract over arity using shapeless

使用 shapeless,我们可以使用他们文档中描述的方法对 arity 进行抽象。

import shapeless.ops.function.FnToProduct
import shapeless.{Generic, HList}
import shapeless._
import syntax.std.function._

def applyProduct[P <: Product, F, L <: HList, R](p: P)(f: F)(implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) =
  f.toProduct(gen.to(p))

val add = (x: Int, y: Int) => x + y

applyProduct(1, 2)(add)

但是,我无法包装此设施,例如:

def applyProduct[P <: Product, F, L <: HList, R](p: P)(f: F)(implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) =
  f.toProduct(gen.to(p))

val add = (x: Int, y: Int) => x + y

def wrapper[F](f: F) {
  applyProduct(1, 2)(f)
}

wrapper(add)

编译器认为它不能 find implicit value for parameter fp: shapeless.ops.function.FnToProduct.Aux[(A, A) => A,L => R]

意思是无法将K元函数转换为相同参数类型的K-sized HList的函数。

我怎样才能让它发挥作用?

编辑:

好吧,现在想象一下,我在某个时间知道函数,但稍后我只会知道参数,所以我想推迟评估

case class Node[P <: Product, F, L <: HList, R](f: F)(implicit gen:Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) {
  def evaluate(p: P) = {
    f.toProduct(gen.to(p))
  }
}

编译器不让我调用:

val add = (x: Int, y: Int) => x + y

val n = Node(add)

//later

n.evaluate(1,2,3)

我不能使用偏应用来解决这个问题吗?

就这么简单:

def wrapper[R](f: (Int, Int) => R) = {
  applyProduct(1, 2)(f)
}

您已经通过在包装器中传递 (1,2) 定义了 P 类型(如 (Int, Int)),因此抽象 F 没有任何意义 -您唯一可以抽象的是 R(结果类型)

解释:

applyProductwrapper 里面对 F 一无所知。因此,为了隐式找到 FnToProduct.Aux[F, ...] scala 编译器需要了解更多关于 F 的信息,因为 "shape" 上面隐式定义了 FnToProduct.Aux(A, A) => A(在你的例子中是 (Int, Int) => Int),而不仅仅是 F,编译器从错误消息中诚实地告诉你。


对编辑的回复:

@ applyProduct(_: Int, _: Int)(add) 
res17: (Int, Int) => Int = 

@ res17(1,2) 
res18: Int = 3

为了避免类型归属,你可以使用像你的 Node 案例 class 这样的东西(使用案例 class 只是为了避免 new 不被认为是很好的风格),但没有引入 class (只是 return 来自函数的 lambda)。但是,在 Node 情况下,如果不传递所有类型参数,Scala 将无法进行正确的类型推断。

不幸的是,您甚至不能在这里方便地使用柯里化(通过将 f: F 作为第一个参数),因为 "implicits" 在不传递所有类型参数的情况下无法解析。也许有破解的方法,但部分应用似乎是最简单易懂的。

P.S。然而,您可能会注意到,对于这种情况,这种部分应用相当于:

@ val ff = add _; val f = ff() 
ff: () => (Int, Int) => Int = 
f: (Int, Int) => Int = ammonite.$sess.cmd9$$$Lambda78/545666041@6e7e60bb

@ f(1,2) 
res34: Int = 3

拥有一个接受两个(或多个)参数(reducer)但被转换为任意数量的函数的函数会更有意义,比如 def abstract[...](f: (A, A) => A)(p: P): A。这将是对 arity 的更真实的抽象。