The Elm Architecture

Функциональный стиль glasses для ваших Android приложений

Сегодня я вам расскажу, как можно использовать функциональное программирование, чтобы сделать разработку Android приложений приятнее, предсказуемее и проще, чем это принято в индустрии. Мы обсудим различные важные концепции, такие как

  • что такое функциональный стиль
  • какие мы можем выделить основополагающие черты
  • как это может помочь в проектировании приложения

Также я дам вам рецепт того, как внедрить TEA в ваше приложение

Функциональный стиль

Стиль построения программ, который рассматривает программу как вычисление математической функции и избегает побочных эффектов и изменения состояния

-- Wikipedia

Когда начинаешь программировать в функциональном стиле и на себе ощущать простоту идей, невольно задумываешься,

  • "почему вокруг так мало функциональщины?"
  • "почему люди считают, что функциональное программирование - это такой способ получить PhD в Computer Science, а решать прикладные задачи лучше в другой парадигме"

А потом я открыл как-то страничку на википедии и до меня дошло, в чём дело - если прочесть определение функционального стиля - то всё становится понятно - вот тебе

  • "математической"- явно для зубрил, ладно, посмотрим чё там дают взамен
  • "избегает побочных эффектов и изменения состояния" - и если ты профессиональный программист - ты сразу такой

ЧТО?

Но в этом же вся суть!

Наши программы сделаны для того чтобы иметь эффект на окружающий мир. Какой стартап не возьми - у всех в целях "мы хотим изменить мир к лучшему!" Но как вы собираетесь изменять мир к лучшему с функциональном программированием, если у него в определении указано, что фишка в том, чтобы ничего не менять. Сразу перед глазами всплывает программист - дзен - буддист, которые все таски закрывает как "не будет исправлено", потому что код прекрасен сам по себе и делать ничего полезного ему не нужно.

И тем не менее, функциональное программирование всё же на слуху, посмотрите только на количество книг, рассказывающих о приёмах функционального программирования на разных языках

И если на JavaScript, Python, Scala ещё можно представить функциональщину, то тут есть ещё Java, Objective-C и C++.

И если посмотреть например, на Manning, они то начиная с 2016 года они выпускают по нескольку книг по фп в год!

ФП на самом деле уже здесь

На самом деле, фп уже здесь

Всё таки в нём есть множество идей, которые уже давно вошли в мейнстрим языки. Сейчас жизнь мобильного дева сложно представить без любой из этих вещей.

Но мы по прежнему используем эти идеи только на маленьких масштабах Когда же возникает мысль "хм, это фп вроде неплохо работает - а почему бы не написать всё приложение с его использованием?" Мы сразу её отметаем, вспоминая об определении из википедии. Мы ведь не можем делать программу на основе концепции, вся суть которой в том чтобы избегать возможности что-либо делать?

Функциональный стиль

Стиль построения программ, в котором код разделяют на три категории

Всем, кто раньше сталкивался с фп и у кого сформировалось мнение, что оно того не стоит, предлагаю перефразировать определение функционального стиля

В ФП можно прийти разными путями, и среди фп сообщества есть свои холивары по поводу того, каких программистов можно называть функциональщиками, каких - нет. И вообще, что есть Торт, а что - нет это вечный вопрос.

Но по моим ощущениям в чём то практики фп согласны - это в том, что функциональный стиль - это про то, как смотреть на код, который мы пишем

Давайте посмотрим на эти три категории внимательнее

Данные

Факты о моделируемой системе

Нам, программистам, не особо стоит объяснять что такое данные, но повторюсь:

Зачастую данные можно представить в виде key-value или списков. Мы привыкли, что "данные" обычно пользовательские, но они на самом деле повсюду и могут быть частью конфигурации либо частью логики

данными можно назвать как прилепленный на холодильник список покупок так и цвет кнопки, который вы передаёте в метод отрисовки GUI фреймворка

Данные сериализуются

%3 cluster_0 Хранилище Data1 Data JSON JSON Data1->JSON Data2 Data' JSON->Data2

Продолжаю капитанить: данные можно сериализовать!

Но, на самом деле, если хорошенько порефлексировать, то это замечательное свойство данных, которое, как мы потом посмотрим, остутствует у других категорий кода. Данные находятся вне времени - если их хранилище достаточно живучее и у нас есть инструмент для их извлечения - мы можем хоть через 1000 лет за ними вернуть и с ними ничего не будет.

Данные - лучшая абстракция

