Элементарные типы и операции над ними. Часть I: типы данных, размер, ограничение.

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

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

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

    Вступление


    Для начала хочу выразить огромную признательность участникам русскоязычной рассылке по Erlang-у в Google-е за поднятие кармы и возможности выложить на хабр данную статью.

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

    1> X=1, Y = 2,
    1> Z = 3,
    1> S=4.
    4

    Указателем завершения ввода и запуск на выполнение является точка. При этом шелл выведет на экран значение возвращаемое последней из команд. В примере выше возвращается значение переменной S. Значения всех инициированных переменных запоминается, а так как в Эрланге нельзя переопределить значение инициированной переменной, то попытка переопределения приведет к ошибке:

    2> Z=2.
    ** exception error: no match of right hand side value 2

    Поэтому если требуется в текущей сессии работы заставить шелл «забыть» значения переменных, то можно воспользоваться функцией f(). Вызванная без аргументов она удаляет все инициализированные переменные. Если в качестве аргумента указать имя переменной, то удалена будут только она (передать список переменных нельзя):

    3> f(Z).
    ok
    4> X = 4.
    ** exception error: no match of right hand side value 4
    5> f().
    ok
    6> X = 4. %все, что идет после знака процента является комментарием
    4

    Для выхода достаточно ввести halt(), или вызвать интерфейс пользовательских команд Crtl+G и ввести q (команда h выведет справку). При выводе цифровых данных в шелле они приводятся к десятичному виду.

    Изложенный материал относится к последней, актуальной на данный момент, версии 5.6.5. Для кодирования строк используется ISO-8859-1 (Latin-1) кодировка. Соответственно и все численные коды символов берутся из этой кодировки. Первая половина (коды 0-127) кодировки соответствует кодам US-ASCII, поэтому проблем с латинским алфавитом не возникает.

    Несмотря на заявление разработчиков о том, что во внутреннем представлении используется Latin-1 «снаружи» виртуальной машины это зачастую совершенно неочевидно. Это возникает оттого, что Эрланг передает и принимает символы в виде кодов. Если для терминала установлена локаль, то коды интерпретируются исходя из установленной кодовой страницы и если это возможно выводятся в виде печатных символов. Вот пример SSH сессии:
    # setenv | grep LANG
    LANG=
    # erl
    Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [hipe] [kernel-poll:false]

    Eshell V5.6.5 (abort with ^G)
    1> [255].
    "\377"
    2> halt().
    # setenv LANG ru_RU.CP1251
    # erl
    Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [hipe] [kernel-poll:false]

    Eshell V5.6.5 (abort with ^G)
    1> [255].
    "я"

    Причем совершенно не важно, что указано в переменной окружения LANG, главное что бы она была установлена.

    1. Элементарные типы


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

    1.1 Число

    Поддерживается два типа чисел. Числа с плавающей запятой (Float) и целые (Integer). Кроме общепринятых форм записи чисел существует две специфичных нотации:
    • $char
      ASCII код (в зависимости от локали) символа char.
    • base#value
      целое число value в системе счисления с основанием base, основание может быть из диапазона 2…36

    Например:
    1> 42.
    42
    2> $A.
    65
    3> $\ .
    10
    4> 2#101.
    5
    5> 16#1f.
    31
    6> 2.3.
    2.3
    7> 2.3e3.
    2.3e3
    8> 2.3e-3.
    0.0023
    9>$я.
    255

    Напоминаю, что цифры при выводе приводятся к десятичному виду.

    Потребление памяти и ограничения. Целое занимает одно машинное слово, что для 32-ух и 64-х разрядных процессорах составляет 4 байта и 8 байт соответственно. Для больших целых 1…N машинных слов. Числа с плавающей точкой в зависимости от архитектуры занимают 4 и 3 машинных слова соответственно.

    1.2 Список

    Список (List) позволяют группировать данные в одну структуру. Список создается при помощи квадратных скобок, элементы списка разделяются запятыми. Элемент списка может быть любого типа. Первый элемент списка называется голова (head), а оставшаяся часть — хвост (tail).

    1> Var = 5.
    5
    2> [1,45, atom, Var,"string", $Z, 2#101].
    [1,45,atom,5,"string",90,5]
    3>List = [1, 9.2, 3], %голова списка 1, хвост [9.2, 3]
    3>List.
    [1,9.2,3]

    Размер списка равен количеству элементов в нем. В примере выше значением переменной List является список, размер списка равен 3.
    Список является динамической структурой. Можно добавлять и удалять элементы списка. Внутри виртуальной машины список представляет собой структуру, которая является односвязным списком, что накладывает определенные особенности обработки, но об этом чуть ниже.

    Потребление памяти и ограничения. Каждый элемент списка занимает одно машинное слово (4 или 8 байт в зависимости от архитектуры) + размер хранимых в элементе данных. Таким образом на 32-ух разрядной архитектуре значение переменной List будет занимать (1 + 1) + (1 + 4) + (1 + 1) = 9 слов или 36 байт.

    1.3 Строка

    На самом деле в Эрланге нет строк (String). Это просто синтаксический сахар который позволяет в более удобной форме записывать список целых чисел. Каждый элемент этого списка представляет собой ASCII код соответствующего символа.

    1> "Surprise".
    "Surprise"
    2> [83,117,114,112,114,105,115,101].
    "Surprise"
    3> "строка".
    "строка"
    4> [$с,$т,$р,$о,$к,$а].
    "строка"
    5> [$с, $т, $р, $о, $к, $а, 1].
    [241,242,240,238,234,224,1]

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

    Потребление памяти и ограничения. Т.к. строка это список целых чисел, а каждый символ это один элемент списка, то на символ уходит 8 или 16 байт (2 машинных слова).

    1.4 Атом

    Атом (Atom) это просто литерал. Он не может быть связан с каким либо цифровым значением подобно константе в других языках. Значение возвращаемое атомом является самим атомом. Атом должен начинаться со строчной буквы и состоят из цифр, латинских букв, знака подчеркивания _ или собачки @. В этом случае его можно не заключать в одиночные кавычки. Если имеется другие символы, то нужно использовать одиночные кавычки для обрамления. Двойные кавычки для этого не подходят, т.к. в них заключают строки.

    Например:
    hello
    phone_number
    'Monday'
    'phone number'

    В строках и в закавыченных атомах можно использовать такие управляющие последовательности:
    Sequence
    Description
    \b
    возврат (backspace)
    \d
    удалить (delete)
    \e
    эскейп (escape)
    \f
    прогон страницы (form feed)
    \
    новая строка (newline)
    \r
    возврат каретки (carriage return)
    \s
    пробел (space)
    \t
    горизонтальная табуляция (tab)
    \v
    вертикальная табуляция (vertical tab)
    \XYZ, \YZ, \Z
    восьмеричный код символа
    \^a...\^z
    \^A...\^Z
    Ctrl + A … Ctrl + Z
    \'
    одиночная кавычка
    \"
    двойная кавычка
    \\
    обратная косая черта

    Имя незаковыченного атома не может быть зарезервированным словом. К таким словам относятся:
    after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor.

    Потребление памяти и ограничения. Каждый объявленный атом является уникальным и его символьное представление хранится во внутренней структуре виртуальной машины которая называется таблица атомов. Атом занимает 4 или 8 байт (одно машинное слово) и является просто ссылкой на элемент таблицы атомов в котором содержится его символьное представление. Сборщик мусора (garbage-collection) не выполняет очистку таблицы атомов. Сама таблица так же занимает место в памяти. Допускается использовать атомы в 255 символов, в общей сложности допустимо использовать 1 048 576 атомов. Таким образом атом в 255 символов будет занимать 255 * 2 + 1 * N машинных слов, где N – количество упоминаний атома в программе.


    1.5 Кортеж

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

    1> {1,2, 2#110, n, $r, [1, 5], "abc"}.
    {1,2,6,n,114,[1,5],"abc"}
    2> Man = {man,
    2>           {name, "Алексей"},
    2>           {height, {meter, 1.86}},
    2>           {age, 27}}.
    {man,{name,"Алексей"},{height,{meter,1.86}},{age,27}}
    3> Man2 = {man,
    3>            {name, "Иван"},
    3>            {height, {meter, 1.80}},
    3>            {age, 25}}.
    4> [Man, Man2].
    [{man,{name,"Алексей"},{height,{meter,1.86}},{age,27}},
     {man,{name,"Иван"},{height,{meter,1.8}},{age,25}}]
    5>{20, 100}.
    {20,100}.
    

    Кортежи удобны тем, что позволяют не только включать в структуру конкретные данные, но и описывать их. Это, а так же фиксированность кортежа позволяют очень эффективно применять их в шаблонах. Будет хорошей практикой при создании кортежа в первый элемент записывать атом описывающий сущность кортежа. Если проводить аналогии с РСУБД, то список является таблицей, каждая строка таблицы это элемент списка, а кортеж находящийся в этом элемент – конкретная запись в соответствующем столбце.

    Потребление памяти и ограничения. Кортеж занимает 2 машинных слова + размер необходимый для хранения непосредственно самих данных. К примеру, кортеж в строке 5 будет занимать (2 + 1) + (2 + 1) = 6 машинных слов или 24 байта на 32-ой архитектуре. Максимальное количество элементов в кортеже 67 108 863.


    1.6 Запись

    Запись (Record) на самом деле является еще одним примером синтаксического сахара и во внутреннем представлении хранится как кортеж. Запись на этапе компиляции преобразуется в кортеж, поэтому использовать записи напрямую в шелле невозможно. Но можно воспользоваться rd() функцией для объявления структуры записи (строка 1). Объявление записи всегда состоит из двух элементов. Первый элемент обязательно должен быть атом называемый имя записи. Второй всегда кортежем, возможно даже пустым, элементы которого являются парой имя_полязначение_поля, при этом имя поля должно быть атомом, а значение любым допустимым типом (в том числе и записью, строка 11).


    Оператором создания кортежа на основании записи (строка 2) является решетка # после которой следует имя записи и кортеж со значениями полей, возможно даже и пустым, но ни когда с именами полей которые не объявлены в описание записи.

    1> rd(person, {name = "", phone = [], address}).
    person
    2> #person{}.
    #person{name = [],phone = [],address = undefined}
    3> #person{phone=[1,2,3], name="Joi", address="Earth"}.
    #person{name = "Joi",phone = [1,2,3],address = "Earth"}

    Преимущество записи перед кортежем в том, что работа с элементами осуществляется по имени поля (имя должно быть атомом и быть объявленным в описании записи, строка 1), а не по номеру позиции. Соответственно порядок присвоения не имеет значения, не нужно помнить, что у нас содержится в первом элементе имя, телефон или адрес. Можно изменить порядок следования полей, добавить новые поля. Кроме того возможно присвоение значения по умолчанию когда соответствующее поле не задано.

    4> rd(person, {name="Smit", phone}).
    person
    5> P = #person{}.
    #person{name = "Smit",phone = undefined}
    6> J = #person{phone = [1,2,3], name = "Joi"}.
    #person{name = "Joi",phone = [1,2,3]}
    7> P#person.name.
    "Smit"
    8> J#person.name.
    "Joi
    9> W = J#person{name="Will"}.
    #person{name = "Will",phone = [1,2,3]}

    Если же при создании записи (строка 4) значение по умолчанию не определено (поле phone), то его значение равно атому undefined. Получить доступ к значению переменной созданной с помощью записи можно используя синтаксис описанный в строках 7 и 8. Можно скопировать значение переменной в новую переменную (строка 9). При этом если значение ни каких полей не определено, то получается полная копия, если же определены, то в новой переменной соответствующие поля переопределяются. Все эти манипуляции ни как не затрагивают ни определение записи, ни значения полей в старой переменной.
    Лично мне это очень напоминает описание и создание экземпляров класса, хотя еще раз подчеркну, что это всего лишь способ хранения в переменной кортежа.

    10> rd(name, {first = "Robert", last = "Ericsson"}).
    name
    11> rd(person, {name = #name{}, phone}).
    person
    12> P = #person{name = #name{first="Robert",last="Virding"}, phone=123}.
    #person{name = #name{first = "Robert",last = "Virding"},
    phone = 123}
    13> First = (P#person.name)#name.first.
    "Robert"

    Пример выше иллюстрирует вложенные записи и синтаксис доступа к внутренним элементам.


    1.7 Бинарные данные и битовые строки

    И бинарный тип (Binaries) и битовые строки (Bit strings) позволяют работать с двоичным кодом напрямую. Отличие бинарного типа от битовой строки в том, что бинарные данные должны состоять только из целого количества байт, т.е. количество бит в них кратно восьми. Битовые же строки позволяют работать с данными на уровне бит, т.е. по сути бинарный тип это частный случай битовой строки количество разрядов в которой кратно восьми. Можно как создавать данные описав их структуру, так и использовать данный тип в шаблонах. Двоичные данные описываются такой структурой:

    <<E1, E2, ... En>>

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

    1> <<20, $W, 50:8, "abc">>.
    <<20,87,50, "abc" >>
    2> <<400>>.
    <<144>>
    3> <<400:16>>.
    <<1,144>>
    4> Var = 30.
    30
    5> <<(Var + 30), (20+5)>>.
    <<60,25>>

    Что бы понять, почему в результате создания двоичных данных в строке 2 мы получили 144 (т.е. 10010000, ведь мы, надеюсь, еще не забыли, что шелл при выводе приводит все цифровые данные к десятичному виду), а не ожидаемые 400 нужно рассмотреть битовый синтаксис описания сегмента.

    Ei = Value |
         Value:Size |
         Value/TypeSpecifierList |
         Value:Size/TypeSpecifierList
    

    Полная форма описания сегмента состоит из значения (Value), размера (Size) и спецификатора ( TypeSpecifierList ). Причем размер и спецификатор являются необязательными и если не заданы принимают значения по умолчанию.

    Значение (Value) в конструкторе может быть числом (целым или с плавающей точкой), битовой строкой или строкой, которая, как мы помним, является на самом деле списком целых чисел. Однако вместе с тем значение сегмента не может быть списком даже целых чисел, т.к. внутри конструктора строка является синтаксическим сахаром для посимвольного преобразования в целые числа, а не в список. Т.е. запись <<«abc”>> является синтаксическим сахаром для <<$a, $b, $c>>, а не <<[$a, $b, $c]>>.
    Внутри шаблонов значение может быть литералом или неопределенной переменной. Вложенные шаблоны недопускаются. В Value так же можно использовать выражения, но в этом случае сегмент должен быть заключен в круглые скобки (строка 5).

    Размер (Size) определяет размер сегмента в юнитах (Unit, о них чуть ниже) и должен быть числом. Значение по умолчанию Size зависит от типа (Type, см. ниже) Value, но может быть и явно задано. Для целых это 8, чисел с плавающей точкой 64, бинарный соответствует количеству байт, битовые строки количеству разрядов. Полный размер сегмента в битах можно вычислить как Size * Unit.
    При использовании в шаблонах величина Size должна быть явно заданной (строка 7) и может не задаваться только для последнего сегмента поскольку в него попадает остаток данных (сродни чтению строки со start символа и до конца строки без указания нужного length количества символов).

    6> Bin = <<30>>.
    <<30>>
    7> <<X:2, Y:3, Z/bits>> = Bin,
    8> Z. %переменная размером в 3 разряда, её значение 110
    <<6:3>>


    Спецификатор ( TypeSpecifierList ) состоит из списка уточняющих опций разделенных дефисом и записанных в произвольном порядке (для большего удобства чтения рекомендую писать unit последним).
    • Type = integer | float | binary | bytes | bitstring | bits
      Указывает тип Value. bytes является короткой формой записи для binary, а bits для bitstring. Значение по умолчанию integer.
    • Signedness = signed | unsigned
      Указывает есть ли у целого знак или это без знаковая величина. Имеет смысл только для целого типа. Значение по умолчанию unsigned (т.е. положительно целое без знака).
    • Endianness = big | little | native
      Порядок байтов. Одним байтом можно закодировать диапазон целых чисел 0…255, поэтому для бОльших чисел требуется два и более байт. К примеру, число 400 закодированное двумя байтами будет иметь вид 00000001 10010000 и первым тут считается старший байт (от старшего к младшему). Это сетевой порядок байт (big-endian). Когда же считается, что первым идет младший байт, то говорят об интеловском порядке байт (little-endian). Значение native означает, что порядок байт будет установлен при загрузке в зависимости от того, какой режим является «родным» для центрального процессора на котором выполняется виртуальная машина. Порядок имеет смысл только для чисел. Значение по умолчанию big.
    • Unit = unit:IntegerLiteral
      Юнит. Число в диапазоне 1…256. Вместе с Size однозначно определяет размер сегмента в битах и не может указываться без явного задания Size. Значение по умолчанию 1 для чисел и битовых строк, и 8 для бинарного типа.

    Таким образом пример из строки 2 должен стать более понятным. В Эрланге конструктор двоичных данных по умолчанию создает сегменты равные одному байту если только явно не указать другой размер. Следовательно, в строке 2 содержится запись вида <<400:8/integer-unsigned-big-unit:1>>, которая и усекается виртуальной машиной до одного последнего байта. При сетевой последовательности байт последним будет байт со значением 10010000, т.е. 144 в десятичной системе. Если же задать последовательность little, то последним будет 00000001 байт, т.е. 1 в десятичной системе. Если же сегмент способен закодировать значение, то усечения происходить не будет.

    9> <<400:16>>.
    <<1,144>>
    10> <<400:16/little>>.
    <<144,1>>
    11> <<400:8/unit:2>>.
    <<1,144>>

    Использую битовый синтаксис одни и те же данные могут быть описаны по разному (строки 9 и 11 описывают одну и туже двухбайтовую структуру).

    Потребление памяти и ограничения. 3…6 бит + непосредственно сами данные. На 32-ой архитектуре возможна манипуляция 536 870 911байтами, на 64-ех разрядной системе 2 305 843 009 213 693 951 байтами. Для обработки структур бОльшего размера придется самостоятельно написать функции обработки.
    Внимание. Запись B=<<1>> будет интерпретироваться как B =<<1>> (т.е. B меньше-равно <1>>). Правильная форма будет с применением пробелов: B = <<1>>.

    1.8 Ссылка

    Ссылка (Reference) представляет собой терм создаваемый функцией make_ref/0 и может считать уникальным. Она может быть использовать для такой структуры данных как первичный ключ.

    Потребление памяти и ограничения. На 32-ух разрядной архитектуре требуется 5 машинных слов на одну ссылку для текущей локальной ноды и 7 слов для удаленной. На 64-ой 4 и 6 слов соответственно. Кроме того ссылка связана с таблицей нод которая также потребляет оперативную память.

    1.9 Булев

    Булев тип (Boolean) является псевдо типом т.к. на самом деле это всего лишь два атома true и false.


    1.10 Объект-функция

    fun
        (Pattern11,...,Pattern1N) [when GuardSeq1] ->
            Body1;
        ...;
        (PatternK1,...,PatternKN) [when GuardSeqK] ->
            BodyK
    end
    

    Объявление функции начинается с ключевого слова fun и завершается ключевым словом end. Набор параметров передается через запятую в круглых скобках, каждый параметр представляет собой шаблон. Если входные параметры совпадают с шаблоном, то выполняется набор инструкций от знака -> и до;. Т.е. по сути входные аргументы выполняют роль входных фильтров. Если не один шаблон не совпал, то генерируется сообщение об ошибке.

    1> Fun = fun({centimetre, X}) -> {meter, X/100} end.
    #Fun<erl_eval.6.13229925>
    2> Fun(10).
    ** exception error: no function clause matching
    erl_eval:'-inside-an-interpreted-fun-'(10)
    3> Fun({centimetre, 10}).
    {meter,0.1}

    Но если определить функцию по другому, то ошибки в строке 2 не возникло бы:

    4> f().
    ok
    5> Fun = fun({centimetre, X}) ->
    5>               {meter, X/100};
    5>           (X) ->
    5>               X/100
    5>       end.
    #Fun<erl_eval.6.13229925>
    6> Fun(10).
    0.1
    

    Поэтому входные аргументы должны быть того же типа, что объявленные в функции. После ключевого слова when и до -> можно включать выражение результатом которого является true либо false. Тело функции выполняется в случае если выражение возвращает true. Если в ходе всех проверок тело функции так и не было выполнено (т.е. функция ни чего не вернула), то генерируется ошибка (строка 12). Переменные внутри функции являются локальными.

    7> F = fun(X) when X<0 ->
    7>          X+1;
    7>        (X) when X>0 ->
    7>          X-1;
    7>        (0) -> 0
    7>     end.
    #Fun<erl_eval.6.13229925>
    8> F(5).
    4
    9> F(-5).
    -4
    10> F(0).
    0
    11> X = fun(Y) when Y>0 -> Y + 1 end.
    #Fun<erl_eval.6.13229925>
    12> X(-5).
    ** exception error: no function clause matching erl_eval:'-inside-an-interpreted-fun-'(-5)
    13> X(5). 
    6
    


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

    14> Adder = fun(X) -> fun(Y) -> X + Y end end.
    #Fun<erl_eval.6.72228031>
    15> Add6 = Adder(6).
    #Fun<erl_eval.6.72228031>
    16> Add6(10).
    16

    Потребление памяти и ограничения. На функцию уходит 9…13 машинных слов + размер окружения. Кроме того функция связана с таблицей функций которая так же занимает память.

    1.11 Идентификатор процесса

    Идентификатор процесса (Pid) возвращают функции spawn/1,2,3,4, spawn_link/1,2,3,4 and spawn_opt/4 при создании Эрланг процесса. Удобным механизмом взаимодействия с процессом может быть обращение к нему по имени, а не через цифровой идентификатор. Поэтому в Эрланге есть возможность связывать с Pid-ом символическое имя и в дальнейшем посылать сообщения процессу используя его имя.

    1> spawn(m, f, []).
    <0.51.0>


    Потребление памяти и ограничения. На данный тип уходит 1 машинное слово для локальных и 5 для удаленных нод. Кроме того функция связана с таблицей нод которая так же занимает память.


    1.12 Идентификатор порта

    Идентификатор порта ( Port Identifier) возвращает функция open_port/2 при создании порта. Порт представляет собой базовый механизм взаимодействия между Эрланг процессами и внешним миром. Он предоставляет байт ориентированный интерфейс связи с внешним программами. Процесс создавший порт называется владелец порта или процессом присоединенным к порту. Все данные проходящие через порт так же проходят и через владельца порта. После завершения процесса сам порт и внешняя программа должны так же завершиться. Внешняя программа должна представлять собой другой процесс операционной системы который принимает и отправляет данные со стандартного входа и выхода. Любой Эрланг процесс может послать данные через порт, но только владелец порта может получить данные через него. Для идентификатора порта, как и для идентификатора процесса, может быть зарегистрировано символьное имя.

    Потребление памяти и ограничения. На данный тип уходит 1 машинное слово для локальных и 5 для удаленных нод. Кроме того функция связана с таблицей нод и таблицей портов которые так же занимает память.


    P.S. На этом первую часть с описанием базовых типов я и завершаю. До того момента пока я не взялся за продолжение крайне рекомендую, для тех, кто еще не успел, ознакомиться с „Начала работы с Erlang“ которая является переводом одной из глав документации „Getting Started With Erlang
    Поделиться публикацией

    Комментарии 69

      +1
      Здорово. Плюсанул, можете перенести в блог «Erlang/OTP».
        0
        Очень интересно, но многое непонятно.
        Как быть с константами? Или объявлять глобальную переменную? Можно ли получить к ней доступ или объявить ее из функции?
          +1
          Через макросы (а-ля #define в си).
            0
            в качестве констант в Эрланге активно используются атомы

            если же нужно именно глобально доступное именованое значение или макрос тогда можно использовать директиву препроцессора -define(name, substitution).

            глобальных переменных не существует, так как не существует переменных вообще.

            в последней версии Эрланга появилась недокументированная возможность создавать независимые екземпляры модулей с глобально доступными в каждой функции переменными, значения которых задаются при инициализации, детально почитать можно здесь: www.erlang.se/workshop/2003/paper/p29-carlsson.pdf
              0
              А можно ли атомы воспринимать как константы в обычном их понимании? Я думал насчет этого, и не кажется, что нет. С атомом вообще не связанно какое- либо значение кроме его литерального представления. Вернуть число атом не может.
                0
                вернуть число не может, но ведь не всегда нужны константы со значением? то есть почти всегда имя константы несет в себе смысл кода который вы пишете, значение же неважно и часто его и не знают. пример — перечисляемые типы данных.
                  0
                  Да. Но. К примеру, в константу можно загнать числовое значение которое использовать в математических выражениях. К примеру величину какого либо дискретного шага при вычислении функции (в математическом понимании этого слова).
              0
              В эрланге нет констант подобных константам в других языках. Я бы даже сказал, что по сути переменные как раз и являются константами для текущей зоны видимости, т.к. значение им может быть присвоено только один раз.

              Я вообще не уверен, что для эрланга можно говорить о глобальной зоне видимости переменных. Работа с данными идет за счет того, что для обработки происходит рекурсивный вызов какой либо функции и в ходе каждой итерации мы получаем новую зону видимости и в ней уже новую переменную значение которой для текущей итерации изменено быть не может.
                0
                Тут чисто практический момент: например мне надо где-то указать адрес сервера, куда надо подключаться. Удобнее всего это вынести в некоторую переменную/константу в глобальной области видимости, чтобы везде где надо легко ее использовать.
                Как это сделать на Эрланге?
                  +2
                  Также как и в си, делаем заголовочный файл, а потом его включаем.

                  в server_info.hrl:

                  -define(SERVER_PORT, 8080).

                  в server_connect.erl:

                  -include(«server_info.hrl»).
                    0
                    Ага, спасибо!

                    Еще чайниковый вопрос: чем отличается файл .erl от .hrl?
                    По какой модели работает include: как в PHP — то есть вложение файла в точку вызова, или как в Python — код подклчается как объект со своим пространством имен? То есть есть ли в Эрланге пространства имен и как с ними работать? Можно модули друг в друга вкладывать?
                    Вызывать примерно так:
                    package:module:submodule:method()?
                      0
                      Вкладывать модули друг в друга нельзя, а -include работает так же как в си (и наверное пхп) — то есть эквивалентно тупой вставке текста из .hrl

                      Соответственно, .hrl это текстовый файл просто, он никак компилятором специально не обрабатывается, а в .erl всегда описание модуля.
                    0
                    Так Irr выше уже писал. Через макросы.
                      0
                      В смысле, что выше с самого начала (3 пост), поэтому я и не стал про это упоминать.
                      0
                      Есть еще файл приложения .app, там можно записывать всякие конфигурационные параметры, которые потом можно узнавать из кода программы в процессе выполнения. То есть получается некий аналог ini файла. Преимущество с макросами — не требует перекомпиляции кода.
                        0
                        Не совсем понял на счет не требует перекомпиляции — т.е. в работающем приложении достаточно в .hrl файле подредактировать макрос и новое значение автоматом подхватится?
                          0
                          .hrl файл требует перекомпиляции, а я пишу про .app файл. Я не точно сформулировал, правильнее так: «Преимущества .app файла по сравнению с макросами — не требует перекомпиляции код».
                  0
                  Спасибо. Никак руки не доходят прочитать официальные мануалы по ерлангу. А так по кусочкам глядишь и познаю.
                    0
                    Я боюсь, что для тех, кто об Эрланге ни чего не знает данная статья не лучший материал. Она обзорная по сути, но делает не обзор возможностей языка. Нет ни примеров программ, ни описания количества вычислительных ресурсов необходимых программе, т.е. нет тех вещей который абсолютному новичку могли бы показать, что написание программы на эрланге может дать какие либо ему преимущества (большая скорость разработки, меньшие аппаратные требования, более высокая стабильность, прочее) по сравнению с тем языком, который он уже знает.

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

                    Поэтому абсолютному новичку видимо стоит все же начать с «Начала работы с Erlang» ссылка на которую приведена в конце (может конечно её стоило поместить в начало, но я как то по привычке ссылку на связанные ресурсы решил дать в конце), тем более, что и сама документация начинается по сути именно с этого материала.
                      0
                      У меня как раз ситуация из 2го абзаца. Я уже знаком с ерлангом и даже модифицировал кое-какие модули ejabberdа. Но при этом нормальной программы с нуля написать не смогу, потому что не изучил основ, подобных описанным в статье. Поэтому для меня она и полезна :)
                    0
                    Запись на этапе компиляции преобразуется в кортеж, поэтому использовать записи напрямую в шелле невозможно.

                    Тут бы лучше про функцию rr() рассказать, потому что описания рекордов чаще всего выносятся в отдельный файл, и из шелла rr() самое то, чтобы их использовать.
                      0
                      Возможно. Но цель описать типы и методы их создания, не описание функций шелла. Поэтому вопрос использования функции вторичен, имхо. Главное, что все примеры можно набрать в шелле и поэкспериментировать с ними без каких либо дополнительных телодвижений.

                      Да и к тому же я не так много работал с BIF-ами, что бы задумываться о таких вещах :D
                      0
                      Функции же просто можно писать:

                      Func(Pattern) when Guard ->
                      ....;
                      Func(Pattern2) ->
                      ....;
                      Func(PatternN) ->
                      ….
                        0
                        Это не функция. Это функция-объект, такой тип элементарных данных. Напоминает Javascript.

                        Если честно, я не знаю, почему создатели языка выделили такой тип, но по всей видимости были причины иметь среди базовых типов и такой.
                          0
                          я понял. просто анонимная функция, и как в других функциональных языках её можно присвоить переменной :)
                            0
                            Относительно её я не стал использовать термин «анонимная функция» именно с тем, что бы не путать лишний раз. Сказали создатели языка, что это отдельный тип, значит так оно и есть :D
                              0
                              Обычные функции можно тоже получить в виде хранимого значения, не только анонимные:

                              > F = fun lists:map/2.
                              #Fun<lists.map.2>
                                0
                                Разве? Мне кажется это пример анонимной функции в которую вложена обычная.
                                  +1
                                  Тогда между угловыми скобками были бы всякие страшные числа, как в примере в статье. Сравните с fun(F, L) -> lists:map(F, L) end.
                        +1
                        Очень хочется увидеть типичную задачу для ерланга и ее реализацию. Что-то вроде простенького работающего кода, делающего что-то полезное. Иначе не ясно для чего весь этот огород.
                          0
                          Да, именно это и планируется. Собственно сама идея статьи с этого и начиналась. Точнее с вопроса о работе с бинарными данными. И это будет в завершающей, третьей части. Я так решил сделать что бы понятно, что происходит в каждой строке кода и почему. К примеру смотря простые примеры программ на Эрланге многие думаю, что "=" оператор присвоения. Перменная Х равна 5, к примеру. Хотя на самом деле это оператор сопоставления. И разница между ними даже в самом методе использования.

                          А пока этого нет, можно для примера прочесть «Начала работы с Erlang».
                            0
                            А можно чуть подробнее про работу со строками и бинарными данными. Я смотрел: в некоторых проектах используются бинарные строки как обчные для хранения текста.
                            КРоме того, вы пишете, что строк как таковых нет. Это, честно, сильно напрягло. Например, мне надо сделать строку с русским текстом, В кодировке UTF-8 это будет 2 байта на один символ. И тут может возникнуть проблема. Только что опробовано в консоли:

                            (erl@laptop)3> <<"ABC">>.
                            <<"ABC">>
                            (erl@laptop)4>
                            <<"АБВ">>.
                            <<208,144,208,145,208,146>>

                            Латинская строка обработалась нормально, а наша разбилась на отдельные байты. Причем даже задать длину данных нельзя — т.к. в UTF-8 разные символы имеют разную длину.
                            ТАк же непонятно как оперировать со строками: разбить/склеить, заменить слова и т.п. Ведь если нет строк, то нет и операций с ними?

                            Спасибо за объяснение.
                              0
                              Строка это последовательность байт.

                              С utf8 вы огребаете ровно тоже, что и в си. Влина строке не равна колличеству байт в ней.

                              В чем разница/проблема?
                                0
                                Наверное, я избалован Питоном, где есть специальный объект unicode для таких строк.
                                Поскольку он часто используется в пмоих прооектах, то я хочу понять как здесь его заменить.
                                  0
                                  Нет, в эрланге нету специального объекта для юникода, и это иногда не очень удобно (по сравнению с питоном). Эрланг вообще очень минималистичный такой язык, авторы его специально делали попроще, чтоб порог вхождения снизить как можно сильнее.

                                  По мне, кстати, идеально было бы иметь удобный способ интеграции его с питоном (все хочу сам как-нибудь написать, но времени не хватает) — и на эрланге делать всю распределенную/управляющую часть, а на питоне всякую сложную обработку данных, куча полезных питонских библиотек очень пригодилась бы тут.
                                    0
                                    Я бы сказал, что отсутствие поддержки юникода не упрощает, а наоборот усложняет язык. Некоторые проекты на Эрланге работают в юникоде — значит эту проблему пришлось решать «вручную».
                                    Если бы можно было подключать питоновские либы, это был бы большой шаг вперед.
                                +2
                                В любом языке строки это не более, чем двоичные последовательности. И то, что мы видим символы это заслуга шела/терминала другого ПО визуального отображения. Просто все эти преобразования давно уже написано до нас и находяться глубоко в недрах системы.

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

                                В Эрланге этого еще нет. Нет к примеру встроеных библиотек для работы с юникодом. И я сильно подозреваю, что написано это если и будет, то очень не скоро, т.к. виртуальная машина вполне хорошо может взаимодейтствовать с внешним ПО и в этом контексте гораздо разумнее воспользовать уже готовым, проверенным решением на том же С, чем городить новый огород который первые несколько лет будет бальшим багодромом.

                                Насчет разной длины это да, но ведь у UTF-8 есть одна очень удобная особенность. В первом байте в двух старших разрядах в нем установлены единицы (т.е. первый байт имеет вид 11xxxxxx), а все последующие байты в старших разрядах имеют единицу и ноль (т.е. 10xxxxxx). Поскольку реально используется только 4 байта, а так же в начале файла можеть быть еще 3 байта BOM-а (EF BB BF), то нужно всего лишь перед работой прочесть первых 7 байт и по их структуре будет видна размерной символа в байтах для данной бинарной строки.

                                В VM идет работа только в однойбайтовой кодировкой. Мне не очень понятно, как у тебя получилось получить данные пример. У меня получается как и положено 3 байта:
                                1> B = <<"АБВ">>,
                                1> bit_size(B).
                                24
                                  0
                                  Юникод кстати обещали в следующем релизе (R12B-6 что ли). Ну то есть не то чтобы все будет сразу шоколадно, с библиотеками и всеми остальными делами, но будет, как минимум, унифицированный способ представления уникодных строк (UTF-32 как список, UTF-8 как binary).
                                    0
                                    Да, приходилось об этом уже слышать.

                                    А почему UTF-32 как список? Что-то не уловил %(
                                      +1
                                      Ну со строкой как списком вообще работать довольно удобно в функциональном стиле, в плане маттерн матчинга и прочих штук, и поскольку числа в эрланге неограниченной разрядности, UTF-32 пихаем в список и нет проблем.

                                      А UTF-8 однозначно в виде списка трудно представить (если побайтно то вся прелесть теряется), поэтому его лучше как binary держать, плюс вроде предлагалось добавить поддержку в binary паттернах юникодных строк (типа, <<u«Превед»>>).
                                    0
                                    Понятно, что внутри нет никаких строк, даже чисел и то нет — есть только последовательности байтов. Вопрос только в том, насколько удобно работать с этими данными. В конечном-то счете есть только один язык программирования — машинный код, а все остальные языки придуманы, чтобы упростить работу программисту, избавить его от совсем тупой рутины типа работы с регистрами и подсчета адресов условных и безусловных переходов. Языки отличаются только тем, насколько они упрощают эту задачу и помогают сократить рутину. А заодно абстрагироваться от конкретной железяки. В этом плане сильная привязка к железу меня смутила.
                                    А здесь получается так: если нет стандартной библиотеки, то придется писать самому — тут-то и выйдет багодром. Когда сотня человек будет писать одно и тоже в своих проектах. Гораздо удобнее, когда есть готовая протестированная либа.

                                    Не очень понял насчет использования готовых либ из С? Их можно как-то подключать?

                                    У меня стабильно выходит 6 байт:
                                    (erl@laptop)6> B = <<"АБВ">>.
                                    <<208,144,208,145,208,146>>
                                    (erl@laptop)7> bit_size(B).
                                    48


                                    Проверено на Erl-5.6.3 на Ubuntu 8.10 и на Erl-5.6.5 на WinXP. Я думал это из-за юникодовой локали, но на винде тот же эффект. Может какие-то настройки неверные?
                                      0
                                      Можно, но я лично не подключал, пока такой надобности еще не возникало. vostryakov может прокоментирует, он точно подключал как минимум либу для обработки UTF, и на сколько я даже помню где-то в каментах обещал рассмотреть этот вопрос.

                                      Насчет 6 байт прямо даже интересно… у меня на WinXP Pro&SP2 Ru стоит VM 5.6.5 и я вижу положенных 3 байта. Все же наверное что-то в система так настроено, ведь поддежки UTF-а я VM на данный момент нет еще точно.

                                      А у кого еще 6 байт выходит?
                                        0
                                        Дык там не надо со стороны VM, достаточно чтоб парсер у компилятора это умел. У меня на убунте тоже 6 :)
                                          0
                                          А они у вас в консоли нормально вводятся?
                                          У меня как-то слишком весело... :)
                                            0
                                            У меня так же :)
                                              0
                                              То есть консоль это сразу преобразует в байтовую последовательность?
                                              Только я не могу понять, что такое \320?

                                              О! Да это вообще восьмеричная последовательность!
                                              В utf-8 русская А кодируется как d0 90 — что как раз и будет 0320 0220.
                                                0
                                                Ну да, консоль там юникод не умеет :(
                                          0
                                          А вы как ставили эрланг?
                                          Может причина в ИДЕ — я запускаю и играюсь в Erlide — видимо она и подменяет настройки.
                                          А в консоли на убунту совсем весело:

                                          Erlang (BEAM) emulator version 5.6.3 [source] [smp:2] [async-threads:0] [kernel-poll:false]

                                          Eshell V5.6.3 (abort with ^G)
                                          1> A = <<"\320\220\320\221\320\222">>.
                                          <<208,144,208,145,208,146>>


                                          Обратите внимание — что появилось при наборе. На клавиатуре набирал русские АБВ!
                                          Локаль в полном порядке — и шелл, и все остальные программы (включая интрпретатор питона нормально работают с русскими буквами).
                                            0
                                            Только что написал небольшой модулек, проверить как там будет с русским:
                                            $ cat a.erl
                                            -module(a).

                                            -export([hello/0]).

                                            hello() ->
                                            A = "Test по-русски",
                                            io:format("~p ~n", [A]).


                                            Запускаем:

                                            $ erl
                                            Erlang (BEAM) emulator version 5.6.3 [source] [smp:2] [async-threads:0] [kernel-poll:false]

                                            Eshell V5.6.3 (abort with ^G)
                                            1> c(a).
                                            {ok,a}
                                            2> a:hello().
                                            [84,101,115,116,32,208,191,208,190,45,209,128,209,131,209,
                                            129,209,129,208,186,208,184]
                                            ok
                                            3>


                                            P.S. А куда делся пробел и ~n?
                                              0
                                              Пробел и ~n никуда не делись, вон они :)

                                              io:format это аналог print, а если нужен sprintf то надо использовать io_lib:format().
                                                0
                                                В конце массива должно быть 32, 10, а может даже и 13, а он заканчивается 208,184 — русской буквой В.
                                                  0
                                                  Ну, правильно, в питоне этот код эквивалентен
                                                  print "%s \n" % (repr(A)),

                                                  Т.е. массив этот — это строковое представление A, а пробел и \n идут как есть в stdout.
                                                    0
                                                    Теперь понял — я думал, что это на этапе вывода строка преобразуется в список чисел, и недоумевал почему внутренний пробел сконвертировался, в конечный нет. Если преобразование происходит чуть раньше на стадии форматирования строки, то все верно — именно такой результат и должен быть.
                                                    Спасибо!
                                                    Меня сбило с толку указание во многих мануалах, что интерпретатор достаточно «интеллектуальный» в части вывода строк на печать :)
                                          0
                                          Не все так ужасно, на самом деле, есть сторонние библиотеки для юникода, и даже в стандартных библиотеках можно найти функции преобразующие utf-8 binary в список unicode code points, дальше с ним можно нормально работать уже как с обычным списком. Особенных велосипедов не требуется изобретать. Хотя, конечно, хотелось бы чуть более серьезную поддержку юникода «из коробки», и вроде как это скоро обещают.

                                          Сишные либы, конечно, можно прикрутить, правда пока нету для этого таких удобных инструментов, как для питона.
                                            0
                                            Что такое «unicode code points»?
                                            Само собой встроенная поддержка была бы очень к месту.
                                              0
                                              Ну у каждого символа в юникоде есть номер, логически, без ограничения на разрядность, он называется code point.

                                              А для того чтобы эти номера хранить в памяти, они каким-то образом кодируются (UTF-8, UTF-16, UTF-32..) в бинарную форму. В UTF-32 вот тупо номер code point'а записывается как 32-битный unsigned int, например.
                                                0
                                                Чуть ниже (в ответе irr) я пояснил несколько моментов про строки в Эрланге и UTF.
                                              0
                                              Давайте я попорядку проясню вопрос с UTF и простыми строками в Эрланге:
                                              1. Строк, как отдельного типа данных нету. В Эрланге — это список. Но! Есть бибилиотека для работы со строками: string, которая содержит все необходимые функции для работы со строками. Но все это работает только с английскими символами.
                                              2. Преобразование в UTF-8, UTF-16, UTF-32 и обратно в обычный ASCII в Эрланге уже есть, но нет библиотеки для работы с полученной строкой UTF
                                              3. Поддержку UTF обещают в следующей версии R13B которая выходит в марте. То есть уже вот-вот и по моему сделать им это будет не сложно.
                                              4. Пока мы используем библиотеку starling для работы с UTF, которая написана на C, но имеет интерфейс для прямого вызова из Эрланговских программ. Это не чудо, Эрланг позволяет вызывать код, написанный на другом языке, при написании специального драйвера связывающего программу на Эрланге и другом языке.
                                                0
                                                Не, на самом деле там есть функции в xmerl (только они в документации не описаны ;) которые utf-8/16/32 что угодно парсят в список юникодных символов и обратно, и дальше с ним можно работать как с обычными строками.
                                                  0
                                                  Т.е. «как с обычными списками».
                                                    0
                                                    Да, о чем я и пишу в пункте 2. Только вот string:to_upper() над ними выполнять смысла большого нет. То есть не получиться с ними работать, как с обычными ASCII строками.
                                                    0
                                                    Спасибо за подробный ответ!
                                                    Можно несколько вопросов?
                                                    1. Не возникает ли из-за этого дикий оверхед в части хранения и обработки таких строк? Я правильно понимаю, что список — это набор пар {значение, указатель на следующий элемент}?
                                                    4. Есть ли какие-то проблемы с этой библиотекой? Хватает ли функционала?
                                                      0
                                                      Я отвечу на вопрос 1: возникает, это 8 байт на символ, поэтому очень большие строки лучше как binary держать (но с другой стороны, с большими строками редко надо производить какие-то нетривиальные строковые операции).

                                                      Ко второму вопросу присоединяюсь :)
                                                        0
                                                        Второй это который? Если о списке, то конечно же да. В VM он же является банальным однонаправленым связанным линейным списком со всеми вытекающими отсюда.
                                                        0
                                                        Функционала нам лично хватает. Найдите в гугле описание этой библиотеки по словам Starling + Erlang. В общем там по моему все обычные функции для работы со строками. Что собственно с ней плохо — это скорость работы. За счет постоянных вызовом этой сторонней библиотеки, скорость достаточно мала на мой взгляд. Я не помню точных цифр, но примерно не более 100 тыс. операции upper_case в секунду. Поэтому и жду поддержку unicode в самом Эрланге, надеюсь на сильное повышение скорости работы :)
                                          0
                                          > то на символ уходит 8 или 16 байт (2 машинных слова)

                                          может 8 или 16 бит?
                                            0
                                            Нет, байт.

                                            >String (is the same as a list of integers): 2 words per character

                                            На 32-ой архитектуре машинное слово равно 4 байтам, значит 2 машинных слова будет 8 байт.

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

                                          Самое читаемое