SlideShare una empresa de Scribd logo
1 de 138
Descargar para leer sin conexión
TRANSACTION IS A MONADTRANSACTION IS A MONAD
JAREK RATAJSKIJAREK RATAJSKI
Software Developer, Wizard, AnarchitectSoftware Developer, Wizard, AnarchitectSoftware Developer, Wizard, Anarchitect
at Engenius GmbHat Engenius GmbHat Engenius GmbH
A STORYA STORY
public static void main(String[] args) {
//take mushrooms from the baskets,
// but not more than 20 from each
List<Integer> mushrooms = new ArrayList<>(
Arrays.asList(14, 52, 31, 62, 71, 22, 34));
int sum = 0;
int i = 0;
while (i < 6) {
if (mushrooms.get(i++) > 20) {
sum = sum + 20;
} else {
sum = sum + mushrooms.get(i);
}
}
System.out.println(sum);
}
And there are bugsAnd there are bugsAnd there are bugs
public static void main(String[] args) {
//take mushrooms from the baskets,
// but not more than 20 from each
List<Integer> mushrooms = new ArrayList<>(
Arrays.asList(14, 52, 31, 62, 71, 22, 34));
int sum = 0;
int i = 0;
while (i < 6) {
if (mushrooms.get(i++) > 20) {
sum = sum + 20;
} else {
sum = sum + mushrooms.get(i);
}
}
System.out.println(sum);
}
bugs in this code have their own bugsbugs in this code have their own bugsbugs in this code have their own bugs
HOW NOT TOHOW NOT TO HAVE BUGS?HAVE BUGS?
You have to be carefulYou have to be carefulYou have to be careful
It does not workIt does not workIt does not work
One cannot be careful 8One cannot be careful 8One cannot be careful 8 hours a dayhours a dayhours a day
ve days a weekve days a weekve days a week
Week after weekWeek after weekWeek after week
Errors will happenErrors will happenErrors will happen
ARE WE DOOMED?ARE WE DOOMED?
PREVENTING BUGSPREVENTING BUGS
PREVENTING BUGSPREVENTING BUGS
write tests
PREVENTING BUGSPREVENTING BUGS
write tests
use the power of type system
PREVENTING BUGSPREVENTING BUGS
write tests
use the power of type system
use safer language features
PREVENTING BUGSPREVENTING BUGS
write tests
use the power of type system
use safer language features
use safer languages
PREVENTING BUGSPREVENTING BUGS
write tests
use the power of type system
use safer language features
use safer languages
...
TESTSTESTS
they do work
additional code
refactoring paradox
Safer languages / safer featuresSafer languages / safer featuresSafer languages / safer features
LAMBDASLAMBDAS
public static void main(String[] args) {
//take mushrooms from the baskets,
// but not more than 20 from each
final List<Integer> mushrooms = new ArrayList<>(
Arrays.asList(14, 52, 31, 62, 71, 22, 34));
final int sum = mushrooms.stream()
.map(m -> Math.min(m, 20))
.reduce(0, (a, b) -> a + b);
System.out.println(sum);
}
BETTERBETTER
BETTERBETTER
less imperative
BETTERBETTER
less imperative
more declarative
BETTERBETTER
less imperative
more declarative
no mutation
BETTERBETTER
less imperative
more declarative
no mutation
separation of concerns
THIS IS ONLY THE BEGINNINGTHIS IS ONLY THE BEGINNING
MUTANTSMUTANTS
public static void main(String[] args) {
//take mushrooms from the baskets,
// but not more than 20 from each
final List<Integer> mushrooms = new ArrayList<>(
Arrays.asList(14, 52, 31, 62, 71, 22, 34)
);
int avg = calcAverage(mushrooms);
final int sum = mushrooms.stream()
.map(m -> Math.min(m, 20))
.reduce(0, (a, b) -> a + b);
System.out.println(sum);
}
FIGHTING MUTANTS WITHFIGHTING MUTANTS WITH
IMMUTABLE LISTSIMMUTABLE LISTS
List<String> a = [...] ;
a.add("sth"); //no change on a (futile operation)
List<String> a = [...] ;
List<String> b = a.add("sth");
VAVRVAVR
final io.vavr.List<Integer> mushrooms =
List.of ( 14, 52, 31, 62, 71, 22, 34);
int avg = calcAverage(mushrooms); //no problem
final int sum = mushreooms
.map(m -> Math.max(m, 20))
.fold(0, (a,b)-> a+b);
System.out.println(sum);
IMMUTABLE (FUNCTIONAL) LISTIMMUTABLE (FUNCTIONAL) LIST
MUTABLEMUTABLE JAVAJAVA
public class Person {
private String firstName;
private String lastName;
private BigDecimal salary;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public BigDecimal getSalary() {
return salary;
}
Immutability in javaImmutability in javaImmutability in java
public class PersonImmutable {
public final String firstName;
public final String lastName;
public final BigDecimal salary;
public PersonImmutable(
String firstName,
String lastName,
BigDecimal salary) {
this.firstName = firstName;
this.lastName = lastName;
this.salary = salary;
}
PersonImmutable withSalary(BigDecimal newSalary) {
return new PersonImmutable(
this.firstName,
this.lastName,
newSalary);
}
}
Immutability inImmutability inImmutability in kotlinkotlinkotlin
data class Person(
val firstName: String,
val lastName : String,
val salary: BigDecimal)
val x:Person = ...
val y = x.copy( salary = BigDecimal("15000"))
Java recordsJava recordsJava records
record Person(
String firstName,
String lastName,
BigDecimal salary )
var x = ...
//val y = x.copy( salary = BigDecimal("15000"))
Two great things You can do to avoid bugsTwo great things You can do to avoid bugsTwo great things You can do to avoid bugs
stop using java.util. collections
prefer immutable objects
use Kotlin
NEXT STEPNEXT STEP
ASPECTSASPECTS
transactions
security
diagnostics
resource handling
FANTASTICFANTASTIC
FRAMEWORKSFRAMEWORKS
Fantastic frameworksFantastic frameworksFantastic frameworks
What do they promise:What do they promise:What do they promise:
concentrate on business logic
let the magic work
inject whatever wherever
Fantastic frameworksFantastic frameworksFantastic frameworks
What they really do:What they really do:What they really do:
leaking abstractions
mess in code
hard to debug problems
null pointer exceptions
slow tests
@Retryable
void myMethod () {
}
@Transactional
void myMethod () {
}
@Transactional
@Retryable
void myMethod () {
}
Is retry inside transaction or transaction insideIs retry inside transaction or transaction insideIs retry inside transaction or transaction inside
retry?retry?retry?
@Transactional
@Retryable
void myMethod () {
}
Libraries > FrameworksLibraries > FrameworksLibraries > Frameworks
What typical coder thinks when you say you doWhat typical coder thinks when you say you doWhat typical coder thinks when you say you do
not use framework to do transactionnot use framework to do transactionnot use framework to do transaction
code fromcode fromcode from
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
throws SQLException {
PreparedStatement updateSales = null;
PreparedStatement updateTotal = null;
String updateString =
"update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
String updateStatement =
"update " + dbName + ".COFFEES " +
"set TOTAL = TOTAL + ? " +
"where COF_NAME = ?";
try {
con.setAutoCommit(false);
updateSales = con.prepareStatement(updateString);
updateTotal = con.prepareStatement(updateStatement);
for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch(SQLException excep) {
JDBCTutorialUtilities.printSQLException(excep);
}
}
} finally {
https://docs.oracle.com/javase/tutorial/jdbc/bashttps://docs.oracle.com/javase/tutorial/jdbc/bashttps://docs.oracle.com/javase/tutorial/jdbc/bas
HIGHER ORDERHIGHER ORDER
FUNCTIONFUNCTION
JOOQ ExampleJOOQ ExampleJOOQ Example
create.transaction(configuration -> {
AuthorRecord author =
DSL.using(configuration)
.insertInto(AUTHOR, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.values("George", "Orwell")
.returning()
.fetchOne();
DSL.using(configuration)
.insertInto(BOOK, BOOK.AUTHOR_ID, BOOK.TITLE)
.values(author.getId(), "1984")
.values(author.getId(), "Animal Farm")
.execute();
// Implicit commit executed here
});
Hibernate can do that as wellHibernate can do that as wellHibernate can do that as well
TransactionUtil.doInHibernate( this::sessionFactory, session -
...
session.persist( item );
...
} );
SECURITYSECURITY
private <T> void renderSecure(Context ctx,
Function<Session, CompletionStage<T>> async
) {
final Option<String> bearer = Option.of(ctx.getRequest().getHeaders().get("Authorizatio
final Option<String> sessionId = bearer.map(b -> b.replace("Bearer ", ""));
final Option<Session> session = sessionId.flatMap(sessionsRepo::getSession);
ctx.render(JsonMapping.toJsonPromise(session.map(
sess -> (CompletionStage<Object>) async.apply(sess)
)
.getOrElse(
CompletableFuture.completedFuture("unauthorized")
)));
}
private Handler movePaddle() {
return ctx -> {
final String gameId = ctx.getPathTokens().get("id");
ctx.getRequest().getBody().then(body -> {
final float targetY = Float.parseFloat(body.getText());
renderSecure(ctx, session -> gamesRepo.movePaddle(gameId, session.userId, targe
});
};
}
https://github.com/javaFunAgain/ratpong/blob/mhttps://github.com/javaFunAgain/ratpong/blob/mhttps://github.com/javaFunAgain/ratpong/blob/m
What if want:What if want:What if want:
a function that is checked for permissions,
does database access,
and is monitored,
and ...
It can get messyIt can get messyIt can get messy
monitor( ()- >
renderSecure( securityContext, (user) ->
doInTransaction (dbSession -> {
}
....
AlternativeAlternativeAlternative
doInMyMightyStack( callContext -> {
System.out.printn("User is " + callContext.getUser
callContext.dataAccess.insert( )
})
But what if:But what if:But what if:
secure(
trx(
secure(
trx(
cache(
If we only had sth toIf we only had sth toIf we only had sth to chainchainchain such e ectssuch e ectssuch e ects
SIDE EFFECTSSIDE EFFECTS
public void notifyGroupOwner(int userId) {
final User user = userService.getUserFromDB(10001);
final Group group = user.getGroup();
final User owner = group.getGroupOwner();
owner.sendMessage("User "+ userId
+ " is not eating mushrooms");
}
side e ects everywhereside e ects everywhereside e ects everywhere
public void notifyGroupOwner(int userId) {
final User user = userService.getUserFromDB(10001);
//may return null, throw exception
final Group group = user.getGroup();
// may return null, is in transaction, throws sometimes excep
final User owner = group.getGroupOwner();
//may return null, may come from cache
owner.sendMessage("User "+ userId + " to straszny niejadek!");
//called async
}
NULLNULLNULL - I call it my billion-dollar mistake- I call it my billion-dollar mistake- I call it my billion-dollar mistake
handling missing datahandling missing datahandling missing data
User user = userService.getUserFromDB(10001);
if ( user != null) {
final Group group = user.getGroup();
if ( group != null ) {
final User owner = group.getGroupOwner();
if ( owner != null ) {
owner.sendMessage("hello there!!!");
}
}
}
WHAT IF WE COULD MAKEWHAT IF WE COULD MAKE
PROBLEMS VISIBLE?PROBLEMS VISIBLE?
root of the problemroot of the problemroot of the problem
User getUserFromDB(int userId) {....
Optional<User> UserService::getUserFromDB(int userId) ...
Optional<Group> User::getGroup(int groupId) ...
Optional<User> Grouo::getGroupOwner() ...
better,better,better,
Optional<User> user = userService.getUserFromDB(10001);
if ( user.isPresent()) {
final Optional<Group> group = user.get().getGroup();
if ( group.isPresent() ) {
final Optional<User> owner = group.get().getGroupOwner
if ( owner.isPresent() ) {
owner.sendMessage("hello there!!!");
}
}
}
better,better,better,
not smart at allnot smart at allnot smart at all
Optional<User> user = userService.getUserFromDB(10001);
if ( user.isPresent()) {
final Optional<Group> group = user.get().getGroup();
if ( group.isPresent() ) {
final Optional<User> owner = group.get().getGroupOwner
if ( owner.isPresent() ) {
owner.sendMessage("hello there!!!");
}
}
}
mapmapmap operationoperationoperation
Optional<String> str = "55";
Optional<Integer> x = str.map( v -> Integer.parseInt(v));
mapmapmap went wrongwent wrongwent wrong
Optional<User> user = userService.getUserFromDB(10001);
Optional<Optional<Group>> group = user.map(u -> u.getGroup());
mapmapmap went wrongwent wrongwent wrong
Optional<User> user = userService.getUserFromDB(10001);
Optional<Optional<Group>> group = user.map(u -> u.getGroup());
flatMapflatMapflatMap to the rescueto the rescueto the rescue
Optional<User> user = userService.getUserFromDB(10001);
Optional<Group> group = user.flatMap(u -> u.getGroup());
Awesome!Awesome!Awesome!
userService.getUserFromDB(10001)
.flatMap(user -> user.getGroup() )
.flatMap(group -> group.getGroupOwner() )
.ifPresent(user-> user.sendMessage("Hallo there Code4Life!
OptionalOptional
declaration of missing data side e ect
basic handling (code) in
map
flatMap
getOrDefault
THAT IS A POWER OFTHAT IS A POWER OF
MONADMONAD
FAILURE - EXCEPTIONSFAILURE - EXCEPTIONS
class Group {
User getGroupOwner() throws NoOwnerException {...
}
}
class User {
Group getGroup() throws NoGroupMemberException {...
}
void sendMessage(String msg) {...
}
}
class UserService {
User getUserFromDB(long key) throws NoSuchUserException {
}
Either<DataError, User> UserService.getUserFromDB(int userId);
// + other services
enum DataError {
NoOwner, NoGroup, NoUser
}
userService.getUserFromDB(10001)
.flatMap(user -> user.getGroup() )
.flatMap(group -> group.getGroupOwner() )
.forEach(user->
user.sendMessage("Hallo there Code4Life,"
+ "there was no problem !")); //on success
A MONADA MONAD
de nition 1 - formalde nition 1 - formalde nition 1 - formal
aaa monadmonadmonad in X is just a monoid in thein X is just a monoid in thein X is just a monoid in the
category of endofunctors of X, withcategory of endofunctors of X, withcategory of endofunctors of X, with
product × replaced by compositionproduct × replaced by compositionproduct × replaced by composition
of endofunctors and unit set byof endofunctors and unit set byof endofunctors and unit set by
the identity endofunctor.the identity endofunctor.the identity endofunctor.
MONADMONAD
de nition 2 - by codede nition 2 - by codede nition 2 - by code
Something that hasSomething that hasSomething that has
flatMap,
map
of (pure)
Something that hasSomething that hasSomething that has
flatMap,
map
of (pure)
TypeclassTypeclassTypeclass
MONADMONAD
de nition 3 - by comparisonde nition 3 - by comparisonde nition 3 - by comparison
CONTAINERSCONTAINERS
String
a type
List<String>
a container of String
Set<String>
a container of String
ArrayList<String>
a container of String
CONTAINERCONTAINER
aaa TypeTypeType + multiplicity+ multiplicity+ multiplicity e ecte ecte ect
MONADMONAD
aaa TypeTypeType +++ somesomesome e ecte ecte ect
EFFECTSEFFECTS
missing data
fails, exceptions
asynchronous computations
multiplicity
transactions
dependency injections
+ combinations+ combinations+ combinations
MORE THEORYMORE THEORY
FunctorFunctorFunctor - is a type(class) that has- is a type(class) that has- is a type(class) that has mapmapmap
Every monad is a functorEvery monad is a functorEvery monad is a functor
Some people call functorSome people call functorSome people call functor mappablemappablemappable
Applicative functorApplicative functorApplicative functor
Something between monad and functorSomething between monad and functorSomething between monad and functor
HasHasHas apapap functionfunctionfunction
ap(F<(A)->B>,F<A>)ap(F<(A)->B>,F<A>)ap(F<(A)->B>,F<A>) -> F<B>-> F<B>-> F<B>
MONADS, FUNCTOR MAKE SIDEMONADS, FUNCTOR MAKE SIDE
EFFECTS EXPLICITLY VISIBLE IN AEFFECTS EXPLICITLY VISIBLE IN A
TYPE SYSTEMTYPE SYSTEM
Applicative functor lets youApplicative functor lets youApplicative functor lets you sumsumsum inside e ectsinside e ectsinside e ects
(potentially parallel sequence)(potentially parallel sequence)(potentially parallel sequence)
Monadic atMap sequences operation one byMonadic atMap sequences operation one byMonadic atMap sequences operation one by
oneoneone
If You have 2 independent db transactions,If You have 2 independent db transactions,If You have 2 independent db transactions,
that can be called concurrently - You can usethat can be called concurrently - You can usethat can be called concurrently - You can use
apapap
If operations on db depend on some otherIf operations on db depend on some otherIf operations on db depend on some other
transaction (success/rollback) use atMaptransaction (success/rollback) use atMaptransaction (success/rollback) use atMap
Monadic transactionMonadic transactionMonadic transaction
It uses JDBI
one of many possible implementations (very
simple)
class Transaction<A> (private val action : (Handle) -> A ) {
fun run(dbi : Jdbi) : A = dbi.inTransaction<A, RuntimeExce
fun <B> map ( f: (A)->B) = Transaction {handle ->
f(action(handle))
}
fun <B> flatMap( f: (A)->Transaction<B>) = Transaction {h
f(action(handle)).action(handle)
}
companion object {
fun <T> pure (obj:T) = Transaction {
obj
Example useExample useExample use
internal fun selectNewJob () = Transaction{
dbHandle ->
dbHandle.createQuery(selectNextJobId)
.mapTo(Long::class.java)
.one()
.map { jobId ->
dbHandle.createUpdate(updateBatch).bind("b
.execute()
jobId
}
}.flatMap { jobId ->
selectNewTasks(jobId)
}
The same can be done with other aspectsThe same can be done with other aspectsThe same can be done with other aspects
/e ects/e ects/e ects
security
cache
diagnostics/monitoring
async
That is a lot ofThat is a lot ofThat is a lot of flatMapflatMapflatMap
Would be better if we hadWould be better if we hadWould be better if we had do notationdo notationdo notation
Four great things You can do to avoid bugsFour great things You can do to avoid bugsFour great things You can do to avoid bugs
stop using java.util. collections
prefer immutable objects
learn lambdas and higher order functions
use kotlin
use monads
use scala
How do we chain multiple e ects?How do we chain multiple e ects?How do we chain multiple e ects?
the worstthe worstthe worst flatMapflatMapflatMap ever...ever...ever...
Future<Secure<Transaction<Optional<Int>>>> myX;
Function<Int, Future<Secure<Transaction<Optional<String>>>>> f
myX.flatMap(f) // ...no go
Function<Secure<Transaction<Optional<Int>>>>, Future<Secure<Tr
Problem of stacking monadsProblem of stacking monadsProblem of stacking monads
Monad TransformersMonad TransformersMonad Transformers FutureTFutureTFutureT,,, OptionTOptionTOptionT
Monad transformers appeared to not be thatMonad transformers appeared to not be thatMonad transformers appeared to not be that
greatgreatgreat
We cannot have use them in kotlin or java (in aWe cannot have use them in kotlin or java (in aWe cannot have use them in kotlin or java (in a
sensible way)sensible way)sensible way)
AllMightyMonadAllMightyMonadAllMightyMonad
write the monad that has all the e ects for
you stack (cache, jdbc, async, etc.)
cry once (writing atMap) enjoy everywhere
Alternatives?Alternatives?Alternatives?
1. zioziozio
2. https://github.com/atnos-org/e /https://github.com/atnos-org/e /https://github.com/atnos-org/e /
No, we will not hack bytecodeNo, we will not hack bytecodeNo, we will not hack bytecode
Project neeProject neeProject nee
class Hasiok {
@Resource
val jdbcConnection: Connection
@Transactional
@Secure
@Cacheable
@Retryable
fun f(p:P) {
//code
}
}
class Hasiok {
val enterprisyF = Nee.pure(
secure
.and(retryable)
.and(cacheable)
.and(transactional)) {conn:ConnectionProvider ->
{p: P ->
//code
}
}
//declaration above means security is checked before retri
//and retrial is made before cache which happens before tr
}
fun readStone() = Nee.pure(cached.andThen(jdbc)) { jdbcProvide
{ id: StoneId ->
val dsl = DSL.using(jdbcProvider.getConnection().g
val record = dsl.selectFrom(Stones.STONES)
.where(Stones.STONES.ID.eq(id))
.fetchOneInto(Stones.STONES)
Stone(record.id, StoneData(record.name, record.pri
}
}.anyError()
fun addNewStone(newStone: StoneData) = seq.next().flatMap
addStone(it, newStone)
}
private fun addStone(stoneId: Long, newStone: StoneData) =
val dsl = DSL.using(jdbcProvider.getConnection().getRe
val insertedRows = dsl.insertInto(Stones.STONES)
.values(stoneId, newStone.name, newStone.price)
.execute()
if (insertedRows == 1) {
Option.some(stoneId)
} else {
Option.none()
}
}
Nee is a hobby, experimental project.Nee is a hobby, experimental project.Nee is a hobby, experimental project.
NeeNeeNee monadmonadmonad has 4 params:has 4 params:has 4 params:
Nee<R,E,P,A>Nee<R,E,P,A>Nee<R,E,P,A>
- R - environment (context)
- E - error type
- P - argument (for cache - I think this was a bas idea)
- A - the real result type
I am slowly adding e ects:I am slowly adding e ects:I am slowly adding e ects:
jdbc,
any transactional resource,
cache,
security,
diagnostic,
Fun!Fun!Fun!
DEPENDENCYDEPENDENCY
INJECTIONINJECTION
open class DocsModule(val config: ConfigModule) {
open val dbProvider: HandleProvider by lazy {
DBProvider(config.cfg)
}
open val timeProvider: TimeProvider by lazy { RealTime }
open val jobsRepository by lazy { JobsRepository(dbProvide
open val zipWriter : StatusZipWriter by lazy {
StatusZipWriter(config)
}
open val docsStatusRepository : DocsStatusRepository by la
MATERIALSMATERIALS
Project ZIOProject ZIOProject ZIO
Fun(c) 2018.7: John De Goes - FP to the MaxFun(c) 2018.7: John De Goes - FP to the MaxFun(c) 2018.7: John De Goes - FP to the Max
The Book of MonadsThe Book of MonadsThe Book of Monads Alejandro Serrano MenaAlejandro Serrano MenaAlejandro Serrano Mena
(experiment)(experiment)(experiment)
https://github.com/ziohttps://github.com/ziohttps://github.com/zio
https://www.youtube.com/watch?https://www.youtube.com/watch?https://www.youtube.com/watch?
v=sxudIMiOo68v=sxudIMiOo68v=sxudIMiOo68
https://github.com/nee ect/neehttps://github.com/nee ect/neehttps://github.com/nee ect/nee
FUNCTIONAL PROGRAMMINGFUNCTIONAL PROGRAMMING
Power of abstractionsPower of abstractionsPower of abstractions
we want to have less bugs
we want testability
we want to have more fun
@jarek000000@jarek000000@jarek000000
Thank youThank youThank you

Más contenido relacionado

La actualidad más candente

Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorProgramming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorFedor Lavrentyev
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFabio Collini
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to SwiftGiordano Scalzo
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinFabio Collini
 
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorProgramming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorFedor Lavrentyev
 
Google Guava for cleaner code
Google Guava for cleaner codeGoogle Guava for cleaner code
Google Guava for cleaner codeMite Mitreski
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my dayTor Ivry
 
6. Generics. Collections. Streams
6. Generics. Collections. Streams6. Generics. Collections. Streams
6. Generics. Collections. StreamsDEVTYPE
 
Google guava - almost everything you need to know
Google guava - almost everything you need to knowGoogle guava - almost everything you need to know
Google guava - almost everything you need to knowTomasz Dziurko
 
Nik Graf - Get started with Reason and ReasonReact
Nik Graf - Get started with Reason and ReasonReactNik Graf - Get started with Reason and ReasonReact
Nik Graf - Get started with Reason and ReasonReactOdessaJS Conf
 
Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2José Paumard
 
Kotlin from-scratch 2 - functions
Kotlin from-scratch 2 - functionsKotlin from-scratch 2 - functions
Kotlin from-scratch 2 - functionsFranco Lombardo
 

La actualidad más candente (20)

Kotlin Generation
Kotlin GenerationKotlin Generation
Kotlin Generation
 
Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorProgramming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
 
Google guava
Google guavaGoogle guava
Google guava
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
 
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorProgramming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
 
Kotlin
KotlinKotlin
Kotlin
 
Google Guava
Google GuavaGoogle Guava
Google Guava
 
Google Guava for cleaner code
Google Guava for cleaner codeGoogle Guava for cleaner code
Google Guava for cleaner code
 
Go ahead, make my day
Go ahead, make my dayGo ahead, make my day
Go ahead, make my day
 
6. Generics. Collections. Streams
6. Generics. Collections. Streams6. Generics. Collections. Streams
6. Generics. Collections. Streams
 
Why Learn Python?
Why Learn Python?Why Learn Python?
Why Learn Python?
 
Google guava - almost everything you need to know
Google guava - almost everything you need to knowGoogle guava - almost everything you need to know
Google guava - almost everything you need to know
 
Developing iOS apps with Swift
Developing iOS apps with SwiftDeveloping iOS apps with Swift
Developing iOS apps with Swift
 
Kotlin standard
Kotlin standardKotlin standard
Kotlin standard
 
Nik Graf - Get started with Reason and ReasonReact
Nik Graf - Get started with Reason and ReasonReactNik Graf - Get started with Reason and ReasonReact
Nik Graf - Get started with Reason and ReasonReact
 
Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2
 
Kotlin from-scratch 2 - functions
Kotlin from-scratch 2 - functionsKotlin from-scratch 2 - functions
Kotlin from-scratch 2 - functions
 
Scala introduction
Scala introductionScala introduction
Scala introduction
 

Similar a Transaction is a monad

ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wildJoe Morgan
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql JOYITAKUNDU1
 
Beauty and the beast - Haskell on JVM
Beauty and the beast  - Haskell on JVMBeauty and the beast  - Haskell on JVM
Beauty and the beast - Haskell on JVMJarek Ratajski
 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfcalderoncasto9163
 
GeoGebra JavaScript CheatSheet
GeoGebra JavaScript CheatSheetGeoGebra JavaScript CheatSheet
GeoGebra JavaScript CheatSheetJose Perez
 
AJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleAJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleChristopher Curtin
 
Java 8 Puzzlers [as presented at OSCON 2016]
Java 8 Puzzlers [as presented at  OSCON 2016]Java 8 Puzzlers [as presented at  OSCON 2016]
Java 8 Puzzlers [as presented at OSCON 2016]Baruch Sadogursky
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with GroovyArturo Herrero
 
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersSoftshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersMatthew Farwell
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript IntroductionDmitry Sheiko
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...Naresha K
 

Similar a Transaction is a monad (20)

ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
 
Miracle of std lib
Miracle of std libMiracle of std lib
Miracle of std lib
 
Eta
EtaEta
Eta
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql
 
Beauty and the beast - Haskell on JVM
Beauty and the beast  - Haskell on JVMBeauty and the beast  - Haskell on JVM
Beauty and the beast - Haskell on JVM
 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
 
GeoGebra JavaScript CheatSheet
GeoGebra JavaScript CheatSheetGeoGebra JavaScript CheatSheet
GeoGebra JavaScript CheatSheet
 
ES6 Overview
ES6 OverviewES6 Overview
ES6 Overview
 
AJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleAJUG April 2011 Cascading example
AJUG April 2011 Cascading example
 
Java 8 Puzzlers [as presented at OSCON 2016]
Java 8 Puzzlers [as presented at  OSCON 2016]Java 8 Puzzlers [as presented at  OSCON 2016]
Java 8 Puzzlers [as presented at OSCON 2016]
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersSoftshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
What is new in Java 8
What is new in Java 8What is new in Java 8
What is new in Java 8
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
 
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...
Effective Java with Groovy & Kotlin How Languages Influence Adoption of Good ...
 
ddd+scala
ddd+scaladdd+scala
ddd+scala
 

Más de Jarek Ratajski

Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021Jarek Ratajski
 
Spring, CDI, Jakarta EE good parts
Spring, CDI, Jakarta EE good partsSpring, CDI, Jakarta EE good parts
Spring, CDI, Jakarta EE good partsJarek Ratajski
 
Eta lang Beauty And The Beast
Eta lang Beauty And The Beast Eta lang Beauty And The Beast
Eta lang Beauty And The Beast Jarek Ratajski
 
Another programming language - jeszcze jeden język
Another programming language - jeszcze jeden językAnother programming language - jeszcze jeden język
Another programming language - jeszcze jeden językJarek Ratajski
 
Fighting null with memes
Fighting null with memesFighting null with memes
Fighting null with memesJarek Ratajski
 
Geecon walking in CODE
Geecon walking in CODEGeecon walking in CODE
Geecon walking in CODEJarek Ratajski
 
Scalaworld lambda core hardcore
Scalaworld lambda core hardcoreScalaworld lambda core hardcore
Scalaworld lambda core hardcoreJarek Ratajski
 
Jdd 2016 DROP DATABASE
Jdd 2016 DROP DATABASEJdd 2016 DROP DATABASE
Jdd 2016 DROP DATABASEJarek Ratajski
 

Más de Jarek Ratajski (14)

respect-estimates.pdf
respect-estimates.pdfrespect-estimates.pdf
respect-estimates.pdf
 
Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021
 
Lambda hardcore
Lambda hardcoreLambda hardcore
Lambda hardcore
 
Scala to assembly
Scala to assemblyScala to assembly
Scala to assembly
 
Spring, CDI, Jakarta EE good parts
Spring, CDI, Jakarta EE good partsSpring, CDI, Jakarta EE good parts
Spring, CDI, Jakarta EE good parts
 
Eta lang Beauty And The Beast
Eta lang Beauty And The Beast Eta lang Beauty And The Beast
Eta lang Beauty And The Beast
 
Another programming language - jeszcze jeden język
Another programming language - jeszcze jeden językAnother programming language - jeszcze jeden język
Another programming language - jeszcze jeden język
 
Fighting null with memes
Fighting null with memesFighting null with memes
Fighting null with memes
 
Geecon walking in CODE
Geecon walking in CODEGeecon walking in CODE
Geecon walking in CODE
 
Scalaworld lambda core hardcore
Scalaworld lambda core hardcoreScalaworld lambda core hardcore
Scalaworld lambda core hardcore
 
Lambda core
Lambda coreLambda core
Lambda core
 
[4 dev] lagom
[4 dev] lagom[4 dev] lagom
[4 dev] lagom
 
Jdd 2016 DROP DATABASE
Jdd 2016 DROP DATABASEJdd 2016 DROP DATABASE
Jdd 2016 DROP DATABASE
 
DROPDB Galactic story
DROPDB Galactic storyDROPDB Galactic story
DROPDB Galactic story
 

Último

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 

Último (20)

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 

Transaction is a monad

  • 1. TRANSACTION IS A MONADTRANSACTION IS A MONAD
  • 2. JAREK RATAJSKIJAREK RATAJSKI Software Developer, Wizard, AnarchitectSoftware Developer, Wizard, AnarchitectSoftware Developer, Wizard, Anarchitect at Engenius GmbHat Engenius GmbHat Engenius GmbH
  • 4.
  • 5.
  • 6. public static void main(String[] args) { //take mushrooms from the baskets, // but not more than 20 from each List<Integer> mushrooms = new ArrayList<>( Arrays.asList(14, 52, 31, 62, 71, 22, 34)); int sum = 0; int i = 0; while (i < 6) { if (mushrooms.get(i++) > 20) { sum = sum + 20; } else { sum = sum + mushrooms.get(i); } } System.out.println(sum); }
  • 7. And there are bugsAnd there are bugsAnd there are bugs public static void main(String[] args) { //take mushrooms from the baskets, // but not more than 20 from each List<Integer> mushrooms = new ArrayList<>( Arrays.asList(14, 52, 31, 62, 71, 22, 34)); int sum = 0; int i = 0; while (i < 6) { if (mushrooms.get(i++) > 20) { sum = sum + 20; } else { sum = sum + mushrooms.get(i); } } System.out.println(sum); }
  • 8. bugs in this code have their own bugsbugs in this code have their own bugsbugs in this code have their own bugs
  • 9. HOW NOT TOHOW NOT TO HAVE BUGS?HAVE BUGS?
  • 10.
  • 11. You have to be carefulYou have to be carefulYou have to be careful
  • 12. It does not workIt does not workIt does not work
  • 13. One cannot be careful 8One cannot be careful 8One cannot be careful 8 hours a dayhours a dayhours a day ve days a weekve days a weekve days a week
  • 14. Week after weekWeek after weekWeek after week
  • 15. Errors will happenErrors will happenErrors will happen
  • 16. ARE WE DOOMED?ARE WE DOOMED?
  • 19. PREVENTING BUGSPREVENTING BUGS write tests use the power of type system
  • 20. PREVENTING BUGSPREVENTING BUGS write tests use the power of type system use safer language features
  • 21. PREVENTING BUGSPREVENTING BUGS write tests use the power of type system use safer language features use safer languages
  • 22. PREVENTING BUGSPREVENTING BUGS write tests use the power of type system use safer language features use safer languages ...
  • 23. TESTSTESTS they do work additional code refactoring paradox
  • 24.
  • 25.
  • 26. Safer languages / safer featuresSafer languages / safer featuresSafer languages / safer features
  • 27. LAMBDASLAMBDAS public static void main(String[] args) { //take mushrooms from the baskets, // but not more than 20 from each final List<Integer> mushrooms = new ArrayList<>( Arrays.asList(14, 52, 31, 62, 71, 22, 34)); final int sum = mushrooms.stream() .map(m -> Math.min(m, 20)) .reduce(0, (a, b) -> a + b); System.out.println(sum); }
  • 32. BETTERBETTER less imperative more declarative no mutation separation of concerns
  • 33. THIS IS ONLY THE BEGINNINGTHIS IS ONLY THE BEGINNING
  • 34. MUTANTSMUTANTS public static void main(String[] args) { //take mushrooms from the baskets, // but not more than 20 from each final List<Integer> mushrooms = new ArrayList<>( Arrays.asList(14, 52, 31, 62, 71, 22, 34) ); int avg = calcAverage(mushrooms); final int sum = mushrooms.stream() .map(m -> Math.min(m, 20)) .reduce(0, (a, b) -> a + b); System.out.println(sum); }
  • 35. FIGHTING MUTANTS WITHFIGHTING MUTANTS WITH IMMUTABLE LISTSIMMUTABLE LISTS
  • 36.
  • 37. List<String> a = [...] ; a.add("sth"); //no change on a (futile operation)
  • 38. List<String> a = [...] ; List<String> b = a.add("sth");
  • 39. VAVRVAVR final io.vavr.List<Integer> mushrooms = List.of ( 14, 52, 31, 62, 71, 22, 34); int avg = calcAverage(mushrooms); //no problem final int sum = mushreooms .map(m -> Math.max(m, 20)) .fold(0, (a,b)-> a+b); System.out.println(sum);
  • 41.
  • 42.
  • 44. public class Person { private String firstName; private String lastName; private BigDecimal salary; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public BigDecimal getSalary() { return salary; }
  • 45.
  • 46. Immutability in javaImmutability in javaImmutability in java public class PersonImmutable { public final String firstName; public final String lastName; public final BigDecimal salary; public PersonImmutable( String firstName, String lastName, BigDecimal salary) { this.firstName = firstName; this.lastName = lastName; this.salary = salary; } PersonImmutable withSalary(BigDecimal newSalary) { return new PersonImmutable( this.firstName, this.lastName, newSalary); } }
  • 47. Immutability inImmutability inImmutability in kotlinkotlinkotlin data class Person( val firstName: String, val lastName : String, val salary: BigDecimal) val x:Person = ... val y = x.copy( salary = BigDecimal("15000"))
  • 48. Java recordsJava recordsJava records record Person( String firstName, String lastName, BigDecimal salary ) var x = ... //val y = x.copy( salary = BigDecimal("15000"))
  • 49. Two great things You can do to avoid bugsTwo great things You can do to avoid bugsTwo great things You can do to avoid bugs stop using java.util. collections prefer immutable objects use Kotlin
  • 53. Fantastic frameworksFantastic frameworksFantastic frameworks What do they promise:What do they promise:What do they promise: concentrate on business logic let the magic work inject whatever wherever
  • 54. Fantastic frameworksFantastic frameworksFantastic frameworks What they really do:What they really do:What they really do: leaking abstractions mess in code hard to debug problems null pointer exceptions slow tests
  • 58. Is retry inside transaction or transaction insideIs retry inside transaction or transaction insideIs retry inside transaction or transaction inside retry?retry?retry? @Transactional @Retryable void myMethod () { }
  • 59. Libraries > FrameworksLibraries > FrameworksLibraries > Frameworks
  • 60. What typical coder thinks when you say you doWhat typical coder thinks when you say you doWhat typical coder thinks when you say you do not use framework to do transactionnot use framework to do transactionnot use framework to do transaction code fromcode fromcode from public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { https://docs.oracle.com/javase/tutorial/jdbc/bashttps://docs.oracle.com/javase/tutorial/jdbc/bashttps://docs.oracle.com/javase/tutorial/jdbc/bas
  • 62. JOOQ ExampleJOOQ ExampleJOOQ Example create.transaction(configuration -> { AuthorRecord author = DSL.using(configuration) .insertInto(AUTHOR, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) .values("George", "Orwell") .returning() .fetchOne(); DSL.using(configuration) .insertInto(BOOK, BOOK.AUTHOR_ID, BOOK.TITLE) .values(author.getId(), "1984") .values(author.getId(), "Animal Farm") .execute(); // Implicit commit executed here });
  • 63. Hibernate can do that as wellHibernate can do that as wellHibernate can do that as well TransactionUtil.doInHibernate( this::sessionFactory, session - ... session.persist( item ); ... } );
  • 64. SECURITYSECURITY private <T> void renderSecure(Context ctx, Function<Session, CompletionStage<T>> async ) { final Option<String> bearer = Option.of(ctx.getRequest().getHeaders().get("Authorizatio final Option<String> sessionId = bearer.map(b -> b.replace("Bearer ", "")); final Option<Session> session = sessionId.flatMap(sessionsRepo::getSession); ctx.render(JsonMapping.toJsonPromise(session.map( sess -> (CompletionStage<Object>) async.apply(sess) ) .getOrElse( CompletableFuture.completedFuture("unauthorized") ))); } private Handler movePaddle() { return ctx -> { final String gameId = ctx.getPathTokens().get("id"); ctx.getRequest().getBody().then(body -> { final float targetY = Float.parseFloat(body.getText()); renderSecure(ctx, session -> gamesRepo.movePaddle(gameId, session.userId, targe }); }; } https://github.com/javaFunAgain/ratpong/blob/mhttps://github.com/javaFunAgain/ratpong/blob/mhttps://github.com/javaFunAgain/ratpong/blob/m
  • 65. What if want:What if want:What if want: a function that is checked for permissions, does database access, and is monitored, and ...
  • 66. It can get messyIt can get messyIt can get messy monitor( ()- > renderSecure( securityContext, (user) -> doInTransaction (dbSession -> { } ....
  • 67. AlternativeAlternativeAlternative doInMyMightyStack( callContext -> { System.out.printn("User is " + callContext.getUser callContext.dataAccess.insert( ) })
  • 68. But what if:But what if:But what if: secure( trx( secure( trx( cache(
  • 69.
  • 70. If we only had sth toIf we only had sth toIf we only had sth to chainchainchain such e ectssuch e ectssuch e ects
  • 71.
  • 72.
  • 74. public void notifyGroupOwner(int userId) { final User user = userService.getUserFromDB(10001); final Group group = user.getGroup(); final User owner = group.getGroupOwner(); owner.sendMessage("User "+ userId + " is not eating mushrooms"); }
  • 75. side e ects everywhereside e ects everywhereside e ects everywhere public void notifyGroupOwner(int userId) { final User user = userService.getUserFromDB(10001); //may return null, throw exception final Group group = user.getGroup(); // may return null, is in transaction, throws sometimes excep final User owner = group.getGroupOwner(); //may return null, may come from cache owner.sendMessage("User "+ userId + " to straszny niejadek!"); //called async }
  • 76. NULLNULLNULL - I call it my billion-dollar mistake- I call it my billion-dollar mistake- I call it my billion-dollar mistake
  • 77. handling missing datahandling missing datahandling missing data User user = userService.getUserFromDB(10001); if ( user != null) { final Group group = user.getGroup(); if ( group != null ) { final User owner = group.getGroupOwner(); if ( owner != null ) { owner.sendMessage("hello there!!!"); } } }
  • 78. WHAT IF WE COULD MAKEWHAT IF WE COULD MAKE PROBLEMS VISIBLE?PROBLEMS VISIBLE?
  • 79. root of the problemroot of the problemroot of the problem User getUserFromDB(int userId) {....
  • 80. Optional<User> UserService::getUserFromDB(int userId) ... Optional<Group> User::getGroup(int groupId) ... Optional<User> Grouo::getGroupOwner() ...
  • 81. better,better,better, Optional<User> user = userService.getUserFromDB(10001); if ( user.isPresent()) { final Optional<Group> group = user.get().getGroup(); if ( group.isPresent() ) { final Optional<User> owner = group.get().getGroupOwner if ( owner.isPresent() ) { owner.sendMessage("hello there!!!"); } } }
  • 82. better,better,better, not smart at allnot smart at allnot smart at all Optional<User> user = userService.getUserFromDB(10001); if ( user.isPresent()) { final Optional<Group> group = user.get().getGroup(); if ( group.isPresent() ) { final Optional<User> owner = group.get().getGroupOwner if ( owner.isPresent() ) { owner.sendMessage("hello there!!!"); } } }
  • 83. mapmapmap operationoperationoperation Optional<String> str = "55"; Optional<Integer> x = str.map( v -> Integer.parseInt(v));
  • 84. mapmapmap went wrongwent wrongwent wrong Optional<User> user = userService.getUserFromDB(10001); Optional<Optional<Group>> group = user.map(u -> u.getGroup());
  • 85. mapmapmap went wrongwent wrongwent wrong Optional<User> user = userService.getUserFromDB(10001); Optional<Optional<Group>> group = user.map(u -> u.getGroup());
  • 86. flatMapflatMapflatMap to the rescueto the rescueto the rescue Optional<User> user = userService.getUserFromDB(10001); Optional<Group> group = user.flatMap(u -> u.getGroup());
  • 87. Awesome!Awesome!Awesome! userService.getUserFromDB(10001) .flatMap(user -> user.getGroup() ) .flatMap(group -> group.getGroupOwner() ) .ifPresent(user-> user.sendMessage("Hallo there Code4Life!
  • 88. OptionalOptional declaration of missing data side e ect basic handling (code) in map flatMap getOrDefault
  • 89. THAT IS A POWER OFTHAT IS A POWER OF MONADMONAD
  • 90. FAILURE - EXCEPTIONSFAILURE - EXCEPTIONS class Group { User getGroupOwner() throws NoOwnerException {... } } class User { Group getGroup() throws NoGroupMemberException {... } void sendMessage(String msg) {... } } class UserService { User getUserFromDB(long key) throws NoSuchUserException { }
  • 91. Either<DataError, User> UserService.getUserFromDB(int userId); // + other services enum DataError { NoOwner, NoGroup, NoUser }
  • 92. userService.getUserFromDB(10001) .flatMap(user -> user.getGroup() ) .flatMap(group -> group.getGroupOwner() ) .forEach(user-> user.sendMessage("Hallo there Code4Life," + "there was no problem !")); //on success
  • 93. A MONADA MONAD de nition 1 - formalde nition 1 - formalde nition 1 - formal
  • 94. aaa monadmonadmonad in X is just a monoid in thein X is just a monoid in thein X is just a monoid in the category of endofunctors of X, withcategory of endofunctors of X, withcategory of endofunctors of X, with product × replaced by compositionproduct × replaced by compositionproduct × replaced by composition of endofunctors and unit set byof endofunctors and unit set byof endofunctors and unit set by the identity endofunctor.the identity endofunctor.the identity endofunctor.
  • 95. MONADMONAD de nition 2 - by codede nition 2 - by codede nition 2 - by code
  • 96. Something that hasSomething that hasSomething that has flatMap, map of (pure)
  • 97. Something that hasSomething that hasSomething that has flatMap, map of (pure) TypeclassTypeclassTypeclass
  • 98. MONADMONAD de nition 3 - by comparisonde nition 3 - by comparisonde nition 3 - by comparison
  • 99. CONTAINERSCONTAINERS String a type List<String> a container of String Set<String> a container of String ArrayList<String> a container of String
  • 100. CONTAINERCONTAINER aaa TypeTypeType + multiplicity+ multiplicity+ multiplicity e ecte ecte ect
  • 101. MONADMONAD aaa TypeTypeType +++ somesomesome e ecte ecte ect
  • 102. EFFECTSEFFECTS missing data fails, exceptions asynchronous computations multiplicity transactions dependency injections + combinations+ combinations+ combinations
  • 104. FunctorFunctorFunctor - is a type(class) that has- is a type(class) that has- is a type(class) that has mapmapmap Every monad is a functorEvery monad is a functorEvery monad is a functor Some people call functorSome people call functorSome people call functor mappablemappablemappable
  • 105. Applicative functorApplicative functorApplicative functor Something between monad and functorSomething between monad and functorSomething between monad and functor HasHasHas apapap functionfunctionfunction ap(F<(A)->B>,F<A>)ap(F<(A)->B>,F<A>)ap(F<(A)->B>,F<A>) -> F<B>-> F<B>-> F<B>
  • 106. MONADS, FUNCTOR MAKE SIDEMONADS, FUNCTOR MAKE SIDE EFFECTS EXPLICITLY VISIBLE IN AEFFECTS EXPLICITLY VISIBLE IN A TYPE SYSTEMTYPE SYSTEM
  • 107. Applicative functor lets youApplicative functor lets youApplicative functor lets you sumsumsum inside e ectsinside e ectsinside e ects (potentially parallel sequence)(potentially parallel sequence)(potentially parallel sequence) Monadic atMap sequences operation one byMonadic atMap sequences operation one byMonadic atMap sequences operation one by oneoneone
  • 108. If You have 2 independent db transactions,If You have 2 independent db transactions,If You have 2 independent db transactions, that can be called concurrently - You can usethat can be called concurrently - You can usethat can be called concurrently - You can use apapap If operations on db depend on some otherIf operations on db depend on some otherIf operations on db depend on some other transaction (success/rollback) use atMaptransaction (success/rollback) use atMaptransaction (success/rollback) use atMap
  • 110. It uses JDBI one of many possible implementations (very simple) class Transaction<A> (private val action : (Handle) -> A ) { fun run(dbi : Jdbi) : A = dbi.inTransaction<A, RuntimeExce fun <B> map ( f: (A)->B) = Transaction {handle -> f(action(handle)) } fun <B> flatMap( f: (A)->Transaction<B>) = Transaction {h f(action(handle)).action(handle) } companion object { fun <T> pure (obj:T) = Transaction { obj
  • 111. Example useExample useExample use internal fun selectNewJob () = Transaction{ dbHandle -> dbHandle.createQuery(selectNextJobId) .mapTo(Long::class.java) .one() .map { jobId -> dbHandle.createUpdate(updateBatch).bind("b .execute() jobId } }.flatMap { jobId -> selectNewTasks(jobId) }
  • 112. The same can be done with other aspectsThe same can be done with other aspectsThe same can be done with other aspects /e ects/e ects/e ects security cache diagnostics/monitoring async
  • 113. That is a lot ofThat is a lot ofThat is a lot of flatMapflatMapflatMap
  • 114. Would be better if we hadWould be better if we hadWould be better if we had do notationdo notationdo notation
  • 115. Four great things You can do to avoid bugsFour great things You can do to avoid bugsFour great things You can do to avoid bugs stop using java.util. collections prefer immutable objects learn lambdas and higher order functions use kotlin use monads use scala
  • 116.
  • 117. How do we chain multiple e ects?How do we chain multiple e ects?How do we chain multiple e ects? the worstthe worstthe worst flatMapflatMapflatMap ever...ever...ever... Future<Secure<Transaction<Optional<Int>>>> myX; Function<Int, Future<Secure<Transaction<Optional<String>>>>> f myX.flatMap(f) // ...no go Function<Secure<Transaction<Optional<Int>>>>, Future<Secure<Tr
  • 118. Problem of stacking monadsProblem of stacking monadsProblem of stacking monads
  • 119. Monad TransformersMonad TransformersMonad Transformers FutureTFutureTFutureT,,, OptionTOptionTOptionT
  • 120. Monad transformers appeared to not be thatMonad transformers appeared to not be thatMonad transformers appeared to not be that greatgreatgreat We cannot have use them in kotlin or java (in aWe cannot have use them in kotlin or java (in aWe cannot have use them in kotlin or java (in a sensible way)sensible way)sensible way)
  • 121. AllMightyMonadAllMightyMonadAllMightyMonad write the monad that has all the e ects for you stack (cache, jdbc, async, etc.) cry once (writing atMap) enjoy everywhere
  • 123. 1. zioziozio 2. https://github.com/atnos-org/e /https://github.com/atnos-org/e /https://github.com/atnos-org/e /
  • 124.
  • 125. No, we will not hack bytecodeNo, we will not hack bytecodeNo, we will not hack bytecode
  • 126.
  • 128. class Hasiok { @Resource val jdbcConnection: Connection @Transactional @Secure @Cacheable @Retryable fun f(p:P) { //code } }
  • 129. class Hasiok { val enterprisyF = Nee.pure( secure .and(retryable) .and(cacheable) .and(transactional)) {conn:ConnectionProvider -> {p: P -> //code } } //declaration above means security is checked before retri //and retrial is made before cache which happens before tr }
  • 130. fun readStone() = Nee.pure(cached.andThen(jdbc)) { jdbcProvide { id: StoneId -> val dsl = DSL.using(jdbcProvider.getConnection().g val record = dsl.selectFrom(Stones.STONES) .where(Stones.STONES.ID.eq(id)) .fetchOneInto(Stones.STONES) Stone(record.id, StoneData(record.name, record.pri } }.anyError()
  • 131. fun addNewStone(newStone: StoneData) = seq.next().flatMap addStone(it, newStone) } private fun addStone(stoneId: Long, newStone: StoneData) = val dsl = DSL.using(jdbcProvider.getConnection().getRe val insertedRows = dsl.insertInto(Stones.STONES) .values(stoneId, newStone.name, newStone.price) .execute() if (insertedRows == 1) { Option.some(stoneId) } else { Option.none() } }
  • 132. Nee is a hobby, experimental project.Nee is a hobby, experimental project.Nee is a hobby, experimental project. NeeNeeNee monadmonadmonad has 4 params:has 4 params:has 4 params: Nee<R,E,P,A>Nee<R,E,P,A>Nee<R,E,P,A> - R - environment (context) - E - error type - P - argument (for cache - I think this was a bas idea) - A - the real result type
  • 133. I am slowly adding e ects:I am slowly adding e ects:I am slowly adding e ects: jdbc, any transactional resource, cache, security, diagnostic,
  • 136. open class DocsModule(val config: ConfigModule) { open val dbProvider: HandleProvider by lazy { DBProvider(config.cfg) } open val timeProvider: TimeProvider by lazy { RealTime } open val jobsRepository by lazy { JobsRepository(dbProvide open val zipWriter : StatusZipWriter by lazy { StatusZipWriter(config) } open val docsStatusRepository : DocsStatusRepository by la
  • 137. MATERIALSMATERIALS Project ZIOProject ZIOProject ZIO Fun(c) 2018.7: John De Goes - FP to the MaxFun(c) 2018.7: John De Goes - FP to the MaxFun(c) 2018.7: John De Goes - FP to the Max The Book of MonadsThe Book of MonadsThe Book of Monads Alejandro Serrano MenaAlejandro Serrano MenaAlejandro Serrano Mena (experiment)(experiment)(experiment) https://github.com/ziohttps://github.com/ziohttps://github.com/zio https://www.youtube.com/watch?https://www.youtube.com/watch?https://www.youtube.com/watch? v=sxudIMiOo68v=sxudIMiOo68v=sxudIMiOo68 https://github.com/nee ect/neehttps://github.com/nee ect/neehttps://github.com/nee ect/nee
  • 138. FUNCTIONAL PROGRAMMINGFUNCTIONAL PROGRAMMING Power of abstractionsPower of abstractionsPower of abstractions we want to have less bugs we want testability we want to have more fun @jarek000000@jarek000000@jarek000000 Thank youThank youThank you