如何通过组合输入元组来组合元组一元函数
How to compose tupled unary functions by combining their input tuples
我已经玩弄 shapeless 一段时间了。
但是,昨天我在尝试组合元组函数时卡住了。
我专门研究的是将两个一元函数 f1: T => R
和 f2: R => U => S
组合成 f: TU => S
,其中 T 是 TupleN 和 TU := (t1, ... , tn, u)
import shapeless.ops.tuple._
implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal{
def compose2[U, S](f2: R => U => S)(implicit p: Prepend[T, Tuple1[U]]): (p.Out => S) = {
// how to provide the two required implicits for Last[p.Out] and Init[p.Out]?
tu => f1.andThen(f2)(tu.init)(tu.last)
}
}
val f1: ((Int, Int)) => Int = x => x._1 * x._2
val f2: ((Int, Int, Int)) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
我一直在努力的是为元组操作last
和init
提供隐式证明,所以上面的代码不会编译!
从逻辑的角度来看,Prepend
的结果感觉微不足道,但我想不出办法。所以欢迎任何想法:)
使用 shapeless 的设施对 arity 进行抽象,我以某种方式更接近了:
import shapeless.ops.function.{FnFromProduct, FnToProduct}
import shapeless.{::, HList}
implicit class Composable[F](val f: F) extends AnyVal{
// the new param U is appended upfront
def compose2[I <: HList, R, U, S](f2: R => U => S)
(implicit ftp: FnToProduct.Aux[F, I => R], ffp: FnFromProduct[U :: I => S]): ffp.Out = {
ffp(list => f2.compose(ftp(f))(list.tail)(list.head))
}
}
val f1: (Int, Int) => Int = (x1,x2) => x1 * x2
val f2: (Int, Int, Int) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
这行得通,但话又说回来,我真的在寻找 compose2
来处理一元元组 Function1
。此外,这导致 f: (U, t1, ..., tn) => S
而不是 f: TU => S
和 TU := (t1, ... , tn, u)
.
正如 Miles 所说,Prepend
的 undo
会更方便,但由于第二部分的长度是固定的,类似于 [=18= 中的方法]一点也不差:
import shapeless.ops.tuple._
implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal {
def compose2[U, S, TU](f2: R => U => S)(implicit
p: Prepend.Aux[T, Tuple1[U], TU],
i: Init.Aux[TU, T],
l: Last.Aux[TU, U]
): (p.Out => S) =
tu => f1.andThen(f2)(i(tu))(l(tu))
}
然后:
scala> val f1: ((Int, Int)) => Int = x => x._1 * x._2
f1: ((Int, Int)) => Int = <function1>
scala> val f2: ((Int, Int, Int)) => Int =
| f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
f2: ((Int, Int, Int)) => Int = <function1>
scala> f2((2, 3, 4))
res1: Int = 10
诀窍是将 Prepend
的输出添加到 compose2
的类型参数列表中——这通常会被推断出来——然后使用 Prepend.Aux
来确保它被正确推断.你会经常发现在 Shapeless 中,你需要通过这种方式在同一个隐式参数列表中引用其他类型 class 实例中类型 class 的输出类型,而 Aux
类型成员使这样做更方便。
我已经玩弄 shapeless 一段时间了。 但是,昨天我在尝试组合元组函数时卡住了。
我专门研究的是将两个一元函数 f1: T => R
和 f2: R => U => S
组合成 f: TU => S
,其中 T 是 TupleN 和 TU := (t1, ... , tn, u)
import shapeless.ops.tuple._
implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal{
def compose2[U, S](f2: R => U => S)(implicit p: Prepend[T, Tuple1[U]]): (p.Out => S) = {
// how to provide the two required implicits for Last[p.Out] and Init[p.Out]?
tu => f1.andThen(f2)(tu.init)(tu.last)
}
}
val f1: ((Int, Int)) => Int = x => x._1 * x._2
val f2: ((Int, Int, Int)) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
我一直在努力的是为元组操作last
和init
提供隐式证明,所以上面的代码不会编译!
从逻辑的角度来看,Prepend
的结果感觉微不足道,但我想不出办法。所以欢迎任何想法:)
使用 shapeless 的设施对 arity 进行抽象,我以某种方式更接近了:
import shapeless.ops.function.{FnFromProduct, FnToProduct}
import shapeless.{::, HList}
implicit class Composable[F](val f: F) extends AnyVal{
// the new param U is appended upfront
def compose2[I <: HList, R, U, S](f2: R => U => S)
(implicit ftp: FnToProduct.Aux[F, I => R], ffp: FnFromProduct[U :: I => S]): ffp.Out = {
ffp(list => f2.compose(ftp(f))(list.tail)(list.head))
}
}
val f1: (Int, Int) => Int = (x1,x2) => x1 * x2
val f2: (Int, Int, Int) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
这行得通,但话又说回来,我真的在寻找 compose2
来处理一元元组 Function1
。此外,这导致 f: (U, t1, ..., tn) => S
而不是 f: TU => S
和 TU := (t1, ... , tn, u)
.
正如 Miles 所说,Prepend
的 undo
会更方便,但由于第二部分的长度是固定的,类似于 [=18= 中的方法]一点也不差:
import shapeless.ops.tuple._
implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal {
def compose2[U, S, TU](f2: R => U => S)(implicit
p: Prepend.Aux[T, Tuple1[U], TU],
i: Init.Aux[TU, T],
l: Last.Aux[TU, U]
): (p.Out => S) =
tu => f1.andThen(f2)(i(tu))(l(tu))
}
然后:
scala> val f1: ((Int, Int)) => Int = x => x._1 * x._2
f1: ((Int, Int)) => Int = <function1>
scala> val f2: ((Int, Int, Int)) => Int =
| f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
f2: ((Int, Int, Int)) => Int = <function1>
scala> f2((2, 3, 4))
res1: Int = 10
诀窍是将 Prepend
的输出添加到 compose2
的类型参数列表中——这通常会被推断出来——然后使用 Prepend.Aux
来确保它被正确推断.你会经常发现在 Shapeless 中,你需要通过这种方式在同一个隐式参数列表中引用其他类型 class 实例中类型 class 的输出类型,而 Aux
类型成员使这样做更方便。