在 scala 无形库中,当 arity > 22 时是否可以编写一个通用的 arity 函数(大概使用无形宏之一)?



以下代码是Shapeless用例之一的典型演示:


def getHList[P <: Product, F, L <: HList](p: P)(implicit gen: Generic.Aux[P, L]): L = {
gen.to(p)
}
val v = getHList(1, 2, 3, 4, 5, 6, 7, 8, 9)

这给出了正确的结果,不幸的是,它依赖于 scala 的元组句法 suger,并且在参数数量> 22 时不起作用:


val v = getHList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
(this generates an error that looks this the follow)
[Error] /xxx/HListSuite.scala:41: 29 more arguments than can be applied to method getHList: (p: P)(implicit gen: shapeless.Generic.Aux[P,L])L
one error found
FAILURE: Build failed with an exception.

我想知道是否有宏或其他 scala 功能可以用来打破这个限制,有什么建议吗?

我使用的是 scala 2.12.8,但可以随时升级到 2.13。

  • 如果您的目标是生成超过 22 的HList,那么有很多方法可以

    type _23 = Succ[_22]
    val _23: _23 = new _23
    1 :: 2 :: 3 :: 4 :: 5 :: 6 :: 7 :: 8 :: 9 :: 10 :: 11 :: 12 :: 13 :: 14 :: 15 :: 16 :: 17 :: 18 :: 19 :: 20 :: 21 :: 22 :: 23 :: HNil
    import shapeless.syntax.std.traversable._
    import shapeless.ops.hlist.Fill
    (1 to 23).toHList[the.`Fill[_23, Int]`.Out]
    (1 to 23).toSizedHList(_23)
    implicitly[_1 *--* _23].apply //takes long
    

    请注意,其中一些计算需要很长时间。

  • 您还可以定义Product23Tuple23

    getHList(Tuple23(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23))
    

    尽管仅带括号的语法糖不起作用。案例类的名称并不重要,它可以MyClass而不是Tuple23

  • 在Dotty有TupleXXL。

  • 如果你的目标是编写一个像getHList这样的方法,你可以尝试让它变得柯里化

    //libraryDependencies += "com.github.dmytromitin" %% "auxify-macros" % "0.6", scalacOptions += "-Ymacro-annotations" (in 2.13)
    import com.github.dmytromitin.auxify.macros.{aux, instance}
    def curriedGetHList[N <: Nat] = new PartiallyAppliedCurriedGetHList[N]
    class PartiallyAppliedCurriedGetHList[N <: Nat] {
    def apply[A](a: A)(implicit cghl: CurriedGetHList[N, A]): cghl.Out = cghl(a)
    }
    @aux @instance
    trait CurriedGetHList[N <: Nat, A] {
    type Out
    def apply(a: A): Out
    }
    object CurriedGetHList {
    implicit def mkCurriedGetHList[N <: Nat, A]
    (implicit
    helper: CurriedGetHListHelper[N, A, A :: HNil]
    ): Aux[N, A, helper.Out] = instance(a => helper(a :: HNil))
    }
    @aux @instance
    trait CurriedGetHListHelper[N <: Nat, A, L <: HList] {
    type Out
    def apply(acc: L): Out
    }
    object CurriedGetHListHelper {
    implicit def one[A, L <: HList]
    (implicit reverse: Reverse[L]): Aux[_1, A, L, reverse.Out] = instance(acc => reverse(acc))
    implicit def succ[N <: Nat, A, L <: HList]
    (implicit helper: Lazy[CurriedGetHListHelper[N, A, A :: L]]): Aux[Succ[N], A, L, A => helper.value.Out] =
    instance(acc => a => helper.value(a :: acc))
    }
    curriedGetHList[_10](1).apply(2)(3)(4)(5)(6)(7)(8)(9)(10)
    

    def curriedGetHList[N <: Nat] = new HListBuilder[N, HNil](HNil)
    class HListBuilder[N <: Nat, L <: HList](l: L) {
    def apply[A](a: A)(implicit bhl: BuildHList[N, L, A]): bhl.Out = bhl(l, a)
    }
    @aux @instance
    trait BuildHList[N <: Nat, L <: HList, A] {
    type Out
    def apply(l: L, a: A): Out
    }
    trait LowPriorityBuildHList {
    implicit def succ[N <: Nat, L <: HList, A]: BuildHList.Aux[Succ[N], L, A, HListBuilder[N, A :: L]] =
    BuildHList.instance((l, a) => new HListBuilder(a :: l))
    }
    object BuildHList extends LowPriorityBuildHList {
    implicit def one[L <: HList, A](implicit reverse: Reverse[A :: L]): Aux[_1, L, A, reverse.Out] =
    instance((l, a) => (a :: l).reverse)
    }
    curriedGetHList[_23](1).apply(2).apply(3).apply(4).apply(5).apply(6).apply(7).apply(8).apply(9).apply(10)
    .apply(11).apply(12).apply(13).apply(14).apply(15).apply(16).apply(17).apply(18).apply(19).apply(20)
    .apply(21).apply(22).apply(23)
    
  • 或者您可以将长元组划分为元组元组并使用深Generic(1 2 3 4(。

  • 另一种选择是编写白盒宏

    import scala.language.experimental.macros
    import scala.reflect.macros.whitebox
    def getHList(xs: Any*): HList = macro getHListImpl
    def getHListImpl(c: whitebox.Context)(xs: c.Tree*): c.Tree = {
    import c.universe._
    xs.foldRight[Tree](q"_root_.shapeless.HNil: _root_.shapeless.HNil")((h, t) => q"_root_.shapeless.::($h, $t)")
    }
    getHList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
    

    由于宏是白盒,因此其返回类型是正确的,Int :: Int :: ... :: HNil.

  • 也许最简单的方法是使用shapeless.ProductArgs

    def getHList = new ProductArgs {
    def applyProduct[L <: HList](l: L): L = l
    }
    getHList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
    

    实际上ProductArgs是通过scala.Dynamic和白盒宏在Shapeless中实现的。

最新更新