zeenea.com.
Pure FP with IO
And without headache
What is FP
Functional programming is programming with functions
Pure functions
No side effects (the less possible)
Side effects
Avoidable
Deferrable
The monad detour
“All methaphor are wrong, but some are useful”
Put a type in a box
pure(a: A): M[A]
Transform the type in the box
map(m: M[A], f: A => B): M[B]
Transform the type in the box
map(m: M[A], f: A => M[B]): M[M[B]]
Stacking the boxes
flatMap(m: M[A], f: A => M[B]): M[B]
A monad represent a concept
Common operations: plural
val users = Seq(User("1", "John"))
val users = Seq()
for (user <- users) {
doStuff(user)
}
val users = Seq(User("1", "John"))
val users = Seq()
users.map(doStuff)
Common operations: optional
val userOpt= User("1", "John")
val userOpt = null
if (userOpt != null) {
doStuff(userOpt)
}
val userOpt = Option(User("1", "John"))
val userOpt = None
users.map(doStuff)
Common operations: error
def fetchUser(id: String) = User(id, "John")
def fetchUser(id: String) = throw new Exception
try {
doStuff(fetchUser("1"))
} catch {
case _: Throwable =>
}
val userErr = Try(User("1", "Loïc"))
val userErr = Failure(new Exception)
userErr.map(doStuff)
Common operations: async
var result: User = null
val r = new Runnable {
override def run(): Unit = {
result = User("1", "John")
}
}
val t = new Thread(r)
t.start()
t.join()
doStuff(result)
val userFut = Future(User("1", "Loïc"))
val userFut = Future(throw new Exception)
Await.result(userFut.map(doStuff), Duration.Inf)
Map operation
Pure function ~ Referentially transparent
An expression is called referentially transparent if it can be replaced with its corresponding value without changing the program's behavior.
Pure function ~ Referentially transparent
sum(Seq(1, 2, 3)) + sum(Seq(1, 2, 3))
val x = sum(Seq(1, 2, 3))
x + x
def sum(in: Seq[Int]): Int = {
println("aaa")
in.sum
}
// aaa
// aaa
// aaa
Pure function ~ Referentially transparent
def trySum(in: Seq[Int]): Try[Int] = Try {
println("aaa")
in.sum
}
for {
r1 <- trySum(Seq(1, 2, 3))
r2 <- trySum(Seq(1, 2, 3))
} yield r1 + r2
val x = trySum(Seq(1, 2, 3))
for {
r1 <- x
r2 <- x
} yield r1 + r2
// aaa
// aaa
// aaa
Who to make code pure?
Creating an IO
class IO[A](private val effect: () => A)
Creating an IO
class IO[A](private val effect: () => A) {
def unsafeRunSync(): A = effect()
}
Creating an IO
class IO[A](private val effect: () => A) {
def map[B](f: A => B): IO[B] = ???
def flatMap[B](f: A => IO[B]): IO[B] = ???
def unsafeRunSync(): A = effect()
}
Creating an IO
class IO[A](private val effect: () => A) {
def map[B](f: A => B): IO[B] =
new IO(() => f(effect()))
def flatMap[B](f: A => IO[B]): IO[B] = ???
def unsafeRunSync(): A = effect()
}
Creating an IO
class IO[A](private val effect: () => A) {
def map[B](f: A => B): IO[B] =
new IO(() => f(effect()))
def flatMap[B](f: A => IO[B]): IO[B] =
new IO(() => f(effect()).effect())
def unsafeRunSync(): A = effect()
}
Creating an IO
class IO[A](private val effect: () => A) {
def map[B](f: A => B): IO[B] =
new IO(() => f(effect()))
def flatMap[B](f: A => IO[B]): IO[B] =
new IO(() => f(effect()).effect())
def unsafeRunSync(): A = effect()
}
object IO {
def apply[A](a: => A): IO[A] = new IO(() => a)
}
Creating an IO
Pure function ~ Referentially transparent
def ioSum(in: Seq[Int]): IO[Int] = IO {
println("aaa")
in.sum
}
val res = for {
r1 <- ioSum(Seq(1, 2, 3))
r2 <- ioSum(Seq(1, 2, 3))
} yield r1 + r2
val x = ioSum(Seq(1, 2, 3))
val res = for {
r1 <- x
r2 <- x
} yield r1 + r2
Pure function ~ Referentially transparent
def ioSum(in: Seq[Int]): IO[Int] = IO {
println("aaa")
in.sum
}
val res = for {
r1 <- ioSum(Seq(1, 2, 3))
r2 <- ioSum(Seq(1, 2, 3))
} yield r1 + r2
res.unsafeRunSync()
val x = ioSum(Seq(1, 2, 3))
val res = for {
r1 <- x
r2 <- x
} yield r1 + r2
res.unsafeRunSync()
// aaa
// aaa
// aaa
// aaa
Pure function ~ Referentially transparent
def ioSum(in: Seq[Int]): IO[Int] = IO {
println("aaa")
in.sum
}
val res = for {
r1 <- ioSum(Seq(1, 2, 3))
r2 <- ioSum(Seq(1, 2, 3))
} yield r1 + r2
res.unsafeRunSync()
val x = ioSum(Seq(1, 2, 3))
val res = for {
r1 <- x
r2 <- x
} yield r1 + r2
res.unsafeRunSync()
// aaa
// aaa
// aaa
// aaa
Pure function ~ Referentially transparent
Pure vs impure
def trySum(in: Seq[Int]): Try[Int] = Try {
println("aaa")
in.sum
}
val res = for {
r1 <- trySum(Seq(1, 2, 3))
r2 <- trySum(Seq(1, 2, 3))
} yield r1 + r2
res.get
def ioSum(in: Seq[Int]): IO[Int] = IO {
println("aaa")
in.sum
}
val res = for {
r1 <- ioSum(Seq(1, 2, 3))
r2 <- ioSum(Seq(1, 2, 3))
} yield r1 + r2
res.unsafeRunSync()
Execute a program
Build a program
Execute it
Conclusion
Conclusion
What's Functional Programming All About ? - Li Haoyi
What is Functional Programming ? - Alex Nedelcu
Don’t Be Scared Of Functional Programming - Jonathan Morgan
Introduction à la Programmation Fonctionnelle - Jean-Baptiste Giraudeau