Implicits in Scala

An Introduction to How and Why


but first

Multiple Parameter Lists

You can do


def func(x: Int, y: Int): Int = x * y
or
def func(x: Int)(y: Int): Int = x * y
						

Or


def func(a: Int, b: Int, c: Int, d: Int, e: Int) = ???
or
def func(a: Int)(b: Int)(c: Int)(d: Int)(e: Int) = ???
or
def func(a: Int, b: Int, c: Int)(d: Int, e: Int) = ???
							

You can divide up your arguments into separate groups.


Implicit Parameters and Values


def func(x: Int)(y: Int): x * y
							

func(3)(4)
res1: Int = 12
func(3)
error: missing arguments for method func
							

implicit val a: Int = 2
def func(x: Int)(implicit y: Int) = x * y
							

func(3)(4)
res1: Int = 12
func(3)
res2: Int = 6
							

If a parameter is marked implicit, if you leave it out the compiler will look for a value of that type marked as implicit and insert it for you.


func(3)
becomes 
func(3)(2)
							

Implicit Values and Parameters ...

Implicit parameters go in the last parameter list


def func(x: Int)(implicit y: Int, h: Helper) = ...
						

Both y and h are implicit

Implicits need to be unambiguous.


implicit val a: Int = 2
implicit val b: Int = 99
def func(x: Int)(implicit y: Int) = x * y
// error: ambiguous implicit values:
						

Implicit Parameters and Values ...

Parameters marked as implicit are available as implicit values

def func1(x: String)(implicit h: Helper) = {
	h.help(x)
}
def func2(a: Int, b:String)(implicit h: Helper) = {
	func1(b)
	...
}
					
Reduce boilerplate of passing along a parameter.
e.g Play: Action { implicit request => ...}

Implicit conversions


implicit def arbitraryStringToInt(str: String): Int = str.length * 10
def func(x: Int): Int = x + 1
							

func(5)
res1: Int = 6
func("cat")
res2: Int = 31
							

If you call a method with a parameter of the wrong type, the compiler will try and find an implicit conversion from the type you are providing to the type expected by the method, and insert the call to that function.


func("cat")
becomes
func(arbitraryStringToInt("cat"))
							

By the way ...

implicit def arbitraryStringToInt(str: String): Int = ...
					
Don't actually do this!

Enrichments

What if wanted to do this?


5.isEven
res1: Boolean = false
						

Or


6 times 7
res2: Int = 42
						

class SuperInt(x: Int) {
	def isEven: Boolean = x % 2 == 0
}
implicit def intToSuperInt(x: Int): SuperInt = new SuperInt(x)
						

5.isEven 
res1: Boolean = false
						

If you invoke a method on an object that doesn't have that method, the compiler looks for an implicit conversion from that obect type to one that does have the method.


Enrichments ...


implicit class SuperInt(x: Int) {
	def isEven: Boolean = x % 2 == 0
	def isOdd: Boolean = !isEven
	def times(y: Int): Int = x * y
}
						

7.isOdd 
res1: Boolean = true
6 times 7 
res2: Int = 42
3.times(4)
res3: Int = 12
						

If you invoke a method on an object that doesn't have that method, the compiler looks for an implicit class that has the method.


Why

Lots of syntactic sugar/helpers in scala are implemented this way. See Predef for examples.

1 to 5 = Range(1, 2, 3, 4, 5)
from implicit def intWrapper(x: Int): RichInt

"cats".map(_.toUpper) = "CATS"
from implicit def augmentString(x: String): StringOps


Typeclass Pattern

What if we want to provide similar "enrichments" across multiple types? E.g. json.

trait Feature[T] {
	def impl(x: T): ??
}

implicit object IntFeature extends Feature[Int] {
	def impl(x: Int) : ???
}
					

trait Feature[T] {
	def impl(x: T): Unit
}

implicit object IntFeature extends Feature[Int] {
	def impl(x: Int) = println(s"int feature for $x")
}
or
implicit val intFeature = new Feature[Int] {...}
					

val intF = implicitly[Feature[Int]]
intF.impl(5) // int feature for 5
					

Typeclass Pattern ...


case class MyClass(i: Int, s: String)

implicit object MyClassFeature extends Feature[MyClass] {
	def impl(x: MyClass) = println(s"feature for MyClass ${x.i}, ${x.s}")
}
					

def usesFeature[T](x: T)(implicit evidence: Feature[T]) = evidence.impl(x)
					

usesFeature(5)
// feature for 5
usesFeature(MyClass(9, "bananas"))
// feature for MyClass 9 bananas
					

On the basis of the type we call usesFeature with, compiler finds the right implicit value.

Evidence? The existence of an implicit value of type Feature[T] provides evidence that we can provide the feature for type T...


Typeclass Pattern ...


usesFeature(MyClass(9, "bananas"))
// feature for MyClass 9 bananas
usesFeature(List(1,2))
// error: could not find implicit value for parameter evidence: Feature[List[Int]]
					

Typeclass pattern ...

Or provide the implicit class so you can call the feature directly


implicit class FeatureOps[T : Feature](x: T) {
	def impl = implicitly[Feature[T]].impl(x) 
}
8.impl
// feature for 8
					

Context Bounds


def checkFeature[T : Feature](x: T): T = x
					

checkFeature(7)
res1: Int = 7
checkFeature(9.99)
// error: cound not find implicit value for evidence parameter of type Feature[Double]
					
This

def checkFeature[T : Feature](x: T): T = x
					
Is just syntactic sugar for

def checkFeature[T](x: T)(implicit ev: Feature[T]): T = x
					

Typeclass pattern ...

An example: sorting.


List(2,4,3,5).sorted
res1: List[Int] = List(2,3,4,5)
				

case class Things(x: Int, y: String)
List(Things(2, "2"), Things(4, "5"), Things(2,"3")).sorted
//error: No implicit Ordering defined for Things
				

in scala.collection.immutable.List


def sorted[B >: A](implicit ord: math.Ordering[B]): List[A]
				

implicit object ThingsOrdering extends math.Ordering[Things] { 
	def compare(a: Things, b: Things): Int = 
		if ( (a.x compare b.x) == 0) 
			a.y compare b.y 
		else 
			a.x compare b.x
}
List(Things(2, "2"), Things(4, "5"), Things(2,"3")).sorted
res1: List[Things] = List(Things(2,2), Things(2,3), Things(4,5))
				

Typeclass pattern ...

Retroactive extension: conforming to an interface without access to source code.

Separation of data and behavior...