Если вы никогда не видели кода на gleam, то выглядит он примерно так. Пример взят из стандартной библиотеки. Стандартная библиотека, кстати, довольно полная, но ещё не совсем документирована. Поэтому, если захотите ей пользоваться, придётся немного смотреть в исходный код.

Синтаксис, наверно, ближе всего к Rust. В нём есть все прелести функционального программирования, например, отсутствует конструкция return.

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

Язык довольно минималистичный. Например, в нём отсутствуют for и if. Также отсутствует синтаксис для словарей, так что pattern matching для них тоже не сделать. Интересующиеся могут почитать The Gleam Book, чтобы узнать, что в gleam есть, а чего нет.

Также, в gleam отсутствуют "атомы". Erlang использует атомы как константы. Например, при успешном завершении функция может вернуть атом ok, а при не успешном - error. В gleam для этих целей используются типы. Напрямую атомы создавать в gleam нельзя, хотите использовать атом - создавайте новый тип. Решение оригинальное и мне очень нравится. Например, для упомянутого случая это выглядит так:

let parsed = case parse_int("123") {
  Error(e) -> io.println("That wasn't an Int")
  Ok(num) -> num
}

Кстати, об ошибках. В gleam нет конструкции try..catch (которая есть в эрланге). Вместо этого, рекомендуется использовать вышеуказанный подход, напоминающий Rust. Вообще, в эрланге существует принцип "let it crash", который не рекомендует ловить исключения, вместо этого, позволяя процессу, в котором исключение возникло, крэшнуться. В принципе, можно сказать, что gleam следует рекомендациям, потому что в нём не существует в принципе возможности поймать исключение.

Функции - приватные по дефолту. Чтобы сделать публичную функцию, нужно ей указать модификатор pub. Подойдёт для лиц, не стремящихся к публичности. Также есть простой и понятный способ для объявления констант на уровне модуля с помощью слова const.

Есть ещё интересная конструкция use, у которой, наверно, нет аналогов в других языках. Автор её описывает как синтаксический сахар для функций-обёрток. Только на стероидах. И приводит такой пример

pub fn main() {
  use <- logger.record_timing
  use db <- database.connect
  use f <- file.open("file.txt")
  // Do with something here...
}

Это эквивалентно следующему коду

pub fn main() {
  logger.record_timing(fn() {
    database.connect(fn(db) {
      file.open("file.txt", fn(f) {
        // Do with something here...
      })
    })
  })
}

В частности, это один из способов решить проблему callbacks hell.

Ну и, конечно, нужно отдельно сказать про типы. Они в gleam опциональные, их можно не объявлять. Можно аннотировать любое выражение типом. Кастомные типы объявляются так

type User {
  LoggedIn(name: String)
  Guest
}

Как видите, можно объявлять несколько типов внутри одного блока type. Но - можно и один. Использовать их потом можно так

fn get_name(user) {
  case user {
    LoggedIn(name) -> name
    Guest -> "Guest user"
  }
}

То есть, типы используются как при pattern-matching'е, так и для статического анализа. Есть также ограничения, которые gleam накладывает на структуры данных: в списках и словарях нельзя иметь значения разных типов. Компилятор запрещает.

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

Отдельно хочу отметить, что в gleam отсутствуют возможности для сумасшедшего мета-программирования, которые есть, например, в языке Elixir (макросы). Что меня очень радует: макросы для высокоуровневого языка - это однозначное зло. Хотя в системных языках выполнять код во время компиляции бывает нужно, и это совершенно нормально. Такие возможности есть, например, в языках Zig, Rust, C++.

gleam при сборке делает source-to-source преобразование своего кода в код на эрланге. Написан он на языке Rust.

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

В общем, изучая возможности gleam, я поймал себя на мысли, что они целиком покрываются синтаксисом питона (я программист на питоне). Ну, или почти целиком: пайплайн-оператора разве что в питоне нет (этого: |>).

Так что, я подумал, что если какой-нибудь, не вполне адекватный, человек решит переделать gleam под питоновский синтаксис, то не встретит на этом пути никаких трудностей. Разумеется, такой человек стал бы использовать компилятор gleam для генерации кода на эрланге.

Возможно ли это, и насколько императивный язык отличается от функционального?

В версии 3.10 в питон добавили match..case с довольно развесистым синтаксисом: с типами, условиями и всем, что только можно было добавить. Выглядит он так:

