SBinary and Scalacheck are part of a small set of libraries that make extensive use of implicit arguments in a style very reminiscient of Haskell type classes. I’m hoping this style of programming will get more common in Scala – it’s a really useful technique and, in my completely unbiased opinion, both SBinary and Scalacheck are fantastic and you absolutely should use them. :-) But in order to do so you need to really understand how implicit arguments in Scala work.
This post is actually for work, as we’re using Scala there and this is a subject which has been confusing one of my colleagues.
As a starting point, in Scala you can declare a method to have multiple argument lists. This isn’t a fantastically useful feature, but here’s how it works:
scala> def foo(x : Int)(y : Int) | = x + y foo: (Int)(Int)Int scala> foo(1)(2); res1: Int = 3 scala> foo(1, 2);:6: error: wrong number of arguments for method foo: (Int)(Int)Int foo(1, 2); ^ scala> foo(1) :6: error: missing arguments for method foo in object $iw; follow this method with `_' if you want to treat it as a partially applied funct ion foo(1) ^
i.e. “exactly the same as a single method parameter list but you have to use a different syntax for calling it”. Hurray.
This has one advantage though. You can declare the last parameter list of a function to be implicit. The syntax for this works as follows:
scala> def speakImplicitly (implicit greeting : String) = println(greeting) speakImplicitly: (implicit String)Unit scala> speakImplicitly("Goodbye world") Goodbye world scala> speakImplicitly:6: error: no implicit argument matching parameter type String was foud. scala> implicit val hello = "Hello world" hello: java.lang.String = Hello world scala> speakImplicitly Hello world
So, we can call this as normal but, additionally, we can leave out the implicit argument list and the compiler will look for a value in the enclosing scope which has been marked as implicit. If we try to do that and there is no such value in scope then the compiler will complain.
Matching implicit arguments
Implicits are totally typesafe, and are selected based on the static type of the arguments. Here are some examples to show how things work.
Implicits of the wrong type
scala> def speakImplicitly (implicit greeting : String) = println(greeting) speakImplicitly: (implicit String)Unit scala> implicit val aUnit = (); aUnit: Unit = () scala> speakImplicitly:7: error: no implicit argument matching parameter type String was found.
Only an implicit of type String will be selected for an implicit argument of type String.
Implicits of the wrong static type
scala> def speakImplicitly (implicit greeting : String) = println(greeting) speakImplicitly: (implicit String)Unit scala> implicit val hello : Any = "Hello world" hello: Any = Hello world scala> speakImplicitly:7: error: no implicit argument matching parameter type String was found.
Implicit selection happens on the *static* type of variables. It’s no use having something of the right dynamic type if the variable isn’t typed accordingly.
scala> def speakImplicitly (implicit greeting : String) = println(greeting) speakImplicitly: (implicit String)Unit scala> implicit val foo = "foo"; foo: java.lang.String = foo scala> implicit val bar = "bar"; bar: java.lang.String = bar scala> speakImplicitly:9: error: ambiguous implicit values: both value bar in object $iw of type => java.lang.String and value foo in object $iw of type => java.lang.String match expected type String
If there are multiple implicit arguments of the same type, it will fail as it has no way of choosing between them. But…
Implicit arguments of subtypes
scala> def sayThings (implicit args : List[Any]) = args.foreach(println(_)) sayThings: (implicit List[Any])Unit scala> implicit val nothingNiceToSay : List[Any] = Nil nothingNiceToSay: List[Any] = List() scala> sayThings scala> implicit val hellos : List[String] = List("Hello world"); hellos: List[String] = List(Hello world) scala> sayThings Hello world
If you have an implicit argument of a subtype, it will also match as an implicit argument of this type. Moreover, if you have two implicit arguments which match and one is a subtype of the other, the more specific type will match.
Parameterized implicits
scala> def implicitly[T](implicit t : T) = t implicitly: [T](implicit T)T scala> implicit val foo = "foo" foo: java.lang.String = foo scala> implicit val aUnit = () aUnit: Unit = () scala> implicitly[String] res2: String = foo scala> implicitly[Unit]
Type parameters can quite happily take part in the implicits mechanism.
Defining implicit arguments
So, we know how to use defined implicit arguments now. But how can we define them? We’ve seen one way:
implicit val foo = "foo"; scala> implicitly[String] res2: String = foo
If this was all we could do then it wouldn’t be that powerful a feature. A nice to have, but ultimately not *that* exciting. Fortunately there are a few more things we can do. For starters, Scala has the uniform access principle, so any (wait, no. That would be too general. We can’t have features without special cases. Sigh. Ok, let’s say most) things you can do with a val you can do with a def
implicit def foo = "foo" scala> implicitly[String] res2: String = foo
This def will be invoked each time we want the implicit. Here’s an example to demonstrate this
scala> implicit def aUnit : Unit = println("Hello world") aUnit: Unit scala> implicitly[Unit] Hello world scala> implicitly[Unit] Hello world scala> implicitly[Unit] Hello world
In general, implicit defs shouldn’t have side effects. It can lead to some really counterintuitive behaviour. This is just for demonstration purposes.
Now, the ability to use defs opens up a bunch of possibilities. For example, they can have type parameters:
scala> implicit def emptyList[T] : List[T] = Nil; emptyList: [T]List[T] scala> implicitly[List[String]] res9: List[String] = List(Hello world) // Oops, we still had an implicit List[String] left over from an earlier example. Note how that was used in preference to the parameterized version. Let's try again. scala> implicitly[List[Int]] res10: List[Int] = List()
Moreover, implicit defs used in this way can themselves have implicit parameters. For example:
scala> case class Foo[T](t : T); defined class Foo scala> implicit val hello = "Hello" hello: java.lang.String = Hello scala> implicit def foo[T](implicit t : T) = Foo[T](t) foo: [T](T)Foo[T] scala> implicitly[Foo[String]] res3: Foo[String] = Foo(Hello)
(Note: I originally tried to write this example with Option. It turns out there’s a bug with how covariant types are handled which made it not work)
The basic idea is that anything marked as implicit which you could write as a single identifier (possibly with a type signature to handhold the type inference system) is valid to be passed as an implicit argument.
More reading
This should provide enough to get you started. Your next step should probably be to check out the documentation for Scalacheck and SBinary (the latter of which is… less than stellar at the moment. I’ll fix that, I promise. :-)). If you’re looking for some slightly more hardcore reading, Generics of a Higher Kind has some interesting examples. Other than that, the best thing to do is play with some code.