Pull to refresh

Comments 44

Я думал GLEAM - это другое)

Специфический профсоюз внутри M$FT

Представляю, что выдаёт гугл тем, кто впервые пытается загуглить этот язык. Зато запоминается!

Спасибо за контент по BEAM, Erlang и Elixir.

Тоже имею 20+ лет опыта разработки и пару лет назад погрузился в Elixir. Давно меня так ничего не впечатляло. Удалось получить работу, удалось написать несколько фичей на нем в прод, но пришлось вернуться к Ruby и менеджменту. Надеюсь еще написать что-то на Elixir, а позже может быть и на Gleam.

Спасибо! Очень знакомая история — после 20 лет в IT думаешь, что тебя уже сложно чем-то удивить. А потом пробуешь ФП на BEAM — и вдруг снова чувствуешь себя джуном, который открывает что-то по-настоящему новое. Давно забытое ощущение.

Интересно, что вы пришли через Elixir. Я сознательно выбрал Gleam именно из-за статических типов — после Go хотелось строгости, но без когнитивной нагрузки Rust. А что в Elixir впечатлило больше всего — BEAM как платформа или сам язык?

Конечно и BEAM как платформа, а так же концепции заложенные в язык и тулинг. Иммутабельность, паттерн-матчинг, пайп-оператов, with, док-тесты, качество документации. Многое, всего уже не помню тк пришлось отойти от темы (сбили на взлете, сокращения).

Да, пайп-оператор и иммутабельность — это то, что меняет голову навсегда. Я теперь даже в Go-коде ловлю себя на том, что думаю пайпами.

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

Зачем он 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-ветке.

В BEAM большая ценность это сама платформа. Акторная модель, возможность запустить, к примеру, 300к параллельных процессов и сохранять отзывчивость. Построение кластерных приложений. Я не вижу конкурентов. За развитием Elixir слежу.

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, можно быть практически уверенным, что доказательство правильное.

Интересная ниша — язык как инструмент доказательства, а не только разработки. С 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. Именно в этом прелесть $(), а не в том, что он когда-то чего-то там полифилил (как постоянно приходится читать от людей, не постигших дзен чейнинга).

Проблема в том, как чисто технически исключить переменные, если, например, мы сначала из всех элементов отбираем карточки, а потом карточки с картинками делаем меньше, а карточки без картинок, но с заголовками — больше. И, конечно, одним запросом.

Тут я пас - фронтенд не моя специализация. Но идея у вас звучит как интересная тема для отдельной статьи.

  1. Думается, что на бэкенде и в других местах ситуация аналогичная. Я просто взял пример, который мне ближе.

  2. Подпишусь на вас, на случай, если вы напишете отдельную статью. Для меня в прикладном смысле это очень интересная тема.

Rust при прочих равных лучше подходит, я считаю. Все перечисленное он лучше умеет. При необходимости можно переключаться на императивный стиль. Экосистема лучше. ИИ агенты с растом дружат лучше, чем с экзотикой. Когнитивная нагрузка выше, если надо работать с чужим кодом. Если код контролировать, то все ок.

Удивлен, что при таком богатом опыте с другими языками, вы еще пишете на Go. Я от него испытал отвращение - все сделано максимально криво и првоцирует писать плохой код.

Про Rust — согласен, что он мощнее как язык. Но для моей задачи (голосовой навык, FSM, Redis, конкурентные сессии) его сила избыточна, а BEAM под капотом Gleam даёт модель конкурентности, которой в Rust нет из коробки — лёгкие процессы, изоляция ошибок, supervision trees. Это не про синтаксис, а про рантайм.

Про Go — я в основном занимаюсь архитектурой, кода пишу мало. Go в нашем стеке — не мой личный выбор, а стандарт, который выбирает компания. И его выбирают именно за то, за что вы его критикуете — он простой, предсказуемый, любой новый разработчик входит в проект за короткий срок. Для команд в 10-50 человек это перевешивает элегантность. Отвращение к Go я понимаю, сам в статье написал про бесконечные «if err != nil» — но архитектурные решения принимаются не по ощущениям одного человека, а по стоимости поддержки для всей команды.

Как будто бы Scala хорошо подошла. Не рассматривали её в качестве альтернативы?

Более зрелые тулы, функциональный подход, грин треды, авторы где если хотите

Scala не рассматривал, честно. Она мощная, но для side-проекта мне был важен минимальный порог входа. Gleam можно выучить за выходные — у Scala порог существенно выше, особенно если до этого не был в JVM-мире.

Мне кажется или у автора все комментарии написаны ИИ

Ага, и твой )

Так точно, товарищ майор))

Причем тут вообще товарищ майор? Как это связано? С кем я общаюсь, openclaw?

Переоцениваете вы меня. Мне до openclaw далеко. Я только-только учусь структурировать мысли - решил по этому поводу статью написать. А "товарищ майор" - это мем, не принимайте близко к сердцу :)

Если бы комментарии писал ИИ - он бы не признался, что у него есть сложности с другими языками, типа Haskell. У него слишком высоки репутационные риски. Кто им будет пользоваться - после таких заявлений?

А почему вы в этом комментарии сменили '—' на '-' внезапно?

Потому что на длинные тире закончились токены :)

Теория мертвого интернета подтверждается )

Sign up to leave a comment.

Articles