Kailuo Wang
kailuowang.github.io
Kai(luo) Wang @iHeartRadio (we'r hiring) github, twitter: @kailuowang
object HRService {
val numOfEmployees: DepartmentId => Int = ...
val employeeDepartment: EmployeeId => DepartmentId = ...
}
object FinanceService {
val budget: DepartmentId => Double = ...
}
object DirectoryService {
val extensionNum: DepartmentId => Int = ...
}
case class Department(
numOfEmployees : Int,
budget : Double,
extensionNum : Int
)
def department(id: DepartmentId) : Department = ???
def extension(employee: EmployeeId) : Int = ???
def department(id: DepartmentId): Department =
Department(
numOfEmployees = HRService.numOfEmployees(id),
budget = FinanceService.budget(id),
extensionNum = DirectoryService.extensionNum(id))
def employExtension(employeeId: EmployeeId): Int = {
val departmentId = HRService.employeeDepartment(employeeId)
DirectoryService.extensionNum(departmentId)
}
Bound variables :(
val employExtension =
HRService.employeeDepartment andThen DirectoryService.extensionNum
def department(id: DepartmentId): Department =
Department(
numOfEmployees = HRService.numOfEmployees(id),
budget = FinanceService.budget(id),
extensionNum = DirectoryService.extensionNum(id))
But what about this?
Function combinator
Applicative
Foldable
Functor
Monad
Semigroup
Monoid
Traverse
val department: =
(HRService.numOfEmployees |@|
FinanceService.budget |@|
DirectoryService.extensionNum) map Department.apply
Associative combination
val department =
for {
numOfEmployee <- HRService.numOfEmployees
budget <- FinanceService.budget
extensionNum <- DirectoryService.extensionNum
} yield Department(numOfEmployee, budget, extensionNum)
Monadic sequential combination
object DirectoryService {
val extensionNum: DepartmentId => Future[Int] = ...
}
Given
def department(id: DepartmentId): Future[Department] =
(HRService.numOfEmployees(id) |@|
FinanceService.budget(id) |@|
DirectoryService.extensionNum(id)) map Department.apply
Fallback to bound variable?
case class Kleisli[F[_], A, B](run: A => F[B]) {
def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] =
Kleisli(a => F.map(run(a))(f))
}
val department: DeparmentId => Future[Department] =
(( Kleisli(HRService.numOfEmployees) |@|
Kleisli(FinanceService.budget) |@|
Kleisli(DirectoryService.extensionNum)) map Department.apply).run
val employeeExtension: Klesili[EmployeeId, Int] =
Kleisli(HRService.employDepartment) andThen Kleisli(DirectoryService.extensionNum)
Associative parellel combination
Sequential combination
object HRService {
val numOfEmployees: DepartmentId => Future[Either[Reason, Int]] = ...
}
Monad Transformer - cats.data.EitherT
case class EitherT[F[_], A, B](value: F[Either[A, B]]){
def bimap[C, D](fa: A => C, fb: B => D)(implicit F: Functor[F])
: EitherT[F, C, D]
= EitherT(F.map(value)(_.bimap(fa, fb)))
}
object HRService {
type ReasonableFuture[T] = EitherT[Future, Reason, T]
def numOfEmployees(id: DepartmentId): ReasonableFuture[Int] = ...
}
def assignDorm(req: AssignDormRoomRequest): Future[Either[Reason, DormRoom]]
case class AssignDormRoomRequest(
name: String,
sex: Sex,
mentor: Mentor)
def registerClass(req: ClassRegRequest): Future[Either[Reason, ClassRegistration]]
case class ClassRegRequest(
studentId: StudentId,
classId: ClassId,
semester: Semester)
a type class and dependent type based generic programming library
automatic type class derivation for Cats and generic utility functions
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)
List(Future(1), Future(2), Future(3)).sequence
//gives a Future(List(1, 2, 3))
val r: Future[Department] = ???(numOfEmployees = Future(52),
budget = Future(100000.0),
extensionNum = Future(30185)).sequence
I wish I can do...
With cats.Traverse
for {
numOfEmployee <- HRService.numOfEmployees(id)
budget <- FinanceService.budget(id)
extensionNum <- DirectoryService.extensionNum(id)
} yield Department(numOfEmployee, budget, extensionNum)
For this code
we can do
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]]
val seq = sequenceGeneric[Department]
def department(id: DepartmentId) : Future[Department] =
seq(numOfEmployees = HRService.numOfEmployees(id),
budget = FinanceService.budget(id),
extensionNum = DirectoryService.extensionNum(id))
sequence: HList of A[_] => A[HList]
E.g.
Future[Int] :: Future[String] :: Future[Boolean] :: HNil =>
Future[Int :: String :: Boolean :: HNil]
Now we can do
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)
Experiments with shapless cats and kittens
trait Definitions {
type Result[_]
implicit def resultMonad: Monad[Result]
type K[A, B] = Kleisli[Result, A, B]
...
}
Building blocks for generic function combinator
Built on abstract types
caveat: SI-2712 partial fix required
trait DefinitionWithEitherT extends Definitions {
type Reason //reason ADT for failure
/**
* Effect of the operations, e.g. [[Future]]
*/
type Effect[_]
type Result[T] = EitherT[Effect, Reason, T]
implicit def effectMonad: Monad[Effect]
implicit lazy val resultMonad: Monad[Result] =
EitherT.catsDataMonadErrorForEitherT[Effect, Reason]
}
An example of EitherT based Result definitions
object ReasonableFuture extends henkan.k.DefinitionWithEitherT {
sealed trait MyReason
case class ExceptionOccurred(msg: Option[String],
ex: Option[Throwable]) extends MyReason
case class UserError(msg: String) extends MyReason
case class Unavailable(missingThingy: String) extends MyReason
type Reason = MyReason
type Effect[X] = Future[X]
implicit val effectMonad: Monad[Effect] = {
import scala.concurrent.ExecutionContext.Implicits.global
cats.std.future.futureInstance
}
}
First let's define some types
object Domain {
//passed in by user
type Request = Map[String, String]
case class RegistrationRecord(basicInfo: BasicInfo,
mentor: Mentor,
dormRoom: DormRoom)
case class BasicInfo(name: String, address: String, age: Int, sex: Sex)
...
}
trait Services {
def wandService: K[GetWandRequest, Wand]
def mentorService: K[AssignMentorRequest, Mentor]
def dormService: K[AssignDormRoomRequest, DormRoom]
//protocols
case class GetWandRequest(name: String, address: String)
case class AssignMentorRequest(wand: Wand, specialty: Specialty)
case class AssignDormRoomRequest(name: String, sex: Sex, mentor: Mentor)
}
def register: K[Request, RegistrationRecord] = ???
Services at disposal
Request Parser
def stringExtractor(key: String): K[Request, String] = K.of((req: Request) ⇒
req.get(key).toResult(UserError(s"missing value of $key")))
We need to extract this BasicInfo out of Request (Map[String, String])
case class BasicInfo(name: String, address: String, age: Int, sex: Sex)
def basicInfoExtractor: K[Request, BasicInfo] = ???
Generic funciton combinator
private lazy val ctbi = composeTo[BasicInfo]
lazy val baseInformationExtractor: K[Request, BasicInfo] = ctbi(
name = stringExtractor("name"),
address = stringExtractor("address"),
age = stringExtractor("age") andThen (a ⇒ Try(a.toInt).toResult()),
sex = stringExtractor("sex") andThen stringToSex
)
import henkan.extractor._
implicit val frString: FieldReader[Result, Request, String] =
stringExtractor _
implicit val frInt: FieldReaderMapper[String, Result[Int]] =
(s: String) ⇒ Try(s.toInt).toResult()
implicit val frSex: FieldReaderMapper[String, Result[Sex]] =
stringtoSex.run
val baseInformationParser: K[Request, BasicInfo] =
Extractor[Result, Request, BasicInfo].apply()
Application
import AutoExtractors._
def register(services: Services): K[Request, RegistrationRecord] = {
import services._
for {
bi ← baseInformationExtractor
mentor ← mentorService.contraMapR(
wand = pure[Request](bi.to[GetWandRequest]()) andThen
wandService,
specialty = stringExtractor("specialty") andThen stringToSpecialty
)
room ← pure[Request](bi.to[AssignDormRoomRequest].set(mentor = mentor)) andThen
dormService
} yield RegistrationRecord(bi, mentor, room)
}
def mentorService: K[AssignMentorRequest, Mentor]
case class AssignMentorRequest(wand: Wand, specialty: Specialty)
def wandService: K[GetWandRequest, Wand]
case class GetWandRequest(name: String, address: String)
case class RegistrationRecord(basicInfo: BasicInfo,
mentor: Mentor,
dormRoom: DormRoom)
def dormService: K[AssignDormRoomRequest, DormRoom]
case class AssignDormRoomRequest(name: String, sex: Sex, mentor: Mentor)
A community of projects and individuals organized around ...
Sorry, I don't know much about the Hogwarts school.
But I do have typelevel stickers...
By Kailuo Wang