В библиотеке scala shapeless можно ли написать общую функцию арности, когда арность › 22 (предположительно, с использованием одного из бесформенных макросов)?

Следующий код является типичной демонстрацией одного из вариантов использования 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)

Это дает правильный результат, к сожалению, он полагается на синтаксический suger кортежа scala и не работает, когда число аргументов> 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 в любое время.


person tribbloid    schedule 31.05.2020    source источник


Ответы (1)


  • Если ваша цель — сгенерировать 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 значения не имеет, вместо Tuple23 может быть MyClass.

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

    or

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

  • Еще один вариант - написать макрос whitebox

    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 реализованы в Shapeless через scala.Dynamic и макрос whitebox.

person Dmytro Mitin    schedule 01.06.2020