Selected highlights of this 1.0 release of shapeless include,
- A new encoding of polymorphic function values which optionally supports
type specific cases, and which is interoperable with Scala's ordinary
monomorphic function values.
```scala
// choose is a function from Sets to Options with no type specific cases
object choose extends (Set ~> Option) {
def default[T](s : Set[T]) = s.headOption
}
// choose is convertible to an ordinary monomorphic function value
val lo = List(Set(1, 3, 5), Set(2, 4, 6)) map choose
lo == List(Option(1), Option(2))
// size is a function from values of arbitrary type to a 'size' which is
// defined via type specific cases
object size extends (Id ~> Const[Int]#λ) {
def default[T](t : T) = 1
}
implicit def sizeInt = size.λ[Int](x => 1)
implicit def sizeString = size.λ[String](s => s.length)
implicit def sizeList[T] = size.λ[List[T]](l => l.length)
implicit def sizeOption[T](implicit cases : size.λ[T]) =
size.λ[Option[T]](t => 1+size(t.get))
implicit def sizeTuple[T, U](implicit st : size.λ[T], su : size.λ[U]) =
size.λ[(T, U)](t => size(t._1)+size(t._2))
size(23) == 1
size("foo") == 3
size((23, "foo")) == 4
```
```scala
val nested = List(Option(List(Option(List(Option(23))))))
val succ = everywhere(inc)(nested)
succ == List(Option(List(Option(List(Option(24))))))
```
- A
Typeable type class which provides a type safe cast operation.
```scala
val a : Any = List(Vector("foo", "bar", "baz"), Vector("wibble"))
val lvs : Option[List[Vector[String]]] = a.cast[List[Vector[String]]]
lvs.isDefined == true
val lvi : Option[List[Vector[Int]]] = a.cast[List[Vector[Int]]]
lvi.isEmpty == true
```
- The mother of all Scala
HList's, which amongst other things,
- is covariant.
```scala
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
val ffff : FFFF = apap // APAP <: FFFF
```
- has a map operation, applying a polymorphic function value (possibly
with type specific cases) 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).
```scala
type SISS = Set[Int] :: Set[String] :: HNil
type OIOS = Option[Int] :: Option[String] :: HNil
val sets : SISS = Set(1) :: Set("foo") :: HNil
val opts : OIOS = sets map choose
opts == Option(1) :: Option("foo") :: HNil
```
- has a zipper for traversal and persistent update.
```scala
val l = 1 :: "foo" :: 3.0 :: HNil
val l2 = l.toZipper.right.put("wibble", 45).toHList
l2 == 1 :: ("wibble", 45) :: 3.0 :: HNil
val l3 = l.toZipper.right.delete.toHList
l3 == 1 :: 3.0 :: HNil
val l4 = l.toZipper.last.left.insert("bar").toHList
l4 == 1 :: "foo" :: "bar" :: 3.0 :: HNil, l5)
```
- has a
unify operation which converts it to an HList of elements
of the least upper bound of the original types.
`scala
val ffff = apap.unify // type inferred as FFFF
`
- supports conversion to an ordinary Scala
List of elements of the
least upper bound of the original types.
`scala
val lf = apap.toList // type inferred as List[Fruit]
lf == List(a, p, a, p)
`
- has a
Typeable type class instance, allowing, eg. vanilla
List[Any]'s or HList's with elements of type Any to be safely
cast to precisely typed HList's.
```scala
val ffff : FFFF = apap.unify // discard precise typing
val apap2 : Option[APAP] = ffff.cast[APAP] // reestablish precise typing
apap2.get == apap
```
These last three bullets 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 it's precise typing reestabilished).
- Conversions between tuples and
HList's, and between ordinary Scala
functions of arbitrary arity and functions which take a single
corresponding HList argument. One application of this is the liftO
function which lifts an ordinary function of arbitrary arity into Option.
```scala
// Round trip from tuple to HList and back
val t1 = (23, "foo", 2.0, true)
val l1 = t1.hlisted
h1 == 23 :: "foo" :: 2.0 :: true :: HNil
val t2 = l1.tupled
t1 == t2
// Lift these ordinary Scala function values into Option
val sum : (Int, Int) => Int = _ + _
val prd : (Int, Int, Int) => Int = _ * _ * _
// Nb. liftO abstracts over the arity of its function arguments
val sumO = liftO(sum) // (Option[Int], Option[Int]) => Option[Int]
val prdO = liftO(prd) // (Option[Int], Option[Int], Option[Int]) => Option[Int]
val s1 = sumO(Some(1), Some(2))
s1 == Option(3)
val s2 = sumO(Some(1), None)
s2 == None
val p1 = prdO(Some(2), Some(3), Some(4))
p1 == Option(24)
val p2 = prdO(Some(2), None, Some(4))
p2 == None
```
shapeless is an exploration of
type class and dependent type based generic programming in Scala.
A series of articles on the implementation techniques used will appear
here.