Generic Poly2 Folder case for shapeless Hlist

2019-05-01 17:42发布

问题:

I am trying to transform the following HList

Some(C(15)) :: None :: Some(B(55)) :: None :: Some(A(195)) :: HNil

to

C(15) :: B(55) :: A(195) :: HNil

Here is what I have at the moment :

  import shapeless._
  case class A(value: Int)
  case class B(value: Int)
  case class C(value: Int)

  trait folderLP extends Poly2 {
    implicit def default[T, L <: HList] = at[T, L]((acc, t) => acc)
  }
  object folder extends folderLP {
    implicit def none[T, L <: HList] = at[None.type, L]((t, acc) => acc)

    implicit def someDiameter[T, L <: HList] = at[Some[C], L]((t, acc) => t.get :: acc)

    implicit def someRatio[T, L <: HList] = at[Some[B], L]((t, acc) => t.get :: acc)

    implicit def someWidth[T, L <: HList] = at[Some[A], L]((t, acc) => t.get :: acc)
  }
  val test = Some(C(15)) :: None :: Some(B(55)) :: None :: Some(A(195)) :: HNil

  val filtered = test.foldRight[HList](HNil)(folder)

this works but I would like to make this generic so that it works for any type wrapped in Some without having to write each case

回答1:

First for a literal answer. Note that most of your T type parameters aren't being used. You can use that T to make your function match any element of type Some[T]:

trait folderLP extends Poly2 {
  implicit def default[T, L <: HList] = at[T, L]((_, acc) => acc)
}

object folder extends folderLP {
  implicit def some[T, L <: HList] = at[Some[T], L]((t, acc) => t.get :: acc)
}

Note that you don't even need the none case if you switch the order of arguments in your default.

Also note that you probably want to use the following definition of filtered:

val filtered = test.foldRight(HNil: HNil)(folder)

This one will have the HNil statically typed as an HNil instead of an HList, which will be useful for pretty much anything you want to do down the line—for example try filtered.length on your original version and then on this one.

You don't even really need a fold for this operation, though—a flatMap will do:

trait filterLP extends Poly1 {
  implicit def any[T] = at[T](_ => HNil)
}

object filter extends filterLP {
  implicit def some[T] = at[Some[T]](_.get :: HNil)
}

And then:

val filtered = test.flatMap(filter)

Finally, it's worth noting that this will only work on an HList where the None and Some elements are statically typed as None and Some—a Some[A] for example that's statically typed as an Option[A] will get filtered out. This makes it kind of unuseful (at least I can't see a practical use), but there's just not really any way you can perform this kind of type-level filter if you don't know at compile time whether the Option is empty or not.