Шось про айтішку – Telegram
Шось про айтішку
1.47K subscribers
447 photos
163 videos
2 files
599 links
Фронтенд, ШІ, 3D друк, FPV, історії з життя та роботи
Download Telegram
Был вопрос по поводу вчерашнего поста: что ломает shallow equals с иммутабельными данными, если значения по ссылке равны?

Сравнение по ссылке работает верно только в том случае, если оба значения действительно ссылаються на один и тот же объект в памяти. То есть здесь все ок: (let [m1 {:x 1} m2 m1] (identical? m1 m2)) ;; true

Но два разных объекта в памяти никогда не будут идентичны: (identical? {:x 1} {:x 1}) ;; false. Хотя структурно они равны: (= {:x 1} {:x 1}) ;; true. Случай когда разные значения в памяти равны структурно возникает довольно часто, поэтому только сравнением по ссылке не обойтись.

На самом деле теоретически это возможно сделать. Если все значения иммутабельны, то по факту нет смысла аллоцировать в памяти структурно равное значение. Достаточно кэшировать такое значение и возвращать на него ссылку при создании структурно равного. Это одна из оптимизация использования памяти с иммутабельными данными (кстати пришла к нам со времен лиспов https://en.wikipedia.org/wiki/Hash_consing). Но на деле выходит, что применять это глобально всегда приносит больше вреда. Поэтому такие штуки обычно выносят в библиотеки и применяют к отдельным частям системы.

P.S. С вопросами пишите в личку @roman01la
Второй вечер залипаю на видео материалы с Ричардом Фейнманом, завидую его умению объяснять так доходчиво:

https://www.youtube.com/watch?v=VdYujzyaX68
https://www.youtube.com/watch?v=P1ww1IXRfTA
https://www.youtube.com/watch?v=EKWGGDXe5MA

Also, из WTF в программировании:
- GHC ограничивает размер таплов до 62, дальше segfault 😀 https://github.com/ghc/ghc/blob/e3f341f334d89c88f388d8e864ed8762d0890a64/libraries/ghc-prim/GHC/Tuple.hs#L170
- Rust 32 https://github.com/diesel-rs/diesel/blob/57e17f224b7938b33487d2a0e6ba83fb524dd42e/diesel/src/macros/tuples.rs
- Scala 22

И посмотрите на прекрасные елки из сигнатур 😀
Опубликовал третий выпуск скринкастов. В этот раз о мемоизации компонентов https://youtu.be/H9yyjdRhstU
​​Пример как React мог бы сообщать о «вредительских» оптимизациях в shouldComponentUpdate или React.memo https://codesandbox.io/s/kmpxv86j85
Из интересного:
Опубликовали технические детали гугловой Fuchsia OS. Перспектива писать UI на Dart не радует, но в целом выглядит интересно https://bzdww.com/article/163937/

Java 12 релизнулась с Shenandoah GC («ultra-low pause time garbage collector») https://wiki.openjdk.java.net/display/shenandoah/Main

Сообщество кложуристов все больше донатит на open source. Теперь выделяют грант в $3000 на поддержку проектов https://www.clojuriststogether.org/news/february-2019-monthly-update/
​​Пробовали когда-нибудь обрабатывать видео в браузере? Chrome дает ряд API для захвата видео и аудио источников, поэтому, в принципе, это возможно.

Комбинировать видео с текстом, изображениями и эффектами можно в WebGL: видео стримится в канвас + покадровый рендрер в текстуры и композинг (https://github.com/bbc/VideoContext) + покадровый захват (https://github.com/spite/ccapture.js/) и экспорт обратно в видео (https://github.com/thenickdude/webm-writer-js/).

При желании это можно распараллелить в воркерах используя OffscreenCanvas.

Единственная проблема сейчас — браузер не дает точный тайминг начала и конца видео, поэтому поймать первые и последние кадры практически невозможно. Альтернатива резать видео на кадры в ffmpeg скомпиленом в wasm (https://github.com/Kagami/ffmpeg.js)
Открыта продажа билетов на Heart of Clojure, 2го августа в Бельгии https://heartofclojure.eu/
Пару недель назад опубликовал последний скринкаст о создании cljs враппера для React (все видео в плейлисте https://www.youtube.com/playlist?list=PLHOTezm7WWkl4rfd6j0fsL65DrxfWz4XM). В итоге библиотека практически добралась до статуса production-ready, поэтому грех было не довести ее до ума: https://github.com/roman01la/uix

Помимо uix уже существует еще два враппера для React с хуками, саспенсом и т.д. Изначально у каждого был свой интерпретатор Hiccup, но в итоге мы взяли самый быстрый из Reagent и коллективными усилиями выжали из него максимум, даже немного обогнали оригинал. По бенчмаркам интерпретация + построение VDOM вышли ~2.5x медленее чистого React, тогда как Reagent примерно в 3.5x медленее.

react x 22989 ops/s, elapsed 435ms
uix-interpret x 9166 ops/s, elapsed 1091ms
reagent-interpret x 6680 ops/s, elapsed 1497ms

Ну и реализация враппера на основе хуков много проще и понятнее, чем поверх классов в JS.

Из интересного конкретно в библиотеке uix:

Есть опциональная прекомпиляция Hiccup макросом. Компилятор сделан на основе форка компилятора Hicada с фолбеком на интерпретацию когда невозможно установить тип значения в compile-time. Type inference в cljs сегодня может давать информацию по типам примитивных значений, что уже сокращает количество вставок интерпретатора в скомпилированный Hiccup (https://kevinlynagh.com/notes/fast-cljs-react-templates/). Чтобы еще больше сократить вставки я экспериментирую с рантайм проверкой через clojure.spec, выходит, что в compile-time функции со спекой пропускают проверку типов откладывая в рантайм проверку спекой.

Есть поддержка ленивой подгрузки компонентов через React.lazy + Suspense, оформлена в виде знакомой кложуристам формы require.

(require-lazy '[uix.components :refer [ui-list]])

И есть возможность перехватывать интерпретацию Hiccup. Например чтобы использовать CSS-in-JS в виде инлайновых стилей. тогда можно писать вот так:

[:button {:css {:font-size 14}} "Submit"]

Пример всего этого можно посмотреть вот здесь: https://github.com/roman01la/uix/blob/master/dev/uix/example.cljs
Апдейт по UIx. Забыл включить кэш в интерпретаторе, теперь рендер в 2х быстрее реагента и всего в 1.5х медленнее ванильного реакта. Никогда не думал, что можно выжать столько из cljs, при этом сохранив относительную идиоматичность в коде.

Успешно "украл" и адаптировал сериализатор Hiccup из Rum для серверного рендеринга на JVM. Поверх него сделал возможность отдавать разметку кусками пока она рендерится, например пушить в manifold stream и отдавать на клиент через aleph.

Добавил синтаксис в Hiccup для порталов в реакте [:-> element :#selector] и сделал для них рендеринг на сервере, но оказалось что сам реакт не умеет этого делать, поэтому пришлось откатить :)

В целом вышла неплохая библиотека, параллельно обкатываю на пэт проекте.

https://github.com/roman01la/uix
О динамических стилях и прекомпиляции макросом.

Стили можно прекомпилировать макросом для оптимизации в рантайме. Например трансформировать декларацию в виде мапы в литерал JS объекта, чтобы обойтись без этой трансформации в рантайме: {:top 10} -> {top: 10}.
Статические части стилей можно вообще писать в файл и оставлять только динамические. Обычно 90% стилей статичны, поэтому имеет смысл убрать их из райнтайма:

 10 :color styles/white}

{:color styles/white}
coponent.css -> .component {top: 10}

Можно пойти еще дальше и перенести в статику константные значения типа (def ^:const white "#fff") (обязательно нужен хинт ^:const), тогда такие значение можно определить анализатором (cljs.analyzer) и заинлайнить в статическую часть стилей. Это хороший кейс когда есть набор переиспользуемых цветов, размеров и т.д.

В таком подходе есть минус — записывая в файл на этапе компиляции нет возможности убрать неиспользуемые стили из рантайма.
Неплохой блог о графике. Для погружения в работу с низкоуровневыми графическими API http://vbomesh.blogspot.com/
Повод побывать в Южной Америке https://clojure-south.com/
Хороший пример использования GraalVM (полиглот VM) для рендернига Reagent приложений на сервере (вызов Node.js из JVM) https://nextjournal.com/kommen/react-server-side-rendering-with-graalvm-for-clojure
Опубликовали все записи докладов с Curry On! https://www.youtube.com/channel/UC-WICcSW1k3HsScuXxDrp0w

Интересный доклад о файберах в JVM https://youtu.be/r6P0_FDr53Q
Файберы в кложе 🙌 https://twitter.com/roman01la/status/1153989387211948032?s=09

Ещё перевести на них core.async, чтобы он действительно напоминал гошный CSP.
Небольшой эксперимент с добавлением async/await в ClojureScript https://twitter.com/roman01la/status/1155602189311717377?s=20

В данном случае вместо core.async, который всегда генерирует огромную стейт машину, здесь я добавил в компилятор обработку функций с мета-тегом ^:async, которые компилятором эмитят синтаксис асинхронных функций в JS async function ..., и специальную форму await.

Дальше сгенерированный JS код отдается в Closure Compiler который в зависимости от указанной версии JS на выходе может оставить async/await как есть или трансформировать в генераторы или ту же стейт машину.

Это интересный эксперимент, но в реальном мире добавление нового, платформозависимого синтаксиса только больше навредит языку и разработчикам. async/await нету в Clojure JVM, поэтому сходу страдает портируемость кода. Ну и в целом необходимость в async/await на клиенте — большой вопрос. Часто вам приходиться разруливать нетривиальные асинхронные флоу во фронтенде? async/await именно для этого неплохо подходит, а отправить пару запросов достаточно промисов.
​​На заметку: многие Webpack плагины решаются макросом в Clojure. Например require SVG иконок в инлайновые компоненты.
Через 30 минут вечерний стрим https://www.youtube.com/c/roman01la/live будем работать над патчами в cljs compiler
Интересный эксперимент с перфомансом чтения transit (https://github.com/cognitect/transit-cljs) в cljs.

Для ридера можно переопределять функции трансформации в map и vector. По умолчанию transit использует transient версии этих структур во время десериализации transit. Это уже быстрее, чем персистентые структуры, но если заменить дефолтные хендлеры на transient структуры (transient bean и transient arrayvector) из библиотеки для интеропа данных cljs-bean (https://github.com/mfikes/cljs-bean), то скорость чтения выростает на 30%.

Так выходит потому, что структуры данных в cljs-bean основаны на объектах JavaScript, а их transient версии тупо мутируют эти объекты, поэтому оверхед минимальный.

С сериализацией transit сложнее и она очень медленная :(

В целом если вам не нужно гонять в браузер расширенные типы данных, то лучше использовать JSON.
Недавно отправил патч в cljs compiler который убирает мертвые ветки в if когда тип значения в выражении теста известен заранее (if test then else) https://clojure.atlassian.net/browse/CLJS-2875

Патч нашел пару интересных ошибок в type inference компилятора.

Одна в loop/recur, когда изменяется тип параметра в цикле https://clojure.atlassian.net/browse/CLJS-3158

Вторая в reify, связанная с присвоением некорректного типа https://clojure.atlassian.net/browse/CLJS-3160