SlideShare a Scribd company logo
Back to Basics: Type Classes
Tomer Gabel, Wix
August, 2014
THE EXPRESSION PROBLEM
“Define a datatype by cases, where one can add new cases to
the datatype and new functions over the datatype, without
recompiling existing code, and while retaining static type
safety (e.g., no casts).”
-- Philip Wadler
Let’s Build a Calculator
• Operators:
– Addition (+)
– Subtraction (-)
– Multiplication (*)
– Division (/)
– Remainder (%)
• Types:
– Integers (32-bit signed)
– Longs (64-bit signed)
– Floats (32-bit IEEE 754)
– Longs (64-bit IEEE 754)
Two Views of the Same Problem
Pattern Matching
sealed trait Operand
case class Int32(value: Int) extends Operand
case class Real32(value: Float) extends Operand
// ...
def addition[T <: Operand](left: T, right: T): T =
(left, right) match {
case (Int32 (l), Int32 (r)) => Int32 (l + r)
case (Real32(l), Real32(r)) => Real32(l + r)
// ...
}
Object Oriented
sealed trait Operand
case class Int32(value: Int) extends Operand {
def add(other: Int) = Int32(value + other)
def subtract(other: Int) = Int32(value - other)
// ...
}
case class Real32(value: Float) extends Operand {
def add(other: Float) = Real32(value + other)
def subtract(other: Float) = Real32(value - other)
// ...
}
Two Views of the Same Problem
Pattern Matching
sealed trait Operand
case class Int32(value: Int) extends Operand
case class Real32(value: Float) extends Operand
// ...
def addition[T <: Operand](left: T, right: T): T =
(left, right) match {
case (Int32 (l), Int32 (r)) => Int32 (l + r)
case (Real32(l), Real32(r)) => Real32(l + r)
// ...
}
Object Oriented
sealed trait Operand
case class Int32(value: Int) extends Operand {
def add(other: Int) = Int32(value + other)
def subtract(other: Int) = Int32(value - other)
// ...
}
case class Real32(value: Float) extends Operand {
def add(other: Float) = Real32(value + other)
def subtract(other: Float) = Real32(value - other)
// ...
}
Two Views of the Same Problem
Pattern Matching
sealed trait Operand
case class Int32(value: Int) extends Operand
case class Real32(value: Float) extends Operand
// ...
def addition[T <: Operand](left: T, right: T): T =
(left, right) match {
case (Int32 (l), Int32 (r)) => Int32 (l + r)
case (Real32(l), Real32(r)) => Real32(l + r)
// ...
}
Object Oriented
sealed trait Operand
case class Int32(value: Int) extends Operand {
def add(other: Int) = Int32(value + other)
def subtract(other: Int) = Int32(value - other)
// ...
}
case class Real32(value: Float) extends Operand {
def add(other: Float) = Real32(value + other)
def subtract(other: Float) = Real32(value - other)
// ...
}
TYPE CLASSES TO THE RESCUE
What’s a Type Class?
• A type class:
– Enables ad-hoc polymorphism
– Statically typed (i.e. type-safe)
– Borrowed from Haskell
• Solves the expression problem:
– Behavior can be extended
– … at compile-time
– ... after the fact
– … without changing/recompiling
existing code
Example #1: Equality
• Scala inherits legacy aspects of Java
– This includes AnyRef.equals:
def equals( other: AnyRef ): Boolean
– So the following compiles:
3.14159265359 == "pi" // Evaluates to false
• What if we wanted to implement type-safe equality?
– Let’s define a type-safe isEqual function:
isEqual( 3.14159265359, "pi” ) // Does not compile!
What’s in a Type Class?
• Three components are
required:
– A signature
– Implementations for
supported types
– A function that requires
a type class This is where things get hairy.
Slight Digression
• A method in Scala can have multiple parameter lists:
def someMethod( x: Int )( y: String )( z: Double ): Unit = {
println( s"x=$x, y=$y, z=$z" )
}
scala> someMethod( 10 )( "abc" )( scala.math.Pi )
x=10, y=abc, z=3.141592653589793
• There are multiple uses for this, but the most important is…
Scala Implicits
• The last parameter list of a method can be marked implicit
• Implicit parameters are filled in by the compiler
– In effect, you require evidence of the compiler
– … such as the existence of a type class in scope
– You can also specify parameters explicitly, if needed
Putting it together
• Let’s define our type class:
trait Equality[ L, R ] {
def equals( left: L, right: R ): Boolean
}
• … and our isEqual function:
def isEqual[ L, R ]( left: L, right: R )
( implicit ev: Equality[ L, R ] ): Boolean =
ev.equals( left, right )
This is where the magic happens
Still missing something!
• We have no implementations of the Equality trait, so nothing works!
scala> isEqual( 3, 3 )
<console>:10: error: could not find implicit value for parameter ev:
Equality[Int,Int]
• We need to implement Equality[ T, T ]:
implicit def sameTypeEquality[ T ] = new Equality[ T, T ] {
def equals( left: T, right: T ) = left.equals( right )
}
• And now it works:
scala> isEqual( 3, 3 )
res1: Boolean = true
Ad-hoc Polymorphism
• Now we’ve met our original goal:
scala> isEqual( 3.14159265359, "pi" )
<console>:11: error: could not find implicit value for parameter ev: Equality[Double,String]
• But what if we wanted to equate doubles and strings?
• Well then, let’s add another implementation!
implicit object DoubleEqualsString extends Equality[ Double, String ] {
def equals( left: Double, right: String ) = left.toString == right
}
• Et voila, no recompilation or code changes needed:
scala> isEqual( 3.14159265359, "pi" )
res5: Boolean = false
QUESTIONS SO FAR
Example #2: Sort Me, Maybe
• Let’s implement a sort
function (e.g. bubble sort)
• With one caveat:
– It should operate on any type
– … for which an ordering exists
• Obviously, we’ll use type
classes!
Possible Solution
trait Ordering[ T ] { def isLessThan( left: T, right: T ): Boolean }
def sort[ T ]( items: Seq[ T ] )( implicit ord: Ordering[ T ] ): Seq[ T ] = {
val buffer = mutable.ArrayBuffer( items:_* )
for ( i <- 0 until items.size;
j <- ( i + 1 ) until items.size )
if ( ord.isLessThan( buffer( j ), buffer( i ) ) ) {
val temp = buffer( i )
buffer( i ) = buffer( j )
buffer( j ) = temp
}
buffer
}
Possible Solution, cont.
• Sample implementation for integers:
implicit object IntOrdering extends Ordering[ Int ] {
def isLessThan( left: Int, right: Int ) = left < right
}
val numbers = Seq( 4, 1, 10, 8, 14, 2 )
Assert( sort( numbers ) == Seq( 1, 2, 4, 8, 10, 14 ) )
Possible Solution, cont.
• Sample implementation for a domain entity:
case class Person( name: String, age: Int )
implicit object PersonOrdering extends Ordering[ Person ] {
def isLessThan( left: Person, right: Person ) =
left.age < right.age
}
val haim = Person( "Haim", 12 )
val dafna = Person( "Dafna", 20 )
val ofer = Person( "Ofer", 1 )
assert( sort( Seq( haim, dafna, ofer ) ) ==
Seq( ofer, haim, dafna ) )
Implicit Search Order
Current Scope
• Defined implicits
• Explicit imports
• Wildcard imports
Companion
• … of T
• … of supertypes of T
Outer Scope
• Enclosing class
REAL WORLD EXAMPLES, PLEASE?
Example #3: Server Pipeline
• REST is good, but annoying to write. Let’s simplify:
case class DTO( message: String )
class MyServlet extends NicerHttpServlet {
private val counter = new AtomicInteger( 0 )
get( "/service" ) {
counter.incrementAndGet()
DTO( "hello, world!" )
}
get( "/count" ) {
counter.get()
}
}
Uses return value;
no direct response manipulation
Example #3: Server Pipeline
• What’s in a server?
– Routing
– Rendering
– Error handling
• Let’s focus on rendering:
trait ResponseRenderer[ T ] {
def render( value : T,
request : HttpServletRequest,
response: HttpServletResponse ): Unit
}
Example #3: Server Pipeline
• A couple of basic renderers:
implicit object StringRenderer extends ResponseRenderer[ String ] {
def render( value: String, request: HttpServletRequest, response: HttpServletResponse ) = {
val w = response.getWriter
try w.write( value )
finally w.close()
}
}
implicit object IntRenderer extends ResponseRenderer[ Int ] {
def render( value: Int, request: HttpServletRequest, response: HttpServletResponse ) =
implicitly[ ResponseRenderer[ String ] ].render( value.toString, request, response )
}
Example #3: Server Pipeline
• Putting it together:
trait NicerHttpServlet extends HttpServlet {
private trait Handler {
type Response
def result: Response
def renderer: ResponseRenderer[ Response ]
}
private var handlers: Map[ String, Handler ] = Map.empty
protected def get[ T : ResponseRenderer ]( url: String )( thunk: => T ) =
handlers += url -> new Handler {
type Response = T
def result = thunk
def renderer = implicitly[ ResponseRenderer[ T ] ]
}
Example #3: Server Pipeline
• And finally:
override def doGet( req: HttpServletRequest, resp: HttpServletResponse ) =
handlers.get( req.getRequestURI ) match {
case None =>
resp.sendError( HttpServletResponse.SC_NOT_FOUND )
case Some( handler ) =>
try handler.renderer.render( handler.result, req, resp )
catch { case e: Exception =>
resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR )
}
}
PHEW.
Take a deep breath
Example #4: JSON Serialization
• Assume we already have a good model for JSON
• How do we add type-safe serialization?
JsonValue
JsonObject JsonArray
JsonBoolean JsonInt
JsonDouble JsonNull
JsonField
Example #4: JSON Serialization
• Let’s start with a typeclass:
trait JsonSerializer[ T ] {
def serialize( value: T ): JsonValue
def deserialize( value: JsonValue ): T
}
• And the corresponding library signature:
def serialize[ T ]( instance: T )( implicit ser: JsonSerializer[ T ] ) =
ser.serialize( instance )
def deserialize[ T ]( json: JsonValue )( implicit ser: JsonSerializer[ T ] ) =
ser.deserialize( json )
Example #4: JSON Serialization
• Define a few basic serializers…
implicit object BooleanSerializer extends JsonSerializer[ Boolean ] {
def serialize( value: Boolean ) = JsonBoolean( value )
def deserialize( value: JsonValue ) = value match {
case JsonBoolean( bool ) => bool
case other => error( other )
}
}
implicit object StringSerializer extends JsonSerializer[ String ] {
def serialize( value: String ) = JsonString( value )
def deserialize( value: JsonValue ) = value match {
case JsonString( string ) => string
case other => error( other )
}
}
Example #4: JSON Serialization
• We can also handle nested structures
– The compiler resolves typeclasses recursively!
• For example, Option[ T ] :
implicit def optionSerializer[ T ]( implicit ser: JsonSerializer[ T ] ) =
new JsonSerializer[ Option[ T ] ] {
def serialize( value: Option[ T ] ) =
value map ser.serialize getOrElse JsonNull
def deserialize( value: JsonValue ) = value match {
case JsonNull => None
case other => Some( ser.deserialize( other ) )
}
}
Require a serializer for T
… and delegate to it
Example #4: JSON Serialization
• What about an arbitrary type?
case class Person( name: String, surname: String, age: Int )
implicit object PersonSerializer extends JsonSerializer[ Person ] {
def serialize( value: Person ) = JsonObject(
JsonField( "name", serialize( value.name ) ),
JsonField( "surname", serialize( value.surname ) ),
JsonField( "age", serialize( value.age ) )
)
def deserialize( value: JsonValue ) = value match {
case obj: JsonObject =>
Person(
name = deserialize[ String ]( obj  "name" ),
surname = deserialize[ String ]( obj  "surname" ),
age = deserialize[ Int ]( obj  "age" )
)
case _ => error( value )
}
}
Summary
• We added serialization for Person after the fact without…
– … modifying the serialization framework
– … modifying the domain object
• We did not compromise:
– … type safety or performance
– … modularity or encapsulation
• This applies everywhere!
clients of either are unaffected!
… and we’re done
• Thank you for your time!
• Questions/comments?
– tomer@tomergabel.com
– @tomerg
– http://www.tomergabel.com
• Code samples:
– http://git.io/aWc9eQ

More Related Content

PDF
Pragmatic Real-World Scala (short version)
PDF
Scala collections api expressivity and brevity upgrade from java
PPTX
Scala for curious
PDF
Programming in Scala: Notes
ODP
Introducing scala
PDF
Getting Started With Scala
PPTX
Intro to Functional Programming in Scala
PPTX
Joy of scala
Pragmatic Real-World Scala (short version)
Scala collections api expressivity and brevity upgrade from java
Scala for curious
Programming in Scala: Notes
Introducing scala
Getting Started With Scala
Intro to Functional Programming in Scala
Joy of scala

What's hot (20)

PDF
Scala collections
PDF
Getting Started With Scala
PDF
Scala categorytheory
PDF
Scala jargon cheatsheet
PDF
High Wizardry in the Land of Scala
PDF
Why Haskell
PDF
Meet scala
PDF
First-Class Patterns
PDF
Scala Bootcamp 1
PPTX
Scala fundamentals
PDF
Scala introduction
PDF
Few simple-type-tricks in scala
PPTX
Practically Functional
PDF
Scala Paradigms
ODP
Functions In Scala
PDF
Metaprogramming in Scala 2.10, Eugene Burmako,
PPT
C# programming
PPTX
Java Generics
PDF
High-Performance Haskell
ODP
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Scala collections
Getting Started With Scala
Scala categorytheory
Scala jargon cheatsheet
High Wizardry in the Land of Scala
Why Haskell
Meet scala
First-Class Patterns
Scala Bootcamp 1
Scala fundamentals
Scala introduction
Few simple-type-tricks in scala
Practically Functional
Scala Paradigms
Functions In Scala
Metaprogramming in Scala 2.10, Eugene Burmako,
C# programming
Java Generics
High-Performance Haskell
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Ad

Similar to Scala Back to Basics: Type Classes (20)

PPTX
Legacy lambda code
PPTX
Scala Refactoring for Fun and Profit
PDF
(How) can we benefit from adopting scala?
PPTX
Java gets a closure
PPTX
Speaking Scala: Refactoring for Fun and Profit (Workshop)
PDF
An introduction to functional programming with Swift
PDF
Introduction à Scala - Michel Schinz - January 2010
PDF
Lecture 5: Functional Programming
PPTX
C++11 - A Change in Style - v2.0
PPTX
Scala 3 Is Coming: Martin Odersky Shares What To Know
PDF
Functional programming ii
PDF
Scala by Luc Duponcheel
PDF
Trafaret: monads and python
PPT
Extractors & Implicit conversions
PDF
Introduction to idris
PPS
Let Us Learn Lambda Using C# 3.0
PPTX
ODP
Python basics
PDF
The Functional Programming Triad of Folding, Scanning and Iteration - a first...
PPT
Functional object
Legacy lambda code
Scala Refactoring for Fun and Profit
(How) can we benefit from adopting scala?
Java gets a closure
Speaking Scala: Refactoring for Fun and Profit (Workshop)
An introduction to functional programming with Swift
Introduction à Scala - Michel Schinz - January 2010
Lecture 5: Functional Programming
C++11 - A Change in Style - v2.0
Scala 3 Is Coming: Martin Odersky Shares What To Know
Functional programming ii
Scala by Luc Duponcheel
Trafaret: monads and python
Extractors & Implicit conversions
Introduction to idris
Let Us Learn Lambda Using C# 3.0
Python basics
The Functional Programming Triad of Folding, Scanning and Iteration - a first...
Functional object
Ad

More from Tomer Gabel (20)

PDF
How shit works: Time
PDF
Nondeterministic Software for the Rest of Us
PDF
Slaying Sacred Cows: Deconstructing Dependency Injection
PDF
An Abridged Guide to Event Sourcing
PDF
How shit works: the CPU
PDF
How Shit Works: Storage
PDF
Java 8 and Beyond, a Scala Story
PDF
The Wix Microservice Stack
PPTX
Scala Refactoring for Fun and Profit (Japanese subtitles)
PDF
Onboarding at Scale
PPTX
Scala in the Wild
PPTX
Put Your Thinking CAP On
PPTX
Leveraging Scala Macros for Better Validation
PDF
A Field Guide to DSL Design in Scala
PPTX
Functional Leap of Faith (Keynote at JDay Lviv 2014)
PDF
5 Bullets to Scala Adoption
PPTX
Nashorn: JavaScript that doesn’t suck (ILJUG)
PDF
Ponies and Unicorns With Scala
PPTX
Lab: JVM Production Debugging 101
PPTX
DevCon³: Scala Best Practices
How shit works: Time
Nondeterministic Software for the Rest of Us
Slaying Sacred Cows: Deconstructing Dependency Injection
An Abridged Guide to Event Sourcing
How shit works: the CPU
How Shit Works: Storage
Java 8 and Beyond, a Scala Story
The Wix Microservice Stack
Scala Refactoring for Fun and Profit (Japanese subtitles)
Onboarding at Scale
Scala in the Wild
Put Your Thinking CAP On
Leveraging Scala Macros for Better Validation
A Field Guide to DSL Design in Scala
Functional Leap of Faith (Keynote at JDay Lviv 2014)
5 Bullets to Scala Adoption
Nashorn: JavaScript that doesn’t suck (ILJUG)
Ponies and Unicorns With Scala
Lab: JVM Production Debugging 101
DevCon³: Scala Best Practices

Recently uploaded (20)

PDF
Designing Intelligence for the Shop Floor.pdf
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
PDF
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PPTX
Transform Your Business with a Software ERP System
PPTX
Embracing Complexity in Serverless! GOTO Serverless Bengaluru
PDF
iTop VPN Free 5.6.0.5262 Crack latest version 2025
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
history of c programming in notes for students .pptx
PPTX
L1 - Introduction to python Backend.pptx
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
System and Network Administraation Chapter 3
PDF
System and Network Administration Chapter 2
PDF
Understanding Forklifts - TECH EHS Solution
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Why Generative AI is the Future of Content, Code & Creativity?
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
Designing Intelligence for the Shop Floor.pdf
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Transform Your Business with a Software ERP System
Embracing Complexity in Serverless! GOTO Serverless Bengaluru
iTop VPN Free 5.6.0.5262 Crack latest version 2025
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
history of c programming in notes for students .pptx
L1 - Introduction to python Backend.pptx
Reimagine Home Health with the Power of Agentic AI​
System and Network Administraation Chapter 3
System and Network Administration Chapter 2
Understanding Forklifts - TECH EHS Solution
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
CHAPTER 2 - PM Management and IT Context
Why Generative AI is the Future of Content, Code & Creativity?
Upgrade and Innovation Strategies for SAP ERP Customers

Scala Back to Basics: Type Classes

  • 1. Back to Basics: Type Classes Tomer Gabel, Wix August, 2014
  • 2. THE EXPRESSION PROBLEM “Define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts).” -- Philip Wadler
  • 3. Let’s Build a Calculator • Operators: – Addition (+) – Subtraction (-) – Multiplication (*) – Division (/) – Remainder (%) • Types: – Integers (32-bit signed) – Longs (64-bit signed) – Floats (32-bit IEEE 754) – Longs (64-bit IEEE 754)
  • 4. Two Views of the Same Problem Pattern Matching sealed trait Operand case class Int32(value: Int) extends Operand case class Real32(value: Float) extends Operand // ... def addition[T <: Operand](left: T, right: T): T = (left, right) match { case (Int32 (l), Int32 (r)) => Int32 (l + r) case (Real32(l), Real32(r)) => Real32(l + r) // ... } Object Oriented sealed trait Operand case class Int32(value: Int) extends Operand { def add(other: Int) = Int32(value + other) def subtract(other: Int) = Int32(value - other) // ... } case class Real32(value: Float) extends Operand { def add(other: Float) = Real32(value + other) def subtract(other: Float) = Real32(value - other) // ... }
  • 5. Two Views of the Same Problem Pattern Matching sealed trait Operand case class Int32(value: Int) extends Operand case class Real32(value: Float) extends Operand // ... def addition[T <: Operand](left: T, right: T): T = (left, right) match { case (Int32 (l), Int32 (r)) => Int32 (l + r) case (Real32(l), Real32(r)) => Real32(l + r) // ... } Object Oriented sealed trait Operand case class Int32(value: Int) extends Operand { def add(other: Int) = Int32(value + other) def subtract(other: Int) = Int32(value - other) // ... } case class Real32(value: Float) extends Operand { def add(other: Float) = Real32(value + other) def subtract(other: Float) = Real32(value - other) // ... }
  • 6. Two Views of the Same Problem Pattern Matching sealed trait Operand case class Int32(value: Int) extends Operand case class Real32(value: Float) extends Operand // ... def addition[T <: Operand](left: T, right: T): T = (left, right) match { case (Int32 (l), Int32 (r)) => Int32 (l + r) case (Real32(l), Real32(r)) => Real32(l + r) // ... } Object Oriented sealed trait Operand case class Int32(value: Int) extends Operand { def add(other: Int) = Int32(value + other) def subtract(other: Int) = Int32(value - other) // ... } case class Real32(value: Float) extends Operand { def add(other: Float) = Real32(value + other) def subtract(other: Float) = Real32(value - other) // ... }
  • 7. TYPE CLASSES TO THE RESCUE
  • 8. What’s a Type Class? • A type class: – Enables ad-hoc polymorphism – Statically typed (i.e. type-safe) – Borrowed from Haskell • Solves the expression problem: – Behavior can be extended – … at compile-time – ... after the fact – … without changing/recompiling existing code
  • 9. Example #1: Equality • Scala inherits legacy aspects of Java – This includes AnyRef.equals: def equals( other: AnyRef ): Boolean – So the following compiles: 3.14159265359 == "pi" // Evaluates to false • What if we wanted to implement type-safe equality? – Let’s define a type-safe isEqual function: isEqual( 3.14159265359, "pi” ) // Does not compile!
  • 10. What’s in a Type Class? • Three components are required: – A signature – Implementations for supported types – A function that requires a type class This is where things get hairy.
  • 11. Slight Digression • A method in Scala can have multiple parameter lists: def someMethod( x: Int )( y: String )( z: Double ): Unit = { println( s"x=$x, y=$y, z=$z" ) } scala> someMethod( 10 )( "abc" )( scala.math.Pi ) x=10, y=abc, z=3.141592653589793 • There are multiple uses for this, but the most important is…
  • 12. Scala Implicits • The last parameter list of a method can be marked implicit • Implicit parameters are filled in by the compiler – In effect, you require evidence of the compiler – … such as the existence of a type class in scope – You can also specify parameters explicitly, if needed
  • 13. Putting it together • Let’s define our type class: trait Equality[ L, R ] { def equals( left: L, right: R ): Boolean } • … and our isEqual function: def isEqual[ L, R ]( left: L, right: R ) ( implicit ev: Equality[ L, R ] ): Boolean = ev.equals( left, right ) This is where the magic happens
  • 14. Still missing something! • We have no implementations of the Equality trait, so nothing works! scala> isEqual( 3, 3 ) <console>:10: error: could not find implicit value for parameter ev: Equality[Int,Int] • We need to implement Equality[ T, T ]: implicit def sameTypeEquality[ T ] = new Equality[ T, T ] { def equals( left: T, right: T ) = left.equals( right ) } • And now it works: scala> isEqual( 3, 3 ) res1: Boolean = true
  • 15. Ad-hoc Polymorphism • Now we’ve met our original goal: scala> isEqual( 3.14159265359, "pi" ) <console>:11: error: could not find implicit value for parameter ev: Equality[Double,String] • But what if we wanted to equate doubles and strings? • Well then, let’s add another implementation! implicit object DoubleEqualsString extends Equality[ Double, String ] { def equals( left: Double, right: String ) = left.toString == right } • Et voila, no recompilation or code changes needed: scala> isEqual( 3.14159265359, "pi" ) res5: Boolean = false
  • 17. Example #2: Sort Me, Maybe • Let’s implement a sort function (e.g. bubble sort) • With one caveat: – It should operate on any type – … for which an ordering exists • Obviously, we’ll use type classes!
  • 18. Possible Solution trait Ordering[ T ] { def isLessThan( left: T, right: T ): Boolean } def sort[ T ]( items: Seq[ T ] )( implicit ord: Ordering[ T ] ): Seq[ T ] = { val buffer = mutable.ArrayBuffer( items:_* ) for ( i <- 0 until items.size; j <- ( i + 1 ) until items.size ) if ( ord.isLessThan( buffer( j ), buffer( i ) ) ) { val temp = buffer( i ) buffer( i ) = buffer( j ) buffer( j ) = temp } buffer }
  • 19. Possible Solution, cont. • Sample implementation for integers: implicit object IntOrdering extends Ordering[ Int ] { def isLessThan( left: Int, right: Int ) = left < right } val numbers = Seq( 4, 1, 10, 8, 14, 2 ) Assert( sort( numbers ) == Seq( 1, 2, 4, 8, 10, 14 ) )
  • 20. Possible Solution, cont. • Sample implementation for a domain entity: case class Person( name: String, age: Int ) implicit object PersonOrdering extends Ordering[ Person ] { def isLessThan( left: Person, right: Person ) = left.age < right.age } val haim = Person( "Haim", 12 ) val dafna = Person( "Dafna", 20 ) val ofer = Person( "Ofer", 1 ) assert( sort( Seq( haim, dafna, ofer ) ) == Seq( ofer, haim, dafna ) )
  • 21. Implicit Search Order Current Scope • Defined implicits • Explicit imports • Wildcard imports Companion • … of T • … of supertypes of T Outer Scope • Enclosing class
  • 23. Example #3: Server Pipeline • REST is good, but annoying to write. Let’s simplify: case class DTO( message: String ) class MyServlet extends NicerHttpServlet { private val counter = new AtomicInteger( 0 ) get( "/service" ) { counter.incrementAndGet() DTO( "hello, world!" ) } get( "/count" ) { counter.get() } } Uses return value; no direct response manipulation
  • 24. Example #3: Server Pipeline • What’s in a server? – Routing – Rendering – Error handling • Let’s focus on rendering: trait ResponseRenderer[ T ] { def render( value : T, request : HttpServletRequest, response: HttpServletResponse ): Unit }
  • 25. Example #3: Server Pipeline • A couple of basic renderers: implicit object StringRenderer extends ResponseRenderer[ String ] { def render( value: String, request: HttpServletRequest, response: HttpServletResponse ) = { val w = response.getWriter try w.write( value ) finally w.close() } } implicit object IntRenderer extends ResponseRenderer[ Int ] { def render( value: Int, request: HttpServletRequest, response: HttpServletResponse ) = implicitly[ ResponseRenderer[ String ] ].render( value.toString, request, response ) }
  • 26. Example #3: Server Pipeline • Putting it together: trait NicerHttpServlet extends HttpServlet { private trait Handler { type Response def result: Response def renderer: ResponseRenderer[ Response ] } private var handlers: Map[ String, Handler ] = Map.empty protected def get[ T : ResponseRenderer ]( url: String )( thunk: => T ) = handlers += url -> new Handler { type Response = T def result = thunk def renderer = implicitly[ ResponseRenderer[ T ] ] }
  • 27. Example #3: Server Pipeline • And finally: override def doGet( req: HttpServletRequest, resp: HttpServletResponse ) = handlers.get( req.getRequestURI ) match { case None => resp.sendError( HttpServletResponse.SC_NOT_FOUND ) case Some( handler ) => try handler.renderer.render( handler.result, req, resp ) catch { case e: Exception => resp.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR ) } }
  • 29. Example #4: JSON Serialization • Assume we already have a good model for JSON • How do we add type-safe serialization? JsonValue JsonObject JsonArray JsonBoolean JsonInt JsonDouble JsonNull JsonField
  • 30. Example #4: JSON Serialization • Let’s start with a typeclass: trait JsonSerializer[ T ] { def serialize( value: T ): JsonValue def deserialize( value: JsonValue ): T } • And the corresponding library signature: def serialize[ T ]( instance: T )( implicit ser: JsonSerializer[ T ] ) = ser.serialize( instance ) def deserialize[ T ]( json: JsonValue )( implicit ser: JsonSerializer[ T ] ) = ser.deserialize( json )
  • 31. Example #4: JSON Serialization • Define a few basic serializers… implicit object BooleanSerializer extends JsonSerializer[ Boolean ] { def serialize( value: Boolean ) = JsonBoolean( value ) def deserialize( value: JsonValue ) = value match { case JsonBoolean( bool ) => bool case other => error( other ) } } implicit object StringSerializer extends JsonSerializer[ String ] { def serialize( value: String ) = JsonString( value ) def deserialize( value: JsonValue ) = value match { case JsonString( string ) => string case other => error( other ) } }
  • 32. Example #4: JSON Serialization • We can also handle nested structures – The compiler resolves typeclasses recursively! • For example, Option[ T ] : implicit def optionSerializer[ T ]( implicit ser: JsonSerializer[ T ] ) = new JsonSerializer[ Option[ T ] ] { def serialize( value: Option[ T ] ) = value map ser.serialize getOrElse JsonNull def deserialize( value: JsonValue ) = value match { case JsonNull => None case other => Some( ser.deserialize( other ) ) } } Require a serializer for T … and delegate to it
  • 33. Example #4: JSON Serialization • What about an arbitrary type? case class Person( name: String, surname: String, age: Int ) implicit object PersonSerializer extends JsonSerializer[ Person ] { def serialize( value: Person ) = JsonObject( JsonField( "name", serialize( value.name ) ), JsonField( "surname", serialize( value.surname ) ), JsonField( "age", serialize( value.age ) ) ) def deserialize( value: JsonValue ) = value match { case obj: JsonObject => Person( name = deserialize[ String ]( obj "name" ), surname = deserialize[ String ]( obj "surname" ), age = deserialize[ Int ]( obj "age" ) ) case _ => error( value ) } }
  • 34. Summary • We added serialization for Person after the fact without… – … modifying the serialization framework – … modifying the domain object • We did not compromise: – … type safety or performance – … modularity or encapsulation • This applies everywhere! clients of either are unaffected!
  • 35. … and we’re done • Thank you for your time! • Questions/comments? – [email protected] – @tomerg – http://www.tomergabel.com • Code samples: – http://git.io/aWc9eQ

Editor's Notes

  • #3: Source: http://en.wikipedia.org/wiki/Expression_problem