SlideShare une entreprise Scribd logo
1  sur  149
Télécharger pour lire hors ligne
Use Applicative
where applicable!
© 2018 Hermann Hueck
https://github.com/hermannhueck/use-applicative-where-applicable
1 / 148
Abstract
Most Scala developers are familiar with monadic processing. Monads provide
flatMap and hence for-comprehensions (syntactic sugar for map and flatMap).
Often we don't need Monads. Applicatives are sufficient in many cases.
In this talk I examine the differences between monadic and applicative
processing and give some guide lines when to use which.
After a closer look to the Applicative trait I will contrast the gist of Either and
cats.data.Validated (the latter being an Applicative but not a Monad).
I will also look at traversing and sequencing which harness Applicatives as
well.
(The code examples are implemented with Cats.)
2 / 148
Agenda
1. Monadic Processing
2. Aside: Effects
3. Aside: Curried functions
4. Where Functor is too weak
5. Applicative Processing
6. Comparing Monad with Applicative
7. The Applicative trait
8. Either vs. Validated
9. Traversals need Applicative
10. Resources
3 / 148
1. Monadic Processing
See: examples.MonadicProcessing
4 / 148
The Problem:
How to compute values wrapped in a context?
Example context/e!ect: Option
def processInts(compute: (Int, Int, Int) => Int)
(oi1: Option[Int], oi2: Option[Int], oi3: Option[Int])
: Option[Int] = ???
5 / 148
The Problem:
How to compute values wrapped in a context?
Example context/e!ect: Option
def processInts(compute: (Int, Int, Int) => Int)
(oi1: Option[Int], oi2: Option[Int], oi3: Option[Int])
: Option[Int] = ???
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3))
// result1: Option[Int] = Some(6)
val result2 = processInts(sum3Ints, Some(1), Some(2), None)
// result2: Option[Int] = None
6 / 148
The Standard Solution:
Monadic Processing with a for-comprehension (monad comprehension)
Example context/e!ect: Option
def processInts(compute: (Int, Int, Int) => Int)
(oi1: Option[Int], oi2: Option[Int], oi3: Option[Int])
: Option[Int] =
for {
i1 <- oi1
i2 <- oi2
i3 <- oi3
} yield compute(i1, i2, i3)
7 / 148
The Standard Solution:
Monadic Processing with a for-comprehension (monad comprehension)
Example context/e!ect: Option
def processInts(compute: (Int, Int, Int) => Int)
(oi1: Option[Int], oi2: Option[Int], oi3: Option[Int])
: Option[Int] =
for {
i1 <- oi1
i2 <- oi2
i3 <- oi3
} yield compute(i1, i2, i3)
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3))
// result1: Option[Int] = Some(6)
val result2 = processInts(sum3Ints, Some(1), Some(2), None)
// result2: Option[Int] = None
8 / 148
Abstracting Option to F[_]: Monad
def processInts[F[_]: Monad](compute: (Int, Int, Int) => Int)
(fi1: F[Int], fi2: F[Int], fi3: F[Int]): F[Int] =
for {
i1 <- fi1
i2 <- fi2
i3 <- fi3
} yield compute(i1, i2, i3)
9 / 148
Abstracting Option to F[_]: Monad
def processInts[F[_]: Monad](compute: (Int, Int, Int) => Int)
(fi1: F[Int], fi2: F[Int], fi3: F[Int]): F[Int] =
for {
i1 <- fi1
i2 <- fi2
i3 <- fi3
} yield compute(i1, i2, i3)
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3))
// result1: Option[Int] = Some(6)
val result2 = processInts(sum3Ints, Some(1), Some(2), None)
// result2: Option[Int] = None
val result3 = processInts(sum3Ints)(List(1, 2), List(10, 20), List(100, 200))
// result3: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222)
val result4 = processInts(sum3Ints)(Future(1), Future(2), Future(3))
Await.ready(result4, 1 second)
// result4 = scala.concurrent.Future[Int] = Future(Success(6))
10 / 148
Abstracting away the Ints
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
for {
a <- fa
b <- fb
c <- fc
} yield compute(a, b, c)
11 / 148
Abstracting away the Ints
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
for {
a <- fa
b <- fb
c <- fc
} yield compute(a, b, c)
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val result1 = processABC(sum3Ints, Some(1), Some(2), Some(3))
// result1: Option[Int] = Some(6)
val result2 = processABC(sum3Ints, Some(1), Some(2), None)
// result2: Option[Int] = None
val result3 = processABC(sum3Ints)(List(1, 2), List(10, 20), List(100, 200))
// result3: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222)
val result4 = processABC(sum3Ints)(Future(1), Future(2), Future(3))
Await.ready(result4, 1 second)
// result4 = scala.concurrent.Future[Int] = Future(Success(6))
12 / 148
Desugaring for-comprehension to map/flatMap
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
fa flatMap { a =>
fb flatMap { b =>
fc map { c =>
compute(a, b, c)
}
}
}
13 / 148
2. Aside: E!ects
14 / 148
What is F[_] ?
F[_] is a type constructor.
It represents the computational context of the operation, also called the
effect of the operation.
e!ect != side e!ect
With the context bound F[_]: Monad we constrain the effect to be a Monad.
This makes F a place holder for any Monad which provides us map and
flatMap.
15 / 148
Some E!ects
F[_] Effect
Option a possibly missing value
List
an arbitrary number of values of the same type (non-
determinism)
Future an asyncronously computed value
Either either a value of a type or a value of another type
Id the effect of having no effect
IO the effect of having a side effect
Tuple2 two adjacent values of possibly different type
Function1
a pure computation on a (yet unknown) input producing an
output
Reader a wrapped Function1
Writer
a values that has another value attached which acts as a sort of
log value
16 / 148
3. Aside: Curried functions
See: examples.CurriedFunctions
17 / 148
Every function is a Function1 ...
18 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
19 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
20 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
// sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e
21 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
The type arrow ( => ) is right associative. Hence we can omit the parentheses.
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
// sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e
22 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
The type arrow ( => ) is right associative. Hence we can omit the parentheses.
The type arrow ( => ) is syntactic sugar for Function1.
A => B is equivalent to Function1[A, B].
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
// sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e
23 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
The type arrow ( => ) is right associative. Hence we can omit the parentheses.
The type arrow ( => ) is syntactic sugar for Function1.
A => B is equivalent to Function1[A, B].
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
// sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e
val sumCurried2: Function1[Int, Function1[Int, Function1[Int, Int]]] = sumCurried
// sumCurried2: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@7567ab8
24 / 148
Every function is a Function1 ...
val sum3Ints : (Int, Int, Int) => Int = _ + _ + _
val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _
... if we curry it.
The type arrow ( => ) is right associative. Hence we can omit the parentheses.
The type arrow ( => ) is syntactic sugar for Function1.
A => B is equivalent to Function1[A, B].
If uncurried again, we get the original function back.
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
// sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e
val sumCurried2: Function1[Int, Function1[Int, Function1[Int, Int]]] = sumCurried
// sumCurried2: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@7567ab8
val sumUncurried: (Int, Int, Int) => Int = Function.uncurried(sumCurried)
// sumUncurried: (Int, Int, Int) => Int = scala.Function$$$Lambda$6605/301079867@1589d895
25 / 148
Partial application of curried functions
26 / 148
Partial application of curried functions
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
27 / 148
Partial application of curried functions
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
val applied1st: Int => Int => Int = sumCurried(1)
28 / 148
Partial application of curried functions
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
val applied1st: Int => Int => Int = sumCurried(1)
val applied2nd: Int => Int = applied1st(2)
29 / 148
Partial application of curried functions
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
val applied1st: Int => Int => Int = sumCurried(1)
val applied2nd: Int => Int = applied1st(2)
val applied3rd: Int = applied2nd(3)
30 / 148
Partial application of curried functions
val sum3Ints: (Int, Int, Int) => Int = _ + _ + _
val sumCurried: Int => Int => Int => Int = sum3Ints.curried
val applied1st: Int => Int => Int = sumCurried(1)
val applied2nd: Int => Int = applied1st(2)
val applied3rd: Int = applied2nd(3)
val appliedAllAtOnce: Int = sumCurried(1)(2)(3)
31 / 148
Advantages of curried functions
32 / 148
Advantages of curried functions
1. As curried functions can be partially applied.
They are better composable than their uncurried counterparts.
33 / 148
Advantages of curried functions
1. As curried functions can be partially applied.
They are better composable than their uncurried counterparts.
2. Curried functions help the compiler with type inference. The compiler
infers types by argument lists from left to right.
34 / 148
Advantages of curried functions
1. As curried functions can be partially applied.
They are better composable than their uncurried counterparts.
2. Curried functions help the compiler with type inference. The compiler
infers types by argument lists from left to right.
def filter1[A](la: List[A], p: A => Boolean) = ??? // uncurried
scala> filter1(List(0,1,2), _ < 2)
<console>:50: error: missing parameter type for expanded function ((x$1: <error>) =>
filter1(List(0,1,2), _ < 2)
^
35 / 148
Advantages of curried functions
1. As curried functions can be partially applied.
They are better composable than their uncurried counterparts.
2. Curried functions help the compiler with type inference. The compiler
infers types by argument lists from left to right.
def filter2[A](la: List[A])(p: A => Boolean) = ??? // curried
scala> filter2(List(0,1,2))(_ < 2)
res5: List[Int] = List(0,1)
def filter1[A](la: List[A], p: A => Boolean) = ??? // uncurried
scala> filter1(List(0,1,2), _ < 2)
<console>:50: error: missing parameter type for expanded function ((x$1: <error>) =>
filter1(List(0,1,2), _ < 2)
^
36 / 148
Curring in Java? -- (Sorry, I couldn't resist ;-)
37 / 148
Curring in Java? -- (Sorry, I couldn't resist ;-)
// Curring with lambdas
Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried =
a -> b -> c -> a + b + c;
Integer result = sum3IntsCurried.apply(1).apply(2).apply(3);
38 / 148
Curring in Java? -- (Sorry, I couldn't resist ;-)
// Curring with lambdas
Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried =
a -> b -> c -> a + b + c;
Integer result = sum3IntsCurried.apply(1).apply(2).apply(3);
// Curring without lambdas
Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried =
new Function<Integer, Function<Integer, Function<Integer, Integer>>>() {
@Override
public Function<Integer, Function<Integer, Integer>> apply(Integer a) {
return new Function<Integer, Function<Integer, Integer>>() {
@Override
public Function<Integer, Integer> apply(Integer b) {
return new Function<Integer, Integer>() {
@Override
public Integer apply(Integer c) {
return a + b + c;
}
};
}
};
}
};
Integer result = sum3IntsCurried.apply(1).apply(2).apply(3);
39 / 148
Curring in Haskell?
40 / 148
Curring in Haskell?
No extra currying!
41 / 148
Curring in Haskell?
No extra currying!
Every function is already curried.
It can take only one parameter ...
... and returns only one value which might be another function.
42 / 148
Curring in Haskell?
No extra currying!
Every function is already curried.
It can take only one parameter ...
... and returns only one value which might be another function.
Invoking functions with more than one parameter is possible.
That is just syntactic sugar for curried functions.
43 / 148
Partial application in Haskell
sum3Ints :: Int -> Int -> Int -> Int -- function already curried
sum3Ints x y z = x + y + z
applied1st = sum3Ints 1 :: Int -> Int -> Int
applied2nd = applied1st 2 :: Int -> Int
applied3rd = applied2nd 3 :: Int
appliedAll = sum3Ints 1 2 3 :: Int
44 / 148
4. Where Functor is too weak
See: examples.WhereFunctorIsTooWeak
45 / 148
Where Functor is too weak ...
46 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
47 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
val add1: Int => Int = 1 + _
val sum2Ints: (Int, Int) => Int = _ + _
48 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
val add1: Int => Int = 1 + _
val sum2Ints: (Int, Int) => Int = _ + _
Option(1) map add1
// res1: Option[Int] = Some(2)
49 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
val add1: Int => Int = 1 + _
val sum2Ints: (Int, Int) => Int = _ + _
Option(1) map add1
// res1: Option[Int] = Some(2)
// Option(1) map sum2Ints
// [error] found : (Int, Int) => Int
// [error] required: Int => ?
// [error] Option(1) map sum2Ints
// [error] ^
50 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
val add1: Int => Int = 1 + _
val sum2Ints: (Int, Int) => Int = _ + _
Option(1) map add1
// res1: Option[Int] = Some(2)
// Option(1) map sum2Ints
// [error] found : (Int, Int) => Int
// [error] required: Int => ?
// [error] Option(1) map sum2Ints
// [error] ^
Option(1) map sum2Ints.curried
// res2: Option[Int => Int] = Some(scala.Function2$$Lambda$4648/454529628@7c9e8cef)
51 / 148
Where Functor is too weak ...
What to do if we want to fuse two Options to one by summing up the
contained values?
Option(1) + Option(2) == Option(3) // ???
val add1: Int => Int = 1 + _
val sum2Ints: (Int, Int) => Int = _ + _
Option(1) map add1
// res1: Option[Int] = Some(2)
// Option(1) map sum2Ints
// [error] found : (Int, Int) => Int
// [error] required: Int => ?
// [error] Option(1) map sum2Ints
// [error] ^
map doesn't help us to fuse another Option(2) to that.
Option(1) map sum2Ints.curried
// res2: Option[Int => Int] = Some(scala.Function2$$Lambda$4648/454529628@7c9e8cef)
52 / 148
... Applicative can help us out
53 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
54 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap Option(1) ap Option(2)
// res4: Option[Int] = Some(3)
55 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap Option(1) ap Option(2)
// res4: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option]
// res5: Option[Int] = Some(3) // pure is the same as Option.apply
56 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap Option(1) ap Option(2)
// res4: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option]
// res5: Option[Int] = Some(3) // pure is the same as Option.apply
Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2))
// res6: Option[Int] = Some(3) // no currying with ap2
57 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap Option(1) ap Option(2)
// res4: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option]
// res5: Option[Int] = Some(3) // pure is the same as Option.apply
Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2))
// res6: Option[Int] = Some(3) // no currying with ap2
Applicative[Option].map2(Option(1), Option(2))(sum2Ints)
// res7: Option[Int] = Some(3) // no pure with map2
58 / 148
... Applicative can help us out
Option(1) map sum2Ints.curried ap Option(2)
// res3: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap Option(1) ap Option(2)
// res4: Option[Int] = Some(3)
sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option]
// res5: Option[Int] = Some(3) // pure is the same as Option.apply
Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2))
// res6: Option[Int] = Some(3) // no currying with ap2
Applicative[Option].map2(Option(1), Option(2))(sum2Ints)
// res7: Option[Int] = Some(3) // no pure with map2
(Option(1), Option(2)) mapN sum2Ints
// res8: Option[Int] = Some(3) // Tuple2#mapN
59 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
60 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried
val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried
val concat: (String, String) => String = _ ++ _
61 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried
val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried
val concat: (String, String) => String = _ ++ _
List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3)
// res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9)
62 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried
val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried
val concat: (String, String) => String = _ ++ _
List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3)
// res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9)
List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200)
// res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400)
63 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried
val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried
val concat: (String, String) => String = _ ++ _
List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3)
// res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9)
List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200)
// res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400)
concat.curried.pure[List] <*> List("ha", "heh", "hmm") <*> List("?", "!", ".")
// res12: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.)
64 / 148
Cartesian Product
When working with Applicative we always get the Cartesian product of two or
more contexts. That isn't obvious when working with the Option context but
becomes evident when we work with List.
val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried
val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried
val concat: (String, String) => String = _ ++ _
List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3)
// res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9)
List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200)
// res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400)
concat.curried.pure[List] <*> List("ha", "heh", "hmm") <*> List("?", "!", ".")
// res12: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.)
List("ha", "heh", "hmm") map concat.curried ap List("?", "!", ".")
// res13: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.)
65 / 148
5. Applicative Processing
See: examples.ApplicativeProcessing
66 / 148
Trait Applicative
67 / 148
Trait Applicative
Applicative is stronger than Functor, but weaker than Monad.
68 / 148
Trait Applicative
Applicative is stronger than Functor, but weaker than Monad.
trait Functor[F[_]] { // simplified
def map[A, B](fa: F[A])(f: A => B): F[B]
}
69 / 148
Trait Applicative
Applicative is stronger than Functor, but weaker than Monad.
trait Functor[F[_]] { // simplified
def map[A, B](fa: F[A])(f: A => B): F[B]
}
trait Applicative[F[_]] extends Functor[F] { // simplified
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
}
70 / 148
Trait Applicative
Applicative is stronger than Functor, but weaker than Monad.
trait Functor[F[_]] { // simplified
def map[A, B](fa: F[A])(f: A => B): F[B]
}
trait Applicative[F[_]] extends Functor[F] { // simplified
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
}
trait Monad[F[_]] extends Applicative[F] { // simplified
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
71 / 148
Replacing monadic with applicative context
72 / 148
Replacing monadic with applicative context
Applicative is strong enough for the problem
we solved before with a monad-comprehension.
73 / 148
Replacing monadic with applicative context
Applicative is strong enough for the problem
we solved before with a monad-comprehension.
Monadic context: F[_] constrained to be a Monad
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
??? // monadic solution
74 / 148
Replacing monadic with applicative context
Applicative is strong enough for the problem
we solved before with a monad-comprehension.
Monadic context: F[_] constrained to be a Monad
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
??? // monadic solution
Applicative context: F[_] constrained to be an Applicative
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
??? // applicative solution
75 / 148
Replacing monadic with applicative context
Applicative is strong enough for the problem
we solved before with a monad-comprehension.
Monadic context: F[_] constrained to be a Monad
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
??? // monadic solution
Applicative context: F[_] constrained to be an Applicative
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
??? // applicative solution
Let's start with Option[Int] again and then abstract the solution. 76 / 148
Solution with Applicative#ap:
Example context/e!ect: Option
def processInts(compute: (Int, Int, Int) => Int)
(oi1: Option[Int], oi2: Option[Int], oi3: Option[Int])
: Option[Int] = {
val f3Curried: Int => Int => Int => Int = compute.curried
val of3: Option[Int => Int => Int => Int] = Some(f3Curried) // lifting
val of2: Option[Int => Int => Int] = of3 ap oi1 // same as: oi1 map of3
val of1: Option[Int => Int] = of2 ap oi2
val result: Option[Int] = of1 ap oi3
result
}
Some(f3Curried) lifts the curried function into the Option context.
77 / 148
Abstracting Option to F[_]: Applicative
def processInts[F[_]: Applicative](compute: (Int, Int, Int) => Int)
(fi1: F[Int], fi2: F[Int], fi3: F[Int])
: F[Int] = {
val fCurried: Int => Int => Int => Int = compute.curried
val ff: F[Int => Int => Int => Int] = Applicative[F].pure(fCurried) // lifting
ff ap fi1 ap fi2 ap fi3
}
Applicative[F].pure(fCurried) lifts the curried function into the generic F
context.
78 / 148
Abstracting away the Ints
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
val fCurried: A => B => C => D = compute.curried
val ff: F[A => B => C => D] = Applicative[F].pure(fCurried)
ff ap fa ap fb ap fc
}
79 / 148
Using pure syntax and <*> (alias for ap)
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
compute.curried.pure[F] <*> fa <*> fb <*> fc
}
80 / 148
Applicative#ap3 saves us from currying
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
Applicative[F].ap3(compute.pure[F])(fa, fb, fc)
}
81 / 148
Applicative#map3 avoids lifting with pure
def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
Applicative[F].map3(fa, fb, fc)(compute)
}
map2, map3 .. map22 are available for Applicative.
82 / 148
Apply is just Applicative without pure
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
Apply[F].map3(fa, fb, fc)(compute)
}
map2, map3 .. map22 come from Apply.
Apply is a base trait of Applicative and provides us map3.
83 / 148
Using Tuple3#mapN for convenience
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C])
: F[D] = {
(fa, fb, fc) mapN compute
}
We just tuple up the three F's and invoke mapN with a function that takes
three parameters. mapN fuses the F's into an F-result.
Cats provides mapN as an enrichment for Tuple2, Tuple3 .. Tuple22.
84 / 148
Comparing the solutions
85 / 148
Comparing the solutions
Monadic solution:
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
for {
a <- fa
b <- fb
c <- fc
} yield compute(a, b, c)
86 / 148
Comparing the solutions
Monadic solution:
def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]): F[D] =
for {
a <- fa
b <- fb
c <- fc
} yield compute(a, b, c)
Applicative solution:
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]) : F[D] =
(fa, fb, fc) mapN compute
87 / 148
Currying again
88 / 148
Currying again
We provided processABC with two parameter lists.
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]) : F[D] =
(fa, fb, fc) mapN compute
89 / 148
Currying again
We provided processABC with two parameter lists.
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]) : F[D] =
(fa, fb, fc) mapN compute
This improves composability and allows us provide the compute function and
the effectful F's in separate steps.
90 / 148
Currying again
We provided processABC with two parameter lists.
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]) : F[D] =
(fa, fb, fc) mapN compute
This improves composability and allows us provide the compute function and
the effectful F's in separate steps.
// providing the computation
def processEffectfulInts[F[_]: Apply](fi1: F[Int], fi2: F[Int], fi3: F[Int])
: F[Int] =
processABC(sum3Ints)(fi1, fi2, fi3)
91 / 148
Currying again
We provided processABC with two parameter lists.
def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D)
(fa: F[A], fb: F[B], fc: F[C]) : F[D] =
(fa, fb, fc) mapN compute
This improves composability and allows us provide the compute function and
the effectful F's in separate steps.
// providing the computation
def processEffectfulInts[F[_]: Apply](fi1: F[Int], fi2: F[Int], fi3: F[Int])
: F[Int] =
processABC(sum3Ints)(fi1, fi2, fi3)
// providing the 'effectful' parameters
val result1 = processEffectfulInts(Option(1), Option(2), Option(3))
// result1: Option[Int] = Some(6)
val result2 = processEffectfulInts(List(1, 2), List(10, 20), List(100, 200))
// result2: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222)
92 / 148
6. Comparing Monad with
Applicative
93 / 148
Sequential vs. parallel
94 / 148
Sequential vs. parallel
Monads enforce sequential operation!
Imagine a for-comprehension.
The generators are executed in sequence, one after the other.
The yield clause is executed after all generators have finished.
95 / 148
Sequential vs. parallel
Monads enforce sequential operation!
Imagine a for-comprehension.
The generators are executed in sequence, one after the other.
The yield clause is executed after all generators have finished.
Applicatives allow for parallel/independent operations!
The operations are by nature independent of each other.
(That does not mean, they are asynchronous.)
96 / 148
Fail-fast semantics
97 / 148
Fail-fast semantics
Monads enforce short-circuiting!
If an 'erraneous' value (e.g. None, Nil or Left) is found in a monadic
computation, the computation stops immediately, because the result will not
change if processing were continued.
That is why we are not able to collect errors.
98 / 148
Fail-fast semantics
Monads enforce short-circuiting!
If an 'erraneous' value (e.g. None, Nil or Left) is found in a monadic
computation, the computation stops immediately, because the result will not
change if processing were continued.
That is why we are not able to collect errors.
Applicatives do not short-circuit!
As applicative operations are independent of each other, processing does not
stop if an error occurs.
It is possible to collect errors (see: cats.data.Validated)
cats.data.Validated has an Applicative instance, but no Monad instance.
We will come to that later.
99 / 148
Composition
100 / 148
Composition
Monads do not compose!
In order to kind of 'compose' Monads we need an extra construct such as a
Monad Transformer which itself is a Monad. I.e.: If we want to compose two
Monads we need a third Monad to stack them up.
101 / 148
Composition
Monads do not compose!
In order to kind of 'compose' Monads we need an extra construct such as a
Monad Transformer which itself is a Monad. I.e.: If we want to compose two
Monads we need a third Monad to stack them up.
Applicatives compose!
Composition of (Functor and) Applicative is a breeze.
It is genuinely supported by these type classes.
See: examples.Composition
102 / 148
Composition - Code
val loi1 = List(Some(1), Some(2))
val loi2 = List(Some(10), Some(20))
Monads do not compose!
Applicatives compose!
def processMonadic(xs: List[Option[Int]], ys: List[Option[Int]]): List[Option[Int]] = {
val otli: OptionT[List, Int] = for {
x <- OptionT[List, Int](xs)
y <- OptionT[List, Int](ys)
} yield x + y
otli.value
}
val result1 = processMonadic(loi1, loi2)
// result1: List[Option[Int]] = List(Some(11), Some(21), Some(12), Some(22))
def processApplicative(xs: List[Option[Int]], ys: List[Option[Int]]): List[Option[Int
Applicative[List].compose[Option].map2(xs, ys)((_:Int) + (_:Int))
val result2 = processApplicative(loi1, loi2)
// result2: List[Option[Int]] = List(Some(11), Some(21), Some(12), Some(22))
103 / 148
When to use which
Use Applicative if all computations are independent of each other.
Howto:
Write a monadic solution with a for-comprehension.
If the generated values (to the left of the generator arrow <- ) are only
used in the yield clause at the end (not in an other generator) the
computations are independent and allow for an applicative solution.
Tuple up the computations and mapN them with a function which fuses
the computation results to a final result.
(This howto is applicable only if the effect in question also has a Monad
instance.)
104 / 148
When to use which - counter example
val result =
for {
x <- Future { computeX }
y <- Future { computeY(x) }
z <- Future { computeZ(x, y) }
} yield resultFrom(z)
In this for-comprehension the subsequent computations depend on the
previous ones.
Hence this for-comprehension cannot be substituted by applicative
processing.
105 / 148
Principle of Least Power
Given a choice of solutions,
pick the least powerful solution
capable of solving your problem.
-- Li Haoyi
106 / 148
7. The Applicative trait
107 / 148
Typeclass Functor
Functor is the base trait of Applicative.
trait Functor[F[_]] {
// ----- primitive map
def map[A, B](fa: F[A])(f: A => B): F[B]
// ----- implementations in terms of the primitive map
def fmap[A, B](fa: F[A])(f: A => B): F[B] = map(fa)(f) // alias for map
def lift[A, B](f: A => B): F[A] => F[B] = fa => map(fa)(f)
def as[A, B](fa: F[A], b: B): F[B] = map(fa)(_ => b)
def void[A](fa: F[A]): F[Unit] = as(fa, ())
def compose[G[_]: Functor]: Functor[Lambda[X => F[G[X]]]] = ???
}
108 / 148
Typeclass Applicative
with primitives pure and ap
trait Applicative[F[_]] extends Functor[F] {
// ----- primitives
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
// ----- implementations in terms of the primitives
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] =
ap(map(fa)(a => f(a, _: B)))(fb)
override def map[A, B](fa: F[A])(f: A => B): F[B] =
ap(pure(f))(fa)
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
map2(fa, fb)((_, _))
}
109 / 148
Typeclass Applicative
with primitives pure and map2
trait Applicative[F[_]] extends Functor[F] {
// ----- primitives
def pure[A](a: A): F[A]
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z]
// ----- implementations in terms of the primitives
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] =
map2(ff, fa)((f, a) => f(a)) // or: map2(ff, fa)(_(_))
override def map[A, B](fa: F[A])(f: A => B): F[B] =
map2(fa, pure(()))((a, _) => f(a))
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
map2(fa, fb)((_, _))
}
110 / 148
Typeclass Applicative
with primitives pure and map and product
trait Applicative[F[_]] extends Functor[F] {
// ----- primitives
def pure[A](a: A): F[A]
override def map[A, B](fa: F[A])(f: A => B): F[B]
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
// ----- implementations in terms of the primitives
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] =
map(product(fa, fb)) { case (a, b) => f(a, b) }
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] =
map2(ff, fa)((f, a) => f(a)) // or: map2(ff, fa)(_(_))
}
111 / 148
Typeclass Applicative
trait Applicative[F[_]] extends Functor[F] {
// ----- primitives
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
// ----- implementations in terms of the primitives
override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
def <*>[A, B](ff: F[A => B])(fa: F[A]): F[B] = ap(ff)(fa) // alias for ap
def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = {
val ffBZ: F[B => Z] = ap(map(ff)(f => (a:A) => (b:B) => f(a, b)))(fa)
ap(ffBZ)(fb)
} // also: ap3, ap4 .. ap22
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] =
ap(map(fa)(a => f(a, _: B)))(fb)
// also: map3, map4 .. map22
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _))
def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = product(fa, fb)
// also: tuple3, tuple4 .. tuple22
def compose[G[_]: Applicative]: Applicative[Lambda[X => F[G[X]]]] = ???
}
112 / 148
Cats typeclass hierarchy (a small section of it)
Complete hierarchy here
113 / 148
7. Either vs. Validated
See: examples.EitherVsValidated
114 / 148
Monadic processing with Either
Monadic processing enforces fail-fast semantics.
After an erraneous operation subsequent operations are not executed.
Hence we get only the first of possibly multiple errors.
115 / 148
Monadic processing with Either
Monadic processing enforces fail-fast semantics.
After an erraneous operation subsequent operations are not executed.
Hence we get only the first of possibly multiple errors.
val result: Either[List[String], Int] =
for {
x <- 5.asRight[List[String]]
y <- List("Error 1").asLeft[Int]
z <- List("Error 2").asLeft[Int] // List("Error 2") is lost!
} yield x + y + z
// result: Either[List[String],Int] = Left(List(Error 1))
116 / 148
Applicative processing with Either
Applicative processing disables fail-fast semantics.
But with Either we get the same result as before.
Because Either has a Monad instance.
117 / 148
Applicative processing with Either
Applicative processing disables fail-fast semantics.
But with Either we get the same result as before.
Because Either has a Monad instance.
val result: Either[List[String], Int] =
( 5.asRight[List[String]],
List("Error 1").asLeft[Int],
List("Error 2").asLeft[Int] // List("Error 2") is lost!
) mapN ((_: Int) + (_: Int) + (_: Int))
// result: Either[List[String],Int] = Left(List(Error 1))
118 / 148
Applicative processing with Validated
cats.data.Validated is designed in analogy to Either.
Instead of Right and Left it has Valid and Invalid.
Validated has an Applicative instance but no Monad instance.
119 / 148
Applicative processing with Validated
cats.data.Validated is designed in analogy to Either.
Instead of Right and Left it has Valid and Invalid.
Validated has an Applicative instance but no Monad instance.
val result: Validated[List[String], Int] =
( 5.valid[List[String]],
List("Error 1").invalid[Int],
List("Error 2").invalid[Int] // List("Error 2") is preserved!
) mapN ((_: Int) + (_: Int) + (_: Int))
// result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2))
120 / 148
Conversions between Either and Validated
Conversion is supported in both directions
Either#toValidated converts an Either to a Validated.
Validated#toEither converts a Validated to an Either.
121 / 148
Conversions between Either and Validated
Conversion is supported in both directions
Either#toValidated converts an Either to a Validated.
Validated#toEither converts a Validated to an Either.
val result: Validated[List[String], Int] =
( 5.asRight[List[String]].toValidated,
List("Error 1").asLeft[Int].toValidated,
List("Error 2").asLeft[Int].toValidated // List("Error 2") is preserved!
) mapN ((_: Int) + (_: Int) + (_: Int))
// result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2))
val resultAsEither = result.toEither
// resultAsEither: Either[List[String],Int] = Left(List(Error 1, Error 2))
122 / 148
Conversions between Either and Validated
Conversion is supported in both directions
Either#toValidated converts an Either to a Validated.
Validated#toEither converts a Validated to an Either.
val result: Validated[List[String], Int] =
( 5.asRight[List[String]].toValidated,
List("Error 1").asLeft[Int].toValidated,
List("Error 2").asLeft[Int].toValidated // List("Error 2") is preserved!
) mapN ((_: Int) + (_: Int) + (_: Int))
// result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2))
val resultAsEither = result.toEither
// resultAsEither: Either[List[String],Int] = Left(List(Error 1, Error 2))
An elaborated example of form validation with Validated (contrasted to a
solution with Either) can be found in the Cats documentation:
https://typelevel.org/cats/datatypes/validated.html
123 / 148
9. Traversals need Applicative
See: examples.Traversals
124 / 148
Recap: Future.traverse and Future.sequence
Most Scala devs know these methods defined in the Future companion object.
125 / 148
Recap: Future.traverse and Future.sequence
Most Scala devs know these methods defined in the Future companion object.
Let's start with Future.sequence. It is the simpler one.
Future.sequence API doc: Simple version of Future.traverse. Asynchronously
and non-blockingly transforms a TraversableOnce[Future[A]] into a
Future[TraversableOnce[A]]. Useful for reducing many Futures into a single
Future.
126 / 148
Recap: Future.traverse and Future.sequence
Most Scala devs know these methods defined in the Future companion object.
Let's start with Future.sequence. It is the simpler one.
Future.sequence API doc: Simple version of Future.traverse. Asynchronously
and non-blockingly transforms a TraversableOnce[Future[A]] into a
Future[TraversableOnce[A]]. Useful for reducing many Futures into a single
Future.
Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]].
val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0))
val fld: Future[List[Double]] = Future.sequence(lfd)
127 / 148
Recap: Future.traverse and Future.sequence
Most Scala devs know these methods defined in the Future companion object.
Let's start with Future.sequence. It is the simpler one.
Future.sequence API doc: Simple version of Future.traverse. Asynchronously
and non-blockingly transforms a TraversableOnce[Future[A]] into a
Future[TraversableOnce[A]]. Useful for reducing many Futures into a single
Future.
Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]].
val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0))
val fld: Future[List[Double]] = Future.sequence(lfd)
Typically in an application we don't have a List[Future[B]], but we produce it
by processing a List[A] asynchronously.
We can first map the List[A] with the function A => Future[B] and then invoke
sequence on the resulting List[Future[B]] in order to get a Future[List[B]].
128 / 148
Recap: Future.traverse and Future.sequence
Most Scala devs know these methods defined in the Future companion object.
Let's start with Future.sequence. It is the simpler one.
Future.sequence API doc: Simple version of Future.traverse. Asynchronously
and non-blockingly transforms a TraversableOnce[Future[A]] into a
Future[TraversableOnce[A]]. Useful for reducing many Futures into a single
Future.
Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]].
val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0))
val fld: Future[List[Double]] = Future.sequence(lfd)
Typically in an application we don't have a List[Future[B]], but we produce it
by processing a List[A] asynchronously.
We can first map the List[A] with the function A => Future[B] and then invoke
sequence on the resulting List[Future[B]] in order to get a Future[List[B]].
val li: List[Int] = List(1, 2, 3)
val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 }
val lfi: List[Future[Double]] = li.map(doubleItAsync)
val fld: Future[List[Double]] = Future.sequence(lfi) 129 / 148
Recap: Future.traverse and Future.sequence
Mapping and sequencing traverses the list twice.
traverse fuses mapping and sequencing into a single traversal.
130 / 148
Recap: Future.traverse and Future.sequence
Mapping and sequencing traverses the list twice.
traverse fuses mapping and sequencing into a single traversal.
Future.traverse takes a List[A] and a mapping function from A => Future[B]
and returns a Future[List[B]].
val li: List[Int] = List(1, 2, 3)
val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 }
val fld: Future[List[Double]] = Future.traverse(li)(doubleItAsync)
131 / 148
Recap: Future.traverse and Future.sequence
Mapping and sequencing traverses the list twice.
traverse fuses mapping and sequencing into a single traversal.
Future.traverse takes a List[A] and a mapping function from A => Future[B]
and returns a Future[List[B]].
val li: List[Int] = List(1, 2, 3)
val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 }
val fld: Future[List[Double]] = Future.traverse(li)(doubleItAsync)
Providing identity as mapping function to traverse has the same effect as
sequence.
val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0))
val fld: Future[List[Double]] = Future.traverse(lfd)(identity)
132 / 148
Typeclass Traverse
Cats generalizes this concept into the Traverse typeclass.
133 / 148
Typeclass Traverse
Cats generalizes this concept into the Traverse typeclass.
trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]]
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(identity)
// map in terms of traverse using the Id context
override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B])
}
134 / 148
Typeclass Traverse
Cats generalizes this concept into the Traverse typeclass.
trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]]
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(identity)
// map in terms of traverse using the Id context
override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B])
}
traverse and sequence require the G[_] to be an Applicative (Future before).
135 / 148
Typeclass Traverse
Cats generalizes this concept into the Traverse typeclass.
trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]]
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(identity)
// map in terms of traverse using the Id context
override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B])
}
traverse and sequence require the G[_] to be an Applicative (Future before).
The structure traversed over (List before) must be Foldable with Functor.
Hence Traverse extends these two traits.
136 / 148
Typeclass Traverse
Cats generalizes this concept into the Traverse typeclass.
trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]]
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(identity)
// map in terms of traverse using the Id context
override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B])
}
traverse and sequence require the G[_] to be an Applicative (Future before).
The structure traversed over (List before) must be Foldable with Functor.
Hence Traverse extends these two traits.
The traverse function can indeed be implemented with map and foldLeft or
foldRight. On the other hand foldLeft, foldRight and map can be implemented
with traverse.
137 / 148
Traverse[List].sequence[Future, A]
Instead of Future.sequence we can use Traverse[List].sequence.
138 / 148
Traverse[List].sequence[Future, A]
Instead of Future.sequence we can use Traverse[List].sequence.
val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0))
val fld1: Future[List[Double]] = Traverse[List].sequence(lfd)
// fld1: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0)))
import cats.syntax.traverse._ // supports 'sequence' as an enrichment of List
val fld2: Future[List[Double]] = lfd.sequence
// fld2: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0)))
139 / 148
Traverse[List].traverse[Future, A, B]
Instead of Future.traverse we can use Traverse[List].traverse.
140 / 148
Traverse[List].traverse[Future, A, B]
Instead of Future.traverse we can use Traverse[List].traverse.
val li: List[Int] = List(1, 2, 3)
val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 }
val fld1: Future[List[Double]] = Traverse[List].traverse(li)(doubleItAsync)
// fld1: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0)))
import cats.syntax.traverse._ // supports 'traverse' as an enrichment of List
val fld2: Future[List[Double]] = li traverse doubleItAsync
// fld2: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0)))
141 / 148
Traverse[Vector].traverse[Option, A, B]
With cats.Traverse can traverse not only over Lists but over any other
Foldable with Functor that has a Traverse instance, e.g. Vector. The mapping
function may produce any applicative effect, e.g. Option instead of Future.
142 / 148
Traverse[Vector].traverse[Option, A, B]
With cats.Traverse can traverse not only over Lists but over any other
Foldable with Functor that has a Traverse instance, e.g. Vector. The mapping
function may produce any applicative effect, e.g. Option instead of Future.
val vi1: Vector[Int] = Vector(3, 2, 1)
val vi2: Vector[Int] = Vector(3, 2, 0)
val divideBy: Int => Option[Double] = x => if (x == 0) None else Some { 6.0 / x }
val ovd1_1 = Traverse[Vector].traverse(vi1)(divideBy)
// ovd1_1: Option[Vector[Double]] = Some(Vector(2.0, 3.0, 6.0))
val ovd1_2 = Traverse[Vector].traverse(vi2)(divideBy)
// ovd1_2: Option[Vector[Double]] = None
import cats.syntax.traverse._ // supports 'traverse' as an enrichment of Vector
val ovd2_1 = vi1 traverse divideBy
// ovd2_1: Option[Vector[Double]] = Some(Vector(2.0, 3.0, 6.0))
val ovd2_2 = vi2 traverse divideBy
// ovd2_2: Option[Vector[Double]] = None
143 / 148
10. Resources
144 / 148
Scala Resources (1/2)
Code and Slides of this Talk:
https://github.com/hermannhueck/use-applicative-where-applicable
Cats documentation:
https://typelevel.org/cats/typeclasses/applicative.html
https://typelevel.org/cats/typeclasses/traverse.html
https://typelevel.org/cats/datatypes/validated.html
Herding Cats, Day 2 and 3:
http://eed3si9n.com/herding-cats/Functor.html
http://eed3si9n.com/herding-cats/Semigroupal.html
http://eed3si9n.com/herding-cats/Apply.html
http://eed3si9n.com/herding-cats/Applicative.html
"Scala with Cats", Chapters 6 and 7
Book by Noel Welsh and Dave Gurnell
https://underscore.io/books/scala-with-cats/
145 / 148
Scala Resources (2/2)
Live Coding Tutorial on Functor and Applicative by Michael Pilquist
FSiS Part 1 - Type Constructors, Functors, and Kind Projector
https://www.youtube.com/watch?v=Dsd4pc99FSY
FSiS Part 2 - Applicative type class
https://www.youtube.com/watch?v=tD_EyIKqqCk
146 / 148
Haskell Resources
Learn You a Haskell for Great Good!, Chapter 11
Online book by Miran Lipovaca
http://learnyouahaskell.com/functors-applicative-functors-and-monoids
Applicative Programming with Effects
Conor McBride and Ross Paterson in Journal of Functional Programming
18:1 (2008), pages 1-13
http://www.staff.city.ac.uk/~ross/papers/Applicative.pdf
The Essence of the Iterator Pattern
Jeremy Gibbons and Bruno C. d. S. Oliveira, Oxford University Computing
Laboratory
https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf
147 / 148
Thanks for Listening
Q & A
https://github.com/hermannhueck/use-applicative-where-applicable
148 / 148
Use Applicative where applicable!

