Взяв HList из Seq[_] и сгенерировав Seq[HList] с декартовым произведением значений

Я новичок в Shapless.

Я пытаюсь написать функцию, которая бы HList последовательности различных типов, преобразовать его в Seq[HList] содержащий декартово произведение оригинала HListи перебрать полученную последовательность

Например:

val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil

cartesianProduct: Seq[Boolean :: Int :: String :: HNil] = Seq(
  true :: 1 :: foo :: HNil, 
  true :: 1 :: bar :: HNil, 
  true :: 2 :: foo :: HNil, 
  true :: 2 :: bar :: HNil, 
  true :: 3 :: foo :: HNil, 
  true :: 3 :: bar :: HNil, 
  false :: 1 :: foo :: HNil, 
  false :: 1 :: bar :: HNil, 
  false :: 2 :: foo :: HNil, 
  false :: 2 :: bar :: HNil, 
  false :: 3 :: foo :: HNil, 
  false :: 3 :: bar :: HNil)

Я смог добиться этого в Intellij Scala Worksheet, используя следующий код:

import shapeless._
import shapeless.ops.hlist.LeftFolder


object combine extends Poly {
  implicit def `case`[T <: HList, S] = use((acc : Seq[T], curr : Seq[S]) => {
    for {
      el <- curr
      v <- acc
    } yield el :: v
  })
}

val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil

val combinations = input.foldLeft(Seq[HNil](HNil))(combine) 

combinations.foreach(println)

Здесь все работает, я полагаю, потому что полный тип input известен компилятору.

Однако, когда я пытаюсь обернуть всю операцию в функцию, полный тип input теряется, и я не могу позвонить foreach на результат foldLeft:

def cartesian[T <: HList](input: T)
         (implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
    .foreach(println)
}

Компилятор жалуется:

  value foreach is not a member of folder.Out
    input.foldLeft(Seq[HNil](HNil))(combine).foreach(println)
                                            ^

Я полагаю, что есть неявное доказательство, которое я могу попросить подтвердить правильную форму input (HList из Seq[_]) и таким образом получить компилятор, чтобы выяснить результирующий тип foldLeft, но я не могу понять, что это может быть...

Надеюсь, кто-то может помочь мне разобраться в этом. Благодарю.

Обновление: моя конечная цель с этим вопросом была, учитывая HList из Seq[_] получить функцию (возможно, для класса case), которая будет принимать функцию с той же аргументностью, что и входной HList, и типы аргументов, соответствующие типам элементов 'Seq' в том же порядке. Например, для ввода выше функция будет f: (Boolean, Int, String) => R Таким образом, я смог бы перебрать декартово произведение входных данных, используя f, Окончательный код выглядит так:

import shapeless._
import shapeless.ops.function.FnToProduct
import shapeless.ops.hlist.LeftFolder
import shapeless.syntax.std.function.fnHListOps


object combine extends Poly {
  implicit def `case`[EL <: HList, S] = use((acc : Seq[EL], curr : Seq[S]) => {
    for {
      el <- curr
      v <- acc
    } yield el :: v
  })
}

case class Cartesian[R <: HList, F, FR](combinations: Seq[R])
                                 (implicit ftp: FnToProduct.Aux[F, R => Unit]) {
  def foreach(f: F) =  combinations.foreach(f.toProduct)
}


def cartesian[T <: HList, R <: HList, F, FR](variants: T)(implicit
                             folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[R]],
                             fnToProd: FnToProduct.Aux[F, R => Unit]
                            ) = {
  val combinations: Seq[R] = variants.foldLeft(Seq[HNil](HNil))(combine)
  Cartesian(combinations)
}

val variants = Seq(true, false) :: Seq("foo", "bar") :: Seq(1, 2, 3) :: HNil

cartesian(variants).foreach((a, b, c) => println(s"$a, $b, $c")) 

Обратите внимание, что типы для аргументов функции a, b, c правильно выведены и Boolean, String, а также Int, В настоящее время тип результата функции передан в foreach должно быть исправлено (в приведенном выше коде это Unit). Это не может быть выведено из переданной функции.

1 ответ

Решение

Проблема не в том, что компилятор ничего не знает о вводе, а в том, что он ничего не знает о выводе.

внутри def cartesian все, что известно компилятору, это то, что после foldLeft вы получаете какой-то тип folder.Out (что зависит от того, что фигура будет бесформенной для вас).

Для обеспечения типов результатов, вы можете использовать LeftFolder.Aux с одним дополнительным параметром типа, например

def cartesian[T <: HList](input: T)
         (implicit folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[Any]]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
    .foreach(println)
}

Теперь компилятор будет знать, что результатом является некоторый подтип Seq[Any]так можно позвонить foreach в теме.


Конечно, это только проблема внутри def, При вызове сайтов типы вывода будут решаться на основе ввода, так что вы сможете сделать это без Aux:

def cartesian2[T <: HList](input: T)
         (implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
}

cartesian2(input).foreach(println)

Запускаемый код: https://scalafiddle.io/sf/n409yNW/2

Другие вопросы по тегам