Сегодня я для себя открыл: язык программирования gleam

a4f5e869773d237ffb07e030533ef3df.png

Если вы никогда не видели кода на 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)  // A logged in user with a name
  Guest                   // A guest user with no details
}

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

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

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

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

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 вполне мог бы иметь синтаксис питона. Надеюсь, я удовлетворил ваше любопытство в плане, как это могло быть, и убедил в том, что синтаксис у питона действительно богат.

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

© Habrahabr.ru