Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Введение в Erlang

Эрланг обычно входит в стандартный список пакетов наиболее популярных дистрибутивов. Для пакетной установки эрланга в зависимости от вашего дистрибутива нужно набрать что-то типа
  yum install erlang
или
  zypper install erlang
Можно собрать и поставить эрланг из исходников - смотрите http://www.erlang.org/doc/installation_guide/INSTALL.html

Итак, эрланг установлен, запускаем в командной строке интерпретатор erl

 $ erl
 Erlang R15B03 (erts-5.9.3) [source] [smp:2:2] 
 
 Eshell V5.9.3  (abort with ^G)
 1> 

Базовые типы

Набираем комментарий и арифметическое выражение:
 1> % вычислим сумму двух чисел
 1> 20+30.
 50
 2> 
Обратите внимание, что после суммы мы ставим точку, это говорит интерпретатору, что команда закончена, и нажав enter, мы получаем ответ - 50. Можно проверить, как работает арифметика больших чисел:
 2> 1234567878 * 345345345345345345.
 426352270180180179753827910
Числа с основанием, отличным от 10, записываются в следующем виде: Основание#3начение. Основание является целым числом из интервала от 2 до 16, а значение — числом, имеющим заданное основание. К примеру, 2#1010 означает число 10 с основанием 2 и - 1б#ЕА означает число -234 с основанием 16, поскольку буквы от А до F кодируют числа от 10 до 15 для чисел с основанием 16.

В Erlang действительные числа (floats) представлены типом Float, например:

  1.234Е-10
Запись Е-10 означает, что точка смещена на 10 разрядов влево. Так, выражение - 1 01.234Е-10 является записью числа 1.234 • Ю или 0.0000000001234. Точность действительных чисел представлена в виде 64 битов и соответствует стандарту IEEE 754-1985.

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

 > My_var = 1.
 1
Если мы попробуем этой переменной присвоить другое значение - получим ошибку:
 > My_var = 2. 
 ** exception error: no match of right hand side value 2
Здесь нас ждет шок: переменные в эрланге не изменяются, их можно проинициализировать всего один раз. Авторы языка говорят, что это дисциплинирует мышление. Во всех языках программирования оператор = означает присваивание, а в эрланге это не так - здесь это сопоставление по образцу. В эрланге переменная - это указатель на область памяти, которую нельзя изменить. В эрланге это сделано для того, чтобы не было проблем с разделяемой памятью и блокировками, которые есть в сях или жабе.

В Erlang вызов переменных является вызовом по значению (call by value): все аргументы функции вычисляются перед выполнением тела функции. Вызов по ссылке невозможен.

Erlang является языком с динамической типизацией (dynamic type system). Типы определяются во время исполнения, также как и правомерность применения операций к переменным.

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

В текущей реализации ВМ Erlang используется копирующий, последовательный сборщик мусора. Сборка мусора проводится отдельно для каждого запущенного процесса: если одному из процессов не хватает памяти, запускается сборщик мусора.

Теперь перейдем к атомам. Они служат тем же целям, что и тип перечисление. Если взять например си, то в нем константы записываются так:

 #define OP_READ 1
 #define OP_WRITE 2
 #define OP_SEEK 3
В эрланге все атомы начинаются с прописной буквы - это основное отличие от переменных. Атомы можно заключить в одинарные кавычки. В Erlang не выделено отдельного типа для логических значений (booleans). Вместо этого совместно с операциями сравнения используются атомы true и false.
 > not((l < 3) and (2==2)).
 false
Логические операторы:
 and
 andalso
 or
 orelse
 xor
 not.
Операторы сравнения:
 ==   Равно
 /=   Не равно
 =:=  В точности равно
 =/=  В точности не равно
 =<   Меньше либо равно
 <    Меньше
 >=   Больше либо равно 
 >    Больше

Кортежи