Contenu connexe

Tendances

46630497 fun-pointer-1
46630497 fun-pointer-146630497 fun-pointer-1
46630497 fun-pointer-1AmIt Prasad
 
Functions in C++
Functions in C++Functions in C++
Functions in C++home
 
F# Presentation
F# PresentationF# Presentation
F# Presentationmrkurt
 
Python programming workshop session 4
Python programming workshop session 4Python programming workshop session 4
Python programming workshop session 4Abdul Haseeb
 
Python programming workshop session 2
Python programming workshop session 2Python programming workshop session 2
Python programming workshop session 2Abdul Haseeb
 
Python workshop session 6
Python workshop session 6Python workshop session 6
Python workshop session 6Abdul Haseeb
 
C++ Overview
C++ OverviewC++ Overview
C++ Overviewkelleyc3
 
Advance python programming
Advance python programming Advance python programming
Advance python programming Jagdish Chavan
 
Composition birds-and-recursion
Composition birds-and-recursionComposition birds-and-recursion
Composition birds-and-recursionDavid Atchley
 
FParsec Hands On - F#unctional Londoners 2014
FParsec Hands On -  F#unctional Londoners 2014FParsec Hands On -  F#unctional Londoners 2014
FParsec Hands On - F#unctional Londoners 2014Phillip Trelford
 
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswift
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswiftSwift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswift
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswiftTomohiro Kumagai
 

