diff --git a/web/_posts/2011-05-05-lesson.textile b/web/_posts/2011-05-05-lesson.textile
index 08b45300..2f0181ba 100644
--- a/web/_posts/2011-05-05-lesson.textile
+++ b/web/_posts/2011-05-05-lesson.textile
@@ -236,53 +236,24 @@ scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)
-Lower type bounds are also supported. They go hand-in-hand with contravariance. Let's say we had some class Node:
+Lower type bounds are also supported; they come in handy with contravariance and clever covariance. List[+T]
is covariant; a list of Birds is a list of Animals. List
defines an operator ::(elem T)
that returns a new List
with elem
prepended. The new List
has the same type as the original:
-scala> class Node[T](x: T) { def sub(v: T): Node[T] = new Node(v) } -- -But, we want to make it covariant in T: - -
-scala> class Node[+T](x: T) { def sub(v: T): Node[T] = new Node(v) } -- -Recall that method arguments are contravariant, and so if we perform our substitution trick, using the same classes as before: +scala> val flock = List(new Bird, new Bird) +flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2) -:6: error: covariant type T occurs in contravariant position in type T of value v - class Node[+T](x: T) { def sub(v: T): Node[T] = new Node(v) } - ^ -
-class Node[Bird](x: Bird) { def sub(v: Bird): Node[Bird] = new Node(v) } +scala> new Chicken :: flock +res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)-is *not* a subtype of +
List
_also_ defines ::[B >: T](x: B)
which returns a List[B]
. Notice the B >: T
. That specifies type B
as a superclass of T
. That lets us do the right thing when prepending an Animal
to a List[Bird]
:
-class Node[Animal](x: Animal) { def sub(v: Animal): Node[Animal] = new Node(v) } -- -because Animal cannot be substituted for Bird in the argument of "sub". However, we can use lower bounding to enforce correctness. - -
-scala> class Node[+T](x: T) { def sub[U >: T](v: U): Node[U] = new Node(v) } -defined class Node - -scala> (new Node(new Bird)).sub(new Bird) -res5: Node[Bird] = Node@4efade06 - -scala> ((new Node(new Bird)).sub(new Bird)).sub(new Animal) -res6: Node[Animal] = Node@1b2b2f7f - -scala> ((new Node(new Bird)).sub(new Bird)).asInstanceOf[Node[Chicken]] -res7: Node[Chicken] = Node@6924181b - -scala> (((new Node(new Bird)).sub(new Bird)).sub(new Animal)).sub(new Chicken) -res8: Node[Animal] = Node@3088890d +scala> new Animal :: flock +res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)-Note also how the type changes with subsequent calls to "sub". +Note that the return type is
Animal
.
h2(#quantification). Quantification
diff --git a/web/_posts/2011-05-06-lesson.textile b/web/_posts/2011-05-06-lesson.textile
index cdafd029..3453e107 100644
--- a/web/_posts/2011-05-06-lesson.textile
+++ b/web/_posts/2011-05-06-lesson.textile
@@ -7,17 +7,20 @@ desc: Advanced Types, view bounds, higher-kinded types, recursive types, structu
This lesson covers:
-* View bounds ("type classes")
-* Higher kinded types & ad-hoc polymorphism
-* F-bounded polymorphism / recursive types
-* Structural types
-* Abstract types members
-* Type erasures & manifests
-* Case study: Finagle
+* "View bounds":#viewbounds ("type classes")
+* "Other Type Bounds":#otherbounds
+* "Higher kinded types & ad-hoc polymorphism":#higher
+* "F-bounded polymorphism / recursive types":#fbounded
+* "Structural types":#structural
+* "Abstract types members":#abstractmem
+* "Type erasures & manifests":#manifest
+* "Case study: Finagle":#finagle
-h3. View bounds ("type classes")
+h2(#viewbounds). View bounds ("type classes")
-*Implicit* functions in scala allow for on-demand function application when this can help satisfy type inference. eg.:
+Sometimes you don't need to specify that one type is equal/sub/super another, just that you could fake it with conversions. A view bound specifies a type that can be "viewed as" another. This makes sense for an operation that needs to "read" an object but doesn't modify the object.
+
+*Implicit* functions allow automatic conversion. More precisely, they allow on-demand function application when this can help satisfy type inference. e.g.:
scala> implicit def strToInt(x: String) = x.toInt @@ -33,7 +36,7 @@ scala> math.max("123", 111) res1: Int = 123-view bounds, like type bounds demand such a function exists for the given type. eg. +view bounds, like type bounds demand such a function exists for the given type. You specify a type bound with
<%
e.g.,
scala> class Container[A <% Int] { def addIt(x: A) = 123 + x } @@ -55,9 +58,17 @@ scala> (new Container[Float]).addIt(123.2F) ^-h3. Other type bounds +h2(#otherbounds). Other type bounds + +Methods can enforce more complex type bounds via implicit parameters. For example,
List
supports sum
on numeric contents but not on others. Alas, Scala's numeric types don't all share a superclass, so we can't just say T <: Number
. Instead, to make this work, Scala's math library defines an implicit Numeric[T]
for the appropriate types T. Then in List
's definition uses it:
-Methods may ask for specific "evidence" for a type, namely:
++sum[B >: A](implicit num: Numeric[B]): B ++ +If you invoke
List(1,2).sum()
, you don't need to pass a _num_ parameter; it's set implicitly. But if you invoke List("whoop").sum()
, it complains that it couldn't set num
.
+
+Methods may ask for some kinds of specific "evidence" for a type without setting up strange objects as with Numeric
. Instead, you can use of these type-relation operators:
|A =:= B|A must be equal to B|
|A <:< B|A must be a subtype of B|
@@ -84,7 +95,7 @@ scala> (new Container("123")).addIt
res15: Int = 246
-h4. Generic programming with views
+h3. Generic programming with views
In the Scala standard library, views are primarily used to implement generic functions over collections. For example, the "min" function (on *Seq[]*), uses this technique:
@@ -96,7 +107,7 @@ def min[B >: A](implicit cmp: Ordering[B]): A = {
reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
}
-
+
The main advantages of this are:
* Items in the collections aren't required to implement *Ordered*, but *Ordered* uses are still statically type checked.
@@ -141,7 +152,7 @@ res37: Ordering[Int] = scala.math.Ordering$Int$@3a9291cf
Combined, these often result in less code, especially when threading through views.
-h3. Higher-kinded types & ad-hoc polymorphism
+h2(#higher). Higher-kinded types & ad-hoc polymorphism
Scala can abstract over "higher kinded" types. For example, suppose that you needed to use several types of containers for several types of data. You might define a Container
interface that might be implemented by means of several container types: an Option
, a List
, etc. You want to define an interface for using values in these containers without nailing down the values' type.
@@ -184,7 +195,7 @@ scala> tupleize(List(1), List(2))
res34: List[(Int, Int)] = List((1,2))
-h3. F-bounded polymorphism
+h2(#fbounded). F-bounded polymorphism
Often it's necessary to access a concrete subclass in a (generic) trait. For example, imagine you had some trait that is generic, but can be compared to a particular subclass of that trait.
@@ -255,7 +266,7 @@ Note how the resulting type is now lower-bound by *YourContainer with MyContaine
No *Ordered[]* exists for the unified type. Too bad.
-h3. Structural types
+h2(#structural). Structural types
Scala has support for *structural types* -- type requirements are expressed by interface _structure_ instead of a concrete type.
@@ -269,7 +280,7 @@ res0: Int = 133
This can be quite nice in many situations, but the implementation uses reflection, so be performance-aware!
-h3. Abstract type members
+h2(#abstractmem). Abstract type members
In a trait, you can leave type members abstract.
@@ -297,18 +308,18 @@ x: List[Int] = List(1)
-h3. Type erasures & manifests
+h2(#manifest). Type erasures & manifests
As we know, type information is lost at compile time due to _erasure_. Scala features *Manifests*, allowing us to selectively recover type information. Manifests are provided as an implicit value, generated by the compiler as needed.
scala> class MakeFoo[A](implicit manifest: Manifest[A]) { def make: A = manifest.erasure.newInstance.asInstanceOf[A] } -scala> (new MakeFoo[String]).make -res10: String = +scala> (new MakeFoo[String]).make +res10: String = ""-h3. Case study: Finagle +h2(#finagle). Case study: Finagle See: https://github.com/twitter/finagle @@ -339,7 +350,7 @@ trait Filter[-ReqIn, +RepOut, +ReqOut, -RepIn] } -An may authenticate requests with a filter. +A service may authenticate requests with a filter.
trait RequestWithCredentials extends Request {