Сделайте ковариантный фильтр в HList

Я намерен фильтровать HList ковариантным образом - я также хотел бы включить подклассы. Таким образом, ковариантный фильтр для Foo должен захватывать элементы Foo, а также Bar. Я создал этот пример, опробовав <:!<, чтобы посмотреть, делает ли он то, что мне нужно.

http://scastie.org/6465

/***
scalaVersion := "2.11.2"

libraryDependencies ++= Seq(
  "com.chuusai" %% "shapeless" % "2.0.0"
)
*/

import shapeless._

final class HListOps[L <: HList](l: L) {
  trait CoFilter[L <: HList, U] extends DepFn1[L] { type Out <: HList }

  object CoFilter {
    def apply[L <: HList, U](implicit filter: CoFilter[L, U]): Aux[L, U, filter.Out] = filter

    type Aux[L <: HList, U, Out0 <: HList] = CoFilter[L, U] { type Out = Out0 }

    implicit def hlistCoFilterHNil[L <: HList, U]: Aux[HNil, U, HNil] =
      new CoFilter[HNil, U] {
        type Out = HNil
        def apply(l: HNil): Out = HNil
      }

    implicit def hlistCoFilter1[L <: HList, H](implicit f: CoFilter[L, H]): Aux[H :: L, H, H :: f.Out] =
      new CoFilter[H :: L, H] {
        type Out = H :: f.Out
        def apply(l: H :: L): Out = l.head :: f(l.tail)
      }

    implicit def hlistCoFilter2[H, L <: HList, U](implicit f: CoFilter[L, U], e: U <:!< H): Aux[H :: L, U, f.Out] =
      new CoFilter[H :: L, U] {
        type Out = f.Out
        def apply(l: H :: L): Out = f(l.tail)
      }
  }

  def covariantFilter[U](implicit filter: CoFilter[L, U]): filter.Out = filter(l)
}

object Main extends App {

  class Foo(val foo: Int)
  class Bar(val bar: Int) extends Foo(bar)
  val l = new Foo(1) :: new Bar(2) :: new Foo(3) :: new Bar(4) :: HNil
  implicit def hlistOps[L <: HList](l: L): HListOps[L] = new HListOps(l)
  print(l.covariantFilter[Bar] != l)

}

Дает мне

[error] /tmp/rendererbI8Iwy0InO/src/main/scala/test.scala:47: could not find implicit value for parameter filter: _1.CoFilter[shapeless.::[Main.Foo,shapeless.::[Main.Bar,shapeless.::[Main.Foo,shapeless.::[Main.Bar,shapeless.HNil]]]],Main.Bar]
[error]   print(l.covariantFilter[Bar] != l)

person Reactormonk    schedule 07.09.2014    source источник
comment
Это был бы гораздо лучший вопрос, если бы вы объяснили, что вы пытаетесь сделать. С первого взгляда я не могу понять, почему вы ожидаете, что hlistCoFilterN даст вам нужный экземпляр (это не тот случай, когда Foo =:= Bar или Bar <:!< Foo), и мне не очень интересно пытаться читать ваши мысли.   -  person Travis Brown    schedule 07.09.2014
comment
@TravisBrown хорошая мысль.   -  person Reactormonk    schedule 07.09.2014
comment
Ах, тогда не хотите проверить l.covariantFilter[Foo]?   -  person Travis Brown    schedule 07.09.2014
comment
@TravisBrown это метод, который должен соответствовать требованиям, поэтому я хотел бы его протестировать.   -  person Reactormonk    schedule 08.09.2014


Ответы (1)


Здесь есть несколько проблем. Во-первых, ваш класс типа определен внутри вашего класса расширения, но вам нужен экземпляр в точке, где вы вызываете covariantFilter. Возможно, компилятор мог бы найти его для вас, но он этого не делает. Тем не менее, намного чище не вкладывать класс типов.

Вторая проблема заключается в том, что ваши два случая hlistCoFilterN на самом деле не охватывают все, что вам нужно. Вы только сообщаете компилятору, что делать в тех случаях, когда тип заголовка является типом фильтра и когда тип фильтра не является подтипом типа заголовка. Как насчет того, где тип головы является подтипом типа фильтра? Вы, вероятно, хотите что-то вроде этого:

import shapeless._

trait CoFilter[L <: HList, U] extends DepFn1[L] { type Out <: HList }

object CoFilter {
  def apply[L <: HList, U](implicit f: CoFilter[L, U]): Aux[L, U, f.Out] = f

  type Aux[L <: HList, U, Out0 <: HList] = CoFilter[L, U] { type Out = Out0 }

  implicit def hlistCoFilterHNil[L <: HList, U]: Aux[HNil, U, HNil] =
    new CoFilter[HNil, U] {
      type Out = HNil
      def apply(l: HNil): Out = HNil
    }

  implicit def hlistCoFilter1[U, H <: U, T <: HList]
    (implicit f: CoFilter[T, U]): Aux[H :: T, U, H :: f.Out] =
      new CoFilter[H :: T, U] {
        type Out = H :: f.Out
        def apply(l: H :: T): Out = l.head :: f(l.tail)
      }

  implicit def hlistCoFilter2[U, H, T <: HList]
    (implicit f: CoFilter[T, U], e: H <:!< U): Aux[H :: T, U, f.Out] =
      new CoFilter[H :: T, U] {
        type Out = f.Out
        def apply(l: H :: T): Out = f(l.tail)
      }
}

implicit final class HListOps[L <: HList](val l: L)  {
  def covariantFilter[U](implicit filter: CoFilter[L, U]): filter.Out = filter(l)
}

(Для справки, вы также можете удалить ограничение H <:!< U и переместить hlistCoFilter2 в черту LowPriorityCoFilter. Я считаю, что эта версия немного яснее в своих намерениях, но избавление от ограничения, возможно, было бы чище.)

Теперь, если у вас есть следующее:

class Foo(val foo: Int)
class Bar(val bar: Int) extends Foo(bar)
val l = new Foo(1) :: new Bar(2) :: new Foo(3) :: new Bar(4) :: HNil

Ваш фильтр будет работать следующим образом:

scala> l.covariantFilter[Foo] == l
res0: Boolean = true

scala> l.covariantFilter[Bar] == l
res1: Boolean = false

Что я думаю, это то, что вы хотите.

person Travis Brown    schedule 07.09.2014
comment
Необходимо ли требование для hlistCoFilter2? Один должен быть по умолчанию в любом случае. - person Reactormonk; 08.09.2014
comment
Вы имеете в виду ограничение <:!<? Нет, но вам придется переместить hlistCoFilter2 в черту LowPriorityWhatever, чтобы расставить приоритеты между ними. - person Travis Brown; 08.09.2014