Slide 1

Slide 1 text

Speaker. Prac*cal FP in Kotlin ടࢿഅ

Slide 2

Slide 2 text

Software Engineer @Rainist We are making Banksalad

Slide 3

Slide 3 text

When “FP” is told

Slide 4

Slide 4 text

•Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism •Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism When “FP” is told

Slide 5

Slide 5 text

When “FP” is told fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) }

Slide 6

Slide 6 text

fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } When “FP” is told

Slide 7

Slide 7 text

fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } When “FP” is told

Slide 8

Slide 8 text

fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } When “FP” is told

Slide 9

Slide 9 text

fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } fun someFancyFunc(f: (Int) -> Int): Int { val immutable = random() tailrec fun fancy(n: Int, acc: Int): Int = if (n > 1) { fancy(n - 1, acc + f(n)) } else { acc } return fancy(immutable, 0) } When “FP” is told

Slide 10

Slide 10 text

fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told

Slide 11

Slide 11 text

fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told

Slide 12

Slide 12 text

fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told

Slide 13

Slide 13 text

fun main(args : Array) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told

Slide 14

Slide 14 text

Functional Code Functional code is characterized by one thing: the absence of side effects. It (a pure function) doesn’t rely on data outside the current function, and it doesn’t change data that exists outside the current function. [1]

Slide 15

Slide 15 text

Imperative Programming Declarative Programming

Slide 16

Slide 16 text

fun fibonacci(n: Int): Int { if (n <= 2) { return 1 } var a = 1 var b = 1 for (i in 2 until n) { val c = a + b a = b b = c } return b } Imperative Programming

Slide 17

Slide 17 text

fun fibonacci(n: Int): Int { if (n <= 2) { return 1 } var a = 1 var b = 1 for (i in 2 until n) { val c = a + b a = b b = c } return b } fun fibonacci(n: Int): Int { if (n <= 2) { return 1 } var a = 1 var b = 1 for (i in 2 until n) { val c = a + b a = b b = c } return b } Imperative Programming

Slide 18

Slide 18 text

fun fibonacci(n: Int): Int { if (n < 2) { return 1 } var a = 1 var b = 1 for (i in 2 until n) { val c = a + b a = b b = c } return b } fun fibonacci(n: Int): Int { if (n <= 2) { return 1 } var a = 1 var b = 1 for (i in 2 until n) { val c = a + b a = b b = c } return b } Imperative Programming

Slide 19

Slide 19 text

Imperative programming is a programming paradigm that uses statements that change a program’s state. It consists of a series of commands for the computer to perform. It focuses on describing the details of how a program operates. [2]

Slide 20

Slide 20 text

Declarative Programming

Slide 21

Slide 21 text

fun fibonacci(n: Int): Int { return if (n <= 2) { 1 } else { fibonacci(n-1) + fibonacci(n - 2) } } Declarative Programming

Slide 22

Slide 22 text

Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. [3]

Slide 23

Slide 23 text

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. [4]

Slide 24

Slide 24 text

•Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism •Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism When “FP” is told

Slide 25

Slide 25 text

•Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism •Language Features (that aid) • Immutable Data • First Class Function • Tail Call Optimization •Programming Techniques (to write) • Map • Reduce • Recursion • Currying •Advantages (of Functional Program) • Parallelization • Lazy Evaluation • Determinism When “FP” is told

Slide 26

Slide 26 text

Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad, …) •Currying, Closure, …

Slide 27

Slide 27 text

Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad, …) •Currying, Closure, …

Slide 28

Slide 28 text

Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad, …) •Currying, Closure, …

Slide 29

Slide 29 text

Non-FP FP

Slide 30

Slide 30 text

Non-FP FP

Slide 31

Slide 31 text

1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5] 1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5]

Slide 32

Slide 32 text

Cake Pattern in Scala

Slide 33

Slide 33 text

interface PizzaRepository { fun pizzas(): List }

Slide 34

Slide 34 text

interface PizzaView : PizzaRepository { fun getPizza(): Pizza = pizzas()[0] } interface PizzaView { val r: PizzaRepository fun getPizza(): Pizza = r.pizzas()[0] }

Slide 35

Slide 35 text

interface PizzaView : PizzaRepository { fun getPizza(): Pizza = pizzas()[0] } interface PizzaView { val r: PizzaRepository fun getPizza(): Pizza = r.pizzas()[0] }

