Scala Implicits - Not to be feared

Preview:

DESCRIPTION

These are the slides from a talk I gave at the Waterloo Scala Meetup on October 9th 2013. The talk was geared toward describing the purpose of implicits, use cases, and getting past that initial hump of "what are they and why would I need them" in order to get people to start exploring the ideas.

Citation preview

ImplicitsScalain

Derek WyattTwitter: @derekwyatt

Email: derek@derekwyatt.orgFriday, 11 October, 13

AgendaLies and Damn LiesUse CasesScopeExampleCode!

Truths

Rules of Thumb

Friday, 11 October, 13

Implicits are NEW

Friday, 11 October, 13

Implicits are NEW

New things can be cool!

Friday, 11 October, 13

Implicits are NEW

New things can be cool!New things can also be scary!

Scala 2.10’s new set of

warnings don’t help the situation.

Friday, 11 October, 13

Implicits are NEW

New things can be cool!New things can also be scary!

Scala 2.10’s new set of

warnings don’t help the situation.

Especially when they’re complicated!

Friday, 11 October, 13

Implicits are NEW

New things can be cool!

Newness + Complexness = Fearsometimes

New things can also be scary!❉

Scala 2.10’s new set of

warnings don’t help the situation.

Especially when they’re complicated!

Friday, 11 October, 13

Implicits are NEW

New things can be cool!

Newness + Complexness = Fearsometimes

Fear Avoidance

New things can also be scary!❉

Scala 2.10’s new set of

warnings don’t help the situation.

Especially when they’re complicated!

Friday, 11 October, 13

Implicits are NEW

New things can be cool!

Newness + Complexness = Fearsometimes

Fear Avoidance

Avoidance

: (New things can also be scary!

Scala 2.10’s new set of

warnings don’t help the situation.

Especially when they’re complicated!

Friday, 11 October, 13

Implicits

Friday, 11 October, 13

ImplicitsAre Not

Friday, 11 October, 13

ImplicitsAre Not

Global variables LIE!

Friday, 11 October, 13

ImplicitsAre Not

Global variablesDynamically Applied

LIE!

DAMN

LIE!

Friday, 11 October, 13

ImplicitsAre Not

Global variablesDynamically AppliedDangerous (...much...)

LIE!

DAMN

LIE!

Fib

Friday, 11 October, 13

ImplicitsAre Not

Global variablesDynamically AppliedDangerous (...much...)

Implicits are like scissors.Use them. Don’t run with them.

LIE!

DAMN

LIE!

Fib

Friday, 11 October, 13

Use CasesWhat are they used for?

Friday, 11 October, 13

Use Case: Type Classes

Friday, 11 October, 13

Use Case: Type Classes

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

Friday, 11 October, 13

Use Case: Type Classes

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

lessThan needs definition

Friday, 11 October, 13

Use Case: Type Classesdef lessThan[A](a: A, b: A)(implicit o: Ordering[A]): Boolean = o.lt(a, b)

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

lessThan needs definition

Friday, 11 October, 13

Use Case: Type Classesdef lessThan[A](a: A, b: A)(implicit o: Ordering[A]): Boolean = o.lt(a, b)

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

Ordering[Int]’s gotta come from somewhere

lessThan needs definition

Friday, 11 October, 13

Use Case: Type Classesdef lessThan[A](a: A, b: A)(implicit o: Ordering[A]): Boolean = o.lt(a, b)

implicit object intOrdering extends Ordering[Int] { def compare(a: Int, b: Int): Int = a - b}

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

Ordering[Int]’s gotta come from somewhere

lessThan needs definition

Friday, 11 October, 13

Use Case: Type Classesdef lessThan[A](a: A, b: A)(implicit o: Ordering[A]): Boolean = o.lt(a, b)

implicit object intOrdering extends Ordering[Int] { def compare(a: Int, b: Int): Int = a - b}

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)) a else b

Ordering[Int]’s gotta come from somewhere

val a = someInt()val b = someOtherInt()val lesser = if (lessThan(a, b)(intOrdering)) a else b

lessThan needs definition

Friday, 11 October, 13

Use Case: Class Extension

Friday, 11 October, 13

Use Case: Class Extensionval hexVals = “implicit”.asHexSeq// Vector(0x69, 0x6D, 0x70, 0x6C, 0x69, 0x63, 0x69, 0x74)