Данные - лучший вид абстракции, они абсолютно открыты для интерпретации Это значит что одни и те же данные можно интерпретировать как угодно например, можно список покупок использовать для того чтобы не забыть ничего в магазине а можно с помощью него же планировать бюджет

Но при этом, как и положено высочайшему уровню абстракции, данные бессмыслены сами по себе. Даже для того чтобы извлечь какой-то смысл из списка покупок, ваш мозг включает встроенный в него интерпретатор и производить из него другие данные, другие смыслы.

Вычисления

Чистые функции без обозримых сайд-эффектов

Вычисления самостоятельны

Кубики lego

%3 r1 B q1 C r1->q1 g t1 A t1->r1 f t1->q1 h

Действия

Вычисления, которые зависят от времени

Распространяются вверх по дереву вызова

Приносят деньги

Основной принцип функционального дизайна

1. Данные

2. Вычисления

3. Действия

После того, как я вам представил все три категории, можно вывести основной, на мой взгляд, принцип ФД. Стараемся использовать в коде как можно больше данных и вычислений. И контролируем, где и когда мы выполняем действия так как они единственные зависят от времени.

Model-View-Presenter

fun onClick(offerId: String) { // Действие
    api.getOffer(offerId) // Действие
        .doOnSubscribe { view.showLoading() } // Действие
        .subscribe { data ->
            view.hideLoading() // Действие
            view.display(data) // Действие
        }
}

Если мы вооружимся этим подходом и взглянем на классический MVP принятый в андроиде с точки зрения функционального дизайна - о ужас! Тут сплошь и рядом одни действия! Не удивительно, что типичный конструктор презентера требует десятков моков в тестах

Model-View-Intent

// Logic.kt

fun accept(state: State, msg: Msg) = when(msg) {
    is OnOfferClick -> if (!state.isLoading) {
            api.getOffer(msg.offerId) // Действие
               .startWith { SetLoading }
               .map { UpdateWithData(it) }
        } else {
            Observable.just(SetLoading)
        }
}

// Reducer.kt

fun state(state: State, update: Update): State = when(update) { // Вычисления
    is UpdateWithData -> state.copy(data = update.data, loading = false)
    is SetLoading -> state.copy(loading = true)
}

MVI

%3 UI UI Logic Logic UI->Logic Intent Logic->UI Effect Update Update Logic->Update API API Logic->API Reducer Reducer Update->Reducer Reducer->UI State' Reducer->Reducer:s State

The Elm-ish Architecture

// Reducer.kt

fun update(state: State, msg: Msg): Pair<State, Cmd?> = when(msg) {
    is OnOfferClick -> {
        val cmd = if (!state.loading) Load(msg.offerId) else null
        state.copy(loading = true) to cmd
    }
    is NewDataArrived -> state.copy(data = update.data, loading = false) to null
}
// EffectHandler.kt

class CommandInterpreter(api: Api) {
    fun interpret(cmd: Command, listener: (Msg) -> Unit) = when(cmd) {
        is Load -> {
            val model = api.getOffer(eff.offerId)
            listener(NewDataArrived(model))
        }
    }
}

TEA

%3 UI UI Reducer Reducer UI->Reducer Msg Reducer->UI State' Cmd Cmd Command Interpreter Reducer->Cmd Cmd Cmd->Reducer Msg API API Cmd->API Cmd2 Command Interpreter Cmd3 Command Interpreter Service Service Cmd2->Service Service2 Service2 Cmd3->Service2

Профит

Тотальный контроль над эффектами и их исполнением

Так и зачем всё это? Для того чтобы достичь тотального контроля над эффектами. Вам больше неинтересно, какой у вас фреймворк для асинхронной работы. RxJava, корутины, просто треды - практически все они выглядят одинаково.

Вам больше не нужно писать моки в тестах - действия вашей системы и так являются данными, которые можно просто ассертать.

Вы можете одной строчкой встроить логирование всего происходящего для того чтобы выловить баг.

1. Данные

2. Вычисления

3. Действия

@Mishkun

@lambda61

И эти принципы лежат во многих функциональных библиотеках и фреймворках. В том числе и в основе The Elm Architecture, принятой в функциональном языке Elm, компилирующемся в JavaScript и позволяющем писать фронтенд в в красивом фп стиле.

SpaceForward
Right, Down, Page DownNext slide
Left, Up, Page UpPrevious slide
GGo to slide number
POpen presenter console
HToggle this help