Slide 36

Slide 36 text

interface PizzaRepositoryImpl : PizzaRepository { override fun pizzas(): List = listOf( Pizza("Cheese Pizza"), Pizza("Some Pizza") ) }

Slide 37

Slide 37 text

val obj = object : PizzaView, PizzaRepositoryImpl {} val obj = object : PizzaView, PizzaRepositoryImpl { override val repository: PizzaRepository get() = this }

Slide 38

Slide 38 text

val obj = object : PizzaView, PizzaRepositoryImpl {} val obj = object : PizzaView, PizzaRepositoryImpl { override val repository: PizzaRepository get() = this }

Slide 39

Slide 39 text

class PizzaActivity : AppCompatActivity(), PizzaView, PizzaRepositoryImpl { /* (...) */ }

Slide 40

Slide 40 text

interface Always : InsteadOfConcreteClass, IfPossible { /* sth */ }

Slide 41

Slide 41 text

data class Always(val ifPossible: Boolean = true)

Slide 42

Slide 42 text

1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5] 1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5]

Slide 43

Slide 43 text

fun addTopping(pizza: Pizza, topping: String) = pizza.copy( toppings = pizza.toppings.plus(topping) )

Slide 44

Slide 44 text

1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5] 1. Writing pure functions is easy, but combining them into a complete application is where things get hard. (…) 4. Because you can’t mutate existing data, you instead use a pattern that I call, “Update as you copy.” 5. Pure functions and I/O don’t really mix. (…) [5]

Slide 45

Slide 45 text

"The I/O monad does not make a function pure. It just makes it obvious that it’s impure.” — Martin Odersky

Slide 46

Slide 46 text

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

Slide 47

Slide 47 text

interface PizzaRepository { fun pizzas(): IO }

Slide 48

Slide 48 text

interface PizzaView : PizzaRepository { fun getPizzas(): IO = pizzas().map { p -> p.copy(name = p.name.reversed()) } }

Slide 49

Slide 49 text

data class PizzaViewModel( val displayName: String, val color: Color ) interface PizzaActivityLike : PizzaView { fun onGetPizzasButtonClicked( sth: Boolean ): IO = getPizzas().filter { /* sth */ }.map { p -> toPizzaViewModel(p) } }

Slide 50

Slide 50 text

class PizzaActivity : AppCompatActivity(), PizzaActivtyLike, PizzaRepositoryImpl { /* (...) */ }

Slide 51

Slide 51 text

interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Pizza { // What if no pizzas are available? return db.getPizzaFromDb() } }

Slide 52

Slide 52 text

interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Pizza? { return try { db.getPizzaFromDb() } catch (oop: OutOfPizzaException) { null } } }

Slide 53

Slide 53 text

interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Either { return try { Left(db.getPizzaFromDb()) } catch (oop: OutOfPizzaException) { Right(oop) } } }

Slide 54

Slide 54 text

sealed class ShadowOfPizza data class RealPizza(val p: Pizza) : ShadowOfPizza() object OutOfPizza : ShadowOfPizza() interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): ShadowOfPizza { return try { RealPizza(db.getPizzaFromDb()) } catch (oop: OutOfPizzaException) { OutOfPizza } } }

Slide 55

Slide 55 text

1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers 1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers .map( )

Slide 56

Slide 56 text

1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers 1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers .map( )

Slide 57

Slide 57 text

1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers 1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers .map( )

Slide 58

Slide 58 text

1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers 1. Use interface always 2. Use only data class 3. Let impure jobs happen only in impure layers 4. Let exception happen only in impure layers .map( )

Slide 59

Slide 59 text

We are hiring! https://rainist.com/recruit/engineer • Back-end Engineer (Scala) • Data Engineer • Data Scientist • Android Engineer (Kotlin) • iOS Engineer • QA Engineer • Security Engineer • … X Engineer

Slide 60

Slide 60 text

Q&A?

Slide 61

Slide 61 text

References (1) https://maryrosecook.com/blog/post/a-practical-introduction- to-functional-programming (2) https://en.wikipedia.org/wiki/Imperative_programming (3) https://en.wikipedia.org/wiki/Declarative_programming (4) https://en.wikipedia.org/wiki/Functional_programming (5) Alvin Alexander, p.101