Last active
December 21, 2015 09:19
-
-
Save sam/6284227 to your computer and use it in GitHub Desktop.
Implicits in Scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
target |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := "implicits" | |
version := "1.0-SNAPSHOT" | |
scalaVersion := "2.10.2" | |
scalacOptions in ThisBuild ++= Seq( | |
"-language:_", | |
"-feature", | |
"-unchecked", | |
"-deprecation") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You can execute this file by downloading it to a directory, | |
// changing to it, and running: sbt run | |
object ImplicitMethodCalls extends App { | |
// Again we start off with an Int | |
val i = 1 | |
// Now we want to do something really evil. | |
// We want to be able to treat Ints like Seqs so we | |
// can map them. | |
// We could do it the right way and just write this: | |
val plusOne = Seq(i).map { n => n + 1 } | |
println(plusOne) | |
// But that's sane. We're going for EVIL. | |
implicit def iAmEvil(i: Int): Seq[Int] = Seq(i) | |
// Now we can call Seq methods on Int! | |
// Int has a "+" method obviously. Let's try that: | |
println(i + 10) | |
// Right. So that worked as expected. Int has a "+" | |
// method so no conversion necessary. | |
// Int *doesn't* have a "map" method though. | |
// Let's try that: | |
println(i.map(_ + 2)) | |
// WHOA. | |
// So what happened is this: The compiler doesn't | |
// find a "map" method on Int. Normally this is where | |
// you'd get a compiler error. Not so quick though! | |
// First it checks it's list of in-scope implicits. Are there | |
// any implicits that convert an Int to a thing that has a "map" | |
// method? Why yes. "iAmEvil" will convert an Int to a Seq[Int]. | |
// And sequences have a map method. We're back in business! | |
// NOTE: This search is only performed in the context of a | |
// missing method. Similar to Ruby's method_missing. If | |
// both types (Int and Seq[Int]) have a method (the "+" method for | |
// example), the compiler is going to resolve the one that | |
// makes the most sense (the one that doesn't actually require | |
// any conversion to work: Int.+ in this case). | |
// What if you wanted to add a whole set of methods though? | |
implicit class Cow(i: Int) { | |
def moo = println("MOO!") | |
def totes = println("I AM TOTES A COW!") | |
} | |
// Implicit classes are basically just like implicit methods in the way that | |
// they're resolved. Does Int have a "moo" method? Nope? OK. Is there an | |
// implicit in-scope that takes an Int and has a "moo" method? Yup. Cow. | |
// The moral of the story is that for implicits it doesn't matter if your type | |
// is a constructor argument or a method parameter. It just matters if there's | |
// an X that can convert it to a Y that has a method Z. Proof: | |
i.moo | |
i.totes | |
// There's that word "convert" again. As you've probably figured out, | |
// implicit method calls are really no different than implicit type conversion. | |
// It's the same functionality that enables both. We're just giving the use-cases/ | |
// techniques a label. At the end of the day they're fundamentally the same as | |
// far as the compiler is concerned. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object ImplicitParameters extends App { | |
// Implicit Parameters are a bit different. Whereas | |
// Implicit Method Invocation or Implicit Type Conversion | |
// only needs to have an implicit in-scope to turn an A into a B, | |
// an implicit parameter needs to both have an in-scope value | |
// defined as an implicit as well as have an implicit parameter | |
// in it's signature: | |
val i = 1 | |
implicit val j = 2 | |
def foo(i: Int)(implicit j: Int) = i + j | |
println(foo(i)) | |
// The implicit parameter itself declares an implicit value, so if | |
// you had another function that took an implicit parameter of | |
// that type, and you called it within the body of your method, | |
// you wouldn't have to explicitly pass it on. | |
// This is actually a common technique for "contextual" values | |
// like scala.concurrent.ExecutionContext. Like so (we'll just stub a | |
// Trait for this example): | |
trait ExecutionContext | |
def nestSomeOperation(i: Int)(implicit ec: ExecutionContext) = { | |
weNeedToGoDeeper(i) | |
} | |
def weNeedToGoDeeper(i: Int)(implicit ec: ExecutionContext) = { | |
println(s"$i INCEPTION!!!") | |
} | |
// A common use-case for needing an ExecutionContext is when | |
// dealing with Futures. | |
// | |
// Imagine for a moment that println was a Future that required | |
// an ExecutionContext. | |
// | |
// Sure, you could import the default ExecutionContext at the top | |
// of every file, and your methods could implicitly use that without | |
// having to declare it as an implicit parameter, but then you're | |
// locked to it in all your source code. If in another project your | |
// buddy Bob wants to provide his own ExecutionContext so he | |
// can tune the threads, timeouts, etc, he's out of luck. You've | |
// gone and hard-coded an imported implicit. | |
// So what can you and Bob work out? | |
// Instead write your code like above. Then it'll work for projects that | |
// are fine with the default since they can just add: | |
// import ExecutionContext at scala.concurrent.ExecutionContext.Implicits.global | |
// to their callers. In Bob's case he can explicitly pass his | |
// own ExecutionContext though and the implicit parameters in the | |
// nested methods will make it flow through system, guaranteeing | |
// the right context is used: | |
val myEC = new Object with ExecutionContext {} | |
nestSomeOperation(i)(myEC) | |
// You'll want to avoid declaring an implicit in your library's code | |
// since you'd be taking away user choice again since you can't | |
// have two implicits of the same type in scope. It produces an ambiguous | |
// implicit error. So that would prevent Bob from declaring "myEC" as an | |
// implicit value and having everything resolve automatically for him. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You can execute this file by downloading it to a directory, | |
// changing to it, and running: sbt run | |
object ImplicitTypeConversionExample extends App { | |
// So we start off with an Int | |
val i = 1 | |
// And we define a method to print Strings | |
def doit(s: String) = println(s"I'VE GONE AND DONE IT! $s") | |
// Now we want to print our Int: | |
// doit(i) | |
// That won't actually even compile since an Int is clearly not a String. | |
// We can get around that though by supplying an Implicit method | |
// that takes an Int parameter and returns a String. Thus satisfying | |
// our method call. | |
implicit def intToString(i: Int): String = i.toString | |
// Now our code will work! | |
doit(i) | |
// What happens is that the compiler knows it can't call the method the | |
// way we've written it, so it checks if there are any implicit Int => String | |
// functions in-scope. This is called "implicit type conversion". It finds | |
// the "intToString" method in it's list of in-scope implicits, sees that | |
// using it would let us call our doit(String) method, and so uses it. | |
// | |
// The name of our implicit method is unimportant. | |
// | |
// That's worth repeating. When first starting out with implicits, you'll | |
// sooner or later find yourself in a situation where things aren't working | |
// the way you thought they should. | |
// | |
// The name of our implicit method is unimportant! | |
// | |
// It doesn't matter at all (other than it not conflict with something else obviously). | |
// You'll be tempted to rename it to see if that makes things work. It won't. | |
// Because... | |
// | |
// THE NAME OF OUR IMPLICIT METHOD IS UNIMPORTANT. | |
// | |
// The only thing that matters is that it's marked as "implicit", and | |
// the function signature of "Int => String" to satisfy the caller. | |
// | |
// On a related note, the name of the parameters is equally unimportant. | |
// | |
// You'd never expect our doit(i:Int) method to change behavior just because | |
// you changed the parameter symbol "i" to "thisIsMyInteger" right? Same deal here. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object ImplicitZChaining extends App { | |
// We just tossed in a Z to get gist.github to sort this | |
// example file after the others. :-p | |
// You can chain implicits together. Check this: | |
val a = 1 | |
implicit class B(i: Int) { | |
def c(implicit d: String) = i.toString + d | |
} | |
implicit val d = "!" | |
println(a.c) | |
// Let's work backwards: | |
// "a" is an Int. Int doesn't have a "c" method. | |
// So the compiler looks for something that takes | |
// an Int and has a "c" method. It finds the | |
// implicit class B. Cool beans. But wait! There's more! | |
// | |
// The "c" method on "B" requires a String. Luckily we've | |
// made the parameter implicit, so it can pick up any | |
// matching implicits in scope of the caller. | |
// | |
// We declare an implicit String "d" with a value of "!" then. | |
// So "c"'s method signature can be satisfied, and ultimately | |
// compile down to: 1.toString + "!" | |
// One last thing to keep in mind that can serve to confuse: | |
// import statements are local to the file or scope you declare | |
// them in. By itself this doesn't really appear to have much | |
// to do with implicits, but since you'll often be importing | |
// implicits from other objects you'll probably bump into it | |
// sooner rather than later and wonder why Class S of Trait T | |
// isn't picking up the implicit you clearly imported in T. | |
// The solution is to make them something that _is_ inherited. | |
// Like a val, or method call. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment