Введение в Эрланг

Сергей Игнатов, JetBrains

Введение в Эрланг

Сергей Игнатов,
sergey.ignatov@jetbrains.com

Кто, зачем и почему

http://www.erlang.org/faq/introduction.html

Да, виртульная машина


                  +--------+   +--------+
                  | Erlang |   | Elixir |
                  +--------+   +--------+
                      ||           ||
                      \/           \/
                    +-----------------+
                    | .beam byte code |
                    +-----------------+
                            ||
                            \/
               +---------------------------+
               | BEAM emulator (Erlang VM) |
               +---------------------------+
        
        

Числа

1 + 2 * 3.              % 7
1 * 1.0 * 7.            % 7.0
1.0e1 - 3.              % 7.0
2#11 + 3#11.            % 7
16#AE.                  % 174
        
        

Основание#Значение (основание от 2 до 36)

Атомы

            ok.
            {error, badarg}.
            'CAPS_ATOM'.
            'with spaces'.
            'atom' = atom.   % true 
        

:symbol из Ruby, 'quoted-value из Lisp
Атом — просто уникальное значение
true и false — тоже атомы

Кортежи

{"String", 200}.
element(1, {"String", 200}).         % "String"
setelement(2, {"String", 200}, 250). % {"String",250} 
        

Неизменяемые структуры, всегда возвращается новый экземпляр.

Списки

[1,2,3].
[1,2,a,"str",3,{}].
[4 | [1,2,3]].           % [4,1,2,3]
[1,2,3] ++ [4,5].        % [1,2,3,4,5]
[1,2,3] -- [1,2] -- [2]. % [2,3]
hd([4,1,2,3]).           % 4
tl([4,1,2,3]).           % [1,2,3]
tl(["last element"]).   % []

Символ «|» — это оператор добавления элемента в голову списка

List comprehension

[2*X || X <- lists:seq(0,10),   X*X > 3].      % [4,6,8,10]
[X*X || X <- [1,2,3,4,5,6,7],   X > 6].        % [49]
[X   || X <- [1,2,foo,bar,5,6], X rem 2 == 0]. % [2,6]
        

Ломая глаза

Color = 16#F09A29.
Pixel = <<Color:24>>.
Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
<<Pix1:24, Pix2:24, Pix3:24, Pix4:24>> = Pixels.
<<R:8, G:8, B:8>> = <<Pix1:24>>.
<<R:8, Rest/binary>> = Pixels.% R=213,Rest = <<45,132,64...>>

Это действительно удобный способ чтения бинарных данных
Важно: необходим пробел между = и <<

Binary comprehension

            Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
RGB = [{R,G,B} || <<R:8,G:8,B:8>> <= Pixels].
RGB = [{213,45,132},{64,76,32},{76,0,0},{234,32,15}].
[X || <<X>> <= <<1,2,3,4,5>>, X rem 2 == 0]. % [2,4]
        

Строки

            "Just a string".
[65,66,67].           % "ABC"
[97,98,99].           % "abc"
[97,98,99] == "abc".  % true 
        

Строка — список целых чисел.

Связывание и сопоставление

            Res = ok.             % ok
Primes = [1,2,3,5,7]. % [1,2,3,5,7]
C = {"A", 20}.        % {"A",20}
L = [C, {"B", 25}].   % [{"A",20},{"B",25}]
        

Присваиваний нет. Есть связывание и сопоставление с образцом.

Связывание и сопоставление. Продолжение

            Name = onlyonce.           % onlyonce
Name = maybetwice.         % error
ok = ok.                   % ok
ok = notok.                % error
N = 12.                    % 12
12 = N.                    % 12
{[], M} = {[], 200}.       % M = 200
[Head|Tail] = [1,2,3,4].   % Head = 1, Tail = [2,3,4]
        

Словари

            M  = #{1 => 1}.
M2 = M#{1 => 10}.
#{1 := X} = M2.
X.                % 10
#{2 := X3} = M.   % ** exception error:
                  % no match of right hand side value #{1 => 1}
            
        

Опять же, персистентная структура данных.

Сравнение

            5 =:= true.           % false
0 == false.           % false
"a" > 10.             % true
1.0 == 1.             % true
1.0 =:= 1.            % false
0 > atom.             % false
sort([atom,[],{},1]). % [1,atom,{},[]]
        

number < atom < reference < fun < port < pid < tuple < list < bit string

Модули

-module(simple).
-export([add/2, hello/0]).

add(A, B) -> internal(A, B).
internal(A, B) -> A + B.
hello() -> io:format("Hello, world!~n").

Имя файла должно совпадать с именем модуля.

Немного грамматики

forms ::= (form .)*
form ::= function | attribute
function ::= function_clause (; function_clause)*
function_clause ::= atom ( args? ) guards? -> body
body ::= expression (, expression)*
        

Про функции

head([H|_]) -> H.
second([_, S|_]) -> S.

same(X, X) -> true;
same(_, _) -> false.
        

Ещё про функции

right_age(Age) when Age >= 16, Age < 104 -> true;
right_age(_)                             -> false.

to_string(Arg) when is_atom(Agr)    -> "atom";
to_string(Arg) when is_list(Agr)    -> "list";
to_string(Arg) when is_integer(Agr) -> "integer";
to_string(_)                        -> "unknown".
        
            function_clause ::= atom ( args? ) guards? -> body
        

И ещё про функции

-module(greet).
-export([say/2]).

say(Gender, Name) ->
    io:format("Hello, " ++ title(Gender) ++ ". " ++ Name).
title(male)   -> "Mr";
title(female) -> "Mr";
title(_)      -> "". 

Сопоставление с образцом. Дубль два

-module(greet).
-export([say/2]).
say(Gender, Name) ->
    Title = case Gender of
                male   -> "Mr" ;
                female -> "Mrs";
                _      -> ""
            end,
    io:format("Hello, " ++ Title ++ ". " ++ Name). 

Условный оператор

works_fine()   -> if 1 == 1 -> fine end.
throws_error() -> if 1 == 2 -> fine end. 
Всегда должна быть ветка по которой пойдет исполнение:
even_or_odd(Num) ->
    if
        Num rem 2 == 0 -> even;
        true           -> odd
    end.

Рекурсия

len([])    -> 0;
len([_|T]) -> 1 + len(T).

reverse([])    -> [];
reverse([H|T]) -> reverse(T) ++ [H].

map(_, [])            -> [];
map(Fun, [Head|Tail]) -> [Fun(Head)|map(Fun, Tail)].
        

Рекурсия. Теперь с оптимизацией

len(L) -> len(L, 0).
len([], Acc)    -> Acc;
len([_|T], Acc) -> len(T, Acc + 1).

reverse(L) -> reverse(L, []).
reverse([], Acc)    -> Acc;
reverse([H|T], Acc) -> reverse(T, [H|Acc]).

map(Fun, [Head|Tail]) -> ...
            

Функции высшего порядка

map(_, [])            -> [];
map(Fun, [Head|Tail]) -> [Fun(Head)|map(Fun, Tail)].

map(fun(X) -> X + 1 end, [1,2,3]).      % [2,3,4]
map(fun(X) -> X * X end, [1,2,3]).      % [1,4,9]
            

Записи

-module(records).
-export([create_bender/0]).
-record(robot, {name, occupation=none,
                hobbies, details=[]}).

create_bender() ->
    #robot{name="Bender Bending Rodríguez",
    occupation=industrial, hobbies=["Kill all humans"],
    details=["Powered by alcohol-based fuels"]}.
            

Макросы — это удобно

-define(head, -module(test)).
?head.
-define(Arity, 2).
-define(name, main).
-export([?name/?Arity]).
-define(Value, ?Arity).
-define(add(A, B), A + B).
-define(title, ?name(X, Y) ->).
?title ?add(X, Y) + ?Value. 

Типизация

-spec(add_one(number()) -> number()).
add_one(X) -> X + 1.
-spec(atom_add() -> number()).
atom_add() -> add_one(2).
-spec(tuple_sum({number(), number()}) -> number()).
tuple_sum({X, Y}) -> X + Y. 

Dialyzer проверяет корректность контрактов.
Typer поможет вывести информацию о типах из исходного кода.

Еще немного о типах

-type suit() :: spades | clubs | hearts | diamonds.
-type value() :: 1..10 | j | q | k.
-type card() :: {suit(), value()}.
kind({_, A}) when A >= 1, A =< 10 -> number;
kind(_) -> face.
main() ->
    number = kind({spades, 7}),
    face   = kind({hearts, k}),
    number = kind({rubies, 4}). % error

Процессы

Модель акторов (1973)

Актор — это и есть процесс, связанный с ним «почтовый ящик» и уникальный идентификатор (pid)

Создание нового процесса

Pid = spawn(Node, Module, Function, Args).
HW = spawn(fun() -> io:format("hello world") end).

G = fun(X) -> timer:sleep(10), io:format("~p~n", [X]) end.
[spawn(fun() -> G(X) end) || X <- lists:seq(1,10)]. 

self() — pid текущего процесса.

Отправка сообщений

Pid ! Message.

Self = self().
Self ! ok.
Self ! {[], 1, atom, "string"}.
Self ! Self.

И их получение

receive
    Pattern1 -> ...;
    Pattern2 -> ...;
    PatternN -> ...
after Timeout ->
    ...
end.
            

Слежка

F = fun() -> timer:sleep(500), exit(reason) end.
spawn(F).       % Pid
link(spawn(F)). % ** exception error 'reason'
spawn_link(F).
spawn_link(Node, Module, Fun, Args).
unlink(Pid).

Между процессами не может быть больше одной ссылки.
Цепочки процессов "погибают" до системного.
process_flag(trap_exit, true) сделает вас системным.

Тотальный контроль

1> monitor(process, spawn(fun() -> timer:sleep(500) end)).
#Ref<0.0.0.77>
2> flush().
Shell got {'DOWN',#Ref<0.0.0.77>,process,<0.63.0>,normal}

spawn_monitor(Function).
spawn_monitor(Module, Function, Args).
demonitor(Ref). 

Может создать любое количество мониторов между процессами.

Обработка ошибок


try Expression of
    SuccessfulPattern1 [Guards] -> ...;
    SuccessfulPattern2 [Guards] -> ...
catch
    Exception:Reason -> ...;
    Error:What -> ...
end. 

Ловля ошибок

1> self().
<0.32.0>
2> X = 1/0.
** exception error: bad argument in an arithmetic expression
3> self().
<0.35.0>
4> X = (catch 1/0).
{'EXIT',{badarith,[{erlang,'/',[1,0],[]}}
5> self().
<0.35.0> 

Что посмотреть

http://www.erlang.org/
http://learnyousomeerlang.com/