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

In scala shapeless library, is it possible to write a generic arity function when the arity > 22 (presumably using one of shapeless macros)?

以下代码是 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。

  • 如果您的目标是生成一个 HList 长于 22 的字符串,那么有很多方法

    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
    

    小心,其中一些计算需要很长时间。

  • 也可以定义Product23,Tuple23

    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))
    

    尽管只有括号的语法糖是行不通的。 case class 的名称并不重要,它可以是 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 ( 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)
    

    由于宏是白盒,其 return 类型将是正确的,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 和 whitebox 宏在 Shapeless 中实现的。