Kotlin и ФП: то, что вы всегда хотели в Java, но боялись попробовать (даже после Java 8) Марат Ахин 15 февраля 2018 г. Санкт-Петербургский политехнический университет 1
Kotlin и ФП: то, что вы всегда хотели в Java, но боялисьпопробовать (даже после Java 8)
Марат Ахин
15 февраля 2018 г.
Санкт-Петербургский политехнический университет
1
Recap
То, о чем забыл рассеянный преподаватель
2
Функциональное программирование
3
Функциональное программирование
Что это такое?
• Функциональная программа является вычисляемым выражением,которое отвечает на интересующий нас вопрос
• Сам процесс вычисления нас особо не интересует• Вычисления являются повторяемыми
4
Функциональное программирование
• Функциональная программа отвечает на вопрос “что?”• Императивная программа отвечает на вопрос “как?”
Черное и белое
5
Функциональное программирование в реальности
Посередине между черным и белым
• Кто-то попытался добавить ФП в свой императивный мир• Java• C++• C#
• Кто-то попытался добавить ИП в свой функциональный мир• OCaml• Clojure
• Кто-то попытался объединить две стороны• Scala• Kotlin
• Кто-то просто странный• JavaScript
6
ФП в Котлине
• Все — это функция объект• Функция, получается, тоже объект, просто не совсем обычного типа
fun <T> stringifyList(list: List<T>, f: (T) -> String): List<String> {return TODO()
}
Wat? Generics?
7
Generics 101
8
Generics 101
• В Котлине есть дженерики• АКА параметрический полиморфизм• Основывается на дженериках из Java
• Привет, type erasure, вот это вот все• Но кое-что работает по-другому (ждите следующей серии!)
fun <T> anyToString(value: T): String = value.toString()
class ValueWrapper<T>(val value: T) {val valueAsString = value.toString()
}
9
Generics 101
10
Как использовать функциональный тип
fun <T> stringifyList(list: List<T>, f: (T) -> String): List<String> {val res = mutableListOf<String>()for (e in list) {
res.add(f.invoke(e))}return res
}
Wat? Mutable lists???
11
Kotlin collections 101
12
Kotlin collections 101
• Коллекции (списки, множества, отображения) в Котлине разделенына неизменяемые и изменяемые на уровне системы типов
• Неизменяемые коллекции не позволяют изменять свое содержимое(ммм…)
• Изменяемые коллекции расширяют неизменяемые и добавляютвозможность модификации
• Для создания коллекций принято использовать функции изстандартной библиотеки
• listOf()/mutableListOf()• setOf()/mutableSetOf()• ...
Более подробно посмотрим в следующих сериях
13
Kotlin collections 101
14
Как использовать функциональный тип
fun <T> stringifyList(list: List<T>, f: (T) -> String): List<String> {val res = mutableListOf<String>()for (e in list) {
res.add(f.invoke(e))}return res
}
15
Как использовать функциональный тип
fun <T> stringifyList(list: List<T>, f: (T) -> String): List<String> {val res = mutableListOf<String>()for (e in list) {
res.add(f(e))}return res
}
16
Как создать функциональный тип (1)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList, ::anyToString)
}}
17
Как создать функциональный тип (2)
class ErrorProcessor {fun buildErrorDesc(e: Error): String = e.toString()
fun buildErrorDescList(): List<String> {val errorList = getErrorList()return stringifyList(errorList, this::buildErrorDesc)
}}
18
Как создать функциональный тип (3)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList,
fun(e: Error): String {return e.toString()
})
}}
19
Как создать функциональный тип (3)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList,
fun(e): String {return e.toString()
})
}}
20
Как создать функциональный тип (3)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList,
fun(e) = e.toString())}
}
21
Как создать функциональный тип (4)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList,
{ e: Error -> e.toString() })}
}
22
Как создать функциональный тип (4)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList,
{ e -> e.toString() })}
}
23
Как создать функциональный тип (4)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList) {
e -> e.toString()}
}}
24
Как создать функциональный тип (4)
class ErrorProcessor {fun buildErrorDescList(): List<String> {
val errorList = getErrorList()return stringifyList(errorList) { it.toString() }
}}
25
Неужели все это придется писать самому?
26
Функции высших порядков
• fold/foldRight• map/flatMap• filter• groupBy• all/any/none• …
Под капот заглянем чуть позже
27
Захват переменных из окружения
class ErrorProcessor {private val freshErrorId
get() = UUID.randomUUID()
private val ignoredErrorTypes = mutableSetOf<ErrorType>()
fun buildErrorDescList(): List<String> =getErrorList()
.filter { it.type !in ignoredErrorTypes }
.map { freshErrorId to it }
.map { (id, error) -> ”[$id] ${error.msg}” }}
28
Функциональные типы с неявным this
• Обозначаются как T.[function type]• Являются очередным способом сделать код более читаемым
fun <T, R> with(receiver: T, block: T.() -> R): R =receiver.block()
public class UserBuilder {public void setName(final String name) { ... }public void setAddress(final Address address) { ...}public void setEmail(final String email) { ... }public User build() { ... }
}
29
Функциональные типы с неявным this
fun buildTestUser(): User =with(UserBuilder()) {
setName(”Test User”)setAddress(null)setEmail(”[email protected]”)build()
}
30
Функциональные типы с неявным this
fun buildTestUser_(ub: UserBuilder): User {ub.setName(”Test User”)ub.setAddress(null)ub.setEmail(”[email protected]”)return ub.build()
}
fun buildTestUser2(): User =with(UserBuilder(), ::buildTestUser_)
31
Функции-расширения
• Воплощение функциональных типов с неявным this надпроизвольными типами
• Являются еще одним очень классным способом сделать код болеечитаемым
• К этому моменту вы, должно быть, начали догадываться, чтоявляется одной из основных идей Котлина…
• Кроме того, позволяют расширять внешний код
fun UserBuilder.buildTestUser(): User =with(this, ::buildTestUser_)
32
Функции-расширения
public inline fun <T> T.apply(block: T.() -> Unit): T {block()return this
}public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)return this
}public inline fun <T, R> T.run(block: T.() -> R): R {
return block()}public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)}
33
Intermission
34
Inline functions
• В Котлине можно пометить функцию как inline
• Компилятор будет пробовать встраивать тело такой функции прямов место вызова
• Более того, он будет пробовать встраивать и переданные вinline-функцию лямбда-аргументы
• Это позволяет улучшить производительность за счет некоторыхдополнительных оптимизаций
• С другой стороны, это усложняет жизнь компилятору
• inline очень интересно дружит с дженериками, но об этом потом
35
Inline functions
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {return flatMapTo(ArrayList<R>(), transform)
}
36
Inline functions
• Лямбда-аргументы у inline-функции встраиваются по умолчанию• Если мы хотим это запретить, используем noinline на аргументе
• Например, если хотим передать эту лямбду дальше или сохранитьее где-нибудь
class FunctionStorage(val functionResults: MutableMap<() -> Any, Any>)
inline fun FunctionStorage.run(noinline body: () -> Any) =functionResults[body] ?: kotlin.run {
val res = body()functionResults[body] = resres
}37
Inline functions
public inline fun <T, R> Iterable<T>.flatMap(/* noinline */ transform: (T) -> Iterable<R>): List<R> {return flatMapTo(ArrayList<R>(), transform) // wat?
}
• Из inline- в inline-функцию передача работает нормально
• Потому что мы просто встраиваем один вызов в другой
38
How to return?
fun foo(angle: Double): Double {if (angle == 0.0) return Double.NaNreturn Math.sin(angle) + Math.cos(angle)
}
39
How to return?
fun bar(angle: Double, f: (Double) -> Double): Double {return f(angle)
}
fun main(args: Array<String>) {bar(Math.PI) { angle ->
if (angle == 0.0) return Double.NaNreturn Math.sin(angle) + Math.cos(angle)// return is not allowed here// wat?
}}
40
How to return?
fun bar(angle: Double, f: (Double) -> Double): Double {return f(angle)
}
fun main(args: Array<String>) {bar(Math.PI) { angle ->
if (angle == 0.0) return@bar Double.NaNreturn@bar Math.sin(angle) + Math.cos(angle)
}}
41
How to return?
fun bar(angle: Double, f: (Double) -> Double): Double {return f(angle)
}
fun main(args: Array<String>) {bar(Math.PI, fun(angle): Double {
if (angle == 0.0) return Double.NaNreturn Math.sin(angle) + Math.cos(angle)
})}
А что для inline-функций?
42
How to return?
inline fun bar(angle: Double, f: (Double) -> Double): Double {return f(angle)
}
fun main(args: Array<String>) {bar(Math.PI) { angle ->
if (angle == 0.0) return Double.NaNreturn Math.sin(angle) + Math.cos(angle)// everything is OK// wat?
}}
43
How to return?
fun youShallNotPrintlnAfterEmptyStr(list: List<String>) {list.forEach { str ->
if (str.isEmpty()) return // Non-local returnprintln(str)
}}
fun youShallPrintlnAfterEmptyStr(list: List<String>) {list.forEach { str ->
if (str.isEmpty()) return@forEachprintln(str)
}}
44
Crossinline
public inline fun <T> Iterable<T>.forEachAsync(action: (T) -> Unit): Unit {for (element in this)
GlobalThreadPool.submit(Callable { action(element) })// Possible non-local return
}
45
Crossinline
public inline fun <T> Iterable<T>.forEachAsync(crossinline action: (T) -> Unit): Unit {for (element in this)
GlobalThreadPool.submit(Callable { action(element) })}
46
Sequences
• В Java 8 появились стримы — лениво вычисляемые потоки данных• В Котлине аналогичная штука называется Sequence
• Ленивый аналог Iterable• Можно делать все то же самое, но лениво!
47
Sequences
public interface Sequence<out T> {public operator fun iterator(): Iterator<T>
}
public fun <T : Any> generateSequence(nextFunction: () -> T?): Sequence<T> = ...
public fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> = ...
48
Basic FP built-ins
49
Juicy insides
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {var accumulator = initialfor (element in this)
accumulator = operation(accumulator, element)return accumulator
}
50
Juicy insides
public operator fun <T> Iterable<T>.plus(elements: Array<out T>): List<T> {if (this is Collection) return this.plus(elements)val result = ArrayList<T>()result.addAll(this)result.addAll(elements)return result
}
51
Juicy insides
public operator fun <T> Collection<T>.plus(elements: Array<out T>): List<T> {val result = ArrayList<T>(this.size + elements.size)result.addAll(this)result.addAll(elements)return result
}
52
Juicy insides
public inline fun <T> List<T>.getOrElse(index: Int, defaultValue: (Int) -> T): T {return if (index >= 0 && index <= lastIndex) get(index)
else defaultValue(index)}
public fun <T> List<T>.getOrNull(index: Int): T? {return if (index >= 0 && index <= lastIndex) get(index)
else null}
53
What I learned today?
54
What I learned today?
• В Котлине есть функции высших порядков• Более того, есть функции с неявным this• А еще есть функции-расширения
Inline-функция-расширение, являющаяся оператором?Без проблем! ,
55
What’s next?
56