The document discusses improving program correctness through the use of types in Scala. It covers techniques like defensive programming, failing fast, design by contract, and using wrapper types to control values and operations. Specific examples include creating algebraic data types to model application data, using tagged types to enforce constraints, and defining wrapper types for currencies and amounts to prevent bugs. The overall message is that types can help catch errors earlier and design programs in a way that is less error-prone.
13. case class Customer(name: String,
preferredCurrency: String) {
require(Currency.isValid(preferredCurrency))
}
val customer = Customer(name = "Joe Bloggs",
preferredCurrency = "SFO")
This is an airport?
14. 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] = ???
}
15. 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
16. 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
17. 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")
}
18. val order: Order = ???
val customer: Customer = ???
val creditCardCharge = order.amount + creditCardRate(customer)
Eeek this a bug
19. 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
}
20. val order: Order = ???
val customer: Customer = ???
val creditCardCharge = order.amount + creditCardRate(customer)
Does not compile
21. Using wrapper types
• Do not abuse primitives
• Control the available values
• Control the available operations
• Move validation to the correct place
28. 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
29. 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])
30. 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])
31. 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)
32. 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])
33. 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
All my slides, notes and references are available on blog
This take is about correctness
Sounds complicate – Not – low hanging fruit – not functional – OOP
In the 90s – Defensive programming
Garbage in garbage out - Robust
Tracing a bug back to its cause – Andy – Shawshank Redemption.
Fail fast
Find bugs quickly – Easier to fix
Requires lots of tests
Exceptions can reach customers
These techniques are about the code
Correctness – choosing the correct data types – a good compile
Maps – x Domain – y Range – example
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)
I want to start by skipping through some simple rules for Correctness
No detail
Covered else where - links in blog
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
Nice properties – once constructed correctly it stays correct
Actors
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
Wrapper types
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
Value class - Extends AnyVal
Private constructor
Currency.from returns an Option
Enumeration
Dangers of refactoring types – broad operations and type inference
This function creditCardRate still compiles
Can use ScalaZ or Scalactic triple equals
Implementation
Can use ScalaZ or Scalactic triple equals
Implementation
Rates and amounts have different semantics
Amounts have a unit, like a currency, they can only be added and subtracted
If you multiply two amounts you change the units
A rate is a scalar, these values can be added, subtracted, multiplied and divided.
Rates can multiply or divide amounts, scaling its size
If you divide two amounts, you get a rate
Now Rates cannot be added to amounts
Primitively type code is weakly typed
Validation – Reduces code and bugs
… Lets look at a simple example
Now the compiler prevents us from getting the average of an empty list
Var-args are syntactic sugar for list arguments
One plus var args == Non-empty var-args
Cannot be empty – operation not supported – empty does not make sense
Validation failures – banking customer’s list of accounts
Compiler will tell everyone else
Algebraic data types == traits and case classes
Grid team – schedule and execute jobs on a cluster of agent machines
Customer code in DMZ – Parallelize large jobs like payroll
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
Enforce with Tag types
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
No control over construction or operations – Semantics require a wrapper type
Best used for marking primary/foreign keys
Fail fast – is not an option
Must always record metrics
Guaranteed to write metrics with the correct value
Bugs where startedAt not set cause compile error
If a function throws IllegalArgumentException or method throws IllegalStateException or InvalidOperationException
Type is too broad consider breaking it up with an ADT
A nested object’s type is specific to it parents instance – multi tenant – encryption
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
Use types to constrain values and operations
Compiler proves your code is correct – reduce tests – move validation to the correct place
reduce the state space of your API making it easier to use
Apply Defensive programming only where required
Fail faster – at compile time
DbC preconditions become types – but Immutability – don’t need invariants – post conditions become types also
Scala is a very broad church – from OOP to FP
Both have lots to teach – lots of techniques can be applied to both
Slides, notes and references on the fledgling workday tech blog
Any questions?