I have an HList in which each column represents a column of a table. Each list in the HList is of the same length.
I'd like to be able to write a function which picks out individual rows of this table as a tuple or an HList of values. Eventually I will convert this to something a bit more sensible (e.g. a Case Class).
import shapeless.PolyDefns.~>
import shapeless.{HList, HNil}
val a = List(1,2,3) :: List("a", "b", "c") :: List(true, false, true) :: HNil
object broken extends (HList ~> HList) {
def apply[T](n:Int, l:HList): HList = {
// I want to pick out the nth element of each HList
// so in the above example, if n==1
// I want to return
// 2 :: "b" :: false :: HNil
???
}
}
broken(1,a)
Can I fix this function so that it works according to what I've described in the comments?
Bonus points: Can I write this as an iterator that transforms my HList "a" above into a sequence of (Int, String, Boolean) or an equivalent HList?
There are a number of ways you could do this, but I'd go with a custom type class:
import shapeless._
trait RowSelect[L <: HList] extends DepFn2[L, Int] {
type Row <: HList
type Out = Option[Row]
}
object RowSelect {
def select[L <: HList](l: L, i: Int)(implicit rs: RowSelect[L]): rs.Out = rs(l, i)
type Aux[L <: HList, Row0 <: HList] = RowSelect[L] { type Row = Row0 }
implicit val hnilRowSelect: Aux[HNil, HNil] = new RowSelect[HNil] {
type Row = HNil
def apply(l: HNil, i: Int): Option[HNil] = Some(HNil)
}
implicit def hconsRowSelect[A, T <: HList](implicit
trs: RowSelect[T]
): Aux[List[A] :: T, A :: trs.Row] = new RowSelect[List[A] :: T] {
type Row = A :: trs.Row
def apply(l: List[A] :: T, i: Int): Option[A :: trs.Row] = for {
h <- l.head.lift(i)
t <- trs(l.tail, i)
} yield h :: t
}
}
Which works like this:
scala> println(RowSelect.select(a, 0))
Some(1 :: a :: true :: HNil)
scala> println(RowSelect.select(a, 1))
Some(2 :: b :: false :: HNil)
scala> println(RowSelect.select(a, 2))
Some(3 :: c :: true :: HNil)
scala> println(RowSelect.select(a, 3))
None
A RowSelect
instance for L
witnesses that L
is an hlist with all List
elements, and provides an operation that optionally selects the item at a specified index from each List
.
You should be able to accomplish the same thing with NatTRel
or a combination of ConstMapper
and ZipWith
and a Poly2
, but a custom type class bundles everything together nicely and in many cases allows more convenient compositionality.
For example, in this case the solution to your bonus question can be pretty straightforwardly written in terms of RowSelect
:
def allRows[L <: HList](l: L)(implicit rs: RowSelect[L]): List[rs.Row] =
Stream.from(0).map(rs(l, _).toList).takeWhile(_.nonEmpty).flatten.toList
And then:
scala> allRows(a).foreach(println)
1 :: a :: true :: HNil
2 :: b :: false :: HNil
3 :: c :: true :: HNil
And similarly if you want to convert these hlists to tuples:
def allRows[L <: HList, R <: HList](l: L)(implicit
rs: RowSelect.Aux[L, R],
tp: ops.hlist.Tupler[R]
): List[tp.Out] =
Stream.from(0).map(rs(l, _).map(tp(_)).toList).takeWhile(_.nonEmpty).flatten.toList
Which gives you:
scala> allRows(a)
res7: List[(Int, String, Boolean)] = List((1,a,true), (2,b,false), (3,c,true))
And so on.