Кортеж (tuple) - это коллекция фиксированного размера, данные внутри одного кортежа могут быть произвольного типа. Кортеж заключен в фигурные скобки:
 > {f1, 2}.
Данный кортеж состоит из атома и целого числа. Кортежи похожи на сишные структуры, только анонимные. Например, в си можно создать структуру точка и потом ее проинициализировать с помощью оператора точка: Если на первом месте кортежа стоит атом, то этот элемент называют тегом (tag).
 struct point 
 {
   int x;
   int y;
 } P;
 
 P.x = 10; P.y = 45;
В эрланге нет оператора точка. Ту же структуру, только сразу проинициализированную, в эрланге можно написать с помощью кортежа так:
 >  P = {10, 45}
Но лучше для удобства записать так - здесь атом фактически является именем структуры - так рекомендовано отцами языка :-)
 >  P = {point, 10, 45}
Чтобы извлечь данные из этой структуры в переменные X и Y:
 >  {point, X, Y} = Point.
Если у вас имеется сложный кортеж и вы хотите извлечь из него данные, то вы можете написать кортеж такой же формы (структуры) как и ваш, но в тех местах откуда вы хотите извлечь данные поместите несвязанные переменные. (Этот метод извлечения данных путем сопоставления по образцу называется унификацией и используется во множестве функциональных и логических языков программирования.)

Кортежи могут быть вложенными:

 > Person = {person,
     {name, joe},
     {height, 1.82},
     {footsize, 42},
     {eyecolour, brown}}.
Обращаю внимание, что для цвета глаз используются атомы как для задания имени поля, так и для его значения. Как извлечь отдельное поле из какой-то сложной структуры ? Сначала проинициализируем сложную структуру:
 > Person={person,{name,{first,joe},{last,armstrong}},{footsize,42}}.
После чего напишем образец для инициализации отдельного поля:
 > {_,{_,{_,Who},_},_} = Person.
Здесь символ подчеркивания называется анонимной переменной.

У кортежей есть встроенные функции, далее по порядку применяются функции: запрос размера, запрос элемента по номеру, обновление элемента кортежа и сравнение кортежей:

 1> tuple_size({abc, {def, 123}, ghi}).
 3
 2> element(2,{abc, {def, 123}, ghi}).
 {def,123}
 3> setelement(2, {abc, {def, 123}}, def).
 {abcdef}
 4> {1,2}<{1,3}.
 true
Кортежи индексируются с 1, а не с 0.

Списки

Списки, как и кортежи, могут хранить обьекты произвольного типа. Списки - коллекции, заключенные в квадратные скобки:
 > ThingsToBuy = [{apples,10},{pears,6},{milk,3}].
 > [1+7,hello,2-2,{cost, apple, 30-20},3].
Первый элемент списка называется головой списка, все остальное - хвост. Голову списка можно отделить от хвоста вертикальной чертой:
 > [1 | 2].
