5 лет назад, после поиска годного решения в RxJs, Beacon, ...etc.js, и остановившись на flyd.js и написал 4-6 проектов используя только три фичи из либы stream, on, combine. Остальное оказалось не у дел, хотелось легкости бандла. Так появился alak, первые версии до 0.3 были всего 20-30 строк и полностью заменили flyd.

Год спустя появилась версия 0.4 уже на 72 строки с паттерн матчингом и mix (аналог combine из flyd или computed из vue). Спустя три года и десятки проектов — появилось ощущение годности делится опытом и релизить 1.0.



fantasy land


Резкое погружение в FRP человека всю жизнь писавшим классический OOP по учебнику, может взорвать голову как резкое погружение на глубину вне батискафа. Мне понадобилось несколько недель чтоб сжиться с мыслью о функциях вместо переменных. А спустя три месяца уже во всю любовался реактивным графом приложения — программу можно видеть как вены или корни/ветви дерева. Думаю мне помог простой интерес к древним языкам вроде санскрита и любовь к эльфийской Quenia. Продувать уши зажимая нос — базовый навык дайвера. Состоявшимся классическим OOP программистам потребуется сдуть пыль со своего навыка погружения в знания. Матёрым функциональщикам возможно уже ничего не поможет, но возможно они помогут нам в преодолении существующих форм и клеше описаний.

Неизвестные имена


В философии монадой называют базовую частицу мироздания — частицей бога, довольно низкоуровневое понятие. В спецификациях fantasy land, и в общем понимании FRP монада — нечто высокоуровневое, содержащее внутри функтор. В википедии есть два определения функтора в общем и OCaml контексте. В контексте alak базовая частица функтор — функция содержащая данные при изменении которых происходит обновление связанных функций/функторов. Всё, просто как в таблицах excel.



В bacon.js подобное зовётся «атомными обновлениями», RxJs так не умеет.

В alak код может выглядеть так:

import A from 'alak'
 // создание функтора потока - единственное назначение библиотеки alak 
const userId = A.flow();
 // создание ещё трёх функторов для дочерних узлов графа 
const followers = A.flow();
const profile = A.flow();
const tweets = A.flow();
//вариант связи userId -> profile ребром getProfile
const getProfile = id => api.getProfile(id).then(profile);
userId.up(getProfile);
//связь остальных узлов
userId.up(id => {
  api.getFollowers(id).then(followers)
  api.getTweets(id).then(tweets);
});

Запустить на codesandbox
При изменении userId произойдёт реактивное изменение followers, profile, tweets.

Всё для начала


Всё использование функтора Alak основывается на способности мыслить потоками, как в Go, но при чтении из канала из него не убывает и вся соль в построении графа с множественными связями/рёбрами, слушателями потока.

Ещё в angular.js этого было достаточно для красивого разруливания стейта запредельной сложности и запутанности. Крестом на angular2 для меня стало навязывание RxJS с сомнительной реактивностью. Увидев ReduX возникло ощущение два шага назад к реализации PureMVC.

Этим постом я, как и многие современные спикеры призываю отбросить процедурное мышление, груз ООП паттернов и начать движение к человеческому/литературному коду. Alak сегодня достаточно хорошо помогает декларативно описать реактивный граф стора.

Мы воспринимаем реактивность вью (jsx/svelte/html-шаблонизаторы) как должное.
— Так отчего у нас нет такой же реактивности в сторах?

Комментарии (21)


  1. hd_keeper
    15.09.2019 22:06
    +1

    А дальше?


  1. Nurked
    15.09.2019 22:24
    +1

    Божешь тыж можешь тыж! Панэ, зачем такое писать?

    Ок. Замечательно. Я бы на самом деле с удовольствием прочитал бы статью про FRP. И судя по всему у вас тут какое-то озарение, так что я бы прочитал вашу статью по FRP. Но ёлки-палки! Я программист с пятнадцатилетним стажем. Я могу собрать процессор из буханки хлеба и умею писать на более чем 40 языках. И при всём при этом мне потребовалось 20 минут на то, чтобы прочитать эти пять абзацев.

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

    А этот пацак все время говорит на языках, продолжения которых не знает! (с)

    Не могли бы вы помочь мне и вашим остальным читателям путём написания статьи которая объясняет базовые понятия FRP, но только очень хорошо и глубого их объясняет. Чтобы мы могли проникнуться всей изумительной красотой вашей статьи.


    1. GlebYP Автор
      16.09.2019 01:09

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

      Копья обломаны в поисках языка/способа описания графа данных. То, как можно в Clojure — вероятно подходит лучше всего, даже смотрел как в этой задаче применить Web Ontology Language, но пока тщетно. В мире JS недостаточно инструментария, с новыми версиями TS особенно от 2.8 появляются лучики надежды. Возможно новый подход будет успешней.

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

      Я считаю некоторые знания — не умом берутся и понимаются. Океан невозможно постичь не оказавшись в нём самостоятельно, сколько книг о нем не прочитай и фильмов не пересмотри. Только пройдя к нему свой путь и решившись нырнуть — можно понять что всё это глупости или ничего лучше в жизни нету. C очень большой вероятностью это будет совсем не то что у такого же коллеги рядом нырнувшего в эти же воды. :)


    1. xGromMx
      18.09.2019 09:49

      Более-менее что-то толковое github.com/funkia/hareactive (по каким-то канонам Конала Эллиота, как автора идеи FRP, что есть изоморфизм к темпоральной логике)


      1. GlebYP Автор
        18.09.2019 10:58

        Аватарка у Конала в твиттере под кайфом :)
        Чистое FRP вероятно как тяжёлые наркотики, потом захочется scalaz и будет не слезть.
        Пожалуй мне хватит одного функтора на всё.
        Возможно получится более человеческий стиль.


  1. k12th
    15.09.2019 22:46
    +1

    Мы воспринимаем реактивность вью (jsx/svelte/html-шаблонизаторы) как должное.
    — Так отчего у нас нет такой же реактивности в сторах?

    Я вот пользуюсь vuex и у меня есть реактивность в сторах:)


    1. GlebYP Автор
      16.09.2019 00:51

      Использование VueX/ReduX мне напоминают манипуляции с JQuery — когда у нас на всё есть отдельное действие. Последние пару лет я искал методы декларативного описания данных в сторе и связей/отношений друг с другом. Не очень удачно в плане доступности понимания решений. У нас нет привычного языка описания данных как HTML, и привычней диспатчить события, каждый раз прописывая мутации.


  1. kovserg
    15.09.2019 22:59

    Всегда интересовало: как организовывается завершение таких конечных автоматов, если его приспичило его немедленно остановить.


    1. GlebYP Автор
      16.09.2019 01:24

      Сперва это можно понять в контексте DOM через
      _.stopPropagation();
      Возможно тут будет подробно описано: learn.javascript.ru/event-bubbling

      Нужно просто добавить остановку в том месте где приспичило остановить.
      В случае alak я добавил тихое обновление узла графа без уведомления дочерних узлов.

      Останавливается опционально определённая ветка.
      — либо при условии, как фильтр
      — либо на совсем закрывается/отрезается


  1. andres_kovalev
    16.09.2019 02:17
    +1

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


    1. GlebYP Автор
      16.09.2019 03:24

      — Реактивные конечные автоматы.
      При решении, реализация которого рассмотрена в этой заметке на примере.

      Предполагаю, лет через N могут появится языки/способы описания отношения данных в подобных решениях.
      Тогда изложенное в статье может стать понятней.


    1. gatoazul
      16.09.2019 13:24

      Такое чувство, что автор получил наркотический приход и теперь пытается нам о нем рассказать.


      1. GlebYP Автор
        16.09.2019 15:05


  1. Scf
    16.09.2019 09:36

    Документации к проекту нет
    Комментариев в исходниках тоже нет
    Примеры минимальны (причем второй пример нерабочий)
    В статье так и не написано, что такое flow, как они комбинируются и где там реактивность.


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


    Вот пример, как надо описывать свои идеи: https://habr.com/ru/post/235121/


    1. GlebYP Автор
      16.09.2019 11:41

      Вдохновлялся иллюстрациями к этой статье в начале пути.

      Есть мнение: код нуждающийся в комментариях — вероятно лучше переписать на понятный без комментариев. Сейчас A.flow() — означает создание нового функтора. Благодаря вам, я понял — это не самая выразительная запись, просто исторически так сложилось. Комментарий в статью добавил, в проекте не нашёл поломки примера.

      Основное интересное решение — без которого нет смысла в комбинациях, я иллюстрировал анимацией в статье. Добавил в текст код и ссылку «запустить» где возможно увидеть пример комбинации на 48 строке, и помедитировать над реактивностью.
      Там очень мало строк.

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


  1. KwI
    16.09.2019 11:38

    Отличная идея, сам нахожусь в поисках и экспериментах с подобными вещами.

    Есть вопрос — как отлаживаете этот реактивный граф? ИМХО вся боль такой реактивности, которую я встречал в mobx например — чертовски сложно уследить, кто где как и с чем связался и куда данные летают.


    1. GlebYP Автор
      16.09.2019 11:56

      MobX — близок по идее, но как-то уже так приловчился работать с потоками.

      В решение одном из проектов на vue можно логировать имя тега компонента, откуда произошло обращение к какому узлу графа или действию. Действия(actions) так же при изменении графа логируют своё имя. Различные типы связей так же логируются откуда куда и что. Если изменение происходит вне стора — можно указать имя, от кого мутировать граф.
      — Так в консоли всегда можно отследить цепочку изменений кто кого на что менял.

      Получается очень много логов, но можно фильтровать.

      Планирую написать на d3 что-то вроде dev-tools, для просмотра такой активности, хотяб просто потому что это будет красиво.


    1. mikolalex
      17.09.2019 13:19

      Посмотрите например это: www.npmjs.com/package/mrr
      Можно включить логирование как для отдельной ячейки, так и для всего графа, с выводом цепочки обновлений зависимых ячеек в консоль.


      1. KwI
        17.09.2019 13:39

        Спасибо, интересное решение!


  1. xGromMx
    18.09.2019 09:44

    Понимание функтора напрочь отсутствует, как и многих других вещей
    В жс нет понятия FRP, тут можно мыслить только RP


    1. GlebYP Автор
      18.09.2019 10:38

      Как так? вы же написали статью для желающих изучать FRP в контексте RxJS.
      И выше рекомендовали библиотеку чистого FRP для js
      А https://ramdajs.com разве не FP?

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

      Мыслите свободно.