Tendances (20)

46630497 fun-pointer-1
46630497 fun-pointer-146630497 fun-pointer-1
46630497 fun-pointer-1
 
C and C++ functions
C and C++ functionsC and C++ functions
C and C++ functions
 
Functions in C++
Functions in C++Functions in C++
Functions in C++
 
F# Presentation
F# PresentationF# Presentation
F# Presentation
 
Advanced C - Part 2
Advanced C - Part 2Advanced C - Part 2
Advanced C - Part 2
 
lets play with "c"..!!! :):)
lets play with "c"..!!! :):)lets play with "c"..!!! :):)
lets play with "c"..!!! :):)
 
Python programming workshop session 4
Python programming workshop session 4Python programming workshop session 4
Python programming workshop session 4
 
Python programming workshop session 2
Python programming workshop session 2Python programming workshop session 2
Python programming workshop session 2
 
Python workshop session 6
Python workshop session 6Python workshop session 6
Python workshop session 6
 
C++ Overview
C++ OverviewC++ Overview
C++ Overview
 
Advance python programming
Advance python programming Advance python programming
Advance python programming
 
Composition birds-and-recursion
Composition birds-and-recursionComposition birds-and-recursion
Composition birds-and-recursion
 
C++ Functions
C++ FunctionsC++ Functions
C++ Functions
 