Для списка [1,2,3] 1 будет являться головой списка, а [2,3] — хвостом, этот список может быть записан в виде [ 1 | [2,3]]. Выполнив аналогичную операцию с хвостом списка, получим [1| [2| [3]]] и далее [ 1 | [ 2 | [ 3 | [ ] ] ] ] . Этот список мож­ но записать ещё одним способом [ 1 , 2 | [ 3 | [ ] ] ] , перед применением конструк­ тора списков могут следовать несколько элементов, разделённых запятыми. Все эти списки эквивалентны исходному списку [1,2,3]. Список, последний хвост которого равен пустому списку, называют регулярным (proper list) или правильно построенным (well-formed list). В следующем примере все списки эквивалентны:
 [one, two, three, four]
 [one, two, three, f o u r | [ ] ]
 [one, two|[three, f o u r] ]
 [one, t w o | [ t h r e e | [ f o u r | [ ] ] ]
 [one|[two|[three|[four| [ ] ] ] ] ]
Как и из всего остального, мы можем извлекать элементы из списка с помощью оператора сопоставления по образцу. Если у нас имеется не пустой список L , тогда выражение [X|Y]=L , где Х и У - это несвязанные переменные, поместит голову списка в Х, а хвост списка - в У.
В кортеже нет возможности отделить голову.

У списков есть встроенные функции, которые нужно вызывать с префиксом lists :

 > lists:max([1,2,3]).
 > lists:reverse([1,2,3]).
 > lists:sort([2,l,3]).
 > lists:split(2,[3,4,10,7,9]).
 > lists:zip([l,2,3],[5.6,7]).
 > lists:delete(2,[l,2,3,2,4,2]).
 > lists:last([l,2,3]).
 > lists:member(5,[1,24]).
 > lists:nth(2.[3,4,16,7,9]).
 > length([l,2,3]).
Списки можно складывать:
 > [l,2,3]++[4,5,6].
 [1,2,3,4,5,6]
Списки можно вычитать:
 > [l,2,2,3,4,4]--[2,4].
 [1,2,3,4]
Прибавить элемент к списку можно двумя способами - в голову с посощью конструктора:
 > [1 | [2,3,4]].
и с помощью сложения:
 > [1] ++ [2,3,4].
Конструктор быстрее, чем сложение.

Строки в эрланге являются частным случаем списков.

 > [65,66,67].
 "ABC"
Строку можно проинициализировать с помощью двойных кавычек :
 > Str = "asdfsdf".
Две строки можно сложить так:
 > S1="123". 
 "123"
 > S2="456".
 "456"
 > S3 = S1 ++ S2.
 "123456"
Но это медленно. Быстрее так:
 > S3 = string:concat("123","456").
У строк отсутствуют встроенные функции. Любителям императивных языков тут будет неуютно.

Оператор for

В эрланге нет стандартного цикла for, нам прийдется изобретать его :-) Напишем свою функцию for, который используем для создания списка чисел от 1 до 10. Создадим файл my_func.erl следующего содержания:
 -module(my_func).
 -export([for/3]).
 
 for(Max, Max, F) -> [F(Max)];
 for(I, Max, F)   -> [F(I)|for(I+1, Max, F)].
Здесь используется все тот же эрланговский алгоритм разбиения списка на голову и хвост. В первой итерации это будет [F(1)|for(2,10,F)], во второй - [F(2)|for(3,10,F)], и т.д. Запускаем интерпретатор:
 > c(my_func).
 > my_func:for(1,10,fun(I) -> I end).
 [1,2,3,4,5,6,7,8,9,10]
Теперь напишем функцию sum, вычисляющую сумму элементов списка: Добавляем в файл my_func.erl код:
 -export([sum/1]).
 
 sum([H|T]) -> H + sum(T);
 sum([]) -> 0.
Запускаем интерпретатор:
 > c(my_func).
 > L = [1,2,3,4,5].
 > my_func:sum(L).
 15
Здесь мы используем все тот же алгоритм отсечения головы от хвоста.

Теперь напишем функцию map, которая удваивает все элементы списка. Добавляем в файл my_func.erl код:

 -export([map/2]).
 
 map(_, [])     -> [];               
 map(F,  [H|T]) -> [F(H)|map(F, T)]. 
 
 
Запускаем интерпретатор:
 > c(my_func).
 > L = [1,2,3,4,5].  
 [1,2,3,4,5]
 > my_func:map(fun(X) -> 2*X end, L).
 [2,4,6,8,10]

Обработчики списков

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

Так, удвоить каждый элемент списка можно вот таким обработчиком:

 > [2*X || X <- L ].
То, что стоит слева от двойной вертикальной черты - это паттерн, а справа - конструктор.

Реализуем сортировку списка: добавим в файл my_func.erl код:

 -export([qsort/1]).
 
 qsort([]) -> [];
 qsort([Pivot|T]) ->
 qsort([X || X <- T, X < Pivot])
 ++ [Pivot] ++
 qsort([X || X <- T, X >= Pivot]).
Здесь ++ - это инфиксный оператор добавления. А Pivot переводится как "центр вращения". Запускаем интерпретатор:
 > c(my_func).
 > L=[23,6,2,9,27,400,78,45,61,82,14].
 > my_func:qsort(L).
 [2,6,9,14,23,27,45,61,78,82,400]
Как работает эта сортировка ? Сначала срабатывает вторая клауза функции qsort:
  [Pivot|T] = L.
которая связывает переменные Pivot -> 23 и T -> [6,2,9,27,400,78,45,61,82,14]. Теперь мы разделяем список Т на два списка : один из элементов, которые меньше чем Pivot, а второй - из элементов, которые больше или равны Pivot.
 4> Smaller = [X || X <- T, X < Pivot].
 [6,2,9,14]
 5> Bigger = [X || X <- T, X >= Pivot].
 [27,400,78,45,61,82]
Теперь мы можем отсортировать списки Smaller и Bigger и соединить их обратно с Pivot:
 qsort( [6,2,9,14] ) ++ [23] ++ qsort( [27,400,78,45,61,82] )
 = [2,6,9,14] ++ [23] ++ [27,45,61,78,82,400]
 = [2,6,9,14,23,27,45,61,78,82,400]

Записи (record)

Записи обьявляются с помощью следующего синтаксиса:
 -record(Name, {
 key1 = Default1,
 key2 = Default2,
 ...
 key3,
 ...
 }).
В командном интерпретаторе вместо record нужно использовать rr. В вышеуказанном примере, Name - это имя всей этой записи. key1, key2 и так далее - это имена полей этой записи. Все эти имена должны быть атомами Эрланга. При этом, key1 и key2 имеют значения по-умолчанию (Default1 и Default2, соответственно), которые присваиваются этим полям при создании новой записи Name, если для них не указано другого значения. Поле key3 является изначально неопределенным полем записи.

Определения записей могут быть либо сразу включены в файлы с исходным кодом Эрланга, либо помещены в файлы с расширением .hrl и потом включены в файлы с исходным кодом (что является единственным способом, чтобы в разных Эрланг-файлах было одно и тоже определение этих записей).

Функции и модули

В основу эрланга положена модульная организация кода. Модули в свою очередь состоят из функций. Модули Эрланга сохраняются в файлах с расширением .erl. Модули должны быть откомпилированы перед тем как их содержимое будет готово к выполнению. Скомпилированный модуль будет иметь расширение .beam. Имя модуля должно совпадать с именем файла. В качестве примера создадим пару структур данных, представляющих собой прямоугольник и круг. Затем извлечем из этих структур значения длин сторон для прямоугольника и радиуса для круга. Например, вот так:
 > Rectangle = {rectangle, 10, 5}.
 > Circle = {circle, 4}.
 > {rectangle, Width, Ht} = Rectangle.
 > {circle, R} = Circle.
Теперь напишем функцию, вычисляющую площади прямоугольника и круга. Сначала напишем текст модуля geometry, который сохраним в отдельном файле geometry.erl:
 -module(geometry).
 -export([area/1]).
 area({rectangle, Width, Ht}) -> Width * Ht;
 area({circle, R}) -> 3.14159 * R * R.
 
В модуле мы записали 2 варианта функции area - они называются клаузами. У каждой клаузы есть заголовок, шаблон аргументов, тело, состоящее из выражений. Шаблоны аргументов у клауз должны быть взаимоисключающие. Теперь запускаем в интерпретаторе компиляцию модуля:
 > c(geometry).
У вас должен появиться бинарник geometry.beam, Теперь вызываем функцию area:
 > geometry:area({rectangle, 10, 5}).
 50
 > geometry:area({circle, 1.4}).
 6.15752
В следующем примере мы напишем два модуля. При этом один модуль будет использовать функцию другого модуля. У нас имеется список покупок, состоящий из имен и их количества:
 1> Buy = [{oranges,4}, {newspaper,1}, {apples,10}, {pears,6}, {milk,3}].
Первый модуль - shop.erl - будет возвращать стоимость одного товара в зависимости от его вида:
 -module(shop).
 -export([cost/1]).
 cost(oranges) -> 5;
 cost(newspaper) -> 8;
 cost(apples) -> 2;
 cost(pears) -> 9;
 cost(milk) -> 7.
Теперь напишем второй модуль shop2.erl, в котором хотим посчитать общую стоимость покупок:
 -module(shop2).
 -export([total/1]).
 total([{What, N}|T]) -> shop:cost(What) * N + total(T);
 total([]) -> 0.
Теперь в командной строке компилируем модули и проверяем:
 > c(shop).
 > c(shop2).
 > shop2:total([{milk,3}]).
 21
 > shop1:total(Buy).
 123
Синтаксис эрланга имеет три вида пунктуации:
1 Запятые (,) разделяют аргументы в вызовах функции, конструкторах данных и шаблонах.
2 Точки (.) (с последующим пробелом) разделяют функции и выражения в оболочке Эрланга.
3 Точка с запятой (;) разделяет клаузы, которые мы используем в различных контекстах: в объявлении функций, а так же в блоках case, if, try..catch и в выражениях receive.

Арность(arity) функции - это количество аргументов, принимаемых этой функцией. В Эрланге, две функции,объявленные в одном модуле, с одним именем, но разным количеством аргументов, представляют собой две различные функции - классический пример перегрузки.

В эрланге анонимные функции - fun - это функции без имени. Создадим переменную Z, присвоив ее анонимной функции:

 > Z = fun(X) -> 2*X end.
Применим ее:
 > Z(2).
 4
Эрланг - это функциональный язык программирования. Кроме всего прочего это означает, что анонимные функции могут быть переданы как аргументы для функции, а также, что функции (или анонимные функции) могут возвращать анонимные функции в качестве результата. Функция, которая возвращает другие функции, или же может принимать другие функции в качестве своих аргументов, называется функцией высшего порядка.

Модуль lists, входящий в стандартные библиотеки Эрланга, экспортирует несколько функций, которые принимают другие функции в качестве аргументов. Наиболее полезная из них это функция lists:map(F, L). Она возвращает список, созданный применением функции F к каждому элементу из списка L. В питоне есть похожая одноименная функция map. Создадим список и применим к ней функцию Z, созданную нами выше:

 > L = [1,2,3,4].
 > lists:map(Z, L).
 [2,4,6,8]
Другая полезная функция - lists:filter(P, L), она возвращает новый список из таких элементов E списка L, для которых функция P(E) равна true. Давайте создадим анонимную функцию Even(X), которая возвращает true, если Х - это четное число:
 > Even = fun(X) -> (X rem 2) =:= 0 end.
Применим ее:
 18> lists:filter(Even, [1,2,3,4,5,6,8]).
 [2,4,6,8]
Функции могут использоваться не только в качестве аргументов других функций (таких как map и filter). Функции могут также возвращаться другими функциями - это обобщение (generalization). Приведем пример - допустим, что у нас есть список чего-либо, предположим фруктов:
 > Fruit = [apple,pear,orange].
Теперь объявим функцию MakeTest(L), которая преобразует любой список (L) в функцию, которая проверяет, находится ли ее аргумент в этом списке L:
 > MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end) end.
 > IsFruit = MakeTest(Fruit).
lists:member(X, L) возвращает true если X находится в списке L, в противном случае она возвращает false. Давайте протестируем нашу функцию:
 > IsFruit(pear).
 true
Также мы можем использовать ее как аргумент функции lists:filter:
 > lists:filter(IsFruit, [dog,orange,cat,apple,bear]).
 [orange,apple]

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье