Share
Embed
Practical FP in Kotlin
[Droid Knights 2018](https://droidknights.github.io/2018/) 에서 발표한 [Practical FP in Kotlin] 발표 자료 입니다.
Sunghyun Hwang
April 22, 2018
Transcript
61 slides
-
•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 -
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) } -
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 -
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 -
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 -
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 -
fun main(args : Array<String>) { val f: (Int) -> Int
= { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array<String>) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told -
fun main(args : Array<String>) { val f: (Int) -> Int
= { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array<String>) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told -
fun main(args : Array<String>) { val f: (Int) -> Int
= { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } fun main(args : Array<String>) { val f: (Int) -> Int = { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told -
fun main(args : Array<String>) { val f: (Int) -> Int
= { i -> i * 2 } someFancyFunc(f) // 130 someFancyFunc(f) // 340 someFancyFunc(f) // 88 } When “FP” is told -
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] -
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 -
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 -
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 -
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] -
fun fibonacci(n: Int): Int { return if (n <= 2)
{ 1 } else { fibonacci(n-1) + fibonacci(n - 2) } } Declarative Programming -
Declarative programming is a programming paradigm that expresses the logic
of a computation without describing its control flow. [3] -
Functional programming is a programming paradigm that treats computation as
the evaluation of mathematical functions and avoids changing-state and mutable data. [4] -
•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 -
•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 -
Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad,
…) •Currying, Closure, … -
Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad,
…) •Currying, Closure, … -
Disclaimer •Practical Approaches for FP •Category Theory (ex. Functor, Monad,
…) •Currying, Closure, … -
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] -
interface PizzaView : PizzaRepository { fun getPizza(): Pizza = pizzas()[0]
} interface PizzaView { val r: PizzaRepository fun getPizza(): Pizza = r.pizzas()[0] } -
interface PizzaView : PizzaRepository { fun getPizza(): Pizza = pizzas()[0]
} interface PizzaView { val r: PizzaRepository fun getPizza(): Pizza = r.pizzas()[0] } -
interface PizzaRepositoryImpl : PizzaRepository { override fun pizzas(): List<Pizza> =
listOf( Pizza("Cheese Pizza"), Pizza("Some Pizza") ) } -
val obj = object : PizzaView, PizzaRepositoryImpl {} val obj
= object : PizzaView, PizzaRepositoryImpl { override val repository: PizzaRepository get() = this } -
val obj = object : PizzaView, PizzaRepositoryImpl {} val obj
= object : PizzaView, PizzaRepositoryImpl { override val repository: PizzaRepository get() = this } -
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] -
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] -
"The I/O monad does not make a function pure. It
just makes it obvious that it’s impure.” — Martin Odersky -
interface PizzaView : PizzaRepository { fun getPizzas(): IO<Pizza> = pizzas().map
{ p -> p.copy(name = p.name.reversed()) } } -
data class PizzaViewModel( val displayName: String, val color: Color )
interface PizzaActivityLike : PizzaView { fun onGetPizzasButtonClicked( sth: Boolean ): IO<PizzaViewModel> = getPizzas().filter { /* sth */ }.map { p -> toPizzaViewModel(p) } } -
interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Pizza {
// What if no pizzas are available? return db.getPizzaFromDb() } } -
interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Pizza? {
return try { db.getPizzaFromDb() } catch (oop: OutOfPizzaException) { null } } } -
interface PizzaRepositoryImpl : PizzaRepository { override fun pizza(): Either<Pizza, Exception>
{ return try { Left(db.getPizzaFromDb()) } catch (oop: OutOfPizzaException) { Right(oop) } } } -
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 } } } -
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( ) -
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( ) -
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( ) -
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( ) -
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 -
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, <Functional Programming, Simplified> p.101