Изначально я планировал выпустить этот опус как дополнение к книге “Learn You Some Erlang”, однако его содержание больше соответствует редакционному материалу, нежели хорошему справочному документу, поэтому я решил просто написать об этом в блоге.

Многие новички в мире Erlang успешно изучают его и начинают играться, не вступая с ним в тесное знакомство. Я прочитал много жалоб именно на синтаксис и эти “козьи шарики” (ant turds — ориг.) — «веселый» способ называть символы ,, ;, .. Например, «Как же они бесят», а также многое другое.

Я упоминал в книге, что Erlang берет свое начало из Prolog. Это дает нам понять, откуда берутся все эти знаки препинания, но это, увы, не заставляет людей проникнуться любовью к подобной пунктуации. И правда, почему-то никто не говорит мне: «А-а! Prolog! Что ж ты раньше не сказал!» Ввиду этого я предлагаю три возможных способа человеческого чтения кода на Erlang, дабы сделать мир добрее.

Шаблон


Мой любимый. Для того, чтобы постигнуть этот метод, нужно перестать смотреть на код как на набор строк и начать думать в Выражениях. Выражение — это любой кусок кода, который возвращает что-либо.

В командной строке Erlang знак точки (.) означает конец выражения. Чтобы выполнить выражение 2+2, нужно поставить точку (и затем нажать Enter), чтобы оно выполнилось и вернуло что-нибудь.
Однако при написании модуля символ точки заканчивает Формы. Формы — это атрибуты модуля или объявления функций. Форма ничего не возвращает и поэтому не является выражением.

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

Но продолжим. Нам потребуется еще два правила:
  1. Запятая (,) разделяет выражения:
    C = A+B, D = A+C
    

    Просто как орех, не правда ли? Заметим, однако, что
    if ... end
    case ... of ... end
    begin ... end
    fun() -> ... end
    try ... of ... catch ... end
    
    — все суть выражения. Например, можно сделать следующее:
    Var = if X > 0 -> valid;
             X =< 0 -> invalid
          end
    
    И получить значение на выходе if...end. Это объясняет, почему мы порой видим запятые (,) после этих языковых конструкций — это всего лишь означает, что далее у нас следует еще одно выражение, которое надо выполнить.
  2. Точка с запятой (;) выполняет две функции.
    1. Она разделяет различные определения функции:
      fac(0) -> 1;
      fac(N) -> N * fac(N-1).
      

    2. Она разделяет различные ветки выражений if … end, case … of … end и других:
      if X < 0 -> negative;
           X > 0 -> positive;
           X == 0 -> zero
      end
      

      Может показаться странным, что последняя ветка выражения не отделяется (;). Но ведь (;) разделяет ветки, но не заканчивает каждую из них. Надо думать в выражениях, а не в строчках. Некоторым легче читать код, если разделитель ставить следующим образом:
      if X < 0 -> negative
       ; X > 0 -> positive
       ; X == 0 -> zero
      end
      

      Такое форматирование лучше подчеркивает роль (;) именно как разделителя. Этот знак идет между ветками и условиями, а не после них.



Обобщив все вышесказанное, мы можем смело заключить, что если после выражения (например case...of...end) следует запятая (,), то затем обязано идти следующее выражение. Точка с запятой (;) ставится в конце ветки функции, а точка (.) на последней ветке функции.

Наше обычное представление, когда символом (;) мы разделяли строки (как, например, в C или Java), сейчас надо просто взять и выкинуть в окно. Вместо этого мы рассматриваем наш код как заполняемый шаблон (отсюда и название метода):
head1(Args) [Guard] ->
     Expr11, Expr12, …, Expr1N;
head2(Args) [Guard] ->
     Expr21, Expr22, …, Expr2N;
headM(Args) [Guard] ->
     ExprM1, ExprM2, …, ExprMN.
%% Где ExprIJ - это выражения

Указанные правила действительно работают, но нужно переключить свой мозг в иной режим чтения кода. Именно здесь заключается самый сложный переход: мы должны перейти от строк и блоков к предопределенному шаблону. Поясню: если вдуматься, то конструкции вроде
for (int i = 0; i >= x; i ++) { … }
// Или даже
for (...);
имеют весьма странный синтаксис по сравнению ко многим другим конструкциям в других языках. Просто мы настолько привыкли к подобным сооружениям, что совершенно спокойно воспринимаем их.

Предложение


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

Здесь мы сравниваем Erlang и язык (можно английский, можно русский). Представьте что вы составляете список вещей для поездки. Хотя нет, не будем представлять, вот он:
Мне нужны следующие вещи в поездке:
    Если будет солнечно, то крем от загара, вода, шляпа;
    Если будет дождь, то зонтик, куртка;
    Если будет ветер, то воздушный змей, рубашка.

Если перевести это на язык Erlang, то все будет выглядеть очень похоже (К сожалению, в таком виде оно работать не будет, так как Erlang поддерживает UTF только в рамках строковых переменных, но никак не исходного кода. Там, по старинке, ISO-latin-1. — прим. пер.)
вещи(солнце) ->
    крем, вода, шляпа;
вещи(дождь) ->
    зонтик, куртка;
вещи(ветер) ->
    змей, рубашка.

Видите, можно просто заменить предметы на выражения и вот оно! А вот выражения вида if … end следует представлять просто как вложенные списки таких вещей.

И, Или, Конец


Еще один способ мне предложили на #erlang. В этом методе, увидев (,), надо читать “И”, (;) становится “ИЛИ” (как вариант, «А ЕЩЕ», — прим. пер.), а (.) означает “КОНЕЦ”. Таким образом объявление функции можно читать как набор вложенных логических предикатов и условий.