Functions
FunctionsFunctions
Functions
 
C++ presentation
C++ presentationC++ presentation
C++ presentation
 
Function lecture
Function lectureFunction lecture
Function lecture
 
FParsec Hands On - F#unctional Londoners 2014
FParsec Hands On -  F#unctional Londoners 2014FParsec Hands On -  F#unctional Londoners 2014
FParsec Hands On - F#unctional Londoners 2014
 
Time for Functions
Time for FunctionsTime for Functions
Time for Functions
 
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswift
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswiftSwift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswift
Swift 3.0 で変わったところ - 厳選 13 項目 #love_swift #cswift
 
Functions in C
Functions in CFunctions in C
Functions in C
 

Similaire à Use Applicative where applicable!

Arrow 101 - Kotlin funcional com Arrow
Arrow 101 - Kotlin funcional com ArrowArrow 101 - Kotlin funcional com Arrow
Arrow 101 - Kotlin funcional com ArrowLeandro Ferreira
 
Functions in advanced programming
Functions in advanced programmingFunctions in advanced programming
Functions in advanced programmingVisnuDharsini
 
Functional Programming in Swift
Functional Programming in SwiftFunctional Programming in Swift
Functional Programming in SwiftSaugat Gautam
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1Taisuke Oe
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and EffectsRaymond Roestenburg
 
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...Ruby Meditation
 
Functors, applicatives, monads
Functors, applicatives, monadsFunctors, applicatives, monads
Functors, applicatives, monadsrkaippully
 
Gentle Introduction to Functional Programming
Gentle Introduction to Functional ProgrammingGentle Introduction to Functional Programming
Gentle Introduction to Functional ProgrammingSaurabh Singh
 
Functional programming with haskell
Functional programming with haskellFunctional programming with haskell
Functional programming with haskellfaradjpour
 
Workhop iOS 1: Fundamentos de Swift
Workhop iOS 1: Fundamentos de SwiftWorkhop iOS 1: Fundamentos de Swift
Workhop iOS 1: Fundamentos de SwiftVisual Engineering
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Scott Wlaschin
 
Introducción a Elixir
Introducción a ElixirIntroducción a Elixir
Introducción a ElixirSvet Ivantchev
 
Dti2143 chapter 5
Dti2143 chapter 5Dti2143 chapter 5
Dti2143 chapter 5alish sha
 
