PunchPolice deny misconduct over viral video of suspect arrested in OyoRTP DesportoEstrela da Amadora supera Fazendense com `bis` de Tom MoustierESPNWeek 7 live: Richard TD run puts Indiana ahead of Ohio State in Big Ten showdownESPN DeportesHaaland se viste de héroe para darle el triunfo al City Premier LeagueThe Jerusalem PostEgypt's Sisi seeks parliament's backing to help defend Saudi Arabia if askedZDF heuteEntdecken Sie das ZDF-NachrichtenstudioVanguardTinubu scraps recruitment waiver for federal universities, strengthens autonomyColliderThe 6 Best Sci-Fi Books Released Since 2020, RankedSRF News«Wir haben die Schnauze voll» – Schwere Ausschreitungen bei frankreichweiten ProtestenPremium TimesGunmen kill four vigilantes in Plateau communityNOSPolitie houdt verdachte aan na explosie bij kapperszaak in VlaardingenInteriaZełenski traci nadzieję, mówi bez złudzeń. "Ta decyzja to dopiero początek"
The Daily Newsstand · Free, Always
Saturday, October 17, 2026

Snake? Eel! Мои эксперименты с питоном и эрлангом продолжаются

Translate

Дорогие читатели-питонисты, хочу вас поздравить с выходом питона 3.15! А пишущим на эрланге - хочу напомнить, что OTP 30 уже наполовину готова.

Этот пост - продолжение саги, начатой предыдущим поросячьим постом. Моя идея насчёт нового языка для BEAM не только не исчезла, но и получила развитие. Правда, брендинг уже другой, все поросята были выгнаны. Теперь на обложке угорь - Eel.

Как это обычно бывает, после написания предыдущего поста я узнал, что Erlang в чистом виде - изящный и минималистичный язык. Но это не отбило у меня охоту экспериментировать. В результате, получилось нечто, сильно навеянное эрлангом.

Эрлангисты должны оценить: переменные в Eel иммутабельны - как в эрланге. То есть, нельзя написать X = X + 1. Нужно писать NewX = X + 1.

Вообще, основную мысль статьи хорошо передаёт сегодняшняя новость в газете Фонтанка. Вот она:

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

Итак, я начал с того, что эрланг совсем не такой странный, каким кажется. И, когда я это осознал, то многие "странности" из эрланга перенёс в Eel как они есть.

Атомы и переменные

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

Если что, в примерах с кодом я буду рекламировать свой синтаксис - он плюс-минус совпадает с синтаксисом питона.

ok, Value = my_module.my_func()

ok - атом, Value - переменная.

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

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

Импорты и экспорты

Выглядят они в эрланге так:

%% -export([имя_функции/арность]).
-export([list_even/2]).

%% -import(имя_модуля, [имя_функции/арность]).
-import(lists, [map/2, seq/2]).

list_even(N) ->
    %% Благодаря импорту выше, мы пишем seq(X, Y) вместо lists:seq(X, Y)
    Numbers = seq(1, N),
    map(fun(N) -> N * 2 end, Numbers).

Импорт позволяет в коде обращаться к функции по имени, не указывая, из какого модуля она берётся. Можно обойтись и без этого:

def list_even(N):
    Numbers = lists.seq(1, N)
    map(lambda Num: Num * 2, Numbers)

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

from lists import map, seq

Арность функций не указывается, пусть импортируются все функции с нужным именем. Многоуровневых импортов, как в питоне, нет:

from httpx._models.requests import Request    # такое не поддерживается!

Экспорты в эрланге нужны для приватной/публичной видимости. Я, как программист на питоне отлично обхожусь без приватных функций, но, может быть, кому-то они нужны. В Eel будет возможность определить приватную функцию словом defp.

Иммутабильность переменных

Наконец, мы подошли к самому интересному: переменные не могут менять своё значение. То есть, что нельзя написать X = X + 1 . Я считаю, что неизменяемые переменные отлично подходят для рантайма, где неизменяемы сами данные, и что последний факт не нужно каким-то образом прятать.

В Eel, для всех новых значений придётся заводить новую переменную. Так что, готовьтесь придумывать префиксы и суффиксы для своих переменных:

ActivatedUser = BaseUser | {status: active}
PublicUser = maps.with([name, status], ActivatedUser)

Кортежи, списки и словари

В эрланге:

% Кортеж
User = {user, 1, "Alex", 25},

% Список
Numbers = [1, 2, 3, 4, 5],

% Разделение на голову и хвост
[Head | Tail] = Numbers

% Создание словаря
Book = #{
    title => "Erlang Programming",
    author => "Joe Armstrong",
    year => 2009
},

В питоне:

# Кортеж
User = (user, 1, "Alex", 25)

# Список
Numbers = [1, 2, 3, 4, 5]

# Разделение на голову и хвост
[Head, *Tail] = Numbers

# Словарь
Book = {
    title: "Erlang Programming",
    author: "Joe Armstrong",
    year: 2009
}

Как видите, совпадает у них только обозначение списка. В эрланге, {1, 2} - это кортеж, а в питоне - множество.

Так вот, в вопросе выбора синтаксиса для литералов мне захотелось проявить демократичность. Как минимум, чтобы можно было вставлять конфиги из документации эрланга через Copy-Paste.

Я думаю разрешить использовать синтаксис {1, 2} для кортежей наравне с (1, 2) и синтаксис #{K => V} для словарей. Аналогично, разрешить синтаксис

Map = #{x => 0}
UpdatedMap = Map#{y => 1}     # создание нового ключа или обновление старого
NewMap = UpdatedMap#{x := 1}  # только обновление старого ключа

В конце концов, сочетание символов => в питоне не используется, как и #{..}. Так что, парсинг усложняется не сильно.

List/dict comprehensions

Циклы for в языках, где данные неизменяемы, не нужны. Зато нужны list/dict comprehensions. Синтаксис для них я взял из питона

NewMap = {
  K: 2 * V    # Можно использовать =>, если очень хочется
  for K, V in Map
}

В питоне итерация по словарю дает нам ключи, но в эрланге принято отдавать пары ключ-значение. Аналогично, будет и tuple/record comprehension:

EvenNumbers = (    # можно использовать и {...}
  2 * N
  for N in seq(1, 10)
)

Генераторы (ленивые списки, streams) в каком-то виде в Eel, вероятно, будут, но использовать для них синтаксис (...), как в питоне, я не хочу. Например, в примере ниже - кортеж, а не генератор:

Numbers = (X for X in seq(1, 10))    # кортеж, а не генератор, как в питоне

Опциональная статическая типизация

Киллер-фича и новинка сезона!

Уж не знаю, что такого в этой статической типизации, но она вдруг всем стала нужна. Тем не менее, посколько синтаксис для функций взят из питона, параметры функций можно аннотировать типами. И им ничего не мешает превращаться в эрланговские guards:

def f(x: int, y: int):
    # превращается в when is_integer(X), is_integer(Y)

Для использования паттерн-матчинга при объявлении функций я придумал конструкцию match def. Подробно останавливаться на ней я не хочу, но так, например, выглядит функция вычисления чисел Фибоначчи:

def fib(N):
    fib(N, 0, 1)

match def fib(N, Current, Next):
    case N = 0:      # это паттерн-матчинг
        Current

    case if N > 0:   # в питоновском match..case условия пишутся после if
        fib(N - 1, Next, Current + Next)

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

Пайплайны

Ни в питоне, ни в эрланге пайплайнов нет, но они очень идут функциональному языку. И я думаю, неплохо бы их добавить:

EvenNumbers =
    seq(1, 10)
    ..map(lambda N: N * 2)

EvenNumbers =
    seq(1, 10)
    ..map:
        lambda N: N * 2

Это 2 эквивалентных способа написать одно и то же.

На этом, я думаю, cheat sheet по новому языку можно закончить. Как вы могли убедиться, семантически Eel очень близок к эрлангу. Этим он отличается, например, от эликсира, который -тоже, как бы, эрланг с синтаксисом Ruby, но делает всё по-своему.

От семантической близости к эрлангу польза, конечно, есть. Если мы захотим форкнуть какой-нибудь эрланговский language server и адаптировать его для своего языка, это будет сделать намного проще. На самом деле, я уже попробовал, ради любопытства, форкнуть elp, и DeepSeek справился без каких-то особых проблем.

CPython

В предыдущем посте я писал о якобы одном языке для двух рантаймов: CPython и BEAM. Признаю, что эта идея была слишком идеалистичной: если рантаймы разные - скорее всего, и языки будут отличаться.

В итоге, я решил не менять семантику CPython, а фичи для него - сильно уменьшить в размерах. Так что они теперь вполне подходят по размерам для нескольких PEP.

Главную фичу - паттерн матчинг - я решил сделать частью assert:

assert Response(status=200, json={'items': items}) = resp if items

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

После ассерта можно писать всё, что можно писать после match..case. Ну - и просто булево условие, как в обычных ассертах. Даже немного больше:

assert pattern1, pattern2, ... if <условия>

Сделано это, чтобы можно было писать ассерты после декларации функций, аказывая паттерны для переменных.

def mycontroller(method: str, data: dict):
    assert 'GET' | 'POST' = method, {'user': user, 'order': order} = data

match def я тоже оставил. Не то, чтобы он особенно был нужен, но у него - ровно такой же синтаксис, как и у assert. И пайплайны, конечно, оставил тоже:

odd_numbers =
    range(10)
    ..filter:
        lambda x: x % 2 != 0

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

Так что, можно смотреть на это и делать PEP в питон для любой понравившейся фичи. Но насчёт форка CPython я передумал. Это не обязательно, это вполне может быть сторонним пакетом. Я так и сделал:

uv add eelang

Только файлы теперь нужно называть с суффиксом .eel:

python -m eel mymodule.eel

Но, чтобы не публиковать пакет ради пакета, я нашёл ему применение. Пайплайны отлично сочетаются с ассертами для тестов, особенно таких:

  • сделать запрос

  • проверить статус и базовую структуру ответа

Поэтому я опубликовал второй пакет, httpee:

uv add httpee

Он позволяет делать именно это:

from httpee import GET, httpee, Response


httpee()
..GET:
      'https://api.example.com/users'
..assert:
      Response(status=200)

Основан он на хипстерском пакете fetch.py от Kenneth Reitz. Это http-клиент в одном файле. Так что, есть все основания его использовать.

Также, я форкнул ruff и его плагин для редактора Zed, чтобы они поддерживали синтаксис .eel. Но это, конечно, ради любопытства. И, конечно, не я, а DeepSeek. Всё отлично работает - подсветка, автокомплит и всё, что положено.

Но это всего лишь proof-of-concept. Я думаю, плагин для IDE и language server будет использовать те же технологии и библиотеки, что и язык Eel для BEAM. В целом, фичи для CPython намного меньше по размеру, чем язык для BEAM, поэтому отлично подходят для обкатки технологий.

Дальнейшие планы

Вообще, наверное, самый прямолинейный путь, чтобы сделать компилятор для Eel - это парсить код (модифицированным) парсером питона и генерировать "абстрактный эрланг". Но как-то стало принято для современных языков предоставлять из коробки плагины для IDE, чаще всего - language server. А для этой цели вышеуказанный прямолинейный путь не годится от слова совсем.

Вообще, в последнее время, прямо какой-то бум LSP и статических анализаторов. Например, только для питона есть ruff/ty, pyrefly, zuban - это только, если брать новые. И почему-то все написаны на Rust. Вот это лично мне непонятно, лично я бы взял плагины для Intellij и писал бы на Kotlin. Кстати, долгое время компиляции - это очевидный и большой минус Rust при разработке с помощью агента. Да, наверное, и без него.

Но это пока поле для исследования. Хотя я, скорее всего, склоняюсь к тому, чтобы использовать платформу JetBrains/PyCharm. Кстати: сейчас они в процессе перехода на EelApi для работы с удалёнными окружениями. Смешно.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.