Comments 44
Спасибо за контент по BEAM, Erlang и Elixir.
Тоже имею 20+ лет опыта разработки и пару лет назад погрузился в Elixir. Давно меня так ничего не впечатляло. Удалось получить работу, удалось написать несколько фичей на нем в прод, но пришлось вернуться к Ruby и менеджменту. Надеюсь еще написать что-то на Elixir, а позже может быть и на Gleam.
Спасибо! Очень знакомая история — после 20 лет в IT думаешь, что тебя уже сложно чем-то удивить. А потом пробуешь ФП на BEAM — и вдруг снова чувствуешь себя джуном, который открывает что-то по-настоящему новое. Давно забытое ощущение.
Интересно, что вы пришли через Elixir. Я сознательно выбрал Gleam именно из-за статических типов — после Go хотелось строгости, но без когнитивной нагрузки Rust. А что в Elixir впечатлило больше всего — BEAM как платформа или сам язык?
Конечно и BEAM как платформа, а так же концепции заложенные в язык и тулинг. Иммутабельность, паттерн-матчинг, пайп-оператов, with, док-тесты, качество документации. Многое, всего уже не помню тк пришлось отойти от темы (сбили на взлете, сокращения).
Зачем он C#-разработчикам, у которых есть F#? С exhaustive pattern matching, pipe-operator, мутабельными переменными и computational expression, на которых красиво делаются и state machines, и ранний return. Всё это в итоге можно собрать в один бинарник как в Go.
F# — отличный язык, и для C#-разработчиков это действительно более естественный шаг в ФП — остаёшься на .NET, вся огромная и зрелая экосистема под рукой. Спорить с этим было бы странно.
Разница — в рантайме. F# работает на CLR, Gleam — на BEAM. Это разные модели конкурентности: лёгкие процессы с изоляцией ошибок, supervision trees, hot code reload. Для голосовых навыков, чатов, real-time систем это имеет значение. Для типичного бэкенда — нет, и F# будет практичнее.
А насчёт мутабельных переменных — я в статье честно написал, что их отсутствие в Gleam иногда бесит. F# тут прагматичнее.
В современных версиях Java есть все те же удобства, куча зрелых библиотек, а пайпы можно писать через optional или stream.
Может я чего-то недопонял из материала, но для меня как джависта серьезных причин присмотреться к этому языку не нашлось.
Справедливо. Если Java закрывает ваши задачи и экосистема устраивает — нет смысла менять ради синтаксического сахара.
Разница глубже, чем пайпы. Optional и Stream — это ФП-элементы поверх императивного языка. В Gleam иммутабельность и exhaustive pattern matching — не опция, а единственный способ писать код. Нет null, нет исключений, нет мутабельного состояния. Компилятор не даёт забыть ветку в case — вообще, ни одну. В Java sealed classes + switch expressions приближаются к этому, но не заставляют.
Тут мог бы подойти котлин, если хочется уйти от многословности java. Получить null-safety на уровне типов и exhaustive pattern matching через when+sealed (компилятор тоже не даст забыть ветку). Плюс он поощряет иммутабельность и функциональщину. Но несмотря на это, всё же является мультипарадигменным ЯП. Ещё очевидный плюс - большая экосистема вокруг языка. Но не факт, что она тут пригодится.
Если цель - писать чистый ФП-код и уйти от минусов jvm к минусам beam, то gleam действительно кажется хорошим вариантом, если исходить из статьи (не писал на нём).
А в elixir, кстати, скоро появится gradual typing, сейчас он уже есть в RC-ветке.
NullPointerException пугает даже меня - не джависта. Одна из причин присмотреться к другим языкам
Можете развернуть мысль, в чем, как вы считаете, боль хаскеля ?
Говорю как человек с двадцатью годами императивного кода, не как эксперт по Haskell. Моя «боль» — это боль порога входа. Чтобы написать HTTP-сервер на Gleam, мне хватило выходных. Чтобы сделать то же на Haskell, мне сначала нужно понять, почему IO — это монада и зачем мне это знать.
Порог входа — не в синтаксисе, а в количестве концепций, которые нужно усвоить до того, как начнёшь писать что-то полезное. Монады, функторы, классы типов, ленивые вычисления по умолчанию, IO как монада. Каждая по отдельности понятна, но все вместе — это серьёзная нагрузка на мозг.
Уверен, что для тех, кто уже в Haskell, это не боль, а сила. Но я писал статью для таких же как я — Go/Java/C#-разработчиков, которые хотят попробовать ФП без огромных временных затрат.
Спасибо. А вот скажите как обстоят дела с логированием тут ? Можно встроить это в pipe ? И как вообще работать с IO ?
Логирование в Gleam можно условно разделить так:
Дебаг. Встроенное ключевое слово echo — вставляешь в любое место пайпа, оно пропускает значение через себя и печатает в stderr с указанием файла и строки:
[1, 2, 3]
|> list.map(fn(x) { x + 1 })
|> echo
|> list.map(fn(x) { x * 2 })Билд-тул предупредит, если забудете убрать перед публикацией. Есть ещё io.debug — делает то же самое, но без метаданных.
Боевое логирование. Пакет logging — обёртка над Erlang logger. Уровни, форматирование, всё стандартно. Есть glimt — более продвинутый, со структурированным JSON-выводом и контекстами.
Мне вот интересен Lean 4. Это одновременно и язык программирования, и помощник для доказательств. Он помогает изучать математику. Если ты доказал теорему, и верифицировал доказательство в Lean, можно быть практически уверенным, что доказательство правильное.
IO является монадой потому, что ввод/вывод в haskell сделали на монадах. Почему нужно знать, что IO монада? Потому, что нужно уметь работать со значениями "упакованными" в монаду IO, а это умеет делать оператор bind -- >>= (еще это умеют делать unsafePerformIO и друзья, но unsafe в названии у них не просто так! И со значениями в монаде IO надо работать именно с помощью bind). Все просто!
Спасибо за объяснение! Вы как раз подтверждаете мою мысль — для вас это «всё просто», и я верю. Но в вашем объяснении уже есть «монада», «bind», «>>=», «unsafePerformIO» и «упаковка значений». Для обычного разработчика, который хочет напечатать строку в консоль, это пять новых концепций до первого результата. В Gleam это io.println("hello") — и готово.
Спасибо за краткое знакомство с Gleam. Буду рад дополнительным материалам по нему.
Оператор |> в Gleam делает код читаемым слева направо, а не изнутри наружу
Крутецки выглядит. А можно передать собранную пайпу как лямбду аргументом куда-нибудь?
Сам пайп |> — это синтаксический сахар, а не значение, его нельзя передать куда-то как аргумент. Но можно собрать цепочку в анонимную функцию и передать её:
let transform = fn(x) {
x
|> string.trim
|> string.uppercase
|> string.append("!")
}
list.map(items, transform)Я перестал понимать. Это во что развернётся?
Если…
raw_body
|> parse_request
|> handle_input
|> response.build
|> json.to_string
…разворачивается в json.to_string(response.build(handle_input(parse_request(raw_body)))), то…
x
|> string.trim
|> string.uppercase
|> string.append("!")
…должно развернуться в string.append(string.uppercase(string.trim(x)), "!")?
Если да, то как-то упячно выглядит. По сравнению с традиционным:
x
.trim()
.uppercase()
.append("!")
Я изначально подумал, что |> удобен, чтобы собирать конвейер из разных частей. Например, handle_input, response.build и json.to_string. А для набора операций обычной иммутабельной строки зачем? Где неявно передаётся this?
А вообще, меня эта тема зацепила тем, что я давно думаю, как сделать идеальные чейнинги (на JS или других, более функциональных, языках), где одним выражением можно результат первого filter разбить на два других filter, и с каждым сделать что-то своё, без промежуточных переменных. Когда говорят, что «jQuery устарел», в этом отношении это правда — он так не умеет )) А хотелось бы.
Да, разворачивается именно так. И вы правы — для строки x.trim().uppercase().append("!") читается лучше. Но в этом и суть: method chaining работает, пока все методы принадлежат одному объекту. Как только нужно протащить данные через функции из разных модулей — parse_request, потом handle_input, потом response.build — методы кончаются, а пайп продолжает работать.
В Gleam нет методов и нет this — функции принадлежат модулям, а не данным. Поэтому string.trim(x) вместо x.trim(). Пайп компенсирует отсутствие методов — и делает это универсально, потому что работает с любой функцией, а не только с методами одного типа.
Кстати, ваша идея про разветвление после filter — это по сути то, что в ФП делается через let. Не промежуточные переменные в императивном смысле, а именованные привязки:
let filtered = items |> list.filter(is_active)
let group_a = filtered |> list.filter(is_premium)
let group_b = filtered |> list.filter(fn(x) { !is_premium(x) })Пайп не пытается заменить всё — он для линейных цепочек. Когда поток разветвляется — let и отдельные пайпы.
Ясно, спасибо.
Кстати, ваша идея про разветвление после filter — это по сути то, что в ФП делается через let. Не промежуточные переменные в императивном смысле, а именованные привязки
Это меня как раз и не устраивает. У вас в примере group_a = filtered |> list.filter(is_premium). Я полагаю, это только для примера, в жизни, понятное дело, напишут не group_a, а premium_group, и нарушится DRY: is_premium УЖЕ описывает ситуацию. Если всерьёз рассмотреть использование имён типа a, это не способ избежать нарушений DRY, это способ замаскировать нарушение. Единственный способ избежать нарушений DRY — вообще избегать всего поименованнного, поэтому я и думаю, как такие вещи писать одним выражением. Хотя бы запись чтоб была исключающая противоречия, уж потом можно подумать про атомарность транзакции и т.п.
Да для примера. Но мне кажется, тут не нарушение DRY, а два разных уровня абстракции. is_premium — это предикат. premium_group — это результат применения предиката к конкретному набору. Они не дублируют друг друга — они на разных этажах.
А потом в коде попадается const buttons = $('.card');, и что должен подумать разработчик? (Как на том детском кубике, где буква «Э» сопровождается словом «Свинья» и изображением утки). Когда человек вместо переменных пишет просто $('.card') или $('button'), это заставляет следить за разметкой. Если нам действительно нужны мутанты, они должны быть явно названы .card-button. Именно в этом прелесть $(), а не в том, что он когда-то чего-то там полифилил (как постоянно приходится читать от людей, не постигших дзен чейнинга).
Проблема в том, как чисто технически исключить переменные, если, например, мы сначала из всех элементов отбираем карточки, а потом карточки с картинками делаем меньше, а карточки без картинок, но с заголовками — больше. И, конечно, одним запросом.
Тут я пас - фронтенд не моя специализация. Но идея у вас звучит как интересная тема для отдельной статьи.
Rust при прочих равных лучше подходит, я считаю. Все перечисленное он лучше умеет. При необходимости можно переключаться на императивный стиль. Экосистема лучше. ИИ агенты с растом дружат лучше, чем с экзотикой. Когнитивная нагрузка выше, если надо работать с чужим кодом. Если код контролировать, то все ок.
Удивлен, что при таком богатом опыте с другими языками, вы еще пишете на Go. Я от него испытал отвращение - все сделано максимально криво и првоцирует писать плохой код.
Про Rust — согласен, что он мощнее как язык. Но для моей задачи (голосовой навык, FSM, Redis, конкурентные сессии) его сила избыточна, а BEAM под капотом Gleam даёт модель конкурентности, которой в Rust нет из коробки — лёгкие процессы, изоляция ошибок, supervision trees. Это не про синтаксис, а про рантайм.
Про Go — я в основном занимаюсь архитектурой, кода пишу мало. Go в нашем стеке — не мой личный выбор, а стандарт, который выбирает компания. И его выбирают именно за то, за что вы его критикуете — он простой, предсказуемый, любой новый разработчик входит в проект за короткий срок. Для команд в 10-50 человек это перевешивает элегантность. Отвращение к Go я понимаю, сам в статье написал про бесконечные «if err != nil» — но архитектурные решения принимаются не по ощущениям одного человека, а по стоимости поддержки для всей команды.
Как будто бы Scala хорошо подошла. Не рассматривали её в качестве альтернативы?
Более зрелые тулы, функциональный подход, грин треды, авторы где если хотите
Мне кажется или у автора все комментарии написаны ИИ
Ага, и твой )
Так точно, товарищ майор))
Причем тут вообще товарищ майор? Как это связано? С кем я общаюсь, openclaw?
Переоцениваете вы меня. Мне до openclaw далеко. Я только-только учусь структурировать мысли - решил по этому поводу статью написать. А "товарищ майор" - это мем, не принимайте близко к сердцу :)
Если бы комментарии писал ИИ - он бы не признался, что у него есть сложности с другими языками, типа Haskell. У него слишком высоки репутационные риски. Кто им будет пользоваться - после таких заявлений?
Теория мертвого интернета подтверждается )
Я пишу бэкенд на Gleam. Вот что я понял за полгода