The document discusses the transition from imperative to declarative programming. It begins by introducing the speakers and defining imperative and declarative programming. It then demonstrates imperative code using futures and options in Scala. It shows how to make the code more functional by using folds and pattern matching. It introduces the idea of using monads and transformers to make the code more generic and modular. It discusses how this allows flexible switching between implementations like Future and Task. Finally, it discusses using StateT to model application state in a pure functional way.
66. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
67. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
val playerRepositoryTask = new PlayerRepository[Task]
68. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
val playerRepositoryTask = new PlayerRepository[Task]
val playerRepositoryId = new PlayerRepository[Id]
69. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
val playerRepositoryTask = new PlayerRepository[Task]
val playerRepositoryId = new PlayerRepository[Id]
val playerRepositoryTry = new PlayerRepository[Try]
70. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
val playerRepositoryTask = new PlayerRepository[Task]
val playerRepositoryId = new PlayerRepository[Id]
val playerRepositoryTry = new PlayerRepository[Try]
class FuturePlayerRepository
extends PlayerRepository[Future] {
def search(playerId: PlayerId): Future[Option[Player]]
def insert(player: Player): Future[Unit]
}
71. trait PlayerRepository[P[_]] {
def search(playerId: PlayerId): P[Option[Player]]
def insert(player: Player): P[Unit]
}
val playerRepositoryFuture = new PlayerRepository[Future]
val playerRepositoryTask = new PlayerRepository[Task]
val playerRepositoryId = new PlayerRepository[Id]
val playerRepositoryTry = new PlayerRepository[Try]
class FuturePlayerRepository
extends PlayerRepository[Future] {
def search(playerId: PlayerId): Future[Option[Player]]
def insert(player: Player): Future[Unit]
}
72. final class PlayerCardsFinder(playerRepository: PlayerRepository){
def find(playerId: PlayerId): Future[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(Future.failed(PlayerNotFound(playerId)))(
player => Future.successful((player.firstCard, player.secondCard)))
)
}
73. final class PlayerCardsFinder(playerRepository: PlayerRepository){
def find(playerId: PlayerId): Future[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(Future.failed(PlayerNotFound(playerId)))(
player => Future.successful((player.firstCard, player.secondCard)))
)
}
final class PlayerCardsFinder[P[_]: Monad](
playerRepository: PlayerRepository[P]) {
def find(playerId: PlayerId): P[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(
MonadErrorThrowable[P].raiseError(PlayerNotFound(playerId)))(player =>
(player.firstCard, player.secondCard).pure[P]))
}
74. final class PlayerCardsFinder(playerRepository: PlayerRepository){
def find(playerId: PlayerId): Future[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(Future.failed(PlayerNotFound(playerId)))(
player => Future.successful((player.firstCard, player.secondCard)))
)
}
final class PlayerCardsFinder[P[_]: Monad](
playerRepository: PlayerRepository[P]) {
def find(playerId: PlayerId): P[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(
MonadErrorThrowable[P].raiseError(PlayerNotFound(playerId)))(player =>
(player.firstCard, player.secondCard).pure[P]))
}
75. final class PlayerCardsFinder(playerRepository: PlayerRepository){
def find(playerId: PlayerId): Future[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(Future.failed(PlayerNotFound(playerId)))(
player => Future.successful((player.firstCard, player.secondCard)))
)
}
final class PlayerCardsFinder[P[_]: Monad](
playerRepository: PlayerRepository[P]) {
def find(playerId: PlayerId): P[(Card, Card)] =
playerRepository
.search(playerId)
.flatMap(
_.fold(
MonadErrorThrowable[P].raiseError(PlayerNotFound(playerId)))(player =>
(player.firstCard, player.secondCard).pure[P]))
}
102. “should create a poker game" in {
val command = CreatePokerGameCommandStub.random()
shouldNotFindPokerGame(…)
shouldInsertPokerGame(…)
(1 to command.amountOfPlayers).foreach {
shouldInsertPlayer(…)
}
commandHandler.handle(command) should beSuccessfulFuture
}
103. “should create a poker game" in {
val command = CreatePokerGameCommandStub.random()
shouldNotFindPokerGame(…)
shouldInsertPokerGame(…)
(1 to command.amountOfPlayers).foreach {
shouldInsertPlayer(…)
}
commandHandler.handle(command) should beSuccessfulFuture
}
104. “should create a poker game" in {
val command = CreatePokerGameCommandStub.random()
shouldNotFindPokerGame(…)
shouldInsertPokerGame(…)
(1 to command.amountOfPlayers).foreach {
shouldInsertPlayer(…)
}
commandHandler.handle(command) should beSuccessfulFuture
}
105. “should create a poker game" in {
val command = CreatePokerGameCommandStub.random()
shouldNotFindPokerGame(…)
shouldInsertPokerGame(…)
(1 to command.amountOfPlayers).foreach {
shouldInsertPlayer(…)
}
commandHandler.handle(command) should beSuccessfulFuture
}
114. case class PokerState(
pokerGameRepositoryState: Map[GameId, PokerGame],
playerRepositoryState: Map[GameId, Map[PlayerId, Player]]
)
val pokerGameRepository = new PokerGameRepository[State] {
override def insert(pokerGame: PokerGame): State[Unit] =
State[Unit].changeState(
state => (state.withGame(pokerGame), Unit)
)
}
type State[A] = StateT[Exception, PokerState, A]
115. "create a poker game" in {
val command = CreatePokerGameCommandStub.random()
var initialState = PokerState.empty
var finalState = PokerState.empty
finalState = finalState.withGame(…)
(1 to command.amountOfPlayers).foreach { _ =>
finalState = finalState.withPlayer(…)
}
commandHandler.handle(command) should
beValidWithState(initialState, finalState)
}
116. "create a poker game" in {
val command = CreatePokerGameCommandStub.random()
var initialState = PokerState.empty
var finalState = PokerState.empty
finalState = finalState.withGame(…)
(1 to command.amountOfPlayers).foreach { _ =>
finalState = finalState.withPlayer(…)
}
commandHandler.handle(command) should
beValidWithState(initialState, finalState)
}
117. "create a poker game" in {
val command = CreatePokerGameCommandStub.random()
var initialState = PokerState.empty
var finalState = PokerState.empty
finalState = finalState.withGame(…)
(1 to command.amountOfPlayers).foreach { _ =>
finalState = finalState.withPlayer(…)
}
commandHandler.handle(command) should
beValidWithState(initialState, finalState)
}
118. "create a poker game" in {
val command = CreatePokerGameCommandStub.random()
var initialState = PokerState.empty
var finalState = PokerState.empty
finalState = finalState.withGame(…)
(1 to command.amountOfPlayers).foreach { _ =>
finalState = finalState.withPlayer(…)
}
commandHandler.handle(command) should
beValidWithState(initialState, finalState)
}
119. "create a poker game" in {
val command = CreatePokerGameCommandStub.random()
var initialState = PokerState.empty
var finalState = PokerState.empty
finalState = finalState.withGame(…)
(1 to command.amountOfPlayers).foreach { _ =>
finalState = finalState.withPlayer(…)
}
commandHandler.handle(command) should
beValidWithState(initialState, finalState)
}
130. Futures are not lazy
We are iterating over a list and then
inserting players
131. Futures are not lazy
We are iterating over a list and then
inserting players
But we’re not doing anything with those
futures!!!
132. Futures are not lazy
We are iterating over a list and then
inserting players
But we’re not doing anything with those
futures!!!
We lose control over when they will
execute. Race conditions!!!!
133. Mocks only return our expected result,
they don't simulate the application behaviour