SlideShare a Scribd company logo
Improving Correctness With Type - Goto Con Berlin
Improving Correctness
with Types
GOTO Berlin December 2015
Iain Hull
iain.hull@workday.com
@IainHull
http://workday.github.io
Defensive programming
Fail fast
Design by contract
Improving Correctness With Type - Goto Con Berlin
What is a function ?
f
x y
Domain Range
Improving Correctness With Type - Goto Con Berlin
Never use nulls … period
“Null references, my billion dollar mistake”
- Tony Hoare, QCon 2009
All data is immutable
Do not throw exceptions
Wrappers
Wrapper Types
case class Customer(name: String,
preferredCurrency: String) {
require(Currency.isValid(preferredCurrency))
}
val customer = Customer(name = "Joe Bloggs",
preferredCurrency = "TXL")
This is an airport?
class Currency private (val code: String) extends AnyVal
object Currency {
val USD: Currency = new Currency("USD")
val EUR: Currency = new Currency("EUR")
// ...
def from(code: String): Option[Currency] = ???
}
case class Customer(name: String,
preferredCurrency: Currency)
def creditCardRate(customer: Customer): BigDecimal = {
if (customer.preferredCurrency == "USD")
BigDecimal("0.015")
else
BigDecimal("0.025")
}
Always false
import org.scalactic.TypeCheckedTripleEquals._
case class Customer(name: String,
preferredCurrency: Currency)
def creditCardRate(customer: Customer): BigDecimal = {
if (customer.preferredCurrency === "USD")
BigDecimal("0.015")
else
BigDecimal("0.025")
}
Does not compile
import org.scalactic.TypeCheckedTripleEquals._
case class Customer(name: String,
preferredCurrency: Currency)
def creditCardRate(customer: Customer): BigDecimal = {
if (customer.preferredCurrency === Currency.USD)
BigDecimal("0.015")
else
BigDecimal("0.025")
}
val order: Order = ???
val customer: Customer = ???
val creditCardCharge = order.amount + creditCardRate(customer)
Eeek this a bug
class MoneyAmount(val amount: BigDecimal) extends AnyVal {
def + (rhs: MoneyAmount): MoneyAmount =
new MoneyAmount(amount + rhs.amount)
def - (rhs: MoneyAmount): MoneyAmount =
new MoneyAmount(amount - rhs.amount)
def * (rhs: Rate): MoneyAmount =
new MoneyAmount(amount * rhs.size)
}
class Rate(val size: BigDecimal) extends AnyVal {
def * (rhs: Rate): MoneyAmount = rhs * this
}
val order: Order = ???
val customer: Customer = ???
val creditCardCharge = order.amount + creditCardRate(customer)
Does not compile
Using wrapper types
• Do not abuse primitives
• Control the available values
• Control the available operations
• Move validation to the correct place
Non empty list
def average(items: List[Int]): Int = items.sum / items.size
scala> average(List(5))
res1: Int = 5
scala> average(List(5, 10, 15))
res2: Int = 10
scala> average(List())
java.lang.ArithmeticException: / by zero
at .average0(<console>:8)
... 35 elided
import org.scalactic.Every
def average(items: Every[Int]): Int = items.sum / items.size
scala> average(Every(5))
res1: Int = 5
scala> average(Every(5, 10, 15))
res2: Int = 10
scala> average(Every())
<console>:10: error: not enough arguments for method apply:
(firstElement: T, otherElements: T*)org.scalactic.Every[T] in
object Every.
Unspecified value parameters firstElement, otherElements.
average(Every())
import org.scalactic.Every
def average(items: Every[Int]): Int = items.sum / items.size
def average(first: Int, rest: Int*): Int =
average(Every(first, rest: _*))
scala> average(5)
res1: Int = 5
scala> average(5, 10, 15)
res2: Int = 10
scala> average()
<console>:11: error: not enough arguments for method average:
(first: Int, rest: Int*)Int.
Unspecified value parameters first, rest.
average()
def describeSomeList(items: List[Int]): String = {
val ave = average(items)
s"A list with average $ave"
}
Does not compile
def maybeNonEmpty(items: List[Int]): Option[Every[Int]] = {
items match {
case head :: tail => Some(Every(head, tail))
case Nil => None
}
}
scala> maybeNonEmpty(List(5, 10, 15))
res1: Option[Every[Int]] = Some(Every(5, 10, 15))
scala> maybeNonEmpty(List())
res2: Option[Every[Int]] = None
def describeSomeList(items: List[Int]): String = {
val ave = average(items)
s”A list with average $ave"
}
def describeSomeList(items: List[Int]): String = {
maybeNonEmpty(items) match {
case Some(is) =>
val ave = average(is)
s"A list with average $ave"
case None =>
"An empty list"
}
}
Does not compile
Non empty lists
• Some lists cannot be empty
• Tell the compiler
• One-plus-var-args idiom
Algebraic data types
Agent Id Type Status Host In Use By
A01 1 Active 10.0.0.1
A02 1 Failed 10.0.0.2 J01
A03 2 Active 10.0.0.3 J03
A04 2 Waiting 10.0.0.4
Job Id Type Status Submitted By Processed By
J01 1 Waiting Fred
J02 1 Active Wilma A01
J03 2 Complete Barney A03
Jobs
Agents
case class Agent(agentId: String,
jobType: Int,
host: String,
port: Int,
status: String, // Waiting | Active | Failed
maybeLastAccessed: Option[DateTime],
inUse: Boolean,
maybeInUseBy: Option[String])
case class Job(referenceId: String,
jobType: Int,
status: String, // Waiting | Active | Complete
submittedBy: String,
submittedAt: DateTime,
maybeStartedAt: Option[DateTime],
maybeProcessedBy: Option[String],
maybeCompletedAt: Option[DateTime])
case class Agent(agentId: String,
jobType: JobType,
address: AgentAddress,
status: AgentStatus,
lastAccessed: Option[DateTime],
inUse: Boolean,
maybeInUseBy: Option[String])
case class Job(referenceId: String,
jobType: JobType,
status: JobStatus,
submittedBy: User,
submittedAt: DateTime,
maybeStartedAt: Option[DateTime],
maybeProcessedBy: Option[String],
maybeCompletedAt: Option[DateTime])
sealed abstract class JobType(val value: Int)
case object SmallJob extends JobType(1)
case object LargeJob extends JobType(2)
case object BatchJob extends JobType(3)
sealed abstract class AgentStatus(val value: String)
case object AgentWaiting extends AgentStatus("Waiting")
case object AgentActive extends AgentStatus("Active")
case object AgentFailed extends AgentStatus("Failed")
sealed abstract class JobStatus(val value: String)
case object JobWaiting extends JobStatus("Waiting")
case object JobActive extends JobStatus("Active")
case object JobCompelete extends JobStatus("Complete")
case class AgentAddress(host: String, port: Int)
case class User(name: String)
case class Agent(agentId: String,
jobType: JobType,
address: AgentAddress,
status: AgentStatus,
lastAccessed: Option[DateTime],
inUse: Boolean,
maybeInUseBy: Option[String])
case class Job(referenceId: String,
jobType: JobType,
status: JobStatus,
submittedBy: User,
submittedAt: DateTime,
maybeStartedAt: Option[DateTime],
maybeProcessedBy: Option[String],
maybeCompletedAt: Option[DateTime])
import tag.@@
trait Foo
def onlyFoo(value: String @@ Foo): String = s"It a foo: $value"
scala> onlyFoo("simple string")
<console>:13: error: type mismatch;
found : String("simple string")
required: tag.@@[String,Foo]
(which expands to) String with tag.Tagged[Foo]
onlyFoo("simple string")
^
scala> val foo = tag[Foo]("Foo String")
foo: tag.@@[String,Foo] = Foo String
scala> onlyFoo(foo)
res2: String = It a foo: Foo String
def anyString(value: String): String = s"Just a string: $value”
scala> anyString(foo)
res6: String = Just a string: Foo String
case class Agent(agentId: String @@ Agent,
jobType: JobType,
address: AgentAddress,
status: AgentStatus,
lastAccessed: Option[DateTime],
inUse: Boolean,
maybeInUseBy: Option[String @@ Job])
case class Job(referenceId: String @@ Job,
jobType: JobType,
status: JobStatus,
submittedBy: User,
submittedAt: DateTime,
maybeStartedAt: Option[DateTime],
maybeProcessedBy: Option[String @@ Agent],
maybeCompletedAt: Option[DateTime])
case class Job(referenceId: String @@ Agent,
jobType: JobType,
status: JobStatus,
submittedBy: User,
submittedAt: DateTime,
maybeStartedAt: Option[DateTime],
maybeProcessedBy: Option[String @@ Job],
maybeCompletedAt: Option[DateTime])
Waiting | Active | Complete
def recordCompletionMetrics(job: Job): Unit = {
for( startedAt <- job.maybeStartedAt ;
completedAt <- job.maybeCompletedAt ) {
writeJobEvent(
event = "Completed",
time = completedAt,
referenceId = job.referenceId,
waitingTime = (startedAt - job.submittedAt),
executionTime = (completedAt - startedAt))
}
}
def recordCompletionMetrics(job: Job): Unit = {
require(job.status = JobComplete)
require(job.maybeStartedAt.isDefined)
require(job.maybeCompletedAt.isDefined)
for (startedAt <- job.maybeStartedAt ;
completedAt <- job.maybeCompletedAt ) {
writeJobEvent (
event = "Completed",
time = completedAt,
referenceId = job.referenceId,
waitingTime = (startedAt - job.submittedAt),
executionTime = (completedAt - startedAt))
}
}
sealed trait Job {
def referenceId: String @@ Job
def status: JobStatus
def submittedBy: User
def submittedAt: DateTime
}
case class WaitingJob(referenceId: String @@ Job,
submittedBy: User,
submittedAt: DateTime)
extends Job {
val status: JobStatus = JobWaiting
}
sealed trait Job {
def referenceId: String @@ Job
def status: JobStatus
def submittedBy: User
def submittedAt: DateTime
}
case class ActiveJob(referenceId: String @@ Job,
submittedBy: User,
submittedAt: DateTime,
startedAt: DateTime,
processedBy: String @@ Agent)
extends Job {
val status: JobStatus = JobActive
}
sealed trait Job {
def referenceId: String @@ Job
def status: JobStatus
def submittedBy: User
def submittedAt: DateTime
}
case class CompleteJob(referenceId: String @@ Job,
submittedBy: User,
submittedAt: DateTime,
startedAt: DateTime,
processedBy: String @@ Agent,
completedAt: DateTime)
extends Job {
val status: JobStatus = JobComplete
}
def recordCompletionMetrics(job: CompleteJob): Unit = {
writeJobEvent(
event = "Completed",
time = job.completedAt,
referenceId = job.referenceId,
waitingTime = (job.startedAt - job.submittedAt),
executionTime = (job.completedAt - job.startedAt))
}
Algebraic data types
• Simply sealed traits and case classes
• Exposes the shape of your data
• Use this shape to control possible states
Path Dependent Types
trait Handle {
def name: Name
def owner: User
}
trait Data {
def stream: InputStream
}
trait Storage {
def create(name: Name, owner: User,
data: InputStream): Handle
def find(name: Name): Option[Handle]
def read(handle: Handle): Try[Data]
}
case class HandleImpl(id: Long,
name: Name,
owner: User) extends Handle
case class DataImpl(stream: InputStream) extends Data
class StorageImpl extends Storage {
// ...
def read(entryDesc: Handle): Try[Data] = {
require(entryDesc.isInstanceOf[HandleImpl])
val impl = entryDesc.asInstanceOf[HandleImpl]
dataStore.read(impl.id)
}
val riakStorage: Storage = ???
val maybeData = for {
handle <- riakStorage.find(someName)
data <- riakStorage.read(handle).toOption
} yield data
val riakStorage: Storage = ???
val memoryStorage: Storage = ???
val maybeData = for {
handle <- riakStorage.find(someName)
data <- memoryStorage.read(handle).toOption
} yield data
IllegalArgumentException
trait HandleLike {
def name: Name
def owner: User
}
trait DataLike {
def stream: InputStream
}
trait Storage {
type Handle <: HandleLike
type Data <: DataLike
def create(name: Name, owner: User,
data: InputStream): Handle
def find(name: Name): Option[Handle]
def read(handle: Handle): Try[Data]
}
private[impl] class StorageImpl extends Storage {
type Handle = HandleImpl
type Data = DataImpl
case class HandleImpl(id: Long,
name: Name,
owner: User) extends HandleLike
case class DataImpl(stream: InputStream) extends DataLike
// ...
def read(entryDesc: Handle): Try[Data] = {
dataStore.read(entryDesc.id)
}
}
val riakStorage: Storage = ???
val memoryStorage: Storage = ???
val maybeData = for {
handle <- riakStorage.find(someName)
data <- memoryStorage.read(handle).toOption
} yield data
error: type mismatch;
found : handle.type (with underlying type riakStorage.Handle)
required: memoryStorage.Handle
data <- memoryStorage.read(handle).toOption
^
val riakStorage1: Storage = ???
val riakStorage2: Storage = ???
val maybeData = for {
handle <- riakStorage1.find(someName)
data <- riakStorage2.read(handle).toOption
} yield data
error: type mismatch;
found : handle.type (with underlying type riakStorage1.Handle)
required: riakStorage2.Handle
data <- riakStorage2.read(handle).toOption
^
Path dependent types
• Family polymorphism
• Remove casts
• Bound to instances not classes
More advanced techniques
• Self-recursive types
• Phantom types
• Shapeless
References: workday.github.io
“Types = For All
Tests = There Exists”
- Amanda Laucher
Improving Correctness With Type - Goto Con Berlin
“A mind is like a parachute,
it doesn’t work unless its open”
- Frank Zappa
http://workday.github.io
Improving Correctness With Type - Goto Con Berlin

More Related Content

PPTX
Improving Correctness with Types Kats Conf
PDF
Anonymous functions in JavaScript
PDF
Reflection in Go
PPTX
What\'s New in C# 4.0
PDF
Let the type system be your friend
PDF
Developer Experience i TypeScript. Najbardziej ikoniczne duo
PPTX
Javascript Basics
PDF
科特林λ學
Improving Correctness with Types Kats Conf
Anonymous functions in JavaScript
Reflection in Go
What\'s New in C# 4.0
Let the type system be your friend
Developer Experience i TypeScript. Najbardziej ikoniczne duo
Javascript Basics
科特林λ學

What's hot (20)

PDF
Promise: async programming hero
PDF
Fun with Lambdas: C++14 Style (part 1)
PDF
Scala taxonomy
PDF
Grammarware Memes
PPT
Templates exception handling
PDF
Java Script Best Practices
PPSX
DIWE - Programming with JavaScript
PDF
SeneJug java_8_prez_122015
PPTX
Ajaxworld
PDF
Qt Widget In-Depth
PPTX
Operator overloading2
PDF
ECMA 入门
PPTX
operator overloading
PDF
Functions in C++
PPTX
Software design principles SOLID
PDF
How do you create a programming language for the JVM?
PDF
Functional solid
PPTX
Type Driven Development with TypeScript
PPT
Operator Overloading
PDF
Protocol-Oriented Programming in Swift
Promise: async programming hero
Fun with Lambdas: C++14 Style (part 1)
Scala taxonomy
Grammarware Memes
Templates exception handling
Java Script Best Practices
DIWE - Programming with JavaScript
SeneJug java_8_prez_122015
Ajaxworld
Qt Widget In-Depth
Operator overloading2
ECMA 入门
operator overloading
Functions in C++
Software design principles SOLID
How do you create a programming language for the JVM?
Functional solid
Type Driven Development with TypeScript
Operator Overloading
Protocol-Oriented Programming in Swift
Ad

Viewers also liked (20)

PDF
Cinco dias desayuno redacción 17 12-2012
PPT
In 209 Presentasjon
PPTX
English
PDF
P.e.f de la facultad
PDF
Similitudine eserciziario teoremi_euclide_mathubi
PPS
amigos de internet
PPTX
Tics presente pasado y futuro
PDF
C27 theodore-stecher-robert-solow-mike-sheppard-ron-gettelfinger-peter-levy-j...
PPT
Top Off Your Perfect Blend with TOPR: The Teaching Online Pedagogical Repository
PDF
Community Media 2012
PDF
Como hacer una tortilla de patatas
PDF
William gibson neuromante
PDF
NAVIDAD EN CABANA-PALLASCA-ANCASH
PDF
Hotelier Indonesia 25th Vol 10 2016
PDF
NEXELL - Company Profile
PDF
M a. rengifo (2012). caracterización óptica de diodos emisores de luz mediant...
PDF
Catastr2
PPTX
What You Missed at Talent Connect 2013 | Webcast
Cinco dias desayuno redacción 17 12-2012
In 209 Presentasjon
English
P.e.f de la facultad
Similitudine eserciziario teoremi_euclide_mathubi
amigos de internet
Tics presente pasado y futuro
C27 theodore-stecher-robert-solow-mike-sheppard-ron-gettelfinger-peter-levy-j...
Top Off Your Perfect Blend with TOPR: The Teaching Online Pedagogical Repository
Community Media 2012
Como hacer una tortilla de patatas
William gibson neuromante
NAVIDAD EN CABANA-PALLASCA-ANCASH
Hotelier Indonesia 25th Vol 10 2016
NEXELL - Company Profile
M a. rengifo (2012). caracterización óptica de diodos emisores de luz mediant...
Catastr2
What You Missed at Talent Connect 2013 | Webcast
Ad

Similar to Improving Correctness With Type - Goto Con Berlin (20)

PPTX
Improving Correctness with Types
PPTX
Scala best practices
PDF
Scala for Java Developers
PDF
Going bananas with recursion schemes for fixed point data types
PDF
Meet scala
PDF
Event Sourcing and Functional Programming
PDF
Functional Programming & Event Sourcing - a pair made in heaven
PDF
Type classes 101 - classification beyond inheritance
PDF
Generic Functional Programming with Type Classes
PDF
Option, Either, Try and what to do with corner cases when they arise
PDF
Towards Reliable Lookups - Scala By The Bay
PPTX
Scala in a Java 8 World
PDF
Scalapeno18 - Thinking Less with Scala
PPTX
Scala Back to Basics: Type Classes
PDF
Scala vs Java 8 in a Java 8 World
PDF
Monads asking the right question
PDF
Scala: A brief tutorial
PDF
Introduction to type classes
PDF
Introduction to type classes in 30 min
PDF
Beyond Scala Lens
Improving Correctness with Types
Scala best practices
Scala for Java Developers
Going bananas with recursion schemes for fixed point data types
Meet scala
Event Sourcing and Functional Programming
Functional Programming & Event Sourcing - a pair made in heaven
Type classes 101 - classification beyond inheritance
Generic Functional Programming with Type Classes
Option, Either, Try and what to do with corner cases when they arise
Towards Reliable Lookups - Scala By The Bay
Scala in a Java 8 World
Scalapeno18 - Thinking Less with Scala
Scala Back to Basics: Type Classes
Scala vs Java 8 in a Java 8 World
Monads asking the right question
Scala: A brief tutorial
Introduction to type classes
Introduction to type classes in 30 min
Beyond Scala Lens

Recently uploaded (20)

PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PPTX
history of c programming in notes for students .pptx
PDF
Understanding Forklifts - TECH EHS Solution
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Transform Your Business with a Software ERP System
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PPTX
Introduction to Artificial Intelligence
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
Odoo Companies in India – Driving Business Transformation.pdf
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
Navsoft: AI-Powered Business Solutions & Custom Software Development
history of c programming in notes for students .pptx
Understanding Forklifts - TECH EHS Solution
How Creative Agencies Leverage Project Management Software.pdf
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PTS Company Brochure 2025 (1).pdf.......
wealthsignaloriginal-com-DS-text-... (1).pdf
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Transform Your Business with a Software ERP System
VVF-Customer-Presentation2025-Ver1.9.pptx
Introduction to Artificial Intelligence
Which alternative to Crystal Reports is best for small or large businesses.pdf
Wondershare Filmora 15 Crack With Activation Key [2025

Improving Correctness With Type - Goto Con Berlin

  • 8. What is a function ? f x y Domain Range
  • 10. Never use nulls … period “Null references, my billion dollar mistake” - Tony Hoare, QCon 2009
  • 11. All data is immutable
  • 12. Do not throw exceptions
  • 14. case class Customer(name: String, preferredCurrency: String) { require(Currency.isValid(preferredCurrency)) } val customer = Customer(name = "Joe Bloggs", preferredCurrency = "TXL") This is an airport?
  • 15. class Currency private (val code: String) extends AnyVal object Currency { val USD: Currency = new Currency("USD") val EUR: Currency = new Currency("EUR") // ... def from(code: String): Option[Currency] = ??? }
  • 16. case class Customer(name: String, preferredCurrency: Currency) def creditCardRate(customer: Customer): BigDecimal = { if (customer.preferredCurrency == "USD") BigDecimal("0.015") else BigDecimal("0.025") } Always false
  • 17. import org.scalactic.TypeCheckedTripleEquals._ case class Customer(name: String, preferredCurrency: Currency) def creditCardRate(customer: Customer): BigDecimal = { if (customer.preferredCurrency === "USD") BigDecimal("0.015") else BigDecimal("0.025") } Does not compile
  • 18. import org.scalactic.TypeCheckedTripleEquals._ case class Customer(name: String, preferredCurrency: Currency) def creditCardRate(customer: Customer): BigDecimal = { if (customer.preferredCurrency === Currency.USD) BigDecimal("0.015") else BigDecimal("0.025") }
  • 19. val order: Order = ??? val customer: Customer = ??? val creditCardCharge = order.amount + creditCardRate(customer) Eeek this a bug
  • 20. class MoneyAmount(val amount: BigDecimal) extends AnyVal { def + (rhs: MoneyAmount): MoneyAmount = new MoneyAmount(amount + rhs.amount) def - (rhs: MoneyAmount): MoneyAmount = new MoneyAmount(amount - rhs.amount) def * (rhs: Rate): MoneyAmount = new MoneyAmount(amount * rhs.size) } class Rate(val size: BigDecimal) extends AnyVal { def * (rhs: Rate): MoneyAmount = rhs * this }
  • 21. val order: Order = ??? val customer: Customer = ??? val creditCardCharge = order.amount + creditCardRate(customer) Does not compile
  • 22. Using wrapper types • Do not abuse primitives • Control the available values • Control the available operations • Move validation to the correct place
  • 24. def average(items: List[Int]): Int = items.sum / items.size scala> average(List(5)) res1: Int = 5 scala> average(List(5, 10, 15)) res2: Int = 10 scala> average(List()) java.lang.ArithmeticException: / by zero at .average0(<console>:8) ... 35 elided
  • 25. import org.scalactic.Every def average(items: Every[Int]): Int = items.sum / items.size scala> average(Every(5)) res1: Int = 5 scala> average(Every(5, 10, 15)) res2: Int = 10 scala> average(Every()) <console>:10: error: not enough arguments for method apply: (firstElement: T, otherElements: T*)org.scalactic.Every[T] in object Every. Unspecified value parameters firstElement, otherElements. average(Every())
  • 26. import org.scalactic.Every def average(items: Every[Int]): Int = items.sum / items.size def average(first: Int, rest: Int*): Int = average(Every(first, rest: _*)) scala> average(5) res1: Int = 5 scala> average(5, 10, 15) res2: Int = 10 scala> average() <console>:11: error: not enough arguments for method average: (first: Int, rest: Int*)Int. Unspecified value parameters first, rest. average()
  • 27. def describeSomeList(items: List[Int]): String = { val ave = average(items) s"A list with average $ave" } Does not compile
  • 28. def maybeNonEmpty(items: List[Int]): Option[Every[Int]] = { items match { case head :: tail => Some(Every(head, tail)) case Nil => None } } scala> maybeNonEmpty(List(5, 10, 15)) res1: Option[Every[Int]] = Some(Every(5, 10, 15)) scala> maybeNonEmpty(List()) res2: Option[Every[Int]] = None
  • 29. def describeSomeList(items: List[Int]): String = { val ave = average(items) s”A list with average $ave" } def describeSomeList(items: List[Int]): String = { maybeNonEmpty(items) match { case Some(is) => val ave = average(is) s"A list with average $ave" case None => "An empty list" } } Does not compile
  • 30. Non empty lists • Some lists cannot be empty • Tell the compiler • One-plus-var-args idiom
  • 32. Agent Id Type Status Host In Use By A01 1 Active 10.0.0.1 A02 1 Failed 10.0.0.2 J01 A03 2 Active 10.0.0.3 J03 A04 2 Waiting 10.0.0.4 Job Id Type Status Submitted By Processed By J01 1 Waiting Fred J02 1 Active Wilma A01 J03 2 Complete Barney A03 Jobs Agents
  • 33. case class Agent(agentId: String, jobType: Int, host: String, port: Int, status: String, // Waiting | Active | Failed maybeLastAccessed: Option[DateTime], inUse: Boolean, maybeInUseBy: Option[String]) case class Job(referenceId: String, jobType: Int, status: String, // Waiting | Active | Complete submittedBy: String, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String], maybeCompletedAt: Option[DateTime])
  • 34. case class Agent(agentId: String, jobType: JobType, address: AgentAddress, status: AgentStatus, lastAccessed: Option[DateTime], inUse: Boolean, maybeInUseBy: Option[String]) case class Job(referenceId: String, jobType: JobType, status: JobStatus, submittedBy: User, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String], maybeCompletedAt: Option[DateTime])
  • 35. sealed abstract class JobType(val value: Int) case object SmallJob extends JobType(1) case object LargeJob extends JobType(2) case object BatchJob extends JobType(3) sealed abstract class AgentStatus(val value: String) case object AgentWaiting extends AgentStatus("Waiting") case object AgentActive extends AgentStatus("Active") case object AgentFailed extends AgentStatus("Failed") sealed abstract class JobStatus(val value: String) case object JobWaiting extends JobStatus("Waiting") case object JobActive extends JobStatus("Active") case object JobCompelete extends JobStatus("Complete") case class AgentAddress(host: String, port: Int) case class User(name: String)
  • 36. case class Agent(agentId: String, jobType: JobType, address: AgentAddress, status: AgentStatus, lastAccessed: Option[DateTime], inUse: Boolean, maybeInUseBy: Option[String]) case class Job(referenceId: String, jobType: JobType, status: JobStatus, submittedBy: User, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String], maybeCompletedAt: Option[DateTime])
  • 37. import tag.@@ trait Foo def onlyFoo(value: String @@ Foo): String = s"It a foo: $value" scala> onlyFoo("simple string") <console>:13: error: type mismatch; found : String("simple string") required: tag.@@[String,Foo] (which expands to) String with tag.Tagged[Foo] onlyFoo("simple string") ^ scala> val foo = tag[Foo]("Foo String") foo: tag.@@[String,Foo] = Foo String scala> onlyFoo(foo) res2: String = It a foo: Foo String def anyString(value: String): String = s"Just a string: $value” scala> anyString(foo) res6: String = Just a string: Foo String
  • 38. case class Agent(agentId: String @@ Agent, jobType: JobType, address: AgentAddress, status: AgentStatus, lastAccessed: Option[DateTime], inUse: Boolean, maybeInUseBy: Option[String @@ Job]) case class Job(referenceId: String @@ Job, jobType: JobType, status: JobStatus, submittedBy: User, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String @@ Agent], maybeCompletedAt: Option[DateTime])
  • 39. case class Job(referenceId: String @@ Agent, jobType: JobType, status: JobStatus, submittedBy: User, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String @@ Job], maybeCompletedAt: Option[DateTime]) Waiting | Active | Complete
  • 40. def recordCompletionMetrics(job: Job): Unit = { for( startedAt <- job.maybeStartedAt ; completedAt <- job.maybeCompletedAt ) { writeJobEvent( event = "Completed", time = completedAt, referenceId = job.referenceId, waitingTime = (startedAt - job.submittedAt), executionTime = (completedAt - startedAt)) } }
  • 41. def recordCompletionMetrics(job: Job): Unit = { require(job.status = JobComplete) require(job.maybeStartedAt.isDefined) require(job.maybeCompletedAt.isDefined) for (startedAt <- job.maybeStartedAt ; completedAt <- job.maybeCompletedAt ) { writeJobEvent ( event = "Completed", time = completedAt, referenceId = job.referenceId, waitingTime = (startedAt - job.submittedAt), executionTime = (completedAt - startedAt)) } }
  • 42. sealed trait Job { def referenceId: String @@ Job def status: JobStatus def submittedBy: User def submittedAt: DateTime } case class WaitingJob(referenceId: String @@ Job, submittedBy: User, submittedAt: DateTime) extends Job { val status: JobStatus = JobWaiting }
  • 43. sealed trait Job { def referenceId: String @@ Job def status: JobStatus def submittedBy: User def submittedAt: DateTime } case class ActiveJob(referenceId: String @@ Job, submittedBy: User, submittedAt: DateTime, startedAt: DateTime, processedBy: String @@ Agent) extends Job { val status: JobStatus = JobActive }
  • 44. sealed trait Job { def referenceId: String @@ Job def status: JobStatus def submittedBy: User def submittedAt: DateTime } case class CompleteJob(referenceId: String @@ Job, submittedBy: User, submittedAt: DateTime, startedAt: DateTime, processedBy: String @@ Agent, completedAt: DateTime) extends Job { val status: JobStatus = JobComplete }
  • 45. def recordCompletionMetrics(job: CompleteJob): Unit = { writeJobEvent( event = "Completed", time = job.completedAt, referenceId = job.referenceId, waitingTime = (job.startedAt - job.submittedAt), executionTime = (job.completedAt - job.startedAt)) }
  • 46. Algebraic data types • Simply sealed traits and case classes • Exposes the shape of your data • Use this shape to control possible states
  • 48. trait Handle { def name: Name def owner: User } trait Data { def stream: InputStream } trait Storage { def create(name: Name, owner: User, data: InputStream): Handle def find(name: Name): Option[Handle] def read(handle: Handle): Try[Data] }
  • 49. case class HandleImpl(id: Long, name: Name, owner: User) extends Handle case class DataImpl(stream: InputStream) extends Data class StorageImpl extends Storage { // ... def read(entryDesc: Handle): Try[Data] = { require(entryDesc.isInstanceOf[HandleImpl]) val impl = entryDesc.asInstanceOf[HandleImpl] dataStore.read(impl.id) }
  • 50. val riakStorage: Storage = ??? val maybeData = for { handle <- riakStorage.find(someName) data <- riakStorage.read(handle).toOption } yield data
  • 51. val riakStorage: Storage = ??? val memoryStorage: Storage = ??? val maybeData = for { handle <- riakStorage.find(someName) data <- memoryStorage.read(handle).toOption } yield data IllegalArgumentException
  • 52. trait HandleLike { def name: Name def owner: User } trait DataLike { def stream: InputStream } trait Storage { type Handle <: HandleLike type Data <: DataLike def create(name: Name, owner: User, data: InputStream): Handle def find(name: Name): Option[Handle] def read(handle: Handle): Try[Data] }
  • 53. private[impl] class StorageImpl extends Storage { type Handle = HandleImpl type Data = DataImpl case class HandleImpl(id: Long, name: Name, owner: User) extends HandleLike case class DataImpl(stream: InputStream) extends DataLike // ... def read(entryDesc: Handle): Try[Data] = { dataStore.read(entryDesc.id) } }
  • 54. val riakStorage: Storage = ??? val memoryStorage: Storage = ??? val maybeData = for { handle <- riakStorage.find(someName) data <- memoryStorage.read(handle).toOption } yield data error: type mismatch; found : handle.type (with underlying type riakStorage.Handle) required: memoryStorage.Handle data <- memoryStorage.read(handle).toOption ^
  • 55. val riakStorage1: Storage = ??? val riakStorage2: Storage = ??? val maybeData = for { handle <- riakStorage1.find(someName) data <- riakStorage2.read(handle).toOption } yield data error: type mismatch; found : handle.type (with underlying type riakStorage1.Handle) required: riakStorage2.Handle data <- riakStorage2.read(handle).toOption ^
  • 56. Path dependent types • Family polymorphism • Remove casts • Bound to instances not classes
  • 57. More advanced techniques • Self-recursive types • Phantom types • Shapeless References: workday.github.io
  • 58. “Types = For All Tests = There Exists” - Amanda Laucher
  • 60. “A mind is like a parachute, it doesn’t work unless its open” - Frank Zappa

Editor's Notes

  • #4: Principle Engineer – Scala, Akka – build elastic grid service – other services use this scalable and reliable async task execution All my slides, notes and references are available on blog So “Improving correctness with types” …
  • #5: Sounds complicate – Not – low hanging fruit – not functional – OOP In the 90s – Defensive programming Garbage in garbage out – Robust Shipping cost zero – country is null – index is -1 – file not found – user entered a bad country code Tracing a bug back to its cause – Andy – Shawshank Redemption.
  • #6: Fail fast Find bugs quickly – Easier to fix Requires lots of tests Exceptions can reach customers
  • #7: Bertrand Meyer - Eiffle Design by Contract Systematic Fail fast - goal removes defensive programming tool support: property checking, JML, Spec#
  • #8: I am sure a lot of you have seen this Knuth quote So what – What does it mean? Formal Methods - not for real code … but lets explore this – first question …
  • #9: Maps – x Domain – y Range All of the domain – Total Function Code define domain + range with types – fucntion's signature – compiler Do not accept all possible values in their domain – fail fast, exceptions – return garbage – Domain swiss cheese Correctness is about choosing types that shrink the domain and the range Filling in the holes – functions "total” – Removing the chance of failure (construction / validation / compiler)
  • #10: I want to start by skipping through some simple rules for Correctness No detail Covered else where - links in blog
  • #11: Tony Hoare introduced Null references in ALGOL W back in 1965 “simply because it was so easy to implement”. He talks about that decision considering it “my billion-dollar mistake”. Simply use Option - Capture nulls from java code
  • #12: Nice properties – once constructed correctly it stays correct Actors
  • #13: Users don't care why it failed From Effective Java there are two types Business logic errors Capture these with types Option / Try / Validation / Or Programming Errors Prevent these with types Option / NonEmptyList / Type safe wrappe
  • #14: Wrapper types Scala
  • #15: Test Currency is not a String – Only 176 international currencies (ISO 4217) They all have exactly three uppercase letters 17576 unique combinations of this rule, plain strings are effectively infinite
  • #16: Value class - Extends AnyVal Private constructor Currency.from returns an Option Enumeration
  • #17: Dangers of refactoring types – broad operations and type inference This function creditCardRate still compiles
  • #18: Can use ScalaZ or Scalactic triple equals Implementation
  • #19: Can use ScalaZ or Scalactic triple equals Implementation
  • #20: Rates and amounts have different semantics In this case the rate is a scalar, these values can be added, subtracted, multiplied and divided. Rates can multiply or divide amounts, scaling their size Amounts have a unit, like a currency, they can only be added and subtracted If you multiply two amounts, you square the unit (probably not what you want) If you divide two amounts, you get a rate
  • #21: Now Rates cannot be added to amounts
  • #22: Rates and amounts have different semantics In this case the rate is a scalar, these values can be added, subtracted, multiplied and divided. Rates can multiply or divide amounts, scaling their size Amounts have a unit, like a currency, they can only be added and subtracted If you multiply two amounts, you square the unit (probably not what you want) If you divide two amounts, you get a rate
  • #23: Primitively type code is weakly typed Validation – Reduces code and bugs Reduces tests required
  • #24: … Lets look at a simple example
  • #26: Now the compiler prevents us from getting the average of an empty list
  • #27: Var-args are syntactic sugar for list arguments One plus var args == Non-empty var-args
  • #31: Cannot be empty – operation not supported – empty does not make sense Validation failures – banking customer’s list of accounts Compiler will tell everyone else
  • #32: Algebraic data types == traits and case classes
  • #33: Grid team – schedule and execute jobs on a cluster of agent machines Customer code in DMZ – Parallelize large jobs like payroll
  • #34: Here is a naive implementation of data model classes One to one mapping from database schema Options are prefixed with Maybe First lets remove some of the primitives
  • #37: Enforce with Tag types
  • #38: Tag types – shapeless – scalaz Leverage an optimization in scala compiler – the tag is removed at runtime Value remains the underlying type with extra information for type checking at compile time
  • #39: No control over construction or operations – Semantics require a wrapper type Best used for marking primary/foreign keys
  • #42: Fail fast – is not an option Must always record metrics
  • #46: Guaranteed to write metrics with the correct value Bugs where startedAt not set cause compile error
  • #47: If a function throws IllegalArgumentException or method throws IllegalStateException or InvalidOperationException Type is too broad consider breaking it up with an ADT
  • #48: Path dependent types
  • #49: Family polymorphism Java XML libraries Handle is created – and consumed by Storage
  • #53: Renamed the public traits Entry and EntryDescription are abstract types – extend the public traits Methods use the abstract member types
  • #54: Implement the abstract types Read method can access id directly
  • #55: Implement the abstract types Read method can access id directly
  • #56: Implement the abstract types Read method can access id directly
  • #57: Multi tenant – encryption
  • #58: Final type of a class is passed into a trait has a generic parameter – enums – generic repositories Any type that only exists at compile time – track objects state – enable/disable operations – type-safe builder A library that pushes the boundary of what’s possible at compile time
  • #59: Spectrum
  • #60: Object oriented abstractions/classes are used to group data or operations For correctness semantics come first, describe them with a type use the compiler to prove your correctness
  • #61: Scala is a very broad church – from OOP to FP Both have lots to teach – lots of techniques can be applied to both
  • #62: Slides, notes and references on the fledgling workday tech blog Any questions?