Paweł Szulc - Trip with monads at Scala in the City2. Why should I write FP code?
1. Modularity
2. Testability
3. Performance
4. Maintainability
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 2
4. FP Building Blocks
case class EitherT[F[_], A, B](
run: F[A / B]
)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 4
5. FP Building Blocks
case class EitherT[F[_], A, B](
run: F[A / B]
)
A / B
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 5
6. FP Building Blocks
case class EitherT[F[_], A, B](
run: F[A / B]
)
A __/ B
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 6
7. FP Building Blocks
case class EitherT[F[_], A, B](
run: F[A / B]
)
A _( )_/ B
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 7
8. FP Building Blocks
case class EitherT[F[_], A, B](
run: F[A / B]
)
A ¯_( )_/¯ B
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 8
10. FP Building Blocks
case class Kleisli[M[_], A, B](
run: A => M[B]
)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 10
11. FP Building Blocks
case class Kleisli[M[_], A, B](
run: A => M[B]
)
type ReaderT[M[_], A, B] = Kleisli[M, A, B]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 11
12. FP Building Blocks
case class Kleisli[M[_], A, B](
run: A => M[B]
)
type ReaderT[M[_], A, B] = Kleisli[M, A, B]
type Reader[A, B] = Kleisli[Id, A, B]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 12
13. FP Building Blocks
case class Kleisli[M[_], A, B](
run: A => M[B]
)
type ReaderT[M[_], A, B] = Kleisli[M, A, B]
type Reader[A, B] = Kleisli[Id, A, B]
type Id[A] = A
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 13
14. FP Building Blocks
case class State[S, A](
run: S => (S, A)
)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 14
15. FP Building Blocks
case class StateT[M[_], S, A](
run: S => M[(S, A)]
)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 15
16. FP Building Blocks
case class StateT[M[_], S, A](
run: S => M[(S, A)]
)
type State[A, B] = StateT[Id, A, B]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 16
17. FP Building Blocks
case class EitherT[F[_], A, B](run: F[A / B])
case class Kleisli[M[_], A, B](run: A => M[B])
type ReaderT[M[_], A, B] = Kleisli[M, A, B]
type Reader[A, B] = Kleisli[Id, A, B]
case class StateT[M[_], S, A](run: S => M[(S, A)])
type State[A, B] = StateT[Id, A, B]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 17
20. Let's write FP application!
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 20
23. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 23
24. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 24
25. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
Forcast for City(Wroclaw) is Temperature(28, Celcius)
Hottest city found so far is (City(Wroclaw), Temperature(28, Celcius))
What is the next city?
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 25
26. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
Forcast for City(Wroclaw) is Temperature(28, Celcius)
Hottest city found so far is (City(Wroclaw), Temperature(28, Celcius))
What is the next city?
London
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 26
27. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
Forcast for City(Wroclaw) is Temperature(28, Celcius)
Hottest city found so far is (City(Wroclaw), Temperature(28, Celcius))
What is the next city?
London
Forcast for City(London) is Temperature(34, Celcius)
Hottest city found so far is (City(London), Temperature(34, Celcius))
What is the next city?
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 27
28. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
Forcast for City(Wroclaw) is Temperature(28, Celcius)
Hottest city found so far is (City(Wroclaw), Temperature(28, Celcius))
What is the next city?
London
Forcast for City(London) is Temperature(34, Celcius)
Hottest city found so far is (City(London), Temperature(34, Celcius))
What is the next city?
kjsdkjssd
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 28
29. The App
> run
Using weather service at http://our-super-weather-forcast-app.com
What is the next city?
Wroclaw
Forcast for City(Wroclaw) is Temperature(28, Celcius)
Hottest city found so far is (City(Wroclaw), Temperature(28, Celcius))
What is the next city?
London
Forcast for City(London) is Temperature(34, Celcius)
Hottest city found so far is (City(London), Temperature(34, Celcius))
What is the next city?
kjsdkjssd
Encountered an error: UknownCity(kjsdkjssd)
>
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 29
31. Domain
case class Temperature(
value: Int,
unit: TempUnit = Celcius
)
case class Forcast(temperature: Temperature)
case class City(name: String)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 31
32. Third-party
class WeatherClient(host: String, port: Int) {
def forcast(city: City): Forcast = city match {
case City("Wroclaw") =>
Forcast(Temperature(28))
case City("London") =>
Forcast(Temperature(34))
}
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 32
33. Helper classes
case class Config(host: String, port: Int)
sealed trait Error
case class UnknownCity(city: String) extends Error
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 33
34. Helper classes
type Requests = Map[City, Forcast]
object Requests {
def empty: Requests = Map.empty[City, Forcast]
def hottest(requests: Requests): (City, Forcast) = ...
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 34
36. So what do we need?
1. Host & port
2. Input / output
3. City by name (errors)
4. Forcast from third-party
5. Hottest city
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 36
37. Host & port
def host: Reader[Config, String] =
Reader(_.host)
def port: Reader[Config, Int] =
Reader(_.port)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 37
38. Input / output
def printLn(line: String): Task[Unit] =
Task.delay(println(line))
def readLn: Task[String] =
Task.delay(scala.io.StdIn.readLine)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 38
39. City by name
def cityByName(cityName: String): Error / City =
cityName match {
case "Wroclaw" =>
/-(City(cityName))
case "London" =>
/-(City(cityName))
case _ =>
-/(UnknownCity(cityName))
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 39
40. Weather forcast
def weather(city: City)(
host: String, port: Int
): Task[Forcast] = Task.delay {
new WeatherClient(host, port).forcast(city)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 40
41. Hottest city
def hottestCity: State[(City, Temperature)] =
State { reqs =>
val temper = Requests.hottest(reqs)
.map(f => f.temperature)
(reqs, temper)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 41
42. Ask city
def askCity: Task[String] = for {
_ <- printLn("What is the next city?")
cityName <- readLn
} yield cityName
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 42
43. def fetchForcast(city: City)(host: String, port: Int): StateReq[Forcast] =
for {
mForcast <- StateT { (reqs: Requests) =>
Task.delay ((reqs, reqs.get(city)))
}
forcast <- StateT { (reqs: Requests) =>
maybeForcast
.cata(_.pure[Task], weather(city)(host, port))
.map (forcast => (reqs, forcast))
}
_ <- StateT { (reqs: Requests) =>
Task.delay ((reqs + (city -> forcast), ()))
}
} yield forcast
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 43
44. Final program
def askFetchJudge =
for {
cityName <- askCity
city <- cityByName(cityName)
h <- host
p <- port
forcast <- fetchForcast(city)(host, port)
_ <- printLn(s"Forcast for $city is ${forcast.temperature}")
hottest <- hottestCity
_ <- printLn(s"Hottest city found so far is $hottest")
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 44
45. Final program
def program = for {
h <- host
p <- port
_ <- printLn(s"Using weather service at http://$h:$p n")
_ <- askFetchJudge.forever
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 45
47. Reality
def askFetchJudge: Effect[Unit] =
for {
cityName <- askCity.liftM[ErrorEitherT].liftM[ConfigReaderT].liftM[RequestsStateT]
city <- EitherT
.fromDisjunction[Task](cityByName(cityName))
.liftM[ConfigReaderT].liftM[RequestsStateT]
h <- StateT[Effect1, Requests, String]((requests: Requests) =>
ReaderT[Effect0, Config, (Requests, String)]((config: Config) =>
EitherT(Task.delay {
((requests, host.run(config))).right[Error]
})))
p <- StateT[Effect1, Requests, Int]((requests: Requests) =>
ReaderT[Effect0, Config, (Requests, Int)]((config: Config) =>
EitherT(Task.delay {
((requests, port.run(config))).right[Error]
})))
forcast <- fetchForcast(city)(h, p)
.mapK[Effect1, Forcast, Requests](
(t: Task[(Requests, Forcast)]) =>
ReaderT[EitherT[Task, Error, ?], Config, (Requests, Forcast)](
κ(EitherT[Task, Error, (Requests, Forcast)](
t.map(_.right[Error])))))
_ <- printLn(s"Forcast for $city is ${forcast.temperature}")
.liftM[ErrorEitherT].liftM[ConfigReaderT].liftM[RequestsStateT]
hottest <- hottestCity.mapK[Effect1, (City, Temperature), Requests](
(t: Task[(Requests, (City, Temperature))]) =>
ReaderT[EitherT[Task, Error, ?],
Config,
(Requests, (City, Temperature))](
κ(EitherT[Task, Error, (Requests, (City, Temperature))](
t.map(_.right[Error])))))
_ <- printLn(s"Hottest city found so far is $hottest")
.liftM[ErrorEitherT]
.liftM[ConfigReaderT]
.liftM[RequestsStateT]
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 47
48. Reality
def program: Effect[Unit] = for {
h <- StateT[Effect1, Requests, String]((requests: Requests) =>
ReaderT[Effect0, Config, (Requests, String)]((config: Config) =>
EitherT(Task.delay {
((requests, host.run(config))).right[Error]
})))
p <- StateT[Effect1, Requests, Int]((requests: Requests) =>
ReaderT[Effect0, Config, (Requests, Int)]((config: Config) =>
EitherT(Task.delay {
((requests, port.run(config))).right[Error]
})))
_ <- printLn(s"Using weather service at http://$h:$p n")
.liftM[ErrorEitherT]
.liftM[ConfigReaderT]
.liftM[RequestsStateT]
_ <- askFetchJudge.forever
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 48
49. "The FP sounds rather like
a mediæval monk, denying
himself the pleasures of life
in the hope that it will
make him virtuous."
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 49
56. def askFetchJudge: Effect[Unit] = ..
type Effect0[A] = EitherT[Task, Error, A]
type Effect1[A] = ReaderT[Effect0, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 56
57. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
}
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 57
58. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
val app: Effect[Unit] = program
implicit val io: SchedulerService =
Scheduler.io("io-scheduler")
}
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 58
59. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
val app: Effect[Unit] = program
implicit val io: SchedulerService =
Scheduler.io("io-scheduler")
app.run(requests).run(config).run
}
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 59
60. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
val app: Effect[Unit] = program
implicit val io: SchedulerService =
Scheduler.io("io-scheduler")
app.run(requests).run(config).run >>= {
case -/(error) =>
printLn(s"Encountered an error:" +
s"${error.shows}")
case /-(_) => ().pure[Task]
}
}
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 60
61. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
val app: Effect[Unit] = program
implicit val io: SchedulerService =
Scheduler.io("io-scheduler")
(app.run(requests).run(config).run >>= {
case -/(error) =>
printLn(s"Encountered an error:" +
s"${error.shows}")
case /-(_) => ().pure[Task]
}).unsafeRunSync
}
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 61
62. Can we do better?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 62
63. So what do we need?
1. Host & port
2. Input / output
3. City by name (errors)
4. Forcast from third-party
5. Hottest city
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 63
64. Host & port
def host: Reader[Config, String] =
Reader(_.host)
def port: Reader[Config, Int] =
Reader(_.port)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 64
65. Host & port
def host[F[_]]: F[String] = ?
def port[F[_]]: F[Int] = ?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 65
66. trait ApplicativeAsk[F[_], E] {
val applicative: Applicative[F]
def ask: F[E]
def reader[A](f: E => A): F[A]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 66
67. trait ApplicativeAsk[F[_], E] {
val applicative: Applicative[F]
def ask: F[E]
def reader[A](f: E => A): F[A]
}
type ConfigAsk[F[_]] = ApplicativeAsk[F, Config]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 67
69. def host[F[_]: ConfigAsk]: F[String] =
ConfigAsk[F].reader(_.host)
def port[F[_]: ConfigAsk]: F[Int] =
ConfigAsk[F].reader(_.port)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 69
70. def host[F[_]: ConfigAsk]: F[String] =
ConfigAsk[F].reader(_.host)
def port[F[_]: ConfigAsk]: F[Int] =
ConfigAsk[F].reader(_.port)
trait ApplicativeAsk[F[_], E] {
val applicative: Applicative[F]
def ask: F[E]
def reader[A](f: E => A): F[A]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 70
71. City by name
def cityByName(cityName: String): Error / City =
cityName match {
case "Wroclaw" => /-(City(cityName))
case "London" => /-(City(cityName))
case _ => -/(UnknownCity(cityName))
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 71
72. City by name
def cityByName[F[_]](
cityName: String
): F[City] =
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 72
73. City by name
def cityByName[F[_]:Applicative](
cityName: String
): F[City] = cityName match {
case "Wroclaw" => City(cityName).pure[F]
case "London" => City(cityName).pure[F]
case _ => ???
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 73
75. trait ApplicativeError[F[_], E] {
val applicative: Applicative[F]
def raiseError[A](e: E): F[A]
}
type ErrorHandler[F[_]] = ApplicativeError[F, Error]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 75
76. City by name
def cityByName[F[_]:Applicative](
cityName: String
): F[City] = cityName match {
case "Wroclaw" => City(cityName).pure[F]
case "London" => City(cityName).pure[F]
case _ => ???
} .
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 76
77. City by name
def cityByName[F[_]: Applicative: ErrorHandler](
cityName: String
): F[City] = cityName match {
case "Wroclaw" => City(cityName).pure[F]
case "London" => City(cityName).pure[F]
case _ => ErrorHandler[F].raiseError(UnknownCity(cityName))
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 77
78. Hottest city
def hottestCity: State[(City, Temperature)] =
State { reqs =>
val temper = Requests.hottest(reqs)
.map(f => f.temperature)
(reqs, temper)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 78
80. trait MonadState[F[_], S] {
val monad: Monad[F]
def get: F[S]
def set(s: S): F[Unit]
def inspect[A](f: S => A): F[A]
def modify(f: S => S): F[Unit]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 80
81. trait MonadState[F[_], S] {
val monad: Monad[F]
def get: F[S]
def set(s: S): F[Unit]
def inspect[A](f: S => A): F[A]
def modify(f: S => S): F[Unit]
}
type RequestsState[F[_]] = MonadState[F, Requests]
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 81
84. Hottest city
def hottestCity[
F[_]: RequestsState
]: F[(City, Temperature)] =
RequestsState[F].inspect(reqs =>
Requests.hottest(reqs).map(_.temperature)
)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 84
85. Hottest city
def hottestCity[
F[_]: RequestsState
]: F[(City, Temperature)] =
RequestsState[F].inspect(reqs =>
Requests.hottest(reqs).map(_.temperature)
)
trait MonadState[F[_], S] {
// (...)
def inspect[A](f: S => A): F[A]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 85
87. Input / output
def printLn(line: String): Task[Unit] =
Task.delay(println(line))
def readLn: Task[String] =
Task.delay(scala.io.StdIn.readLine)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 87
88. Input / output
trait Console[F[_]] {
def readLine: F[String]
def printLn(line: String): F[Unit]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 88
89. Ask City
def askCity[F[_]: Console: Monad]: F[String] =
for {
_ <- Console[F].printLn("What is the next city?")
cityName <- Console[F].readLine
} yield cityName
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 89
90. Weather forcast
def weather(city: City)(
host: String, port: Int
): Task[Forcast] = Task.delay {
new WeatherClient(host, port).forcast(city)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 90
92. Fetch forcast
def fetchForcast[F[_]: Weather: RequestsState: Monad](city: City): F[Forcast] =
for {
maybeForcast <- RequestsState[F].inspect(_.get(city))
forcast <- maybeForcast.cata(
_.pure[F],
Weather[F].forcast(city)
)
_ <- RequestsState[F].modify(_ + (city -> forcast))
} yield forcast
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 92
94. Final program
def askFetchJudge[F[_]: Console: Weather :
RequestsState: ErrorHandler:Monad]: F[Unit] =
for {
cityName <- askCity[F]
city <- cityByName[F](cityName)
forcast <- fetchForcast[F](city)
_ <- Console[F].printLn(s"Forcast for $city is ${forcast.temperature}")
hottest <- hottestCity[F]
_ <- Console[F].printLn(s"Hottest city found so far is $hottest")
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 94
95. Final program
def program[F[_]: ConfigAsk: Console: Weather:
RequestsState:ErrorHandler:Monad]: F[Unit] =
for {
h <- host[F]
p <- port[F]
_ <- Console[F].printLn(s"Using weather service at http://$h:$p")
_ <- askFetchJudge[F].forever
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 95
96. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = ???
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 96
97. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 97
98. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = ???
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 98
99. object Weather {
def monixWeather(config: Config): Weather[Task] =
new Weather[Task] {
val client: WeatherClient = new
WeatherClient(config.host, config.port)
def forcast(city: City): Task[Forcast] =
Task.delay {
client.forcast(city)
}
}
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 99
100. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = ???
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 100
101. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = Weather.monixWeather(config)
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 101
102. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = Weather.monixWeather(config)
implicit val console = ???
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 102
103. object Console {
val monixConsole: Console[Task] =
new Console[Task] {
def readLine: Task[String] =
Task.delay(scala.io.StdIn.readLine)
def printLn(line: String): Task[Unit] =
Task.delay(println(line))
}
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 103
104. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = Weather.monixWeather(config)
implicit val console = ???
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 104
105. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = Task[A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 105
106. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = StateT[Task, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 106
107. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect1[A] = ReaderT[Task, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 107
108. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect1[A] = ReaderT[Effect0, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
val app: Effect[Unit] = program[Effect]
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 108
109. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect1[A] = ReaderT[Effect0, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
val app: Effect[Unit] = program[Effect]
(app.run(requests).run(config).run >>= {
case -/(error) => console.printLn(s"Encountered an error: ${error.shows}")
case /-(_) => ().pure[Task]
}).unsafeRunSync
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 109
112. def askFetchJudge[F[_]: Console: Weather :
RequestsState: ErrorHandler:Monad]: F[Unit] =
for {
cityName <- askCity[F]
city <- cityByName[F](cityName)
forcast <- fetchForcast[F](city)
_ <- Console[F].printLn(s"Forcast for $city is ${forcast.temperature}")
hottest <- hottestCity[F]
_ <- Console[F].printLn(s"Hottest city found so far is $hottest")
} yield ()
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 112
117. Can we do better?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 117
118. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect1[A] = ReaderT[Effect0, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 118
119. private[twm] class AtomicMonadState[S](atomic: Atomic[S])
extends MonadState[Task, S] {
val monad: Monad[Task] = Monad[Task]
def get: Task[S] = Task.delay(atomic.get)
def set(s: S): Task[Unit] = Task.delay(atomic.set(s))
def inspect[A](f: S => A): Task[A] = Task.delay(f(atomic.get))
def modify(f: S => S): Task[Unit] = Task.delay(atomic.transform(f))
}
object AtomicMonadState {
def create[S <: AnyRef](s: S): AtomicMonadState[S] =
new AtomicMonadState(AtomicAny[S](s))
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 119
120. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect1[A] = ReaderT[Effect0, Config, A]
type Effect[A] = StateT[Effect1, Requests, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 120
121. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect[A] = ReaderT[Effect0, Config, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
implicit val requestsState =
AtomicMonadState.create(Requests.empty)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 121
122. object ApplicativeAsk {
def constant[
F[_]:Applicative, E
](e: E): ApplicativeAsk[F, E] =
new DefaultApplicativeAsk[F, E] {
val applicative: Applicative[F] = Applicative[F]
def ask: F[E] = applicative.point(e)
}
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 122
123. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect0[A] = EitherT[Task, Error, A]
type Effect[A] = ReaderT[Effect0, Config, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
implicit val requestsState =
AtomicMonadState.create(Requests.empty)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 123
124. def main(args: Array[String]): Unit = {
val config = Config("localhost", 8080)
val requests = Requests.empty
type Effect[A] = EitherT[Task, Error, A]
implicit val weather = Weather.monixWeather(config)
implicit val console = Console.monixConsole
implicit val requestsState =
AtomicMonadState.create(Requests.empty)
implicit val configAsk =
ApplicativeAsk.constant[Task, Config](config)
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 124
127. Can we do better?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 127
128. type Effect[A] = EitherT[Task, Error, A]
Last frontier
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 128
137. Thank you very much
Pawel Szulc
@rabbitonweb
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 137