Skip to content

Commit

Permalink
Merge pull request #44 from twitter/lahosken_typefest
Browse files Browse the repository at this point in the history
new ex for Lower type bds. Clarify view bds. new ex for other type bds.
  • Loading branch information
mariusae committed Sep 4, 2012
2 parents a3d2ea0 + 1687960 commit 6ff8726
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 61 deletions.
47 changes: 9 additions & 38 deletions web/_posts/2011-05-05-lesson.textile
Expand Up @@ -236,53 +236,24 @@ scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)
</pre>

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. <code>List[+T]</code> is covariant; a list of Birds is a list of Animals. <code>List</code> defines an operator <code>::(elem T)</code> that returns a new <code>List</code> with <code>elem</code> prepended. The new <code>List</code> has the same type as the original:

<pre>
scala> class Node[T](x: T) { def sub(v: T): Node[T] = new Node(v) }
</pre>

But, we want to make it covariant in T:

<pre>
scala> class Node[+T](x: T) { def sub(v: T): Node[T] = new Node(v) }
<console>: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) }
^
</pre>

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)

<pre>
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)
</pre>

is *not* a subtype of
<code>List</code> _also_ defines <code>::[B >: T](x: B)</code> which returns a <code>List[B]</code>. Notice the <code>B >: T</code>. That specifies type <code>B</code> as a superclass of <code>T</code>. That lets us do the right thing when prepending an <code>Animal</code> to a <code>List[Bird]</code>:

<pre>
class Node[Animal](x: Animal) { def sub(v: Animal): Node[Animal] = new Node(v) }
</pre>

because Animal cannot be substituted for Bird in the argument of "sub". However, we can use lower bounding to enforce correctness.

<pre>
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)
</pre>

Note also how the type changes with subsequent calls to "sub".
Note that the return type is <code>Animal</code>.

h2(#quantification). Quantification

Expand Down
57 changes: 34 additions & 23 deletions web/_posts/2011-05-06-lesson.textile
Expand Up @@ -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.:

<pre>
scala> implicit def strToInt(x: String) = x.toInt
Expand All @@ -33,7 +36,7 @@ scala> math.max("123", 111)
res1: Int = 123
</pre>

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 <code><%</code> e.g.,

<pre>
scala> class Container[A <% Int] { def addIt(x: A) = 123 + x }
Expand All @@ -55,9 +58,17 @@ scala> (new Container[Float]).addIt(123.2F)
^
</pre>

h3. Other type bounds
h2(#otherbounds). Other type bounds

Methods can enforce more complex type bounds via implicit parameters. For example, <code>List</code> supports <code>sum</code> on numeric contents but not on others. Alas, Scala's numeric types don't all share a superclass, so we can't just say <code>T <: Number</code>. Instead, to make this work, Scala's math library <a href="http://www.azavea.com/blogs/labs/2011/06/scalas-numeric-type-class-pt-1/">defines an implicit <code>Numeric[T]</code> for the appropriate types T</a>. Then in <code>List</code>'s definition uses it:

Methods may ask for specific "evidence" for a type, namely:
<pre>
sum[B >: A](implicit num: Numeric[B]): B
</pre>

If you invoke <code>List(1,2).sum()</code>, you don't need to pass a _num_ parameter; it's set implicitly. But if you invoke <code>List("whoop").sum()</code>, it complains that it couldn't set <code>num</code>.

Methods may ask for some kinds of specific "evidence" for a type without setting up strange objects as with <code>Numeric</code>. 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|
Expand All @@ -84,7 +95,7 @@ scala> (new Container("123")).addIt
res15: Int = 246
</pre>

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:

Expand All @@ -96,7 +107,7 @@ def min[B >: A](implicit cmp: Ordering[B]): A = {
reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
}
</pre>

The main advantages of this are:

* Items in the collections aren't required to implement *Ordered*, but *Ordered* uses are still statically type checked.
Expand Down Expand Up @@ -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 <code>Container</code> interface that might be implemented by means of several container types: an <code>Option</code>, a <code>List</code>, etc. You want to define an interface for using values in these containers without nailing down the values' type.

Expand Down Expand Up @@ -184,7 +195,7 @@ scala> tupleize(List(1), List(2))
res34: List[(Int, Int)] = List((1,2))
</pre>

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.

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down Expand Up @@ -297,18 +308,18 @@ x: List[Int] = List(1)
</pre>


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.

<pre>
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 = ""
</pre>

h3. Case study: Finagle
h2(#finagle). Case study: Finagle

See: https://github.com/twitter/finagle

Expand Down Expand Up @@ -339,7 +350,7 @@ trait Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
}
</pre>

An may authenticate requests with a filter.
A service may authenticate requests with a filter.

<pre>
trait RequestWithCredentials extends Request {
Expand Down

0 comments on commit 6ff8726

Please sign in to comment.