Why Haskell Matters
Why Haskell MattersWhy Haskell Matters
Why Haskell Mattersromanandreg
 

Similaire à Use Applicative where applicable! (20)

Monads in Swift
Monads in SwiftMonads in Swift
Monads in Swift
 
Arrow 101 - Kotlin funcional com Arrow
Arrow 101 - Kotlin funcional com ArrowArrow 101 - Kotlin funcional com Arrow
Arrow 101 - Kotlin funcional com Arrow
 
Functions in advanced programming
Functions in advanced programmingFunctions in advanced programming
Functions in advanced programming
 
Functional Programming in Swift
Functional Programming in SwiftFunctional Programming in Swift
Functional Programming in Swift
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and Effects
 
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...
The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Or...
 
Dnipro conf
Dnipro confDnipro conf
Dnipro conf
 
Functors, applicatives, monads
Functors, applicatives, monadsFunctors, applicatives, monads
Functors, applicatives, monads
 
Gentle Introduction to Functional Programming
Gentle Introduction to Functional ProgrammingGentle Introduction to Functional Programming
Gentle Introduction to Functional Programming
 
Functional programming with haskell
Functional programming with haskellFunctional programming with haskell
Functional programming with haskell
 
Practical cats
Practical catsPractical cats
Practical cats
 
Workhop iOS 1: Fundamentos de Swift
Workhop iOS 1: Fundamentos de SwiftWorkhop iOS 1: Fundamentos de Swift
Workhop iOS 1: Fundamentos de Swift
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)
 
Introducción a Elixir
Introducción a ElixirIntroducción a Elixir
Introducción a Elixir
 
10. funtions and closures IN SWIFT PROGRAMMING
10. funtions and closures IN SWIFT PROGRAMMING10. funtions and closures IN SWIFT PROGRAMMING
10. funtions and closures IN SWIFT PROGRAMMING
 
Embedded C - Day 2
Embedded C - Day 2Embedded C - Day 2
Embedded C - Day 2
 
Dti2143 chapter 5
Dti2143 chapter 5Dti2143 chapter 5
Dti2143 chapter 5
 
C++ L05-Functions
C++ L05-FunctionsC++ L05-Functions
C++ L05-Functions
 
Why Haskell Matters
Why Haskell MattersWhy Haskell Matters
Why Haskell Matters
 

Plus de Hermann Hueck

What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?Hermann Hueck
 
Future vs. Monix Task
Future vs. Monix TaskFuture vs. Monix Task
Future vs. Monix TaskHermann Hueck
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and HaskellHermann Hueck
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaHermann Hueck
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Hermann Hueck
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickHermann Hueck
 

Plus de Hermann Hueck (8)

A Taste of Dotty
A Taste of DottyA Taste of Dotty
A Taste of Dotty
 
What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?
 
Pragmatic sbt
Pragmatic sbtPragmatic sbt
Pragmatic sbt
 
Future vs. Monix Task
Future vs. Monix TaskFuture vs. Monix Task
Future vs. Monix Task
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and Haskell
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with Slick
 

Dernier

VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionOnePlan Solutions
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfVishalKumarJha10
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
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
 
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
 
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
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdfPearlKirahMaeRagusta1
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfproinshot.com
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech studentsHimanshiGarg82
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
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
 

Dernier (20)

VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
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 ...
 
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
 
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
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
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
 

