Elm 0.17

Jun. 22nd, 2016 12:52 pm
thedeemon: (office)
[personal profile] thedeemon
Elm - это хаскелеподобный чистый функциональный язык для всяких безобразий в браузере, т.е. "компилирующийся" в JavaScript. Знаменит прежде всего тем, что на нем написан визуализатор квантовой механики из предыдущего поста. :)
Три года назад я писал про тогдашний Elm, с тех пор он заметно изменился, а в последней на сегодня версии произошло существенное изменение в архитектуре, отчего большая часть старого кода и описаний перестала быть актуальной.
Изначально он появился как воплощение идей FRP, и thesis (дипломную работу?) автора Elm'a я могу всем рекомендовать как замечательное изложение идей и разных подходов к FRP, плавно переходящее в объяснение исходной архитектуры Elm'а (и без этого объяснения научиться тогдашнему Elm'у было трудно). Там вся динамика была построена на идее сигналов, когда есть обычные иммутабельные данные типов A,B,C... и есть отдельная категория типов A',B',C'... описывающих такие же данные, но меняющие свои значения со временем (навроде Time -> A'), и есть функтор Signal из первой категории во вторую. Пишешь чистый код, работающий с простыми иммутальными данными, потом этим функтором лифтишь свои чистые функции в мир динамически меняющихся значений. Есть набор внешних источников событий/данных, вроде координат мыши, т.е. уже живущих во второй категории, и нужно построить в ней функцию из нужных источников событий/данных в некое дерево контролов, элементов. А рантайм уже сам позаботится о том, чтобы все события и новые данные проходили как надо через все преобразования и получающееся на выходе дерево превращалось в DOM страницы. Ну и были во второй категории специальные комбинаторы для обращения с временнЫми потоками данных, вроде соединения, сворачивания и пр.
Потом в Elm'е появились Mailbox'ы и Elm Architecture, в которой программа описывалась двумя функциями view и update и начальным значением пользователького типа Model (содержащего все данные). update получала значение произвольного заданного пользователем типа Action (обычно это перечисление разных действий) и текущее значение модели, возвращала обновленное значение модели, а view отображала значение модели в дерево элементов, принимая одним из параметров "адрес" (значение специального типа Mailbox). Возвращаемые ф-ей view элементы в своих атрибутах могли иметь функции "что делать при нажатии/изменении", эти функции получали тот самый "адрес", чтобы слать свои оповещения туда. Так все оставалось иммутабельным и чистым, а рантайм заботился о доставке всех событий в форме пользовательского типа Action в функцию update, так осуществлялся круговорот событий-реакций в колесе сансары. Как видите, в явном виде сигналы уже не участвовали в Elm Architecture, но в разных API еще оставались.
В свежей версии 0.17 авторы сказали "прощай FRP" и выкинули все сигналы нафиг. И API для построения дерева элементов поменяли заодно. Зато добавили модный способ работы с первоклассными эффектами, как у взрослых. Осталась Elm Architecture, но уже другая. Теперь программа это
program : { init : (model, Cmd msg), 
         update : msg -> model -> (model, Cmd msg), 
         subscriptions : model -> Sub msg, 
         view : model -> Html msg }  -> Program Never

Т.е. описываешь свой тип Model с любыми нужными данными, описываешь свой тип сообщений (как Action раньше), и три функции: view, update и subscriptions. view тупо отображает модель в DOM HTML, но тип дерева элементов параметризован твоим типом сообщений, ибо в атрибутах элементов вставляются функции реакций на события, которые производят значения этого самого типа твоих сообщений. Им теперь не нужно знать ни про какие мэйлбоксы, не нужно туда ничего слать, просто произвести сообщение, рантайм сам знает куда его доставлять. Кроме того есть функция subscriptions, которая исходя из текущего состояния модели говорит, какие внешние события нам интересно слушать, и тип ее ответа тоже параметризован типом наших сообщений, т.к. внешние события приходят в рамках все того же потока сообщений, а когда подписываешься на внешнее событие, говоришь, как его завернуть в твой тип сообшений. Ну и ф-я update, которая получает все эти сообщения твоего типа и меняет модель, а заодно может произвести "команду" - указание рантайму произвести некоторый эффект, подобно значению типа IO Smthng в хаскеле. Выражение действий в виде данных обещает много бенефитов, но я в эту тему пока не вдавался.Вот такой теперь Elm, больше никаких сигналов, но по-прежнему все чисто функционально.
В версии 0.17 зачем-то изменился синтаксис описания модулей, там мелочь поменялась, но из-за нее некоторые библиотеки не собираются, надо одну строчку поменять в заголовке модуля.

А еще в Elm'e неожиданно оказалась встроенная поддержка WebGL: там не просто есть нужная библиотека, а компилятор умеет распарсить текст шейдеров на GLSL и проверить согласованность используемых типов между шейдерами и основной программой! Во-первых, сама библиотека для работы с WebGL более высокоуровневая и намного более удобная в обращении, чем то, что я видел в примерах про WebGL на JavaScript'e. В JS, похоже, просто копировали Си, там даже указатели есть, и приседаний на каждый чих нужно не меньше дюжины. В элмовском модуле WebGL все это безобразие спрятано, а выставлен довольно чистый и удобный API. Программа на Elm'e производит HTML дерево, соответственно конечная точка в WebGL это
toHtml : List (Attribute msg) -> List Renderable -> Html msg
и наша задача произвести список Renderable, которые получаются так:
render
    :  Shader attributes uniforms varyings
    -> Shader {} uniforms varyings
    -> Drawable attributes
    -> uniforms
    -> Renderable

где
type Drawable attributes
    = Triangle (List (attributes, attributes, attributes))
    | Lines (List (attributes, attributes))
    | LineStrip (List attributes)
    ...

Т.е. кусок графики (Renderable) получается из четырех вещей: двух шейдеров (вершин и пикселей), описания геометрии и общих данных для шейдеров. Причем и геометрия, и общие данные описываются пользовательскими типами - что хочешь, то туда и передавай. Например, набор треугольников представлен списком троек, но троек чего? Чего скажешь: хошь координат, хошь более сложных структур. Вертексный шейдер получит значения из этих списков в виде атрибутов - данных, меняющихся от вершины к вершине, а в качестве uniform данных (общих для всех вершин) получит то, что передашь, тут тоже твой тип, сам решаешь. А как в render передать шейдер? Это значение типа Shader attributes uniforms varyings (где все три типа-параметра - твои, какие скажешь), и описывается значение шейдера специальным синтаксисом с текстом шейдера на GLSL. Компилятор посмотрит, какие данные в тексте шейдера описаны как attribute, uniform и varying, и убедится, что они соответствуют полям твоих типов для attributes, uniforms и varyings, что переданы параметрами типу Shader. Тут происходит связь города и деревни, связь кода на Elm и кода на GLSL. Пример из моей программы:
sphVertSh : Shader { attr | position:Vec3, color:Vec3 } { unif | perspective:Mat4, pos:Vec3 } { v:Vec3 }
sphVertSh = [glsl| 

attribute vec3 position;
attribute vec3 color;
uniform mat4 perspective;
uniform vec3 pos;
varying vec3 v;

void main () {
    gl_Position = perspective * vec4(position*0.1 + pos, 1.0);
    v = position; 
}
|]

Такой вершинный шейдер будет вызван для каждой вершины сферы, он получит данные о вершине в виде attribute vec3 position и color, вычислит требуемое значение позиции и заодно запишет что нужно в выходное значение varying v, которое потом пиксельный шейдер получит проинтерполированным себе на вход и будет использовать для вычисления цвета пикселя. Поскольку оба вершинный и пиксельный шейдер передаются вместе в render, типы их uniforms и varyings обязаны совпадать, таким образом компилятор проверит не только то, что я в шейдеры правильные данные передаю, но и что вершинный шейдер производит именно такие данные, которые принимает пиксельный. При том, что тип этих данных я придумываю сам. Если сравните это с JS'ным WebGL или сишным OpenGL, увидите пропасть как в удобстве, так и в уровне статического контроля. Там все намного труднее делается и с минимумом проверок.

Другие впечатления после написания полтыщи строк. Близкий к хаскелю синтаксис и отличный вывод типов в сочетании с referential transparency от чистоты и иммутабельности позволяют очень легко массировать код: любой кусок можно элементарно превратить в функцию, перенести в другое место, параметризовать, при этом не порождается бойлерплейта и впечатления от процесса очень положительные. Компилятор очень шустрый (единственная быстрая программа на хаскеле из мне известных), сообщения об ошибках более чем подробные и ласковые, об этом авторы особенно заботились. В синтаксисе нет where, и это очень хорошо, на мой взгляд. По-прежнему есть что-то вроде встроенных тайпклассов (вроде number), но нельзя описывать свои тайпклассы или добавлять свои инстансы, когда работаешь с комлексными числами об этом сильно жалеешь. Можно описывать свои операторы, но только на самом внешнем уровне, т.е. определить оператор локально (чтобы было замыкание, задействовать значения из текущего скоупа) нельзя, это тоже обидно немножко.
Но общие впечатления очень положительные. Язык стал проще и одновременно удобнее. Компилятор стал стабильнее и вообще образцовый во многих отношениях. Есть очень классный менеджер пакетов и их репозиторий (причем они умеют гарантировать соблюдение логики semver, это отдельная тема). Есть удобная штука elm reactor, навроде debug mode в рельсах, когда поменял исходник, нажал в браузере F5 и все пересобралось и перезапустилось, благо компилируется мгновенно. А еще там удобные record'ы с row polymorphism'ом! О чем еще мечтать? :)

Date: 2016-06-22 06:33 am (UTC)
From: [identity profile] chaource.livejournal.com
Я когда-то тоже фанатѣлъ отъ Elm. Но сейчасъ я недоволенъ, что убрали типъ сигналовъ. Стало все еще болѣе куцымъ, чѣмъ было раньше. Работа съ асинхронными процессами теперь будетъ еще болѣе ad hoc, чѣмъ раньше.

По-моему, когда нѣтъ явныхъ сигналовъ въ видѣ функтора, то работа программы становится совершенно загадочной и таинственной. Хотя рантаймъ возможно и сталъ болѣе эффективнымъ, потому что у программиста стало меньше функцiй, и рантаймъ можетъ больше оптимизировать.

А гдѣ модуль для работы cъ WebGL?

Date: 2016-06-22 06:49 am (UTC)
From: [identity profile] chaource.livejournal.com
По-моему, когда нѣтъ явныхъ сигналовъ въ видѣ функтора, то работа программы становится совершенно загадочной и таинственной. Наиболѣе, на мой взглядъ, логичный способъ думать про "колесо Сансары", это рекурсивно опредѣлить сигналы Model, View, Input другъ черезъ друга. Сигналы должны быть монадой (чего въ Elm никогда не было), должно быть можно рекурсивно опредѣлять сигналы (этого въ Elm тоже не было и нѣтъ), и тогда все получается примѣрно так:

type alias Updater = ControlValue -> Model -> Model
render : Model -> View
views : Signal View = fmap render models
getInput: View -> Signal updater (отъ одной кнопки на View можетъ происходить цѣлый Signal событiй-командъ)
models : Signal Model = foldp initialModel updaters
updaters: Signal Updater = getInput >>= views (flattening сигналъ сигналовъ командъ въ простой сигналъ)

То есть сигналы views, models, updaters опредѣляются рекурсивно другъ черезъ друга. Чистые сигналы и рекурсiя. Не нужно никакихъ дополнительныхъ и странныхъ конструкцiй-костылей вродѣ mailbox, message, command, subscription, task, action, etc.

Но Elm никогда такъ и не реализовалъ этого, хотя въ mailing list это подробно обсуждалось. Въ каждой версiи Elm были свои костыли, каждый разъ новые.

Завтра, кстати, я иду на встрѣчу съ создателемъ Elm и буду тамъ что-нибудь писать на новомъ Elm. Создатель Elm себѣ на умѣ и никого не слушаетъ.
Edited Date: 2016-06-22 06:55 am (UTC)

Date: 2016-06-22 12:30 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Эту схему наверняка можно на PureScript выразить и попробовать использовать, тогда станет ясно, лучше она или хуже.

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

Date: 2016-06-23 06:26 am (UTC)
From: [identity profile] kika.livejournal.com
> Завтра, кстати, я иду на встрѣчу съ создателемъ Elm и буду тамъ что-нибудь писать на новомъ Elm.

Я смотрел давно на ваш аватар и все тужился вспомнить где же я вас видел. Теперь понятно. Вас Сергей зовут?

Date: 2016-06-22 06:57 am (UTC)
From: [identity profile] juan-gandhi.livejournal.com
Надо же. Я тогда сходил разок на митап, не был впечатлен.

Спасибо! Более убедительно, чем purescript.

Date: 2016-06-23 06:27 am (UTC)
From: [identity profile] kika.livejournal.com
Фигассе. Вот что с людьми делает травма явой и ее фреймворками.

Date: 2016-06-22 07:24 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
а чем отличается тайпкласс от type alias навроде примера с Рунге-Кутта в следующем посте?

Date: 2016-06-22 07:59 am (UTC)
From: [identity profile] thedeemon.livejournal.com
В основном тем, что неявно передается. type alias там просто record описывает, экземпляр ее надо явно в функцию передать.

Date: 2016-06-22 08:43 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
ага, ну, в Агде тоже. Собственно, тайпклассы хороши, когда по единственному экземпляру для каждого типа.

Date: 2016-06-22 12:45 pm (UTC)
From: [identity profile] swizard.livejournal.com
> Но общие впечатления очень положительные. Язык стал проще и одновременно удобнее.

Всё так. Но, с другой стороны, автор взял направление на отвязывание Elm от JS, типа, это всего лишь одна из платформ, в которую мы компилируемся. А дальше будет WebAssemply, потом ещё что-то, и тд.

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

Как бы, не то, чтобы я имел какое-то принципиальное философское мнение по этому вопросу, но при портировании существующего софта на новый Elm теперь мне надо вообще всё нафиг переписывать :(

Date: 2016-06-22 01:05 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
В смысле выпилили?
Вот я смотрю на пакет elm-linear-algebra, там почти весь код имеет вид
inverseOrthonormal : Mat4 -> Mat4
inverseOrthonormal = Native.MJS.m4x4inverseOrthonormal

и все подобные упомянутые ф-ии из Native.MJS живут в соответствующем обычном .js файле.

Date: 2016-06-22 01:56 pm (UTC)
From: [identity profile] swizard.livejournal.com
Им самим можно (иначе как бы они webapi делали), а пользователям уже нет. Раньше была подробная инфа как эти нативные модули писать и связывать с Elm, щас токо через javascript-as-service: http://guide.elm-lang.org/interop/javascript.html , а все остальное должно быть в elm-lang.

Вот мужик, например, с горя начал делать свой elm на платформе purescript'а: https://github.com/rgrempel/elm-web-api/issues/17

Date: 2016-06-28 02:29 am (UTC)
wizzard: (Default)
From: [personal profile] wizzard
Closed world assumption это печалька, да. Все более-менее успешные языки имеют развитый FFI, unsafe и прочие костыли для построения своих интерфейсов с внешним миром

Date: 2016-06-23 06:37 am (UTC)
From: [identity profile] kika.livejournal.com
Кул стори. Я влюбился в Эльм где-то в районе 0.12 и довольно долго в него игрался, ходил на митапы, ездил Эвану по ушам про Эрланг и все такое. А как собрался таки написать часть своего приложения на Эльме так сразу соскочил на Purescript. Я начал писать компонент, сделал очень простенькую демку и показал ее одному из потенциальных кастомеров и он возопил О! Мечтаю о таком! Только в виде API пожалуйста, я туда REST`ом шлю, а ты мне эту красоту в ответ.
И тут дети поняли что принесли они из леса не язык программирования, а фигню какую-то. Потому что такая вот простая задача на Эльме не решается вообще от слова совсем. И ушел я на пюрескрипт, и скучаю по вменяемым сообщениям об ошибке и скорости компилятора. Но с тех пор я уже пописал на пюрескрипте и для node (вот ту самую красоту) и для Google Apps. И ничего из этого на Эльме я бы сделать не смог.

Просто люди как-то не сразу понимают что эльм - это не язык, это язык+вебфреймворк. И когда они спрашивают "а можно ли на эльме написать ....", это звучит примерно как "а можно ли на Angular написать компилятор С".

Date: 2016-06-23 10:09 pm (UTC)
From: [identity profile] stdray.livejournal.com
А можно хотя бы в двух словах про случившийся с Эльмом затык? Какие-то выбранные автором языка идиомы оказалось на сервер не ввернуть или что?

Date: 2016-06-24 12:09 am (UTC)
From: [identity profile] kika.livejournal.com
В отличие от Purescript, Эльм таскает с собой довольно внушительный рантайм и этот рантайм рассчитан на жизнь внутри броузера. Можно конечно сделать обертку, которая будет имитировать жизнь в броузере для nodejs, но при наличии жесткого ограничения в Эльме на нативные модули это становится довольно затейливым приключением.
Есть какой-то чувак, который упражнялся в запуске на ноде, и у него даже что-то получалось, но это чистая экспериментальная маргинальщина (last time I checked).

Эльм это вебфреймворк, все просто. Загонять его на сервер можно, но как бы это сказать....

Date: 2016-07-06 11:33 am (UTC)
From: [identity profile] udpn.livejournal.com
А зачем мучаться с компиляцией хаскелеподобных языков в джаваскрипт, исполняемый нодежсом, если можно спокойно пойти и написать на хаскелле? Вам изоморфизма не хватает? Тогда есть GHCJS, например.

Когда PureScript был на начальных этапах и в IRC было около 10 человек, я пытался исправить положение и пояснить людям, как надо, но Фил Фриман пилил то, что ему хочется, а не то, что объективно полезно. В результате получилось то, что получилось, с невменяемым FFI и монадой Eff. Это не то, на чём я бы порекомендовал кому-то писать код. Почитайте, как у них ($) работает, например. Это я к тому, что маргинальщина не только запуск на нодежсе, а вообще, всецело.
Edited Date: 2016-07-06 11:39 am (UTC)

Date: 2016-07-07 06:17 am (UTC)
From: [identity profile] kika.livejournal.com
Много причин зачем. ghcjs небыстр и совсем недавно был довольно глючен (не знаю как сейчас). Писать на чистом хаскеле имеет больше смысла, но тогда надо искать заменители всего, уже давно существующего на платформе ноды. Если _уже_ есть проект на ноде и что-то чистый js/cs задолбал, то purescript позволяет легко и безболезненно попробовать что-то принципиально новое, не меняя ничего больше, что в большом проекте представляет собой непростую задачу.

> я пытался исправить положение и пояснить людям, как надо

Я иногда рад что в США давно запретили автоматическое оружие, потому что люди "поясняющие как надо" достойны только нахождения с receiving end of the machine gun. Есть простое правило поведения приличного человека: знаешь как надо? пойди и сделай как надо.

> Фил Фриман пилил то, что ему хочется, а не то, что объективно полезно.

Фил - объективный гений мелкого разлива и объективно выпилил именно то что объективно полезно большому количеству людей. Таких как я, эмигрантов с Эльма, которые уперлись в "у нас монад нет, но на самом деле есть, у нас native нет, но на самом деле есть, но мы вам не покажем..." в коммунити все больше и больше.

> результате получилось то, что получилось, с невменяемым FFI

У PS самый вменяемый FFI из всех функциональных языков что я видел, а я видел их почти все. Он разумен, тонок, опасен ровно настолько насколько может быть опасен и крайне легок в интеграции. Если вы про дурацкие "исполняемые строки", то это стало прошлым в районе 0.7 и по-моему в 0.8.5 уже вообще не поддерживается. Я пишу очень много этого FFI на пурескрипте и нарадоваться на него не могу.

> и монадой Eff

Конечно, у Хаскеля с аморфной монадой IO все гораздо лучше чем у языка с row-based полиморфизмом.
Вы забыли еще омерзительные рекорды у пурескрипта, которые никому не нужны и совершенно отвратительны, не то что в Хаскеле, в котором их до сих пор не изобрели.

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 25th, 2026 02:49 am
Powered by Dreamwidth Studios