Wednesday, January 30, 2008

Синтаксис Erlang в двух словах.

Чтобы писать веб-приложения на эрланге, надо его хотя бы немного знать, поэтому попробую в пару постов уложить основные понятия.

Тут лучше конечно почитать какой-нибудь более серьезный туториал, начиная с собственно "Getting Started with Erlang" (русский перевод на рсдн), или "Thinking In Erlang". Однако ж, поскольку 30-50 страниц осилить не у каждого поднимется мозг, я здесь изобразил небольшую шпаргалку (не претендующую на полноценное введение).

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

Для начала самые примитивные вещи:

  • Прежде всего, Эрланг — это язык с динамической типизацией, никакой статической проверки типов тут нет.

  • Литералы-числа в Erlang: 1, 4, 15, 25, 16#1F (в шестнадцатеричной системе), 2#01110110 (в двоичной).
    > 1.
    1
    > 1+2.
    3
    > 1+1.5.
    2.50000
  • Литералы-символы: на самом деле это не отдельный тип, а просто целое число — код символа : $a, $b, $\n $_
    > $a.
    97
    > $\n.
    10
  • Списки задаются в квадратных скобках:
    > [].
    []
    > X = [1,2].
    [1,2].
    > [3 | X].
    [3, 1, 2]
  • Строки — "aba a", "ga a ga".
    > [$a, $b, $c].
     "abc"
    Строки — это на самом деле просто списки символов. Поэтому памяти они занимают немало — в общей сложности 8 байт на один символ строки (на 32-битной машине).
  • Битовые строки (binaries) — эффективное представление бинарных данных
    > <<1, 3, "ba">>.
     <<1,3,98,97>>
    
    Их можно сопоставлять с шаблонами, и быстро передавать туда-сюда. Очень полезная фича Эрланга (правда мы ее использовать сильно не будем).
  • Атомы
    abds
    more
    hello
    ok
    undefined
    
    Начинаются с маленькой буквы, и представляют собой что-то среднее между enum'ами и константными строками, пишутся без кавычек и удобны для разных меток и идентификаторов. Атомы очень похожи на строки, но их нельзя склеивать, вычислять длину и пр., длина их ограничена 256 байтами. Они существенно эффективнее строк в обращении.
  • Еще любые значения еще можно объединять в пары, тройки, четверки, пятерки и так далее, с помощью фигурных скобок: 
    {14, 15} 
    {0, "ba", 3} 
    {$a, [1,2], {3,4}} 
    

Имена переменных всегда начинаются с большой буквы (несмотря на то что они везде называются "переменными", менять их нельзя).

Имена функций и модулей обязательно начинаются с маленькой буквы. Сигнатура функции состоит из ее имени и количества параметров — то есть format/2 и format/3 это разные функции.

Паттерн матчинг (сопоставление с шаблоном) в Erlang выглядит так:

case A of
    Pattern1 -> ...;
    Pattern2 -> ...;
    Pattern3 -> ...;
end

В паттернах можно использовать значок _ который матчит все что угодно (wildcard). В жизни используется как-то так:

case Foo of
    {"HELLO", Name} -> send_response("Hello, " ++ Name);
    "BYE" -> send_response("Bye");
    _ -> send_response("Unknown command")
end

Еще паттерны можно использовать прямо в списке параметров функции:

foo({_, B}) ->
    ...;

foo(X) ->
    ...

что эквивалентно

foo(Y) ->
    case Y of
        {_, B} -> ...
        X -> ...
end.


Плюс, значения можно матчить с помощью знака "равно":

X = {1, 3},
{A, B} = X.

теперь A = 1, а B = 3

Причем, если переменная слева уже определена, в паттерне используется ее значение:

   > B = 4.

Получаем run-time ошибку badmatch, потому что B у нас уже равно трем.