Use Applicative where applicable!

  • 1. Use Applicative where applicable! © 2018 Hermann Hueck https://github.com/hermannhueck/use-applicative-where-applicable 1 / 148
  • 2. Abstract Most Scala developers are familiar with monadic processing. Monads provide flatMap and hence for-comprehensions (syntactic sugar for map and flatMap). Often we don't need Monads. Applicatives are sufficient in many cases. In this talk I examine the differences between monadic and applicative processing and give some guide lines when to use which. After a closer look to the Applicative trait I will contrast the gist of Either and cats.data.Validated (the latter being an Applicative but not a Monad). I will also look at traversing and sequencing which harness Applicatives as well. (The code examples are implemented with Cats.) 2 / 148
  • 3. Agenda 1. Monadic Processing 2. Aside: Effects 3. Aside: Curried functions 4. Where Functor is too weak 5. Applicative Processing 6. Comparing Monad with Applicative 7. The Applicative trait 8. Either vs. Validated 9. Traversals need Applicative 10. Resources 3 / 148
  • 4. 1. Monadic Processing See: examples.MonadicProcessing 4 / 148
  • 5. The Problem: How to compute values wrapped in a context? Example context/e!ect: Option def processInts(compute: (Int, Int, Int) => Int) (oi1: Option[Int], oi2: Option[Int], oi3: Option[Int]) : Option[Int] = ??? 5 / 148
  • 6. The Problem: How to compute values wrapped in a context? Example context/e!ect: Option def processInts(compute: (Int, Int, Int) => Int) (oi1: Option[Int], oi2: Option[Int], oi3: Option[Int]) : Option[Int] = ??? val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3)) // result1: Option[Int] = Some(6) val result2 = processInts(sum3Ints, Some(1), Some(2), None) // result2: Option[Int] = None 6 / 148
  • 7. The Standard Solution: Monadic Processing with a for-comprehension (monad comprehension) Example context/e!ect: Option def processInts(compute: (Int, Int, Int) => Int) (oi1: Option[Int], oi2: Option[Int], oi3: Option[Int]) : Option[Int] = for { i1 <- oi1 i2 <- oi2 i3 <- oi3 } yield compute(i1, i2, i3) 7 / 148
  • 8. The Standard Solution: Monadic Processing with a for-comprehension (monad comprehension) Example context/e!ect: Option def processInts(compute: (Int, Int, Int) => Int) (oi1: Option[Int], oi2: Option[Int], oi3: Option[Int]) : Option[Int] = for { i1 <- oi1 i2 <- oi2 i3 <- oi3 } yield compute(i1, i2, i3) val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3)) // result1: Option[Int] = Some(6) val result2 = processInts(sum3Ints, Some(1), Some(2), None) // result2: Option[Int] = None 8 / 148
  • 9. Abstracting Option to F[_]: Monad def processInts[F[_]: Monad](compute: (Int, Int, Int) => Int) (fi1: F[Int], fi2: F[Int], fi3: F[Int]): F[Int] = for { i1 <- fi1 i2 <- fi2 i3 <- fi3 } yield compute(i1, i2, i3) 9 / 148
  • 10. Abstracting Option to F[_]: Monad def processInts[F[_]: Monad](compute: (Int, Int, Int) => Int) (fi1: F[Int], fi2: F[Int], fi3: F[Int]): F[Int] = for { i1 <- fi1 i2 <- fi2 i3 <- fi3 } yield compute(i1, i2, i3) val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val result1 = processInts(sum3Ints, Some(1), Some(2), Some(3)) // result1: Option[Int] = Some(6) val result2 = processInts(sum3Ints, Some(1), Some(2), None) // result2: Option[Int] = None val result3 = processInts(sum3Ints)(List(1, 2), List(10, 20), List(100, 200)) // result3: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222) val result4 = processInts(sum3Ints)(Future(1), Future(2), Future(3)) Await.ready(result4, 1 second) // result4 = scala.concurrent.Future[Int] = Future(Success(6)) 10 / 148
  • 11. Abstracting away the Ints def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = for { a <- fa b <- fb c <- fc } yield compute(a, b, c) 11 / 148
  • 12. Abstracting away the Ints def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = for { a <- fa b <- fb c <- fc } yield compute(a, b, c) val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val result1 = processABC(sum3Ints, Some(1), Some(2), Some(3)) // result1: Option[Int] = Some(6) val result2 = processABC(sum3Ints, Some(1), Some(2), None) // result2: Option[Int] = None val result3 = processABC(sum3Ints)(List(1, 2), List(10, 20), List(100, 200)) // result3: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222) val result4 = processABC(sum3Ints)(Future(1), Future(2), Future(3)) Await.ready(result4, 1 second) // result4 = scala.concurrent.Future[Int] = Future(Success(6)) 12 / 148
  • 13. Desugaring for-comprehension to map/flatMap def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = fa flatMap { a => fb flatMap { b => fc map { c => compute(a, b, c) } } } 13 / 148
  • 15. What is F[_] ? F[_] is a type constructor. It represents the computational context of the operation, also called the effect of the operation. e!ect != side e!ect With the context bound F[_]: Monad we constrain the effect to be a Monad. This makes F a place holder for any Monad which provides us map and flatMap. 15 / 148
  • 16. Some E!ects F[_] Effect Option a possibly missing value List an arbitrary number of values of the same type (non- determinism) Future an asyncronously computed value Either either a value of a type or a value of another type Id the effect of having no effect IO the effect of having a side effect Tuple2 two adjacent values of possibly different type Function1 a pure computation on a (yet unknown) input producing an output Reader a wrapped Function1 Writer a values that has another value attached which acts as a sort of log value 16 / 148
  • 17. 3. Aside: Curried functions See: examples.CurriedFunctions 17 / 148
  • 18. Every function is a Function1 ... 18 / 148
  • 19. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ 19 / 148
  • 20. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. 20 / 148
  • 21. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. val sumCurried: Int => Int => Int => Int = sum3Ints.curried // sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e 21 / 148
  • 22. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. The type arrow ( => ) is right associative. Hence we can omit the parentheses. val sumCurried: Int => Int => Int => Int = sum3Ints.curried // sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e 22 / 148
  • 23. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. The type arrow ( => ) is right associative. Hence we can omit the parentheses. The type arrow ( => ) is syntactic sugar for Function1. A => B is equivalent to Function1[A, B]. val sumCurried: Int => Int => Int => Int = sum3Ints.curried // sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e 23 / 148
  • 24. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. The type arrow ( => ) is right associative. Hence we can omit the parentheses. The type arrow ( => ) is syntactic sugar for Function1. A => B is equivalent to Function1[A, B]. val sumCurried: Int => Int => Int => Int = sum3Ints.curried // sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e val sumCurried2: Function1[Int, Function1[Int, Function1[Int, Int]]] = sumCurried // sumCurried2: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@7567ab8 24 / 148
  • 25. Every function is a Function1 ... val sum3Ints : (Int, Int, Int) => Int = _ + _ + _ val sum3Ints2: Function3[Int, Int, Int, Int] = _ + _ + _ ... if we curry it. The type arrow ( => ) is right associative. Hence we can omit the parentheses. The type arrow ( => ) is syntactic sugar for Function1. A => B is equivalent to Function1[A, B]. If uncurried again, we get the original function back. val sumCurried: Int => Int => Int => Int = sum3Ints.curried // sumCurried: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@43357c0e val sumCurried2: Function1[Int, Function1[Int, Function1[Int, Int]]] = sumCurried // sumCurried2: Int => (Int => (Int => Int)) = scala.Function3$$Lambda$4346/99143056@7567ab8 val sumUncurried: (Int, Int, Int) => Int = Function.uncurried(sumCurried) // sumUncurried: (Int, Int, Int) => Int = scala.Function$$$Lambda$6605/301079867@1589d895 25 / 148
  • 26. Partial application of curried functions 26 / 148
  • 27. Partial application of curried functions val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val sumCurried: Int => Int => Int => Int = sum3Ints.curried 27 / 148
  • 28. Partial application of curried functions val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val sumCurried: Int => Int => Int => Int = sum3Ints.curried val applied1st: Int => Int => Int = sumCurried(1) 28 / 148
  • 29. Partial application of curried functions val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val sumCurried: Int => Int => Int => Int = sum3Ints.curried val applied1st: Int => Int => Int = sumCurried(1) val applied2nd: Int => Int = applied1st(2) 29 / 148
  • 30. Partial application of curried functions val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val sumCurried: Int => Int => Int => Int = sum3Ints.curried val applied1st: Int => Int => Int = sumCurried(1) val applied2nd: Int => Int = applied1st(2) val applied3rd: Int = applied2nd(3) 30 / 148
  • 31. Partial application of curried functions val sum3Ints: (Int, Int, Int) => Int = _ + _ + _ val sumCurried: Int => Int => Int => Int = sum3Ints.curried val applied1st: Int => Int => Int = sumCurried(1) val applied2nd: Int => Int = applied1st(2) val applied3rd: Int = applied2nd(3) val appliedAllAtOnce: Int = sumCurried(1)(2)(3) 31 / 148
  • 32. Advantages of curried functions 32 / 148
  • 33. Advantages of curried functions 1. As curried functions can be partially applied. They are better composable than their uncurried counterparts. 33 / 148
  • 34. Advantages of curried functions 1. As curried functions can be partially applied. They are better composable than their uncurried counterparts. 2. Curried functions help the compiler with type inference. The compiler infers types by argument lists from left to right. 34 / 148
  • 35. Advantages of curried functions 1. As curried functions can be partially applied. They are better composable than their uncurried counterparts. 2. Curried functions help the compiler with type inference. The compiler infers types by argument lists from left to right. def filter1[A](la: List[A], p: A => Boolean) = ??? // uncurried scala> filter1(List(0,1,2), _ < 2) <console>:50: error: missing parameter type for expanded function ((x$1: <error>) => filter1(List(0,1,2), _ < 2) ^ 35 / 148
  • 36. Advantages of curried functions 1. As curried functions can be partially applied. They are better composable than their uncurried counterparts. 2. Curried functions help the compiler with type inference. The compiler infers types by argument lists from left to right. def filter2[A](la: List[A])(p: A => Boolean) = ??? // curried scala> filter2(List(0,1,2))(_ < 2) res5: List[Int] = List(0,1) def filter1[A](la: List[A], p: A => Boolean) = ??? // uncurried scala> filter1(List(0,1,2), _ < 2) <console>:50: error: missing parameter type for expanded function ((x$1: <error>) => filter1(List(0,1,2), _ < 2) ^ 36 / 148
  • 37. Curring in Java? -- (Sorry, I couldn't resist ;-) 37 / 148
  • 38. Curring in Java? -- (Sorry, I couldn't resist ;-) // Curring with lambdas Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried = a -> b -> c -> a + b + c; Integer result = sum3IntsCurried.apply(1).apply(2).apply(3); 38 / 148
  • 39. Curring in Java? -- (Sorry, I couldn't resist ;-) // Curring with lambdas Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried = a -> b -> c -> a + b + c; Integer result = sum3IntsCurried.apply(1).apply(2).apply(3); // Curring without lambdas Function<Integer, Function<Integer, Function<Integer, Integer>>> sum3IntsCurried = new Function<Integer, Function<Integer, Function<Integer, Integer>>>() { @Override public Function<Integer, Function<Integer, Integer>> apply(Integer a) { return new Function<Integer, Function<Integer, Integer>>() { @Override public Function<Integer, Integer> apply(Integer b) { return new Function<Integer, Integer>() { @Override public Integer apply(Integer c) { return a + b + c; } }; } }; } }; Integer result = sum3IntsCurried.apply(1).apply(2).apply(3); 39 / 148
  • 41. Curring in Haskell? No extra currying! 41 / 148
  • 42. Curring in Haskell? No extra currying! Every function is already curried. It can take only one parameter ... ... and returns only one value which might be another function. 42 / 148
  • 43. Curring in Haskell? No extra currying! Every function is already curried. It can take only one parameter ... ... and returns only one value which might be another function. Invoking functions with more than one parameter is possible. That is just syntactic sugar for curried functions. 43 / 148
  • 44. Partial application in Haskell sum3Ints :: Int -> Int -> Int -> Int -- function already curried sum3Ints x y z = x + y + z applied1st = sum3Ints 1 :: Int -> Int -> Int applied2nd = applied1st 2 :: Int -> Int applied3rd = applied2nd 3 :: Int appliedAll = sum3Ints 1 2 3 :: Int 44 / 148
  • 45. 4. Where Functor is too weak See: examples.WhereFunctorIsTooWeak 45 / 148
  • 46. Where Functor is too weak ... 46 / 148
  • 47. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? 47 / 148
  • 48. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? val add1: Int => Int = 1 + _ val sum2Ints: (Int, Int) => Int = _ + _ 48 / 148
  • 49. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? val add1: Int => Int = 1 + _ val sum2Ints: (Int, Int) => Int = _ + _ Option(1) map add1 // res1: Option[Int] = Some(2) 49 / 148
  • 50. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? val add1: Int => Int = 1 + _ val sum2Ints: (Int, Int) => Int = _ + _ Option(1) map add1 // res1: Option[Int] = Some(2) // Option(1) map sum2Ints // [error] found : (Int, Int) => Int // [error] required: Int => ? // [error] Option(1) map sum2Ints // [error] ^ 50 / 148
  • 51. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? val add1: Int => Int = 1 + _ val sum2Ints: (Int, Int) => Int = _ + _ Option(1) map add1 // res1: Option[Int] = Some(2) // Option(1) map sum2Ints // [error] found : (Int, Int) => Int // [error] required: Int => ? // [error] Option(1) map sum2Ints // [error] ^ Option(1) map sum2Ints.curried // res2: Option[Int => Int] = Some(scala.Function2$$Lambda$4648/454529628@7c9e8cef) 51 / 148
  • 52. Where Functor is too weak ... What to do if we want to fuse two Options to one by summing up the contained values? Option(1) + Option(2) == Option(3) // ??? val add1: Int => Int = 1 + _ val sum2Ints: (Int, Int) => Int = _ + _ Option(1) map add1 // res1: Option[Int] = Some(2) // Option(1) map sum2Ints // [error] found : (Int, Int) => Int // [error] required: Int => ? // [error] Option(1) map sum2Ints // [error] ^ map doesn't help us to fuse another Option(2) to that. Option(1) map sum2Ints.curried // res2: Option[Int => Int] = Some(scala.Function2$$Lambda$4648/454529628@7c9e8cef) 52 / 148
  • 53. ... Applicative can help us out 53 / 148
  • 54. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) 54 / 148
  • 55. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap Option(1) ap Option(2) // res4: Option[Int] = Some(3) 55 / 148
  • 56. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap Option(1) ap Option(2) // res4: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option] // res5: Option[Int] = Some(3) // pure is the same as Option.apply 56 / 148
  • 57. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap Option(1) ap Option(2) // res4: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option] // res5: Option[Int] = Some(3) // pure is the same as Option.apply Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2)) // res6: Option[Int] = Some(3) // no currying with ap2 57 / 148
  • 58. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap Option(1) ap Option(2) // res4: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option] // res5: Option[Int] = Some(3) // pure is the same as Option.apply Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2)) // res6: Option[Int] = Some(3) // no currying with ap2 Applicative[Option].map2(Option(1), Option(2))(sum2Ints) // res7: Option[Int] = Some(3) // no pure with map2 58 / 148
  • 59. ... Applicative can help us out Option(1) map sum2Ints.curried ap Option(2) // res3: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap Option(1) ap Option(2) // res4: Option[Int] = Some(3) sum2Ints.curried.pure[Option] ap 1.pure[Option] ap 2.pure[Option] // res5: Option[Int] = Some(3) // pure is the same as Option.apply Applicative[Option].ap2(sum2Ints.pure[Option])(Option(1), Option(2)) // res6: Option[Int] = Some(3) // no currying with ap2 Applicative[Option].map2(Option(1), Option(2))(sum2Ints) // res7: Option[Int] = Some(3) // no pure with map2 (Option(1), Option(2)) mapN sum2Ints // res8: Option[Int] = Some(3) // Tuple2#mapN 59 / 148
  • 60. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. 60 / 148
  • 61. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried val concat: (String, String) => String = _ ++ _ 61 / 148
  • 62. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried val concat: (String, String) => String = _ ++ _ List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3) // res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9) 62 / 148
  • 63. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried val concat: (String, String) => String = _ ++ _ List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3) // res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9) List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200) // res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400) 63 / 148
  • 64. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried val concat: (String, String) => String = _ ++ _ List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3) // res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9) List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200) // res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400) concat.curried.pure[List] <*> List("ha", "heh", "hmm") <*> List("?", "!", ".") // res12: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.) 64 / 148
  • 65. Cartesian Product When working with Applicative we always get the Cartesian product of two or more contexts. That isn't obvious when working with the Option context but becomes evident when we work with List. val add2: Int => Int => Int = ((_:Int) + (_:Int)).curried val mult2: Int => Int => Int = ((_:Int) * (_:Int)).curried val concat: (String, String) => String = _ ++ _ List((_:Int) * 0, (_:Int) + 100, (x:Int) => x * x) ap List(1, 2, 3) // res10: List[Int] = List(0, 0, 0, 101, 102, 103, 1, 4, 9) List(add2, mult2) <*> List[Int](1, 2) <*> List[Int](100, 200) // res11: List[Int] = List(101, 201, 102, 202, 100, 200, 200, 400) concat.curried.pure[List] <*> List("ha", "heh", "hmm") <*> List("?", "!", ".") // res12: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.) List("ha", "heh", "hmm") map concat.curried ap List("?", "!", ".") // res13: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.) 65 / 148
  • 66. 5. Applicative Processing See: examples.ApplicativeProcessing 66 / 148
  • 68. Trait Applicative Applicative is stronger than Functor, but weaker than Monad. 68 / 148
  • 69. Trait Applicative Applicative is stronger than Functor, but weaker than Monad. trait Functor[F[_]] { // simplified def map[A, B](fa: F[A])(f: A => B): F[B] } 69 / 148
  • 70. Trait Applicative Applicative is stronger than Functor, but weaker than Monad. trait Functor[F[_]] { // simplified def map[A, B](fa: F[A])(f: A => B): F[B] } trait Applicative[F[_]] extends Functor[F] { // simplified def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] } 70 / 148
  • 71. Trait Applicative Applicative is stronger than Functor, but weaker than Monad. trait Functor[F[_]] { // simplified def map[A, B](fa: F[A])(f: A => B): F[B] } trait Applicative[F[_]] extends Functor[F] { // simplified def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] } trait Monad[F[_]] extends Applicative[F] { // simplified def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } 71 / 148
  • 72. Replacing monadic with applicative context 72 / 148
  • 73. Replacing monadic with applicative context Applicative is strong enough for the problem we solved before with a monad-comprehension. 73 / 148
  • 74. Replacing monadic with applicative context Applicative is strong enough for the problem we solved before with a monad-comprehension. Monadic context: F[_] constrained to be a Monad def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = ??? // monadic solution 74 / 148
  • 75. Replacing monadic with applicative context Applicative is strong enough for the problem we solved before with a monad-comprehension. Monadic context: F[_] constrained to be a Monad def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = ??? // monadic solution Applicative context: F[_] constrained to be an Applicative def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = ??? // applicative solution 75 / 148
  • 76. Replacing monadic with applicative context Applicative is strong enough for the problem we solved before with a monad-comprehension. Monadic context: F[_] constrained to be a Monad def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = ??? // monadic solution Applicative context: F[_] constrained to be an Applicative def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = ??? // applicative solution Let's start with Option[Int] again and then abstract the solution. 76 / 148
  • 77. Solution with Applicative#ap: Example context/e!ect: Option def processInts(compute: (Int, Int, Int) => Int) (oi1: Option[Int], oi2: Option[Int], oi3: Option[Int]) : Option[Int] = { val f3Curried: Int => Int => Int => Int = compute.curried val of3: Option[Int => Int => Int => Int] = Some(f3Curried) // lifting val of2: Option[Int => Int => Int] = of3 ap oi1 // same as: oi1 map of3 val of1: Option[Int => Int] = of2 ap oi2 val result: Option[Int] = of1 ap oi3 result } Some(f3Curried) lifts the curried function into the Option context. 77 / 148
  • 78. Abstracting Option to F[_]: Applicative def processInts[F[_]: Applicative](compute: (Int, Int, Int) => Int) (fi1: F[Int], fi2: F[Int], fi3: F[Int]) : F[Int] = { val fCurried: Int => Int => Int => Int = compute.curried val ff: F[Int => Int => Int => Int] = Applicative[F].pure(fCurried) // lifting ff ap fi1 ap fi2 ap fi3 } Applicative[F].pure(fCurried) lifts the curried function into the generic F context. 78 / 148
  • 79. Abstracting away the Ints def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { val fCurried: A => B => C => D = compute.curried val ff: F[A => B => C => D] = Applicative[F].pure(fCurried) ff ap fa ap fb ap fc } 79 / 148
  • 80. Using pure syntax and <*> (alias for ap) def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { compute.curried.pure[F] <*> fa <*> fb <*> fc } 80 / 148
  • 81. Applicative#ap3 saves us from currying def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { Applicative[F].ap3(compute.pure[F])(fa, fb, fc) } 81 / 148
  • 82. Applicative#map3 avoids lifting with pure def processABC[F[_]: Applicative, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { Applicative[F].map3(fa, fb, fc)(compute) } map2, map3 .. map22 are available for Applicative. 82 / 148
  • 83. Apply is just Applicative without pure def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { Apply[F].map3(fa, fb, fc)(compute) } map2, map3 .. map22 come from Apply. Apply is a base trait of Applicative and provides us map3. 83 / 148
  • 84. Using Tuple3#mapN for convenience def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = { (fa, fb, fc) mapN compute } We just tuple up the three F's and invoke mapN with a function that takes three parameters. mapN fuses the F's into an F-result. Cats provides mapN as an enrichment for Tuple2, Tuple3 .. Tuple22. 84 / 148
  • 86. Comparing the solutions Monadic solution: def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = for { a <- fa b <- fb c <- fc } yield compute(a, b, c) 86 / 148
  • 87. Comparing the solutions Monadic solution: def processABC[F[_]: Monad, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]): F[D] = for { a <- fa b <- fb c <- fc } yield compute(a, b, c) Applicative solution: def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = (fa, fb, fc) mapN compute 87 / 148
  • 89. Currying again We provided processABC with two parameter lists. def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = (fa, fb, fc) mapN compute 89 / 148
  • 90. Currying again We provided processABC with two parameter lists. def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = (fa, fb, fc) mapN compute This improves composability and allows us provide the compute function and the effectful F's in separate steps. 90 / 148
  • 91. Currying again We provided processABC with two parameter lists. def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = (fa, fb, fc) mapN compute This improves composability and allows us provide the compute function and the effectful F's in separate steps. // providing the computation def processEffectfulInts[F[_]: Apply](fi1: F[Int], fi2: F[Int], fi3: F[Int]) : F[Int] = processABC(sum3Ints)(fi1, fi2, fi3) 91 / 148
  • 92. Currying again We provided processABC with two parameter lists. def processABC[F[_]: Apply, A, B, C, D](compute: (A, B, C) => D) (fa: F[A], fb: F[B], fc: F[C]) : F[D] = (fa, fb, fc) mapN compute This improves composability and allows us provide the compute function and the effectful F's in separate steps. // providing the computation def processEffectfulInts[F[_]: Apply](fi1: F[Int], fi2: F[Int], fi3: F[Int]) : F[Int] = processABC(sum3Ints)(fi1, fi2, fi3) // providing the 'effectful' parameters val result1 = processEffectfulInts(Option(1), Option(2), Option(3)) // result1: Option[Int] = Some(6) val result2 = processEffectfulInts(List(1, 2), List(10, 20), List(100, 200)) // result2: List[Int] = List(111, 211, 121, 221, 112, 212, 122, 222) 92 / 148
  • 93. 6. Comparing Monad with Applicative 93 / 148
  • 95. Sequential vs. parallel Monads enforce sequential operation! Imagine a for-comprehension. The generators are executed in sequence, one after the other. The yield clause is executed after all generators have finished. 95 / 148
  • 96. Sequential vs. parallel Monads enforce sequential operation! Imagine a for-comprehension. The generators are executed in sequence, one after the other. The yield clause is executed after all generators have finished. Applicatives allow for parallel/independent operations! The operations are by nature independent of each other. (That does not mean, they are asynchronous.) 96 / 148
  • 98. Fail-fast semantics Monads enforce short-circuiting! If an 'erraneous' value (e.g. None, Nil or Left) is found in a monadic computation, the computation stops immediately, because the result will not change if processing were continued. That is why we are not able to collect errors. 98 / 148
  • 99. Fail-fast semantics Monads enforce short-circuiting! If an 'erraneous' value (e.g. None, Nil or Left) is found in a monadic computation, the computation stops immediately, because the result will not change if processing were continued. That is why we are not able to collect errors. Applicatives do not short-circuit! As applicative operations are independent of each other, processing does not stop if an error occurs. It is possible to collect errors (see: cats.data.Validated) cats.data.Validated has an Applicative instance, but no Monad instance. We will come to that later. 99 / 148
  • 101. Composition Monads do not compose! In order to kind of 'compose' Monads we need an extra construct such as a Monad Transformer which itself is a Monad. I.e.: If we want to compose two Monads we need a third Monad to stack them up. 101 / 148
  • 102. Composition Monads do not compose! In order to kind of 'compose' Monads we need an extra construct such as a Monad Transformer which itself is a Monad. I.e.: If we want to compose two Monads we need a third Monad to stack them up. Applicatives compose! Composition of (Functor and) Applicative is a breeze. It is genuinely supported by these type classes. See: examples.Composition 102 / 148
  • 103. Composition - Code val loi1 = List(Some(1), Some(2)) val loi2 = List(Some(10), Some(20)) Monads do not compose! Applicatives compose! def processMonadic(xs: List[Option[Int]], ys: List[Option[Int]]): List[Option[Int]] = { val otli: OptionT[List, Int] = for { x <- OptionT[List, Int](xs) y <- OptionT[List, Int](ys) } yield x + y otli.value } val result1 = processMonadic(loi1, loi2) // result1: List[Option[Int]] = List(Some(11), Some(21), Some(12), Some(22)) def processApplicative(xs: List[Option[Int]], ys: List[Option[Int]]): List[Option[Int Applicative[List].compose[Option].map2(xs, ys)((_:Int) + (_:Int)) val result2 = processApplicative(loi1, loi2) // result2: List[Option[Int]] = List(Some(11), Some(21), Some(12), Some(22)) 103 / 148
  • 104. When to use which Use Applicative if all computations are independent of each other. Howto: Write a monadic solution with a for-comprehension. If the generated values (to the left of the generator arrow <- ) are only used in the yield clause at the end (not in an other generator) the computations are independent and allow for an applicative solution. Tuple up the computations and mapN them with a function which fuses the computation results to a final result. (This howto is applicable only if the effect in question also has a Monad instance.) 104 / 148
  • 105. When to use which - counter example val result = for { x <- Future { computeX } y <- Future { computeY(x) } z <- Future { computeZ(x, y) } } yield resultFrom(z) In this for-comprehension the subsequent computations depend on the previous ones. Hence this for-comprehension cannot be substituted by applicative processing. 105 / 148
  • 106. Principle of Least Power Given a choice of solutions, pick the least powerful solution capable of solving your problem. -- Li Haoyi 106 / 148
  • 107. 7. The Applicative trait 107 / 148
  • 108. Typeclass Functor Functor is the base trait of Applicative. trait Functor[F[_]] { // ----- primitive map def map[A, B](fa: F[A])(f: A => B): F[B] // ----- implementations in terms of the primitive map def fmap[A, B](fa: F[A])(f: A => B): F[B] = map(fa)(f) // alias for map def lift[A, B](f: A => B): F[A] => F[B] = fa => map(fa)(f) def as[A, B](fa: F[A], b: B): F[B] = map(fa)(_ => b) def void[A](fa: F[A]): F[Unit] = as(fa, ()) def compose[G[_]: Functor]: Functor[Lambda[X => F[G[X]]]] = ??? } 108 / 148
  • 109. Typeclass Applicative with primitives pure and ap trait Applicative[F[_]] extends Functor[F] { // ----- primitives def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] // ----- implementations in terms of the primitives def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] = ap(map(fa)(a => f(a, _: B)))(fb) override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _)) } 109 / 148
  • 110. Typeclass Applicative with primitives pure and map2 trait Applicative[F[_]] extends Functor[F] { // ----- primitives def pure[A](a: A): F[A] def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] // ----- implementations in terms of the primitives def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = map2(ff, fa)((f, a) => f(a)) // or: map2(ff, fa)(_(_)) override def map[A, B](fa: F[A])(f: A => B): F[B] = map2(fa, pure(()))((a, _) => f(a)) def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _)) } 110 / 148
  • 111. Typeclass Applicative with primitives pure and map and product trait Applicative[F[_]] extends Functor[F] { // ----- primitives def pure[A](a: A): F[A] override def map[A, B](fa: F[A])(f: A => B): F[B] def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] // ----- implementations in terms of the primitives def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = map(product(fa, fb)) { case (a, b) => f(a, b) } def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = map2(ff, fa)((f, a) => f(a)) // or: map2(ff, fa)(_(_)) } 111 / 148
  • 112. Typeclass Applicative trait Applicative[F[_]] extends Functor[F] { // ----- primitives def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] // ----- implementations in terms of the primitives override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) def <*>[A, B](ff: F[A => B])(fa: F[A]): F[B] = ap(ff)(fa) // alias for ap def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = { val ffBZ: F[B => Z] = ap(map(ff)(f => (a:A) => (b:B) => f(a, b)))(fa) ap(ffBZ)(fb) } // also: ap3, ap4 .. ap22 def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] = ap(map(fa)(a => f(a, _: B)))(fb) // also: map3, map4 .. map22 def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _)) def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = product(fa, fb) // also: tuple3, tuple4 .. tuple22 def compose[G[_]: Applicative]: Applicative[Lambda[X => F[G[X]]]] = ??? } 112 / 148
  • 113. Cats typeclass hierarchy (a small section of it) Complete hierarchy here 113 / 148
  • 114. 7. Either vs. Validated See: examples.EitherVsValidated 114 / 148
  • 115. Monadic processing with Either Monadic processing enforces fail-fast semantics. After an erraneous operation subsequent operations are not executed. Hence we get only the first of possibly multiple errors. 115 / 148
  • 116. Monadic processing with Either Monadic processing enforces fail-fast semantics. After an erraneous operation subsequent operations are not executed. Hence we get only the first of possibly multiple errors. val result: Either[List[String], Int] = for { x <- 5.asRight[List[String]] y <- List("Error 1").asLeft[Int] z <- List("Error 2").asLeft[Int] // List("Error 2") is lost! } yield x + y + z // result: Either[List[String],Int] = Left(List(Error 1)) 116 / 148
  • 117. Applicative processing with Either Applicative processing disables fail-fast semantics. But with Either we get the same result as before. Because Either has a Monad instance. 117 / 148
  • 118. Applicative processing with Either Applicative processing disables fail-fast semantics. But with Either we get the same result as before. Because Either has a Monad instance. val result: Either[List[String], Int] = ( 5.asRight[List[String]], List("Error 1").asLeft[Int], List("Error 2").asLeft[Int] // List("Error 2") is lost! ) mapN ((_: Int) + (_: Int) + (_: Int)) // result: Either[List[String],Int] = Left(List(Error 1)) 118 / 148
  • 119. Applicative processing with Validated cats.data.Validated is designed in analogy to Either. Instead of Right and Left it has Valid and Invalid. Validated has an Applicative instance but no Monad instance. 119 / 148
  • 120. Applicative processing with Validated cats.data.Validated is designed in analogy to Either. Instead of Right and Left it has Valid and Invalid. Validated has an Applicative instance but no Monad instance. val result: Validated[List[String], Int] = ( 5.valid[List[String]], List("Error 1").invalid[Int], List("Error 2").invalid[Int] // List("Error 2") is preserved! ) mapN ((_: Int) + (_: Int) + (_: Int)) // result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2)) 120 / 148
  • 121. Conversions between Either and Validated Conversion is supported in both directions Either#toValidated converts an Either to a Validated. Validated#toEither converts a Validated to an Either. 121 / 148
  • 122. Conversions between Either and Validated Conversion is supported in both directions Either#toValidated converts an Either to a Validated. Validated#toEither converts a Validated to an Either. val result: Validated[List[String], Int] = ( 5.asRight[List[String]].toValidated, List("Error 1").asLeft[Int].toValidated, List("Error 2").asLeft[Int].toValidated // List("Error 2") is preserved! ) mapN ((_: Int) + (_: Int) + (_: Int)) // result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2)) val resultAsEither = result.toEither // resultAsEither: Either[List[String],Int] = Left(List(Error 1, Error 2)) 122 / 148
  • 123. Conversions between Either and Validated Conversion is supported in both directions Either#toValidated converts an Either to a Validated. Validated#toEither converts a Validated to an Either. val result: Validated[List[String], Int] = ( 5.asRight[List[String]].toValidated, List("Error 1").asLeft[Int].toValidated, List("Error 2").asLeft[Int].toValidated // List("Error 2") is preserved! ) mapN ((_: Int) + (_: Int) + (_: Int)) // result: cats.data.Validated[List[String],Int] = Invalid(List(Error 1, Error 2)) val resultAsEither = result.toEither // resultAsEither: Either[List[String],Int] = Left(List(Error 1, Error 2)) An elaborated example of form validation with Validated (contrasted to a solution with Either) can be found in the Cats documentation: https://typelevel.org/cats/datatypes/validated.html 123 / 148
  • 124. 9. Traversals need Applicative See: examples.Traversals 124 / 148
  • 125. Recap: Future.traverse and Future.sequence Most Scala devs know these methods defined in the Future companion object. 125 / 148
  • 126. Recap: Future.traverse and Future.sequence Most Scala devs know these methods defined in the Future companion object. Let's start with Future.sequence. It is the simpler one. Future.sequence API doc: Simple version of Future.traverse. Asynchronously and non-blockingly transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]. Useful for reducing many Futures into a single Future. 126 / 148
  • 127. Recap: Future.traverse and Future.sequence Most Scala devs know these methods defined in the Future companion object. Let's start with Future.sequence. It is the simpler one. Future.sequence API doc: Simple version of Future.traverse. Asynchronously and non-blockingly transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]. Useful for reducing many Futures into a single Future. Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]]. val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0)) val fld: Future[List[Double]] = Future.sequence(lfd) 127 / 148
  • 128. Recap: Future.traverse and Future.sequence Most Scala devs know these methods defined in the Future companion object. Let's start with Future.sequence. It is the simpler one. Future.sequence API doc: Simple version of Future.traverse. Asynchronously and non-blockingly transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]. Useful for reducing many Futures into a single Future. Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]]. val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0)) val fld: Future[List[Double]] = Future.sequence(lfd) Typically in an application we don't have a List[Future[B]], but we produce it by processing a List[A] asynchronously. We can first map the List[A] with the function A => Future[B] and then invoke sequence on the resulting List[Future[B]] in order to get a Future[List[B]]. 128 / 148
  • 129. Recap: Future.traverse and Future.sequence Most Scala devs know these methods defined in the Future companion object. Let's start with Future.sequence. It is the simpler one. Future.sequence API doc: Simple version of Future.traverse. Asynchronously and non-blockingly transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]. Useful for reducing many Futures into a single Future. Simply spoken: sequence turns a List[Future[A]] into a Future[List[A]]. val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0)) val fld: Future[List[Double]] = Future.sequence(lfd) Typically in an application we don't have a List[Future[B]], but we produce it by processing a List[A] asynchronously. We can first map the List[A] with the function A => Future[B] and then invoke sequence on the resulting List[Future[B]] in order to get a Future[List[B]]. val li: List[Int] = List(1, 2, 3) val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 } val lfi: List[Future[Double]] = li.map(doubleItAsync) val fld: Future[List[Double]] = Future.sequence(lfi) 129 / 148
  • 130. Recap: Future.traverse and Future.sequence Mapping and sequencing traverses the list twice. traverse fuses mapping and sequencing into a single traversal. 130 / 148
  • 131. Recap: Future.traverse and Future.sequence Mapping and sequencing traverses the list twice. traverse fuses mapping and sequencing into a single traversal. Future.traverse takes a List[A] and a mapping function from A => Future[B] and returns a Future[List[B]]. val li: List[Int] = List(1, 2, 3) val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 } val fld: Future[List[Double]] = Future.traverse(li)(doubleItAsync) 131 / 148
  • 132. Recap: Future.traverse and Future.sequence Mapping and sequencing traverses the list twice. traverse fuses mapping and sequencing into a single traversal. Future.traverse takes a List[A] and a mapping function from A => Future[B] and returns a Future[List[B]]. val li: List[Int] = List(1, 2, 3) val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 } val fld: Future[List[Double]] = Future.traverse(li)(doubleItAsync) Providing identity as mapping function to traverse has the same effect as sequence. val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0)) val fld: Future[List[Double]] = Future.traverse(lfd)(identity) 132 / 148
  • 133. Typeclass Traverse Cats generalizes this concept into the Traverse typeclass. 133 / 148
  • 134. Typeclass Traverse Cats generalizes this concept into the Traverse typeclass. trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]] def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = traverse(fga)(identity) // map in terms of traverse using the Id context override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B]) } 134 / 148
  • 135. Typeclass Traverse Cats generalizes this concept into the Traverse typeclass. trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]] def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = traverse(fga)(identity) // map in terms of traverse using the Id context override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B]) } traverse and sequence require the G[_] to be an Applicative (Future before). 135 / 148
  • 136. Typeclass Traverse Cats generalizes this concept into the Traverse typeclass. trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]] def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = traverse(fga)(identity) // map in terms of traverse using the Id context override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B]) } traverse and sequence require the G[_] to be an Applicative (Future before). The structure traversed over (List before) must be Foldable with Functor. Hence Traverse extends these two traits. 136 / 148
  • 137. Typeclass Traverse Cats generalizes this concept into the Traverse typeclass. trait Traverse[F[_]] extends Foldable[F] with Functor[F] { // simplified def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]) : G[F[B]] def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = traverse(fga)(identity) // map in terms of traverse using the Id context override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse(fa)(f: A => Id[B]) } traverse and sequence require the G[_] to be an Applicative (Future before). The structure traversed over (List before) must be Foldable with Functor. Hence Traverse extends these two traits. The traverse function can indeed be implemented with map and foldLeft or foldRight. On the other hand foldLeft, foldRight and map can be implemented with traverse. 137 / 148
  • 138. Traverse[List].sequence[Future, A] Instead of Future.sequence we can use Traverse[List].sequence. 138 / 148
  • 139. Traverse[List].sequence[Future, A] Instead of Future.sequence we can use Traverse[List].sequence. val lfd: List[Future[Double]] = List(Future(2.0), Future(4.0), Future(6.0)) val fld1: Future[List[Double]] = Traverse[List].sequence(lfd) // fld1: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0))) import cats.syntax.traverse._ // supports 'sequence' as an enrichment of List val fld2: Future[List[Double]] = lfd.sequence // fld2: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0))) 139 / 148
  • 140. Traverse[List].traverse[Future, A, B] Instead of Future.traverse we can use Traverse[List].traverse. 140 / 148
  • 141. Traverse[List].traverse[Future, A, B] Instead of Future.traverse we can use Traverse[List].traverse. val li: List[Int] = List(1, 2, 3) val doubleItAsync: Int => Future[Double] = x => Future { x * 2.0 } val fld1: Future[List[Double]] = Traverse[List].traverse(li)(doubleItAsync) // fld1: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0))) import cats.syntax.traverse._ // supports 'traverse' as an enrichment of List val fld2: Future[List[Double]] = li traverse doubleItAsync // fld2: scala.concurrent.Future[List[Double]] = Future(Success(List(2.0, 4.0, 6.0))) 141 / 148
  • 142. Traverse[Vector].traverse[Option, A, B] With cats.Traverse can traverse not only over Lists but over any other Foldable with Functor that has a Traverse instance, e.g. Vector. The mapping function may produce any applicative effect, e.g. Option instead of Future. 142 / 148
  • 143. Traverse[Vector].traverse[Option, A, B] With cats.Traverse can traverse not only over Lists but over any other Foldable with Functor that has a Traverse instance, e.g. Vector. The mapping function may produce any applicative effect, e.g. Option instead of Future. val vi1: Vector[Int] = Vector(3, 2, 1) val vi2: Vector[Int] = Vector(3, 2, 0) val divideBy: Int => Option[Double] = x => if (x == 0) None else Some { 6.0 / x } val ovd1_1 = Traverse[Vector].traverse(vi1)(divideBy) // ovd1_1: Option[Vector[Double]] = Some(Vector(2.0, 3.0, 6.0)) val ovd1_2 = Traverse[Vector].traverse(vi2)(divideBy) // ovd1_2: Option[Vector[Double]] = None import cats.syntax.traverse._ // supports 'traverse' as an enrichment of Vector val ovd2_1 = vi1 traverse divideBy // ovd2_1: Option[Vector[Double]] = Some(Vector(2.0, 3.0, 6.0)) val ovd2_2 = vi2 traverse divideBy // ovd2_2: Option[Vector[Double]] = None 143 / 148
  • 145. Scala Resources (1/2) Code and Slides of this Talk: https://github.com/hermannhueck/use-applicative-where-applicable Cats documentation: https://typelevel.org/cats/typeclasses/applicative.html https://typelevel.org/cats/typeclasses/traverse.html https://typelevel.org/cats/datatypes/validated.html Herding Cats, Day 2 and 3: http://eed3si9n.com/herding-cats/Functor.html http://eed3si9n.com/herding-cats/Semigroupal.html http://eed3si9n.com/herding-cats/Apply.html http://eed3si9n.com/herding-cats/Applicative.html "Scala with Cats", Chapters 6 and 7 Book by Noel Welsh and Dave Gurnell https://underscore.io/books/scala-with-cats/ 145 / 148
  • 146. Scala Resources (2/2) Live Coding Tutorial on Functor and Applicative by Michael Pilquist FSiS Part 1 - Type Constructors, Functors, and Kind Projector https://www.youtube.com/watch?v=Dsd4pc99FSY FSiS Part 2 - Applicative type class https://www.youtube.com/watch?v=tD_EyIKqqCk 146 / 148
  • 147. Haskell Resources Learn You a Haskell for Great Good!, Chapter 11 Online book by Miran Lipovaca http://learnyouahaskell.com/functors-applicative-functors-and-monoids Applicative Programming with Effects Conor McBride and Ross Paterson in Journal of Functional Programming 18:1 (2008), pages 1-13 http://www.staff.city.ac.uk/~ross/papers/Applicative.pdf The Essence of the Iterator Pattern Jeremy Gibbons and Bruno C. d. S. Oliveira, Oxford University Computing Laboratory https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf 147 / 148
  • 148. Thanks for Listening Q & A https://github.com/hermannhueck/use-applicative-where-applicable 148 / 148