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

What's hot (20)

PDF
Scala collections
Inphina Technologies
 
PDF
Getting Started With Scala
Xebia IT Architects
 
PDF
Scala categorytheory
Knoldus Inc.
 
PDF
Scala jargon cheatsheet
Ruslan Shevchenko
 
PDF
High Wizardry in the Land of Scala
djspiewak
 
PDF
Why Haskell
Susan Potter
 
PDF
Meet scala
Wojciech Pituła
 
PDF
First-Class Patterns
John De Goes
 
PDF
Scala Bootcamp 1
Knoldus Inc.
 
PPTX
Scala fundamentals
Alfonso Ruzafa
 
PDF
Scala introduction
vito jeng
 
PDF
Few simple-type-tricks in scala
Ruslan Shevchenko
 
PPTX
Practically Functional
djspiewak
 
PDF
Scala Paradigms
Tom Flaherty
 
ODP
Functions In Scala
Knoldus Inc.
 
PDF
Metaprogramming in Scala 2.10, Eugene Burmako,
Vasil Remeniuk
 
PPT
C# programming
umesh patil
 
PPTX
Java Generics
Zülfikar Karakaya
 
PDF
High-Performance Haskell
Johan Tibell
 
ODP
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Sanjeev_Knoldus
 
Scala collections
Inphina Technologies
 
Getting Started With Scala
Xebia IT Architects
 
Scala categorytheory
Knoldus Inc.
 
Scala jargon cheatsheet
Ruslan Shevchenko
 
High Wizardry in the Land of Scala
djspiewak
 
Why Haskell
Susan Potter
 
Meet scala
Wojciech Pituła
 
First-Class Patterns
John De Goes
 
Scala Bootcamp 1
Knoldus Inc.
 
Scala fundamentals
Alfonso Ruzafa
 
Scala introduction
vito jeng
 
Few simple-type-tricks in scala
Ruslan Shevchenko
 
Practically Functional
djspiewak
 
Scala Paradigms
Tom Flaherty
 
Functions In Scala
Knoldus Inc.
 
Metaprogramming in Scala 2.10, Eugene Burmako,
Vasil Remeniuk
 
C# programming
umesh patil
 
Java Generics
Zülfikar Karakaya
 
High-Performance Haskell
Johan Tibell
 
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Sanjeev_Knoldus
 

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

PDF
Generic Functional Programming with Type Classes
Tapio Rautonen
 
PDF
Demystifying Shapeless
Jared Roesch
 
PDF
Type classes 101 - classification beyond inheritance
Alexey Raga
 
PDF
Demystifying Type Class derivation with Shapeless
Yurii Ostapchuk
 
PDF
Scala for Java Developers
Martin Ockajak
 
PDF
Scala Implicits - Not to be feared
Derek Wyatt
 
PPTX
Do I need tests when I have the compiler - Andrzej Jóźwiak - TomTom Dev Day 2020
Andrzej Jóźwiak
 
PDF
Type Driven Development @ Confitura 2014
Maciek Próchniak
 
PDF
Scala Type Classes: Basics and More
sukanthajra
 
PDF
Quark: A Purely-Functional Scala DSL for Data Processing & Analytics
John De Goes
 
PDF
Lecture 5: Functional Programming
Eelco Visser
 
PDF
Introduction To Scala
Innar Made
 
PDF
Introduction to scala
Michel Perez
 
PDF
Scala in Practice
Francesco Usai
 
PDF
An Introduction to Scala (2014)
William Narmontas
 
PDF
Scala: A brief tutorial
Oliver Szymanski
 
PDF
The Scala Programming Language
league
 
PDF
Java/Scala Lab 2016. Руслан Шевченко: Несколько трюков scala-разработки, приг...
GeeksLab Odessa
 
PDF
Scalapeno18 - Thinking Less with Scala
Daniel Sebban
 
PPTX
Improving Correctness with Types Kats Conf
Iain Hull
 
Generic Functional Programming with Type Classes
Tapio Rautonen
 
Demystifying Shapeless
Jared Roesch
 
Type classes 101 - classification beyond inheritance
Alexey Raga
 
Demystifying Type Class derivation with Shapeless
Yurii Ostapchuk
 
Scala for Java Developers
Martin Ockajak
 
Scala Implicits - Not to be feared
Derek Wyatt
 
Do I need tests when I have the compiler - Andrzej Jóźwiak - TomTom Dev Day 2020
Andrzej Jóźwiak
 
Type Driven Development @ Confitura 2014
Maciek Próchniak
 
Scala Type Classes: Basics and More
sukanthajra
 
Quark: A Purely-Functional Scala DSL for Data Processing & Analytics
John De Goes
 
Lecture 5: Functional Programming
Eelco Visser
 
Introduction To Scala
Innar Made
 
Introduction to scala
Michel Perez
 
Scala in Practice
Francesco Usai
 
An Introduction to Scala (2014)
William Narmontas
 
Scala: A brief tutorial
Oliver Szymanski
 
The Scala Programming Language
league
 
Java/Scala Lab 2016. Руслан Шевченко: Несколько трюков scala-разработки, приг...
GeeksLab Odessa
 
Scalapeno18 - Thinking Less with Scala
Daniel Sebban
 
Improving Correctness with Types Kats Conf
Iain Hull
 
Ad

More from Tomer Gabel (20)

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

Recently uploaded (20)

PDF
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
PPTX
Human Resources Information System (HRIS)
Amity University, Patna
 
PPTX
Migrating Millions of Users with Debezium, Apache Kafka, and an Acyclic Synch...
MD Sayem Ahmed
 
PPTX
Tally_Basic_Operations_Presentation.pptx
AditiBansal54083
 
PPTX
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
PDF
유니티에서 Burst Compiler+ThreadedJobs+SIMD 적용사례
Seongdae Kim
 
PPTX
The Role of a PHP Development Company in Modern Web Development
SEO Company for School in Delhi NCR
 
PDF
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
PPTX
Comprehensive Guide: Shoviv Exchange to Office 365 Migration Tool 2025
Shoviv Software
 
PDF
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
PDF
Capcut Pro Crack For PC Latest Version {Fully Unlocked} 2025
hashhshs786
 
PDF
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
PDF
GetOnCRM Speeds Up Agentforce 3 Deployment for Enterprise AI Wins.pdf
GetOnCRM Solutions
 
PPTX
How Apagen Empowered an EPC Company with Engineering ERP Software
SatishKumar2651
 
PDF
Executive Business Intelligence Dashboards
vandeslie24
 
PPTX
Feb 2021 Cohesity first pitch presentation.pptx
enginsayin1
 
PPTX
MailsDaddy Outlook OST to PST converter.pptx
abhishekdutt366
 
PPTX
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
PDF
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pdf
Varsha Nayak
 
PPTX
Platform for Enterprise Solution - Java EE5
abhishekoza1981
 
Salesforce CRM Services.VALiNTRY360
VALiNTRY360
 
Human Resources Information System (HRIS)
Amity University, Patna
 
Migrating Millions of Users with Debezium, Apache Kafka, and an Acyclic Synch...
MD Sayem Ahmed
 
Tally_Basic_Operations_Presentation.pptx
AditiBansal54083
 
Agentic Automation Journey Session 1/5: Context Grounding and Autopilot for E...
klpathrudu
 
유니티에서 Burst Compiler+ThreadedJobs+SIMD 적용사례
Seongdae Kim
 
The Role of a PHP Development Company in Modern Web Development
SEO Company for School in Delhi NCR
 
Mobile CMMS Solutions Empowering the Frontline Workforce
CryotosCMMSSoftware
 
Comprehensive Guide: Shoviv Exchange to Office 365 Migration Tool 2025
Shoviv Software
 
Unlock Efficiency with Insurance Policy Administration Systems
Insurance Tech Services
 
Capcut Pro Crack For PC Latest Version {Fully Unlocked} 2025
hashhshs786
 
Linux Certificate of Completion - LabEx Certificate
VICTOR MAESTRE RAMIREZ
 
GetOnCRM Speeds Up Agentforce 3 Deployment for Enterprise AI Wins.pdf
GetOnCRM Solutions
 
How Apagen Empowered an EPC Company with Engineering ERP Software
SatishKumar2651
 
Executive Business Intelligence Dashboards
vandeslie24
 
Feb 2021 Cohesity first pitch presentation.pptx
enginsayin1
 
MailsDaddy Outlook OST to PST converter.pptx
abhishekdutt366
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pptx
Varsha Nayak
 
Why Businesses Are Switching to Open Source Alternatives to Crystal Reports.pdf
Varsha Nayak
 
Platform for Enterprise Solution - Java EE5
abhishekoza1981
 

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