Skip to content

Commit

Permalink
Added notes for implicit.ly.
Browse files Browse the repository at this point in the history
  • Loading branch information
milessabin committed Dec 19, 2011
1 parent b60a89a commit 48b1604
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
172 changes: 172 additions & 0 deletions notes/1.0.markdown
@@ -0,0 +1,172 @@
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
```

* An implementation of [Scrap your Boilerplate with Class](http://goo.gl/pR1OV)
which provides generic map and fold operations over arbitrarily nested data
structures,

```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
```
5 changes: 5 additions & 0 deletions notes/about.markdown
@@ -0,0 +1,5 @@
[shapeless](https://github.com/milessabin/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](http://www.chuusai.com/blog).

0 comments on commit 48b1604

Please sign in to comment.