В заключение...


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

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


  1. davidovsv
    22.05.2015 16:57
    +2

    если после выражения (например case...of...end) следует точка (,)

    Опечатка?


    1. serenheit Автор
      22.05.2015 17:00
      +2

      Безусловно. Спасибо, поправил.


  1. sokal32
    22.05.2015 23:06
    +2

    Хз что там люди говорят, просто обожаю синтаксис Erlang! Он намного легче воспринимается человеческим мозгом, просто людей портят всякие Java, PHP, C# и пр. гадость. Всем Erlang!)


  1. mikhanoid
    22.05.2015 23:13
    +1

    Иногда вот думаешь, а, может, ну его нафиг этот синтаксис? Бросить всё и начать писать на Lisp-е. И никаких тебе проблем и мучений с форматированием. Только голая семантика. Тем более, lfe.io — (lisp (flavored (erlang))) — существует.


    1. nwalker
      22.05.2015 23:47
      +1

      Он по мотивам common lisp и сейчас не умеет reader macros, то есть паттерны там выглядят просто кошмарно.


      1. mikhanoid
        23.05.2015 08:10
        +1

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


        1. nwalker
          23.05.2015 15:01
          +1

          Вообще, я зачем-то смешал теплое с мягким… Влияние CL — это было скорее про verbosity имен, что я тоже не одобряю, но с этим еще моно жить.
          А вот с verbosity паттернв все гораздо хуже. На данный момент описание паттернов в lfe просто чудовищно многословны и нет никакой возможности их сократить хотя бы до уровня erlang-а.

          Erlang                    LFE
          [H | T]                 (cons H T)
          {A, B}                  (tuple a b)
          <<A:5, B:A/binary>>     (binary (a (size 5)) (b binary (size a)) 
          

          Я не уверен, что последний пример заработает, но суть, надеюсь, ясна.
          То есть, если я начну писать на lfe вместо erlang, мой код точно не станет короче. Станет ли понятнее — сложный вопрос, но лично меня полноценные выражения вместо специального синтаксиса скорее отвлекают.

          Вероятно, если бы была поддержка reader macro, я бы смог заменить это на какой-то компактный спецсинтаксис(да хоть бы оригинальный эрланговский), но ее сейчас нет, а что в этом случае можно поделать обычными макросами — я даже не знаю.


          1. mikhanoid
            23.05.2015 19:24
            +1

            Так-то оно так, конечно. Зато такая громоздкость выражений склоняет к быстрому вводу абстракций. Например, вместо последнего, в LFE очень быстро появится нечто вроде (packet a b).


            1. nwalker
              24.05.2015 04:09
              +1

              Действительно появится. Я почитал документацию, если все правильно понял — переменные нельзя реиспользовать в паттерне. Пичаль везде.

              Это, конечно, хорошо, но я бы предпочел вводить абстракции, когда мне нужно, а не когда размер паттерна выходит за грань разумного. Ну и да, если бы все было так просто… Я сейчас развлекаюсь с MPEGTS, там такие упакованные структурки, что паттерн на 8 полей по binary получается запросто, и как его разинлайнить — тоже не очень понятно.

              Вообще, конечно, это все bias. Мне очень хочется иметь более гибкий и изящный язык под BEAM, чем Erlang — но только язык, без дополнительных наворотов. Чтобы добавил компилятор в конфиг rebar-а, и пользуешься, смешивая его с эрлангом в любой пропорции. Два более-менее живых проекта — lfe и elixir, и ни один моим требованиям как-то не соответствует. Хоть свой лисп пиши, очень печально.


              1. mikhanoid
                24.05.2015 08:09
                +1

                У меня околонулевой опыт в Lisp-ах, но уже такое ощущение, что абстракции надо просто вводить всегда и даже не задумываться: надо или не надо. Ответ всегда — надо.

                Хоть свой лисп пиши, очень печально.

                Почему печально? Наоборот. Окно возможностей. И задачка для магистранта-аспиранта. Вот как бы Вы отнеслись к упомянутому уже Clojure поверх Erlang VM?


                1. grossws
                  24.05.2015 19:08
                  +1

                  абстракции надо просто вводить всегда и даже не задумываться: надо или не надо. Ответ всегда — надо.
                  Не соглашусь по двум причинам:
                  — абстракции не бесплатны, как в контексте машинных (накладные расходы есть почти всегда, даже в c/c++), так и человеческих ресурсов (например, если абстракции не идеально соответствуют предметной области, то сведение к другим абстракциям требует сваливания во всё более низкоуровневый вариант и подъём до другой модели мира),
                  — абстракции текут (для эффективного использования абстракций приходится понимать более низкоуровневую реализацию, иначе начинается работа с абстракцией как с магическим заклинанием: как-то работает, но шаг в сторону и у исполняющего башка единорога вместо своей).

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


                  1. mikhanoid
                    24.05.2015 20:22
                    +1

                    В Lisp есть макросы, которые позволяют без накладных расходов применять абстракции. Ну, и просто сам язык такой: если возникает желание абстрагировать, то, скорее всего, пора её вводить. Это как с переменными в более привычных языках: если есть ощущение, что хочется переменную, видимо, пора её делать.


                1. nwalker
                  24.05.2015 19:56
                  +1

                  Не получается Сlojure поверх BEAM. Слишком много динамики в рантайме требуется, в BEAM столько нет.


    1. serenheit Автор
      23.05.2015 00:03
      +1

      Ой, какая приятная штучка. Обязательно надо поиграться.
      Но если Lisp, то, имхо, уже все-таки Clojure.


  1. Envek
    23.05.2015 11:49
    +2

    А рубисты помучавшись с Эрлангом (а он всё же, гад, быстр), взяли и сделали подсластитель Эликсир — всё то же самое, но на вкус привычнее :-)


    1. serenheit Автор
      25.05.2015 01:20
      +2

      Elixir и правда прикольный язык. Особенно хорошо о нем написал Джо Армстронг (я надеюсь в этом хабе все знают этого человека?) у себя в блоге. Если интересно, могу попробовать перевести и ее тоже.


      1. Slavenin999
        25.05.2015 10:21
        +1

        Если интересно, могу попробовать перевести и ее тоже.

        Было бы интересно почитать.