Skip to content

Instantly share code, notes, and snippets.

@IainHull
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IainHull/dcde1207d4a8f6d0f942 to your computer and use it in GitHub Desktop.
Save IainHull/dcde1207d4a8f6d0f942 to your computer and use it in GitHub Desktop.
Improve your correctness with Types - Scala Days SF 2015
case class Order(orderId: String,
customerId: String,
items: Vector[OrderItem],
amount: MoneyAmount,
submittedAt: DateTime)
case class Customer(name: String,
preferredCurrency: Currency)
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
}
def creditCardRate4(customer: Customer): Rate = {
if (customer.preferredCurrency === Currency.USD)
new Rate(BigDecimal("0.015"))
else
new Rate(BigDecimal("0.025"))
}
object Test {
val order: Order = ???
val customer: Customer = ???
// Now rates can only multiply amounts
val creditCardCharge = order.amount * creditCardRate4(customer)
}
case class Customer(name: String,
preferredCurrency: String) {
require(Currency.isValid(preferredCurrency))
}
case class Customer(name: String,
preferredCurrency: String) {
require(Currency.isValid(preferredCurrency))
}
val customer = Customer(name = "Joe Bloggs",
preferredCurrency = "SFO")
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] = ???
}
trait EntryDescriptionLike {
def name: Name
def owner: User
}
trait EntryLike {
def data: InputStream
}
trait Storage {
type Entry <: EntryLike
type EntryDescription <: EntryDescriptionLike
def create(name: Name, owner: User): EntryDescription
def find(name: Name): Option[EntryDescription]
def read(entryDescription: EntryDescription): Entry
}
class StorageImpl extends Storage {
type Entry = EntryImpl
type EntryDescription = EntryDescriptionImpl
private[StorageImpl] case class EntryImpl(val data: InputStream) extends EntryLike
private[StorageImpl] case class EntryDescriptionImpl(id: Long, name: Name, owner: User) extends EntryDescriptionLike
def create(name: Name, owner: User): EntryDescription = ???
def find(name: Name): Option[EntryDescription] = ???
def read(entryDescription: EntryDescription): Entry = {
dataStore.read(entryDescription.id)
}
}
scala> val riakStorage1 = new RiakStorage
riakStorage1: RiakStorage = RiakStorage@3605ce8f
scala> val memoryStorage = new MemoryStorage
memoryStorage: MemoryStorage = MemoryStorage@680aef16
scala> val riakStorage2 = new RiakStorage
riakStorage2: RiakStorage = RiakStorage@70c14473
scala> val desc = riakStorage1.create(name, user)
desc: riakStorage1.EntryDescription = EntryDescriptionImpl(1,Some Path,aUser)
scala> memoryStorage.find(desc)
<console>:22: error: type mismatch;
found : riakStorage1.EntryDescription
(which expands to) riakStorage1.EntryDescriptionImpl
required: Name
memoryStorage.find(desc)
^
scala> riakStorage2.find(desc)
<console>:21: error: type mismatch;
found : riakStorage1.EntryDescription
(which expands to) riakStorage1.EntryDescriptionImpl
required: Name
riakStorage2.find(desc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment