Kailuo Wang
kailuowang.github.io
github, twitter: @kailuowang
case class Department(
numOfEmployees : Int,
budget : Double,
extensionNum : Int
)
object Service {
def department(id: DepartmentId): Future[Department] = {
for {
numOfEmployee <- HRService.numOfEmployees(id)
budget <- FinanceService.budget(id)
extensionNum <- DirectoryService.extensionNum(id)
} yield Department(numOfEmployee, budget, extensionNum)
}
}
object HRService {
def numOfEmployees(id: DepartmentId): Future[Int] = ???
}
object FinanceService {
def budget(id: DepartmentId): Future[Double] = ???
}
object DirectoryService {
def extensionNum(id: DepartmentId): Future[Int] = ???
}
What's the problem?
Sequential
Error-prone
object Service {
def department(id: DepartmentId): Future[Department] = {
val numOfEmployeeF = HRService.numOfEmployees(id)
val budgetF = FinanceService.budget(id)
val extensionNumF = DirectoryService.extensionNum(id)
for {
numOfEmployee <- numOfEmployeeF
budget <- budgetF
extensionNum <- extensionNumF
} yield Department(
numOfEmployee = numOfEmployee,
budget = budget,
extensionNum = extensionNum )
}
}
Improved
Safe? yes.
Performant? Yes.
DRY? not so much.
Applicative
Apply
Foldable
Functor
Id
Invariant
Monad
MonadCombine
MonadFilter
Monoid
MonoidK
Semigroup
SemigroupK
Show
Traverse
def department(id: DepartmentId): Future[Department] =
( HRService.numOfEmployees(id)
|@| FinanceService.budget(id)
|@| DirectoryService.extensionNum(id)) map Department.apply
val department3: DeparmentId => Future[Department] =
(( Kleisli(HRService.numOfEmployees)
|@| Kleisli(FinanceService.budget)
|@| Kleisli(DirectoryService.extensionNum)) map Department.apply).run
object Service {
def department(id: DepartmentId): Future[Department] = {
val numOfEmployeeF = HRService.numOfEmployees(id)
val budgetF = FinanceService.budget(id)
val extensionNumF = DirectoryService.extensionNum(id)
for {
numOfEmployee <- numOfEmployeeF
budget <- budgetF
extensionNum <- extensionNumF
} yield Department(
numOfEmployee = numOfEmployee,
budget = budget,
extensionNum = extensionNum )
}
}
V.S.
still error-prone
val department2: DeparmentId => Future[Department] =
(( HRService.numOfEmployees
|@| FinanceService.budget
|@| DirectoryService.extensionNum) map {
(_ |@| _ |@| _) map Department.apply }
shapeless is a type class and dependent type based generic programming library for Scala
generic by shape
Map("numberOfEmployee" -> 52, "budget" -> 10000, "extensionNumber" -> 30815)
Map("name" -> "Mike", "sex" -> "male", "address" -> "123 ABC Ave")
case class Department(numOfEmployees: Int, budget: Double, extensionNum: String)
case class Student(name: String, sex: Sex, address: Address)
Genericity by Structure
Genericity by Shape
automatic type class derivation for Cats and generic utility functions
List(Option(1), Option(2), Option(3)).sequence
//gives a Option(List(1, 2, 3))
val r: Future[Department] = ???(numOfEmployees = Future(52),
budget = Future(100000.0),
extensionNum = Future(30185)).sequence
I wish I can do...
From the power of cats.Traverse
val seq = sequenceGeneric[Department]
def department(id: DepartmentId): Future[Department] =
seq(numOfEmployees = HRService.numOfEmployees(id),
budget = FinanceService.budget(id),
extensionNum = DirectoryService.extensionNum(id))
val department2: String => Future[Department] =
seq(numOfEmployees = Kleisli(HRService.numOfEmployees),
budget = Kleisli(FinanceService.budget),
extensionNum = Kleisli(DirectoryService.extensionNum)).run
import cats.Semigroup, cats.syntax.semigroup._
import semigroup._, legacy._
val department1 = Department(23, 20000.0, 23911)
val department2 = Department(4, 1000.0, 92711)
val Department(totalEmployee, totalBudget, _) = department1 |+| department2
assert(totalEmployee == 27)
assert(totalBudget == 21000.0)
Miles Sabin's talk on derivation in kittens
Lift
Traverse
Sequence
object TypeSafeConfigReader {
def reader[T](f: (Config, String) ⇒ T): FieldReader[Option, Config, T]
= FieldReader { (cfg, fieldName) ⇒
if (cfg.hasPath(fieldName)) Option(f(cfg, fieldName))
else None
}
implicit val rInt = reader(_.getInt(_))
implicit val rDouble = reader(_.getDouble(_))
implicit val rString = reader(_.getString(_))
implicit val rLong = reader(_.getLong(_))
implicit val rDuration = reader(_.getDuration(_))
implicit val rBoolean = reader(_.getBoolean(_))
implicit val rConfig = reader(_.getConfig(_))
}
conversion between runtime-shaped data and staticcally-shaped data
A tiny typesafe config -> case class lib
case class FarmSettings(
name: String,
barnHouse: BarnHouseSettings,
numOfAnimals: Int
)
val cfgString =
"""
| name: "Old McDonald"
| numOfAnimals: 5
| barnHouse {
| footage: 2045.5
| openHours: 8h
| }
""".stripMargin
val result: Option[FarmSettings] = extract[Option, FarmSettings](
ConfigFactory.parseString(cfgString)
)
assert( result == Some(
FarmSettings(
"Old McDonald",
BarnHouseSettings(
2045.5d,
Duration.ofHours(8),
false
),
5
)))
Henkan
conversion between runtime-shaped data and staticcally-shaped data
case class BarnHouseSettings(
footage: Double,
openHours: Duration,
parkTractor: Boolean = false
)
import java.time.LocalDate
import henkan.syntax.convert._
object caseClassConversion {
case class Employee(name: String,
address: String,
dateOfBirth: LocalDate,
salary: Double = 50000d)
case class UnionMember(name: String,
address: String,
dateOfBirth: LocalDate)
employee.to[UnionMember]()
unionMember.to[Employee]()
//conversion with different value
unionMember.to[Employee].set(salary = 60000.0)
}
conversion between case classes
default value is respected
By Kailuo Wang