С операторами все почти как обычно:

  • арифметические, +-/*%
  • сравнения =/ , ==, =>, =<
  • склеивание списков ++
  • декомпозиция списков (оператор cons) |
  • битовые band, bor, bxor, bnot

Вот, собственно, практически все. Раздражают в Эрланге поначалу только немного странные правила расстановки точек, запятых и точек с запятой. В двух словах — точка ставится в конце определения функции, запятая — между выражениями в определении функции, а точка с запятой разделяет выражения в case .. of и if.

 


В качестве примера — функция, получающая md5 хэш строки. В стандартной библиотеке уже есть функция md5(), которая получает хэш в виде битовой строки.

> H = erlang:md5("boooo").
<<174,62,131,226,250,179,167,216,104,61,142,239,171,209,231,77>>

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

Для начала можно преобразовать битовую строку в список байт с помощью стандартной функции:

> binary_to_list(H).
[174,62,131,226,250,179,167,216,104,61,142,239,171,209,231,77]

Теперь функция, которая преобразует 4 бита в один hex символ:

hex(V) ->
    if
    V < 10 ->
            $0 + V;
    true ->
        $a + (V - 10)
     end.

Да, if в erlang — лишь упрощенный вариант case .. of, где вместо паттернов — булевые выражения. Так что кейс "true ->" тут играет роль else

Теперь один байт можно преобразовать в hex так:

> E = 31.
> [hex(E bsr 4), hex(E band 16#F)].
"1f"

Ну и с помощью функции lists:foldl применить это ко всей исходной строке:

binary_to_hex(Bin) ->
    lists:foldl(fun (E, Acc) ->
            [hex(E bsr 4) | [hex(E band 16#F) | Acc]] end,
        [],
        lists:reverse(binary_to_list(Bin))).
% returns hex representation of md5 in reverse byte order
md5_hex(Bin) ->
    binary_to_hex(erlang:md5(Bin)).

Тут

  • fun (E, Acc) -> ... end
         это определение лямбда-функции
  • lists:foldl() это известный функциональным программистам foldl.
  • lists:reverse() разворачивает список задом наперед
  • Конструкция вида [A | B] приклеивает в начало списка B элемент A (см. cons).

Итого получаем

>md5_hex("booo").
"1c052f260d1b34423c32e7c7b29026b9"

Весь исходник (utils.erl)

Про ключевую фичу Erlang — поддержку распределенных приложений поговорим в следующей серии.

25 comments:

Anonymous said...

Отлично! Наверное было бы неплохо сразу рассказать и о том, как написаное сохранить в файл/вызвать оттуда, чтобы экспериментировать было попроще (-module(md5hex). -export(md5_hex/1) или чего-нибудь подобное).

Anonymous said...

Честно сказать, выглядит ужасающе. :) Синтаксис, конечно, ОЧЕНЬ непривычный.

Хотя некоторые элементы знакомы:
списки - из питона
{} - это тоже из питона (только там круглые скобки "()"), кортежами называется
атомы - очень напоминают :string из ruby

"Паттерн матчинг" - выражения, конечно, заумные. :) Всегда switch был.

Ну это я так...

> foo({_, B}) ->
Поясни "_". В функцию мы передаем кортеж (один аргумент) из двух элементов. Мы на тип первого аргумента никаких ограничений не накладываем. Зачем тогда wildcard? Ведь оно по сути аналогично foo({A, B}). Или я где-то недопонял?

PS. Сорри, читать маны как-то неохота. :)
PPS. Кажись, ты на работе на C++ пишешь. Откуда такая тяга к функциональщине? Или почему ты всё-же работаешь C++-программистом?

Anonymous said...

о. люблю такие посты.

будем ждать про написание распределёнщины.

lrrr said...

fxposter>
Да, кстати -- для питоновода тут особенных проблем быть не должно, а атомы это действительно то же самое что ruby symbols.

switch это не совсем то, switch это ведь то же самое что

if (X == A) then ..
elseif (X == B) then ...
elseif (X == C) then ...

А паттерн матчинг более мощная вещь:

if (match(X, A)) then ..
elseif (match(X, B)) then ...
elseif (match(X, C)) then ...

Да, foo({_, B}) будет иметь тот же эффект что и foo({A, B}), но если A нас не интересует, лучше вместо него подставить "_". Особенно это удобно в случаях типа foo({_, _, _, _, B, _, _})

P.S. Насчет C++ все просто: все-таки тяга к функциональщине -- сама по себе недостаточно мощная причина, чтобы менять работу, есть еще много других факторов :)

lrrr said...

Wunar>
Сложно все впихнуть в один пост %) В принципе это все можно и прямо в интерпретаторе набирать.

Но я на всякий случай еще приаттачил сейчас исходник модуля (см. в конце), и там кое-какие пояснения есть по этому поводу.

Anonymous said...

На самом деле в Ruby тогда тоже не обычный switch/case, а такой вот "паттерн-матчинг", ведь можно использовать регекспы:

case (x)
when /^x.*/ ...
when /^a.b$/ ...
...

Anonymous said...

выглядит как на скорую руку написанный для игрушки скриптовый язык

Sergey Kishchenko said...

>>Да, foo({_, B}) будет иметь тот же эффект что и foo({A, B}), но если A нас не интересует, лучше вместо него подставить "_". Особенно это удобно в случаях типа foo({_, _, _, _, B, _, _})

Скорее, лучше сказать, что в случае с foo({A, B}) и неиспользованием A будет ворнинг, а мы ведь ворнинги не любим :) На самом деле, _ - просто анонимная переменная. Дабы подавить сообщение о ворнинге можно также использовать имена переменных, начинающихся с _, например foo({_Name, _Message, Login, Password, _, _, _}). Это удобней - видно, что предполагается передавать, хотя различия есть: {_,_}={20,30} прокатит, а {_A,_A}={20,30} - нет.

P.S. С интересом буду следить. Пока изучу джангу, хотя может потом вернусь к Эрлангу - уж очень он приятен :)

Alexander Stavonin said...

На мой взгляд, еще вот сюда стоит заглянуть, очень так же крайне полезно.
http://gzip.rsdn.ru/article/erlang/GettingStartedWithErlang.xml

lrrr said...

edoc_modnar> Но там это только для строк работает, насколько я понимаю?

Сергей Кищенко> Да, спасибо -- про _A я не знал :)

kaa.python> Спасибо, добавил ссылку

Unknown said...

А-а-а-а-аааааааааа!!!!! Я сошел с ума. Большое спасибо. Моск взорвался :) Теперь понимаю, что есть языки, которые фортрану77 никогда не догнать по сложности прочтения кода. Хотя он наверно проектировался не для читателей, а для писателей.

lrrr said...

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

Кстати синтаксис там во многом от Пролога, и первая версия Erlang на Прологе была написана.

_winnie said...

1) А как запустить erl-файл?
2) Как ввести с клавиатуры два числа, и вывести их сумму?

Там на RSDN рассказывается как запустить из интерпретатора
c(модуль)
модуль:функция
, как это сделать из обычной командной строки?

C:\test\erl>dir /b c:\soft\dev\erl5.6\bin
dialyzer.exe
erl.exe
erl.ini
erlc.exe
escript.exe
start.boot
start_clean.boot
start_sasl.boot
typer.exe
werl.exe

И ещё такой вопрос, как выйти из интерпретатора? Когда я нажимаю на Ctrl+C он почему-то падает.

erl.exe - Ошибка приложения
Инструкция по адресу "0x00922f91" обратилась к памяти по адресу "0x00000114". Память не может быть "read".

Что бы люди загорелись, надо рассказать как правильно запустить %)

lrrr said...

_winnie>
На самом деле эрланг это такая хрень которая изначально предназначена для написания серверов. Поэтому из командной строчки запускать не очень удобно.

Я обычно делаю так:

Сначала компилируем в beam
erlc -W module
Потом запускаем
erl -run Module Function [Args..]

Правда после того как функция отработает, интерпретатор останется висеть. Более правильный вариант -- сделать приложение (Application), но это надо маны почитать.

И ближе к тому что надо -- интерфейс для скриптинга через escript, у модуля должна быть функция main/1, запускать соотв-но как escript myapp.erl

Насчет Ctrl+C не могу ничего сказать сейчас, винды под рукой нету :)

Anonymous said...

Кста про матчинг. Там when есть.


% поиск в списке max значения
-module(tut6).
-export([list_max/1]).

list_max([Head | Rest]) ->
list_max(Rest, Head).

list_max([], Res) ->
Res;
list_max([Head | Rest], Result_so_far) when Head > Result_so_far ->
list_max(Rest, Head);
list_max([Head | Rest], Result_so_far) ->
list_max(Rest, Result_so_far).

39> c(tut6).
{ok, tut6}
40> tut6:list_max([1, 2, 3, 4, 5, 7, 4, 3, 2, 1]).
7


взято там-же http://gzip.rsdn.ru/article/erlang/GettingStartedWithErlang.xml

Anonymous said...

-1) А как запустить erl-файл?

сначала скомпилировать
1 в шеле c(module). и запустить module:fun().
2 в командной строке:
erlc fac1.erl компилируем, запускаем
erl -noshell -s module fun param
тк интерпритатор сам не завершается - можно добавить -s init stop
или прописать init:stop(). в конце программы в erl файле.

-Когда я нажимаю на Ctrl+C он -почему-то падает.

ctrl+break под win

под win, кста, удобно использовать файлик .erlang положив его в папку, куда поставлен ерланг. а в нём прописать примерно так:
c:cd("C:/progect/erlang/code" ).
io:format("Now in:~p~n" , [element(2,file:get_cwd())]).

Anonymous said...

забыл..
escript под win так и не получилось запустить, если ко-то осилит, поделитесь, пожалуйста, рецептом.

lrrr said...

Гм, у меня escript под виндой без проблем запустился

Anonymous said...
This comment has been removed by a blog administrator.
lrrr said...

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

Юрий said...

А есть язык типа эрланг, но заточенный для создания десктоп приложений?

Mamut said...

hex(V) when V < 10 ->
$0 + V;
hex(V) ->
$a + (V - 10).

gringo said...

не могли бы подсказать как erlang и русский подружить, а то "Привет мир"
2> tut:helloword(1).
\320\237\321\200\320\270\320\262\320\265\321\202 \320\274\320\270\321\200!ok
(erland R12 + fedora)

lrrr said...

Гм на самом деле точно не знаю, знаю только что такая проблема есть. Для работы с юникодными строками вообще библиотеки, конечно, существуют, но вот чтоб именно в консоль их красиво выводить -- не видел пока.

Anonymous said...

Крайне интересный видеокурс по созданию динамических оперденей на базе eralng и mnesia:
http://www.erlang-mnesia-video.ru/