Scala Back to Basics: Type Classes

Tomer Gabel
Tomer GabelConsulting Engineer at Substrate Software Services
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
1 of 35

Recommended

Pragmatic Real-World Scala (short version) by
Pragmatic Real-World Scala (short version)Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Jonas Bonér
72.8K views108 slides
Scala collections api expressivity and brevity upgrade from java by
Scala collections api  expressivity and brevity upgrade from javaScala collections api  expressivity and brevity upgrade from java
Scala collections api expressivity and brevity upgrade from javaIndicThreads
677 views9 slides
Scala for curious by
Scala for curiousScala for curious
Scala for curiousTim (dev-tim) Zadorozhniy
514 views88 slides
Programming in Scala: Notes by
Programming in Scala: NotesProgramming in Scala: Notes
Programming in Scala: NotesRoberto Casadei
2.3K views192 slides
Introducing scala by
Introducing scalaIntroducing scala
Introducing scalaMeetu Maltiar
1.8K views41 slides
Getting Started With Scala by
Getting Started With ScalaGetting Started With Scala
Getting Started With ScalaMeetu Maltiar
6.4K views35 slides

More Related Content

What's hot

Scala collections by
Scala collectionsScala collections
Scala collectionsInphina Technologies
2.9K views25 slides
Getting Started With Scala by
Getting Started With ScalaGetting Started With Scala
Getting Started With ScalaXebia IT Architects
2K views35 slides
Scala categorytheory by
Scala categorytheoryScala categorytheory
Scala categorytheoryKnoldus Inc.
1.4K views22 slides
Scala jargon cheatsheet by
Scala jargon cheatsheetScala jargon cheatsheet
Scala jargon cheatsheetRuslan Shevchenko
880 views31 slides
High Wizardry in the Land of Scala by
High Wizardry in the Land of ScalaHigh Wizardry in the Land of Scala
High Wizardry in the Land of Scaladjspiewak
3.6K views72 slides
Why Haskell by
Why HaskellWhy Haskell
Why HaskellSusan Potter
6.8K views101 slides

What's hot(20)

Scala categorytheory by Knoldus Inc.
Scala categorytheoryScala categorytheory
Scala categorytheory
Knoldus Inc.1.4K views
High Wizardry in the Land of Scala by djspiewak
High Wizardry in the Land of ScalaHigh Wizardry in the Land of Scala
High Wizardry in the Land of Scala
djspiewak3.6K views
First-Class Patterns by John De Goes
First-Class PatternsFirst-Class Patterns
First-Class Patterns
John De Goes3.3K views
Scala introduction by vito jeng
Scala introductionScala introduction
Scala introduction
vito jeng1.4K views
Practically Functional by djspiewak
Practically FunctionalPractically Functional
Practically Functional
djspiewak1.5K views
Functions In Scala by Knoldus Inc.
Functions In Scala Functions In Scala
Functions In Scala
Knoldus Inc.2.2K views
Metaprogramming in Scala 2.10, Eugene Burmako, by Vasil Remeniuk
Metaprogramming  in Scala 2.10, Eugene Burmako, Metaprogramming  in Scala 2.10, Eugene Burmako,
Metaprogramming in Scala 2.10, Eugene Burmako,
Vasil Remeniuk5.3K views
C# programming by umesh patil
C# programming C# programming
C# programming
umesh patil434 views
High-Performance Haskell by Johan Tibell
High-Performance HaskellHigh-Performance Haskell
High-Performance Haskell
Johan Tibell18.6K views
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ... by Sanjeev_Knoldus
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Scala traits training by Sanjeev Kumar @Kick Start Scala traits & Play, organ...
Sanjeev_Knoldus4.9K views

Similar to Scala Back to Basics: Type Classes

Legacy lambda code by
Legacy lambda codeLegacy lambda code
Legacy lambda codePeter Lawrey
1.5K views35 slides
Scala Refactoring for Fun and Profit by
Scala Refactoring for Fun and ProfitScala Refactoring for Fun and Profit
Scala Refactoring for Fun and ProfitTomer Gabel
985 views31 slides
(How) can we benefit from adopting scala? by
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?Tomasz Wrobel
1.1K views69 slides
Java gets a closure by
Java gets a closureJava gets a closure
Java gets a closureTomasz Kowalczewski
1.6K views56 slides
Speaking Scala: Refactoring for Fun and Profit (Workshop) by
Speaking Scala: Refactoring for Fun and Profit (Workshop)Speaking Scala: Refactoring for Fun and Profit (Workshop)
Speaking Scala: Refactoring for Fun and Profit (Workshop)Tomer Gabel
765 views33 slides
An introduction to functional programming with Swift by
An introduction to functional programming with SwiftAn introduction to functional programming with Swift
An introduction to functional programming with SwiftFatih Nayebi, Ph.D.
836 views37 slides

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

Legacy lambda code by Peter Lawrey
Legacy lambda codeLegacy lambda code
Legacy lambda code
Peter Lawrey1.5K views
Scala Refactoring for Fun and Profit by Tomer Gabel
Scala Refactoring for Fun and ProfitScala Refactoring for Fun and Profit
Scala Refactoring for Fun and Profit
Tomer Gabel985 views
(How) can we benefit from adopting scala? by Tomasz Wrobel
(How) can we benefit from adopting scala?(How) can we benefit from adopting scala?
(How) can we benefit from adopting scala?
Tomasz Wrobel1.1K views
Speaking Scala: Refactoring for Fun and Profit (Workshop) by Tomer Gabel
Speaking Scala: Refactoring for Fun and Profit (Workshop)Speaking Scala: Refactoring for Fun and Profit (Workshop)
Speaking Scala: Refactoring for Fun and Profit (Workshop)
Tomer Gabel765 views
An introduction to functional programming with Swift by Fatih Nayebi, Ph.D.
An introduction to functional programming with SwiftAn introduction to functional programming with Swift
An introduction to functional programming with Swift
Introduction à Scala - Michel Schinz - January 2010 by JUG Lausanne
Introduction à Scala - Michel Schinz - January 2010Introduction à Scala - Michel Schinz - January 2010
Introduction à Scala - Michel Schinz - January 2010
JUG Lausanne1.4K views
Lecture 5: Functional Programming by Eelco Visser
Lecture 5: Functional ProgrammingLecture 5: Functional Programming
Lecture 5: Functional Programming
Eelco Visser1.1K views
C++11 - A Change in Style - v2.0 by Yaser Zhian
C++11 - A Change in Style - v2.0C++11 - A Change in Style - v2.0
C++11 - A Change in Style - v2.0
Yaser Zhian1K views
Scala 3 Is Coming: Martin Odersky Shares What To Know by Lightbend
Scala 3 Is Coming: Martin Odersky Shares What To KnowScala 3 Is Coming: Martin Odersky Shares What To Know
Scala 3 Is Coming: Martin Odersky Shares What To Know
Lightbend6.1K views
Extractors & Implicit conversions by Knoldus Inc.
Extractors & Implicit conversionsExtractors & Implicit conversions
Extractors & Implicit conversions
Knoldus Inc.2.1K views
The Functional Programming Triad of Folding, Scanning and Iteration - a first... by Philip Schwarz
The Functional Programming Triad of Folding, Scanning and Iteration - a first...The Functional Programming Triad of Folding, Scanning and Iteration - a first...
The Functional Programming Triad of Folding, Scanning and Iteration - a first...
Philip Schwarz970 views

More from Tomer Gabel

How shit works: Time by
How shit works: TimeHow shit works: Time
How shit works: TimeTomer Gabel
342 views53 slides
Nondeterministic Software for the Rest of Us by
Nondeterministic Software for the Rest of UsNondeterministic Software for the Rest of Us
Nondeterministic Software for the Rest of UsTomer Gabel
329 views39 slides
Slaying Sacred Cows: Deconstructing Dependency Injection by
Slaying Sacred Cows: Deconstructing Dependency InjectionSlaying Sacred Cows: Deconstructing Dependency Injection
Slaying Sacred Cows: Deconstructing Dependency InjectionTomer Gabel
1.3K views34 slides
An Abridged Guide to Event Sourcing by
An Abridged Guide to Event SourcingAn Abridged Guide to Event Sourcing
An Abridged Guide to Event SourcingTomer Gabel
1K views32 slides
How shit works: the CPU by
How shit works: the CPUHow shit works: the CPU
How shit works: the CPUTomer Gabel
1.8K views38 slides
How Shit Works: Storage by
How Shit Works: StorageHow Shit Works: Storage
How Shit Works: StorageTomer Gabel
914 views44 slides

More from Tomer Gabel(20)

How shit works: Time by Tomer Gabel
How shit works: TimeHow shit works: Time
How shit works: Time
Tomer Gabel342 views
Nondeterministic Software for the Rest of Us by Tomer Gabel
Nondeterministic Software for the Rest of UsNondeterministic Software for the Rest of Us
Nondeterministic Software for the Rest of Us
Tomer Gabel329 views
Slaying Sacred Cows: Deconstructing Dependency Injection by Tomer Gabel
Slaying Sacred Cows: Deconstructing Dependency InjectionSlaying Sacred Cows: Deconstructing Dependency Injection
Slaying Sacred Cows: Deconstructing Dependency Injection
Tomer Gabel1.3K views
An Abridged Guide to Event Sourcing by Tomer Gabel
An Abridged Guide to Event SourcingAn Abridged Guide to Event Sourcing
An Abridged Guide to Event Sourcing
Tomer Gabel1K views
How shit works: the CPU by Tomer Gabel
How shit works: the CPUHow shit works: the CPU
How shit works: the CPU
Tomer Gabel1.8K views
How Shit Works: Storage by Tomer Gabel
How Shit Works: StorageHow Shit Works: Storage
How Shit Works: Storage
Tomer Gabel914 views
Java 8 and Beyond, a Scala Story by Tomer Gabel
Java 8 and Beyond, a Scala StoryJava 8 and Beyond, a Scala Story
Java 8 and Beyond, a Scala Story
Tomer Gabel747 views
The Wix Microservice Stack by Tomer Gabel
The Wix Microservice StackThe Wix Microservice Stack
The Wix Microservice Stack
Tomer Gabel1.7K views
Scala Refactoring for Fun and Profit (Japanese subtitles) by Tomer Gabel
Scala Refactoring for Fun and Profit (Japanese subtitles)Scala Refactoring for Fun and Profit (Japanese subtitles)
Scala Refactoring for Fun and Profit (Japanese subtitles)
Tomer Gabel6.6K views
Onboarding at Scale by Tomer Gabel
Onboarding at ScaleOnboarding at Scale
Onboarding at Scale
Tomer Gabel1.5K views
Scala in the Wild by Tomer Gabel
Scala in the WildScala in the Wild
Scala in the Wild
Tomer Gabel2.8K views
Put Your Thinking CAP On by Tomer Gabel
Put Your Thinking CAP OnPut Your Thinking CAP On
Put Your Thinking CAP On
Tomer Gabel3.5K views
Leveraging Scala Macros for Better Validation by Tomer Gabel
Leveraging Scala Macros for Better ValidationLeveraging Scala Macros for Better Validation
Leveraging Scala Macros for Better Validation
Tomer Gabel1.4K views
A Field Guide to DSL Design in Scala by Tomer Gabel
A Field Guide to DSL Design in ScalaA Field Guide to DSL Design in Scala
A Field Guide to DSL Design in Scala
Tomer Gabel6.5K views
Functional Leap of Faith (Keynote at JDay Lviv 2014) by Tomer Gabel
Functional Leap of Faith (Keynote at JDay Lviv 2014)Functional Leap of Faith (Keynote at JDay Lviv 2014)
Functional Leap of Faith (Keynote at JDay Lviv 2014)
Tomer Gabel1.5K views
5 Bullets to Scala Adoption by Tomer Gabel
5 Bullets to Scala Adoption5 Bullets to Scala Adoption
5 Bullets to Scala Adoption
Tomer Gabel2.7K views
Nashorn: JavaScript that doesn’t suck (ILJUG) by Tomer Gabel
Nashorn: JavaScript that doesn’t suck (ILJUG)Nashorn: JavaScript that doesn’t suck (ILJUG)
Nashorn: JavaScript that doesn’t suck (ILJUG)
Tomer Gabel5.9K views
Ponies and Unicorns With Scala by Tomer Gabel
Ponies and Unicorns With ScalaPonies and Unicorns With Scala
Ponies and Unicorns With Scala
Tomer Gabel961 views
Lab: JVM Production Debugging 101 by Tomer Gabel
Lab: JVM Production Debugging 101Lab: JVM Production Debugging 101
Lab: JVM Production Debugging 101
Tomer Gabel2.1K views
DevCon³: Scala Best Practices by Tomer Gabel
DevCon³: Scala Best PracticesDevCon³: Scala Best Practices
DevCon³: Scala Best Practices
Tomer Gabel3.7K views

Recently uploaded

Agile 101 by
Agile 101Agile 101
Agile 101John Valentino
7 views20 slides
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated... by
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...TomHalpin9
5 views29 slides
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J... by
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...Deltares
9 views24 slides
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI... by
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Marc Müller
37 views83 slides
Keep by
KeepKeep
KeepGeniusee
75 views10 slides
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge... by
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...Deltares
17 views12 slides

Recently uploaded(20)

Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated... by TomHalpin9
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...
TomHalpin95 views
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J... by Deltares
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...
Deltares9 views
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI... by Marc Müller
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Marc Müller37 views
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge... by Deltares
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...
DSD-INT 2023 Delft3D FM Suite 2024.01 2D3D - New features + Improvements - Ge...
Deltares17 views
Airline Booking Software by SharmiMehta
Airline Booking SoftwareAirline Booking Software
Airline Booking Software
SharmiMehta5 views
DSD-INT 2023 Wave-Current Interaction at Montrose Tidal Inlet System and Its ... by Deltares
DSD-INT 2023 Wave-Current Interaction at Montrose Tidal Inlet System and Its ...DSD-INT 2023 Wave-Current Interaction at Montrose Tidal Inlet System and Its ...
DSD-INT 2023 Wave-Current Interaction at Montrose Tidal Inlet System and Its ...
Deltares10 views
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko... by Deltares
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...
Deltares14 views
Gen Apps on Google Cloud PaLM2 and Codey APIs in Action by Márton Kodok
Gen Apps on Google Cloud PaLM2 and Codey APIs in ActionGen Apps on Google Cloud PaLM2 and Codey APIs in Action
Gen Apps on Google Cloud PaLM2 and Codey APIs in Action
Márton Kodok5 views
DSD-INT 2023 Machine learning in hydraulic engineering - Exploring unseen fut... by Deltares
DSD-INT 2023 Machine learning in hydraulic engineering - Exploring unseen fut...DSD-INT 2023 Machine learning in hydraulic engineering - Exploring unseen fut...
DSD-INT 2023 Machine learning in hydraulic engineering - Exploring unseen fut...
Deltares7 views
DSD-INT 2023 Salt intrusion Modelling of the Lauwersmeer, towards a measureme... by Deltares
DSD-INT 2023 Salt intrusion Modelling of the Lauwersmeer, towards a measureme...DSD-INT 2023 Salt intrusion Modelling of the Lauwersmeer, towards a measureme...
DSD-INT 2023 Salt intrusion Modelling of the Lauwersmeer, towards a measureme...
Deltares5 views
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with... by sparkfabrik
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...
sparkfabrik5 views
.NET Developer Conference 2023 - .NET Microservices mit Dapr – zu viel Abstra... by Marc Müller
.NET Developer Conference 2023 - .NET Microservices mit Dapr – zu viel Abstra....NET Developer Conference 2023 - .NET Microservices mit Dapr – zu viel Abstra...
.NET Developer Conference 2023 - .NET Microservices mit Dapr – zu viel Abstra...
Marc Müller38 views
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports by Ra'Fat Al-Msie'deen
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug ReportsBushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports
AI and Ml presentation .pptx by FayazAli87
AI and Ml presentation .pptxAI and Ml presentation .pptx
AI and Ml presentation .pptx
FayazAli8711 views
Navigating container technology for enhanced security by Niklas Saari by Metosin Oy
Navigating container technology for enhanced security by Niklas SaariNavigating container technology for enhanced security by Niklas Saari
Navigating container technology for enhanced security by Niklas Saari
Metosin Oy13 views

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? – tomer@tomergabel.com – @tomerg – http://www.tomergabel.com • Code samples: – http://git.io/aWc9eQ

Editor's Notes

  1. Source: http://en.wikipedia.org/wiki/Expression_problem