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) }
-: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) }
-                                      ^
-
- -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) -
-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 {