The “Pimp”

Friday, 11 October, 13

Use Case: Class Extensionval hexVals = “implicit”.asHexSeq// Vector(0x69, 0x6D, 0x70, 0x6C, 0x69, 0x63, 0x69, 0x74)

The “Pimp”

implicit class HexableString(s: String) { def asHexSeq: Seq[String] = s map { c => f”0x$c%02X” }}

The implicit class definition provides the extension method

Friday, 11 October, 13

Use Case: Class Extensionval hexVals = “implicit”.asHexSeq// Vector(0x69, 0x6D, 0x70, 0x6C, 0x69, 0x63, 0x69, 0x74)

The “Pimp”

implicit class HexableString(s: String) { def asHexSeq: Seq[String] = s map { c => f”0x$c%02X” }}

The implicit class definition provides the extension method

Bonus: If you extend implicit classes from AnyVal, no temporary object construction will occur.

Friday, 11 October, 13

Use Case: Declutteringval future1 = someCall(“a parameter”, 5.seconds)val future2 = someCall(345, 5.seconds)val future3 = someCall(235.9352, 5.seconds)

Friday, 11 October, 13

Use Case: Declutteringval future1 = someCall(“a parameter”, 5.seconds)val future2 = someCall(345, 5.seconds)val future3 = someCall(235.9352, 5.seconds)

Blurgh

Friday, 11 October, 13

Use Case: Declutteringval future1 = someCall(“a parameter”, 5.seconds)val future2 = someCall(345, 5.seconds)val future3 = someCall(235.9352, 5.seconds)

Blurgh

def someCall[A](a: A)(implicit timeout: Duration): Future[A] = ???

But, if we define someCall this way...

Friday, 11 October, 13

Use Case: Declutteringval future1 = someCall(“a parameter”, 5.seconds)val future2 = someCall(345, 5.seconds)val future3 = someCall(235.9352, 5.seconds)

Blurgh

def someCall[A](a: A)(implicit timeout: Duration): Future[A] = ???

But, if we define someCall this way...

implicit val myTimeoutValue = 5.seconds

val future1 = someCall(“a parameter”)val future2 = someCall(345)val future3 = someCall(235.9352)

And define an implicit value, we can simplify the calls...

Friday, 11 October, 13

Use Case: Internal DSLsCreate your own sub-language with ease

Friday, 11 October, 13

Use Case: Internal DSLsCreate your own sub-language with ease

implicit class Recoverable[A](f: => A) { def recover(g: Throwable => A): A = try { f } catch { case t: Throwable => g(t) }}

Friday, 11 October, 13

Use Case: Internal DSLsCreate your own sub-language with ease

implicit class Recoverable[A](f: => A) { def recover(g: Throwable => A): A = try { f } catch { case t: Throwable => g(t) }} def thisThrows(): Int = throw new Exception(“Argh!”)

val stable = thisThrows() recover { t => if (t.getMessage == “Argh!”) 10 else 5} // stable == 10

Friday, 11 October, 13

Use Case: Other stuff...

Friday, 11 October, 13

Use Case: Other stuff...Overriding defaults

def func[A](a: A)(implicit tout: Duration = 3.seconds): Future[A]

Friday, 11 October, 13

Use Case: Other stuff...Overriding defaults

def func[A](a: A)(implicit tout: Duration = 3.seconds): Future[A]

Decoupled Dependency Injection

