使用 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
(结果类型)
解释:
applyProduct
在 wrapper
里面对 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 的更真实的抽象。
使用 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
(结果类型)
解释:
applyProduct
在 wrapper
里面对 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 的更真实的抽象。