Let’s revisit the need for exhaustive matches and consider the situation where we have a sealed class hierarchy, which we discussed in Sealed Class Hierarchies. As an example, suppose we define the following code to represent the allowed message types or “methods” for HTTP:
Define a sealed, abstract base class HttpMethod. Because it is declared sealed, the only allowed subtypes must be defined in this file.
2
Define a method for the body of the HTTP message.
3
Define eight case classes that extend HttpMethod. Note that each declares a constructor argument body: String, which is a val because each of these types is a case class. This val implements the abstract def method in HttpMethod.
4
An exhaustive pattern-match expression, even though we don’t have a default clause, because the method argument can only be an instance of one of the eight case classes we’ve defined.
Predef defines a method called implicitly. Combined with a type signature addition, it provides a useful shorthand way of defining method signatures that take a single implicit argument, where that argument is a parameterized type.
Consider the following example, which wraps the List method sortBy:
def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): List[A] =
list.sortBy(f)(ord)
def sortBy2[B : Ordering](f: A => B): List[A] =
list.sortBy(f)(implicitly[Ordering[B]])
}
val list = MyList(List(1,3,5,2,4))
list sortBy1 (i => -i)
list sortBy2 (i => -i)
List.sortBy is one of several sorting methods available for many of the collections. It takes a function that transforms the arguments into something that satisfies math.Ordering, which is analogous to Java’s Comparable abstraction. An implicit argument is required that knows how to order instances of type B.
Besides passing contexts, implicit arguments can be used to control capabilities.
For example, an implicit user session argument might contain authorization tokens that control whether or not certain API operations can be invoked on behalf of the user or to limit data visibility.
Suppose you are constructing a menu for a user interface and some menu items are shown only if the user is logged in, while others are shown only if the user isn’t logged in:
def createMenu(implicit session: Session): Menu = {
val defaultItems = List(helpItem, searchItem)
val accountItems =
if (session.loggedin()) List(viewAccountItem, editAccountItem)
Suppose we have a method with parameterized types and we want to constrain the allowed types that can be used for the type parameters.
If the types we want to permit are all subtypes of a common supertype, we can use object-oriented techniques and avoid implicits. Let’s consider that approach first.
We saw an example in Call by Name, Call by Value, where we implemented a resource manager:
The type parameter R must be a subtype of any type with the close():Unit method. Or, if we can assume that all resources we’ll manage implement a Closable trait (recall that traits replace and extend Java interfaces; see Traits: Interfaces and “Mixins” in Scala):
def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = // 2
println(s"Seq[Int]: $seq")
def m(seq: Seq[String])(implicit s: StringMarker.type): Unit = // 3
println(s"Seq[String]: $seq")
}
import M._ // 4
m(List(1,2,3))
m(List("one", "two", "three"))
1
Define two special-purpose implicit objects that will be used to disambiguate the methods affected by type erasure.
2
Redefinition of the method that takes Seq[Int]. It now has a second argument list expecting an implicit IntMarker. Note the type, IntMarker.type. This is how to reference the type of a singleton object!
3
The method for Seq[String].
4
Import the implicit values and the methods, then use them. The code compiles and prints the correct output.