shapeless provides a comprehensive Scala HList
which has many features not shared by other HList implementations.
It has a map
operation, applying a polymorphic function value across its elements. This means that it subsumes both
typical HList
's and also KList
's (HList
's whose elements share a common outer type constructor).
import poly._
object choose extends (Set ~> Option) {
def apply[T](s: Set[T]) = s.headOption
}
val sets = Set(1) :: Set("foo") :: HNil
val opts = sets map choose
opts should be(res0 :: res1 :: HNil)
It also has a flatMap Operation
import poly.identity
val l = (23 :: "foo" :: HNil) :: HNil :: (true :: HNil) :: HNil
l flatMap identity should be(res0 :: res1 :: res2 :: HNil)
It has a set of fully polymorphic fold operations which take a polymorphic binary function value. The fold is sensitive
to the static types of all of the elements of the HList
. Given the earlier definition of size,
object addSize extends Poly2 {
implicit def default[T](implicit st: shapelessex.size.Case.Aux[T, Int]) =
at[Int, T] { (acc, t) => acc + size(t) }
}
val l = 23 :: "foo" :: (13, "wibble") :: HNil
l.foldLeft(0)(addSize) should be(res0)
It also has a zipper for traversal and persistent update,
import syntax.zipper._
val l = 1 :: "foo" :: 3.0 :: HNil
l.toZipper.right.put(("wibble", 45)).reify should be(res0 :: res1 :: res2 :: HNil)
l.toZipper.right.delete.reify should be(res3 :: res4 :: HNil)
It is covariant,
object CovariantHelper {
trait Fruit
case class Apple() extends Fruit
case class Pear() extends Fruit
type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil
type APAP = Apple :: Pear :: Apple :: Pear :: HNil
val a: Apple = Apple()
val p: Pear = Pear()
val apap: APAP = a :: p :: a :: p :: HNil
}
import scala.reflect.runtime.universe._
implicitly[TypeTag[APAP]].tpe.typeConstructor <:< typeOf[FFFF] should be(res0)
And it has a unify operation which converts it to an HList of elements of the least upper bound of the original types,
apap.isInstanceOf[FFFF] should be(res0)
apap.unify.isInstanceOf[FFFF] should be(res1)
It supports conversion to an ordinary Scala List
of elements of the least upper bound of the original types,
apap.toList should be(res0)
And it has a Typeable
type class instance (see below), allowing, eg. vanilla List[Any]
's or HList
's with
elements of type Any
to be safely cast to precisely typed HList
's.
These last three features make this HList
dramatically more practically useful than HList
's are typically thought to be:
normally the full type information required to work with them is too fragile to cross subtyping or I/O boundaries.
This implementation supports the discarding of precise information where necessary.
(eg. to serialize a precisely typed record after construction), and its later reconstruction.
(eg. a weakly typed deserialized record with a known schema can have its precise typing reestablished).
import syntax.typeable._
val ffff: FFFF = apap.unify
val precise: Option[APAP] = ffff.cast[APAP]
precise should be(res0)