match user:
  case LoggedIn(name):
    # serve user
  case Guest():
    # serve guest

Годится ли он? Не совсем. Есть небольшая проблема: match должен быть выражением, должен возвращать значение. То есть, должно быть так

result = match user:
  # и дальше по тексту

То же самое касается других ключевых слов: if, for, try. Только в том случае, если блок match стоит последним в функции, годится его текущий синтаксис.

Про классы и весь ООП можно забыть (и нужно). Классы должны использоваться только для аннотации типов. В остальном же - ванильный питоновский синтаксис вполне самодостаточен. Можно брать парсер для питона и парсить им код.

Но не бывает так, чтобы у кого-то была возможность добавить ещё синтаксис, и он этого не сделал. Если это случится в нашем случае, то что это будет? Вот возможные варианты.

1) Pipeline operator (|>)

Уж очень хорошо в коде смотрится. Наиболее полезен в конце выражений match, if, for:

result = match obj:
  case {'data': data}: data
|> process_data()
|> process_more()

Также блок match может сам стоять после оператора пайплайна:

result = match obj:
  # что-то
|> match:
  # ещё что-то

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

2) Анонимные функции

В питоне есть лямды, но они неполноценные. Полноценные бы выглядели так:

f = def(x, y):
  x + y

Вообще-то, это то же самое, что объявить функцию обычным способом. Зато лямду можно использовать после оператора пайплайна:

result = match value:
  # что-то
|> def(x):
  # что-то на выходе

3) Pattern matching на операции присваивания

MyType(data) = obj

Это удобно - иметь такую возможность. Синтаксис - такой же, как в части case у match.

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

Что вы скажете?

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


  1. wvladw92
    00.00.0000 00:00
    +7

    Какие у этого яп преимущества? Позволяет более продуктивно решать какие-то задачи?


    1. abetkin Автор
      00.00.0000 00:00

      нет, но я верю в силу пробелов (то есть, отступов)


    1. abetkin Автор
      00.00.0000 00:00

      а у gleam преимущества очевидные: статическая типизация и платформа эрланга


      1. Hungryee
        00.00.0000 00:00
        +4

        Мне кажется, подразумевался вопрос «Зачем он вообще нужен»


        1. abetkin Автор
          00.00.0000 00:00
          +3

          Ну платформа эрланга довольно уникальная, она одна такая) BEAM называется. Для распределённых систем в основном используется и серверного кода. Языки со статической типизацией, кроме gleam, для неё существуют, но они больше на Haskell похожи


    1. gibson_dev
      00.00.0000 00:00

      Фактически это просто еще один язык для виртуальной машины BEAM (основной язык - эрланг), умеет все тоже самое но более чистый и лаконичный синтаксис. В принципе тоже самое что и Elixir


  1. dopusteam
    00.00.0000 00:00

    Есть ещё интересная конструкция use, у которой, наверно, нет аналогов в других языках

    Выглядит как using в последних версиях c#


    1. Inobelar
      00.00.0000 00:00
      +1

      А мне напомнило with из пайтона


      1. abetkin Автор
        00.00.0000 00:00

        В эрланге тоже есть "ресурсы": ввод/вывод построен вокруг "портов" и процессов. Так что потенциальное применение "with" ещё предстоит определить)


  1. ValeraBgg
    00.00.0000 00:00

    Это ж Scala с её pattern matching и for comprehension, а pipe operator взят из F#


    1. gibson_dev
      00.00.0000 00:00
      +4

      Паттерн матчинг появился в Эрланге сильно раньше чем сама скала)


    1. abetkin Автор
      00.00.0000 00:00

      и точно, Scala! Я и забыл про неё :)


    1. abetkin Автор
      00.00.0000 00:00

      Scala не подойдёт как синтаксис для gleam (а нужен именно синтаксис, реализация есть). На скале же иногда получается настоящий крипто-код


  1. pulsatrix
    00.00.0000 00:00
    -3

    Нет языка лучше Питона. Ну еще си(++) для скорости. Ну и ассемблер — мозги потренировать.


    1. klis
      00.00.0000 00:00
      -3

      Как перекладывание байт в регистрах тренирует мозги?


      1. pulsatrix
        00.00.0000 00:00
        +1

        Как перекладывание байт в регистрах тренирует мозги?

        Как пинг-понг.