improving correctness with types kats conf
TRANSCRIPT
![Page 1: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/1.jpg)
Improving Correctness with Types
Functional Kats September 2015
![Page 3: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/3.jpg)
Defensive Programming
![Page 4: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/4.jpg)
Fail Fast
![Page 5: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/5.jpg)
Design by Contract
![Page 6: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/6.jpg)
What is a function ?
f
x y
Domain Range
![Page 7: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/7.jpg)
![Page 8: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/8.jpg)
Never use nulls … period
“Null references, my billion dollar mistake”- Tony Hoare, QCon 2009
![Page 9: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/9.jpg)
All data is immutable
Almost all
![Page 10: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/10.jpg)
Do not throw exceptions
![Page 11: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/11.jpg)
“Bad programmers worry about the code.
Good programmers worry about data structures and their relationships.”
- Linus Torvalds
![Page 12: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/12.jpg)
Wrappers
Wrapper Types
![Page 13: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/13.jpg)
case class Customer(name: String, preferredCurrency: String) { require(Currency.isValid(preferredCurrency))} val customer = Customer(name = "Joe Bloggs", preferredCurrency = "SFO")
This is an airport?
![Page 14: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/14.jpg)
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] = ???}
![Page 15: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/15.jpg)
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
![Page 16: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/16.jpg)
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
![Page 17: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/17.jpg)
val order: Order = ???val customer: Customer = ??? val creditCardCharge = order.amount + creditCardRate(customer)
Eeek this a bug
![Page 18: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/18.jpg)
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): Rate = rhs * this}
![Page 19: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/19.jpg)
Using wrapper types• Control the available values• Control the available operations
![Page 20: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/20.jpg)
Non Empty List
![Page 21: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/21.jpg)
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
![Page 22: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/22.jpg)
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())
![Page 23: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/23.jpg)
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()
![Page 24: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/24.jpg)
def describeSomeList(items: List[Int]): String = { val ave = average(items) s"A list with average $ave"}
Does not compile
![Page 25: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/25.jpg)
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
![Page 26: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/26.jpg)
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(es) => val ave = average(es) s"A list with average $ave" case None => "An empty list" }}
Does not compile
![Page 27: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/27.jpg)
Algebraic Data Types
![Page 28: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/28.jpg)
Agent Id Type Status Host In Use ByA01 1 Active 10.0.0.1A02 1 Failed 10.0.0.2 J01A03 2 Active 10.0.0.3 J03A04 2 Waiting 10.0.0.4
Job Id Type
Status Submitted By
Processed By
J01 1 Waiting FredJ02 1 Active Wilma A01J03 2 Complet
eBarney A03
Jobs
Agents
![Page 29: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/29.jpg)
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])
![Page 30: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/30.jpg)
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])
![Page 31: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/31.jpg)
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)
![Page 32: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/32.jpg)
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])
![Page 33: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/33.jpg)
import tag.@@
trait Foodef 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
![Page 34: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/34.jpg)
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])
![Page 35: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/35.jpg)
case class Job(referenceId: String @@ Agent, jobType: JobType, status: JobStatus, submittedBy: User, submittedAt: DateTime, maybeStartedAt: Option[DateTime], maybeProcessedBy: Option[String @@ Job], maybeCompletedAt: Option[DateTime])
Active | Complete
![Page 36: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/36.jpg)
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)) }}
![Page 37: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/37.jpg)
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)) }}
![Page 38: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/38.jpg)
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}
![Page 39: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/39.jpg)
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}
![Page 40: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/40.jpg)
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}
![Page 41: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/41.jpg)
def recordCompletionMetrics(job: CompleteJob): Unit = { writeJobEvent( event = "Completed", time = job.completedAt, referenceId = job.referenceId, waitingTime = (job.startedAt - job.submittedAt), executionTime = (job.completedAt - job.startedAt))}
![Page 42: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/42.jpg)
Algebraic data types• Simply sealed traits and case classes• Exposes the shape of your data• Use this shape to control possible
states
![Page 43: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/43.jpg)
Path Dependent Types
![Page 44: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/44.jpg)
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]}
![Page 45: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/45.jpg)
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)}
![Page 46: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/46.jpg)
val riakStorage: Storage = ???
val maybeData = for { handle <- riakStorage.find(someName) data <- riakStorage.read(handle).toOption} yield data
![Page 47: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/47.jpg)
val riakStorage: Storage = ???val memoryStorage: Storage = ???
val maybeData = for { handle <- riakStorage.find(someName) data <- memoryStorage.read(handle).toOption} yield data
IllegalArgumentException
![Page 48: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/48.jpg)
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]}
![Page 49: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/49.jpg)
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) }}
![Page 50: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/50.jpg)
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 ^
![Page 51: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/51.jpg)
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 ^
![Page 52: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/52.jpg)
Path dependent types• Family polymorphism • Remove casts• Bound to instances not classes
![Page 53: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/53.jpg)
![Page 54: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/54.jpg)
More advanced techniques
• Self-recursive types• Phantom types• Shapeless
![Page 55: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/55.jpg)
Defensive Programmi
ng
Fail Fast
Design by
Contract
Types
![Page 56: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/56.jpg)
“A mind is like a parachute, it doesn’t work unless its open”
- Frank Zappa
![Page 57: Improving Correctness with Types Kats Conf](https://reader036.vdocuments.mx/reader036/viewer/2022062900/58ee2dea1a28ab6a6b8b4631/html5/thumbnails/57.jpg)
http://workday.github.io