Implicits Inspected and Explained
ScalaDays 2016 Berlin
Tim Soethout - ING Bank
http://blog.timmybankers.nl
Outline: Introduction - Implicits - Resolving - Type Classes - Wrap up
- Welcome!
- Many Scala users see implicits everywhere, but their exact use remains a bit magic
- Goal of talk is to give you an intuition on how implicits work and where to look when things go haywire.
- Target audience is Scala developers, who already know the language and syntax
- We already heard a lot about implicits. Martin emphasised the importance of implicits for the Scala Language in the keynore. Yesterday a lot of the talks in the advanced track used and abused implicits a lot.
- Outline: Context, Implicits, Scoping, TypeclassesAbout myself
Tim Soethout
- Bachelor CKI, Master Computing Science in Utrecht - Lot's of Haskell
- Now Scala
@ ING:
- Popularising Functional Programming
+ Scale out
+ Scala => JVM
- Lightbend/Typesafe certified trainer: Scala & Akka
- 3 courses
@ CWI: Research institute in the Netherlands for Math and Computer Science
Mention about ING:
- Internal training each month, Scala in upcoming
- If you want to know more, please come talk to meWhat?
- Use values without explicit reference
- OO: is a + has a
- Implicits add: is viewable as a
- Loose Coupling, Tight Cohesion
- is a: extends: Cow is an Animal
- has a: members: Animal has a name
- Cow is viewable as a coat hanger
- Enables: Small core interfaces + rich views
Implicits enable you to use values without explicitly referencing them. This enables you to write less code, and let implicits do the heavy lifting for you. We will see how this can be useful.
If we look at Object Orientation, traditionally we see is a and has a, respectively 'a Cow is an Animal' and 'an Animal has a name' as field for example. Implicits enable is viewable as a on top of this. This means we can convert values to other more rich values implicitly. 'a Cow is viewable as a coat hanger'.This enables small and clean core interfaces and rich views on top of this, resulting in Loose Coupling, but Tight Cohesion.Examples
trait ScalaActorRef
def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = ...
}
actorRef ! SomeMessage("text")
object Future {
def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = ...
}
Future {
doExpensiveComputation()
}
- implicit to pass in sender from actor context
- ExectutionContext to handle the thread management
Let's look at some examples.
In Akka when sending a message the tell (bang) is implemented with an implicit parameter sender. This enables us to send message to other actors without having to pass in itself in as the sender explicitly.
Another example is the implicit ExecutionContext when using Futures. ExecutionContext takes care of all the handing of the threads used by Futures under the hood. The implicit allows you to select a runtime for this once and use it implicitly for all the applicable Futures. The interface also allows you pass in another thread handling mechanism explicitly.Examples (2)
trait TraversableOnce[+A] {
def sum[B >: A](implicit num: Numeric[B]): B = ...
}
List(1,2,3).sum
res0: Int = 6
- Numeric type class to allow sum on things that can be added
It is also used in the Scala collections library. For example you can use sum on any TraversableOnce if there is an implicit available which makes sure we can do plus on the elements of the collection. This is an example of a type class.Examples (3)
@implicitNotFound("Builder is not fully configured: Cluster: ${HasCluster}, Codec: ${HasCodec},
HostConnectionLimit: ${HasHostConnectionLimit}")
private[builder] trait ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
class ClientBuilder[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit] private[finagle](...) {
def build()(
implicit THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
): Service[Req, Rep] = ...
}
val builder: ClientBuilder[Request, Response, Yes, Yes, Nothing] =
ClientBuilder()
.codec(Http())
.hosts("twitter.com:80")
builder.build()
Error:(24, 15) Builder is not fully configured: Cluster: com.twitter.finagle.builder.ClientConfig.Yes,
Codec: com.twitter.finagle.builder.ClientConfig.Yes, HostConnectionLimit: Nothing
builder.build()
^
- Builder only allows build() when all the configuration has been done
- @implicitNotFound custom error message when Implicit was not found.
This example is a bit more involved. Finagle provides a type safe builder class, which only builds if all the required settings are configured. The build() method can only be called when an implicit value is available which proofs that all settings are configured. If we construct a partially configured builder and try to build() it, we will get a compile time error. The annotation @implicitNotFound provides us with a custom error message when the implicit can not be found.Implicits enable
- DSLs
- Type evidence
- Reduce verbosity
- Type classes
- Dependency Injection at Compile time
- Extending libraries
- Some examples, but there is more
- DSLs: reuse standard Scala types and collections by extending them with your library functions
- Type evidence can help with creating more type safe interfaces, i.e. in Builders, constraint evidence
- Pass in dependencies depending on which implicit is in scope, used in Play & Slick
- Less writing, more implicit
I hope these examples have convinced you of the power of implicits. Implicits allow for a lot more. They can be used to extend types, including all the types in the standard library, allowing for powerful DSLs to be created embedded in the language.
They can provide type evidence as we saw in the Finagle builder and also in the TraversableOnce.sum example where a Numeric[B] had to be available.
They allow us to write less by leaving ceremony out, and thus reduce verbosity.
Implits enable us to create type classes in Scala, which add features for generic programming and extending of libraries.
We can do dependency injection by having different implicits in scope and this is all resolved at compile time.
And we can extend libraries or code that is out of our control.But beware
- Resolution rules can be difficult
- Automatic conversions
- Do not overuse
- Powerful, but can be magical in grasping, IDE support is really handy here
- Simple lexical examination not always enough
- Be careful with conversions, implicits can help, also when being explicit
+ Semantics can be different (List to Stream or Double to Int)
- If you do find yourself using implicits, always ask yourself if there is a way to achieve the same thing without their help. Principle of least power - Li Haoyi - which Martin referenced in the keynote
But take care. Implicits are a powerful extensions but being less explicit can become more magical. It is not always clear which method or field is being called just by looking at the code. Fortunately IDE's can help here.
Also, if values can be converted to values of other types, this can be triggered without you as developer even noticing and can result in doing to much work or possibly worse, automatic translations between types with different semantics, a List to a Stream for example.
If you can achieve your goal without implicits do not use them.Demo
- Explain how REPLesent works (r and n)
- ImplicitParameter.sc
- ImplicitConversion.sc
- ImplicitClass.sc
- Explain about IDE supportScoping
Odersky Explains
Lookup precedence:
By name only, without any prefix
In "implicit scope":- companion/package object of
- the source type
- its parameters + supertype and supertraits
1. Visible as locals/members of enclosing classes and packages or as imports
2. companion objects that bear some relation to the type
- the importance is to be as general as possible without reverting to whole program analysis like Haskell does).
- This is so that library developers can provide sensible defaults in Companion Objects and Package Objects and users can override those by importing or defining implicits in local scopeDemo
- First show scoping package
- Scoping.scTypeclass uses
- Ad-hoc polymorphism
- Extension of libraries
trait Numeric[T] extends Ordering[T] {
def plus(x: T, y: T): T
def minus(x: T, y: T): T
def times(x: T, y: T): T
...
}
- Term originates from Haskell, because no subtyping, to allow overloading of functions and operators
- Extending library without changing library code, loose coupling
+ Examples: Comparability, printability, summing
+ Type class captures common sets of operations
- Example sort a collection if elements of collection are comparable
+ Can be done by inheritance, interface etc
+ But, static and only when writing the element type
+ Basically extend the library
- Can be implemented in Scala using implicitsDemo
Typeclass for JSON Serialisation
- Naive with subtyping
- Typeclass + improvements
- Adding JSonSerialisable everywhere will clutter your implemetation => High Coupling
- typeclass/Naive.sc
- typeclass/JsonSerializer.scRecap
- Implicits are powerful
- Be careful with conversions
- Implicit precedence: first look local, then in companion/package object
- Typeclasses to extend libraries
Questions?
- Powerful. Allow to write expressive code.
- Use cases range from DSLs to extending libraries to full blown DI.
- Use your IDE to help spot conversions and see which implicits are used
- Typeclasses, extend in Nonintrusive way, enabling loose coupling
- Don't forget next slides on code
1
Implicits Inspected and Explained
ScalaDays 2016 Berlin
Tim Soethout - ING Bank
http://blog.timmybankers.nl
Outline: Introduction - Implicits - Resolving - Type Classes - Wrap up
- Welcome!
- Many Scala users see implicits everywhere, but their exact use remains a bit magic
- Goal of talk is to give you an intuition on how implicits work and where to look when things go haywire.
- Target audience is Scala developers, who already know the language and syntax
- We already heard a lot about implicits. Martin emphasised the importance of implicits for the Scala Language in the keynore. Yesterday a lot of the talks in the advanced track used and abused implicits a lot.
- Outline: Context, Implicits, Scoping, Typeclasses