class Database(ec: ExecutionContext) { def create(row: Row): Future[Result] = ??? def delete(id: RowId): Future[Result] = ??? // etc...}

Friday, 11 October, 13

Use Case: Other stuff...Overriding defaults

def func[A](a: A)(implicit tout: Duration = 3.seconds): Future[A]

Decoupled Dependency Injection

class Database(ec: ExecutionContext) { def create(row: Row): Future[Result] = ??? def delete(id: RowId): Future[Result] = ??? // etc...}

NO!

Friday, 11 October, 13

Use Case: Other stuff...Overriding defaults

def func[A](a: A)(implicit tout: Duration = 3.seconds): Future[A]

Decoupled Dependency Injection

class Database { def create(row: Row)(implicit ec: ExecutionContext): Future[Result] def delete(id: RowId)(implicit ec: ExecutionContext): Future[Result] // etc...}

We can now vary the ExecutionContext at any point by supplying the right implicit value

Friday, 11 October, 13

Implicit ScopeRules!!!!

Friday, 11 October, 13

Implicit ScopeRules!!!!

There are a Lot of Rules

Friday, 11 October, 13

Implicit ScopeRules!!!!

There are a Lot of RulesRead “Scala In Depth”*

*Josh SuerethFriday, 11 October, 13

Implicit ScopeRules!!!!

There are a Lot of RulesRead “Scala In Depth”*Implicits without the Import Tax*

*Josh SuerethFriday, 11 October, 13

Implicit ScopeRules!!!!

There are a Lot of RulesRead “Scala In Depth”*Implicits without the Import Tax*

*Josh Suereth

I don’t know them super well, and I haven’t cut my

arm off yet...

Friday, 11 October, 13

Creating a Protocol

Friday, 11 October, 13

Creating a Protocolan Implicit

Friday, 11 October, 13

Creating a Protocolan Implicit

We want:actor emit Message(“Hello”)

Friday, 11 October, 13

Creating a Protocolan Implicit

We want:actor emit Message(“Hello”)

To Produce:actor ! Envelope(ComponentType(“Client”), ComponentType(“DBActor”), ComponentId(“/user/supervisor/DB”), WorkId(“764efa883dd7671c4a3bbd9e”), MsgType(“org.my.Message”), MsgNum(1), Message(“Hello”))

Friday, 11 October, 13

An Actor Derivationtrait EnvelopingActor extends Actor with EnvelopeImplicits with ActorRefImplicits { implicit val myCompType = ComponentType(getClass.getSimpleName) implicit val myCompId = ComponentId(self.path)

private var currentWorkId = unknownWorkId implicit def workId: WorkId = currentWorkId

private var currentMsgNum = MsgNum(-1) implicit def msgNum: MsgNum = currentMsgNum

def derivedReceive: Receive

def derivedReceiveWrapper(wrapped: Receive): Receive = ???

final def receive = derivedReceiveWrapper(derivedReceive)}

Friday, 11 October, 13

An Actor Derivationtrait EnvelopingActor extends Actor with EnvelopeImplicits with ActorRefImplicits { implicit val myCompType = ComponentType(getClass.getSimpleName) implicit val myCompId = ComponentId(self.path)

private var currentWorkId = unknownWorkId implicit def workId: WorkId = currentWorkId

private var currentMsgNum = MsgNum(-1) implicit def msgNum: MsgNum = currentMsgNum

def derivedReceive: Receive

def derivedReceiveWrapper(wrapped: Receive): Receive = ???

final def receive = derivedReceiveWrapper(derivedReceive)}

Sets up the implicits in a high priority

scope

Friday, 11 October, 13

The Receive Wrapperdef derivedReceiveWrapper(wrapped: Receive): Receive = { case Envelope(_, _, _, workId, _, messageNum, message) => currentWorkIdVar = workId currentMessageNumVar = messageNum wrapped(message) case message => currentWorkIdVar = createWorkId() currentMessageNumVar = MessageNum(-1) wrapped(message)}

Friday, 11 October, 13

The Receive Wrapperdef derivedReceiveWrapper(wrapped: Receive): Receive = { case Envelope(_, _, _, workId, _, messageNum, message) => currentWorkIdVar = workId currentMessageNumVar = messageNum wrapped(message) case message => currentWorkIdVar = createWorkId() currentMessageNumVar = MessageNum(-1) wrapped(message)}

Ensures that the values that vary (workId and msgNum) are updated in the implicit scope.

Friday, 11 October, 13

Envelope Implicitstrait EnvelopeImplicits { import scala.language.implicitConversions

implicit def any2Envelope(a: Any) (implicit fromCompType: ComponentType, fromCompId: ComponentId, workId: WorkId, msgNum: MsgNum) = Envelope(fromCompType, fromCompId, unknownCompId, MsgType(a.getClass.getSimpleName), workId, msgNum, a)}

Allows us to substitute a concrete Envelope value where an Any has been supplied. The implicit parameters make

this possible.Friday, 11 October, 13

ActorRef Implicitstrait ActorRefImplicits { implicit class PimpedActorRef(ref: ActorRef) { def emit(envelope: Envelope) (implicit sender: ActorRef = Actor.noSender): Unit = { ref.tell(envelope.copy( toComponentId = ComponentId(ref.path), msgNum = envelope.msgNum.increment ), sender) } }}

The emit method demands an Envelope. When you call emit, that starts the implicit conversion! any2Envelope

creates it, and we update it here with better values.Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Envelope( ComponentType(“SomeActor”), ComponentType(“MyActor”), ComponentId(“/user/myactor”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(0), Message(“SomeActor sent”))

Incoming

Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Envelope( ComponentType(“SomeActor”), ComponentType(“MyActor”), ComponentId(“/user/myactor”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(0), Message(“SomeActor sent”))

Incoming

Envelope( ComponentType(“MyActor”), ComponentType(“SomeOtherActor”), ComponentId(“/user/someother”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(1), Message(“I got: SomeActor sent”))

Outgoing

Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Envelope( ComponentType(“SomeActor”), ComponentType(“MyActor”), ComponentId(“/user/myactor”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(0), Message(“SomeActor sent”))

Incoming

Envelope( ComponentType(“MyActor”), ComponentType(“SomeOtherActor”), ComponentId(“/user/someother”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(1), Message(“I got: SomeActor sent”))

OutgoingChained

Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Envelope( ComponentType(“SomeActor”), ComponentType(“MyActor”), ComponentId(“/user/myactor”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(0), Message(“SomeActor sent”))

Incoming

Envelope( ComponentType(“MyActor”), ComponentType(“SomeOtherActor”), ComponentId(“/user/someother”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(1), Message(“I got: SomeActor sent”))

Outgoing

Maintained

Friday, 11 October, 13

Using the Protocolclass MyActor extends EnvelopingActor { def derivedRecieve: Receive = { case Message(str) => someOtherActor emit Message(s”I got: $str”) }}

Envelope( ComponentType(“SomeActor”), ComponentType(“MyActor”), ComponentId(“/user/myactor”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(0), Message(“SomeActor sent”))

Incoming

Envelope( ComponentType(“MyActor”), ComponentType(“SomeOtherActor”), ComponentId(“/user/someother”), WorkId(“ad7e877e41ff32a2”), MsgType(“org.my.Message”), MsgNum(1), Message(“I got: SomeActor sent”))

Outgoing

Incremented

Friday, 11 October, 13

All the Implicits

Friday, 11 October, 13

All the ImplicitsPimp ActorRef with Emit

Friday, 11 October, 13

All the ImplicitsPimp ActorRef with Emit

Convert Any to envelope

Friday, 11 October, 13

All the ImplicitsPimp ActorRef with Emit

Convert Any to envelope

Simplify the API

Friday, 11 October, 13

All the ImplicitsPimp ActorRef with Emit

Convert Any to envelope

Simplify the API

actor emit Message(”Here’s a messa

ge”)

Just in case you forgot...

Friday, 11 October, 13

All the ImplicitsPimp ActorRef with Emit

Convert Any to envelope

Simplify the API

actor emit Message(”Here’s a messa

ge”)

Just in case you forgot...SIMPLE

Friday, 11 October, 13

The Last Use Case

Friday, 11 October, 13

The Last Use Case

Implicits help you put complexity

where it belongs...

In your libraries!&Away from your Users!Friday, 11 October, 13

Pitfalls&Rules of Thumb

Friday, 11 October, 13

Pitfalls&Rules of ThumbUse very specific types ComponentType(s: String) String()NOT

Friday, 11 October, 13

Pitfalls&Rules of ThumbUse very specific types ComponentType(s: String) String()NOT

Enable by import, not compiler switchesKeeps libraries self-contained and avoids surprises

Friday, 11 October, 13

Pitfalls&Rules of ThumbUse very specific types ComponentType(s: String) String()NOT

Enable by import, not compiler switchesKeeps libraries self-contained and avoids surprises

Push parameters to methods if you canThis keeps implicit resolution more flexible

Friday, 11 October, 13

Pitfalls&Rules of ThumbUse very specific types ComponentType(s: String) String()NOT

Enable by import, not compiler switchesKeeps libraries self-contained and avoids surprises

Push parameters to methods if you canThis keeps implicit resolution more flexible

Use the right tool for the right job!!

Friday, 11 October, 13

ImplicitsScalain

Derek WyattTwitter: @derekwyatt

Email: derek@derekwyatt.org

Thanks to @heathermiller for the presentation style

Source code is available at:https://github.com/primal-github/implicit-messaging

Friday, 11 October, 13

Recommended