`

СПЕЦІАЛЬНІ
ПАРТНЕРИ
ПРОЕКТУ

Чи використовує ваша компанія ChatGPT в роботі?

BEST CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Erlang. Знакомство не по правилам

Статья опубликована в №24 (690) от 7 июля

+33
голоса

Как, опять? Еще один язык программирования? Летом? В тридцатиградусную жару? Опять «hello, world»? Спокойствие, только спокойствие. Ничего этого не будет. Все будет совершенно неправильно и не так, как обычно. А уж что с этим делать дальше – решать не автору.

Почему все-таки Erlang? Датчанин Агнер Краруп Эрланг навсегда вошел в историю как создатель нового раздела прикладной математики – теории массового обслуживания. Его работа 1909 г. «Теория вероятностей и телефонные переговоры» стала фундаментом последующих исследований, во многом благодаря которым мы сейчас пользуемся чуть ли не всеми доступными обыденными вещами (массовое конвейерное производство и логистика – это основы снижения цены промышленных изделий, и процессы функционирования этих двух столпов общества потребления изучаются, моделируются и оптимизируются в первую очередь с помощью математического аппарата теории массового обслуживания). Эрланг изучал поведение телефонной системы «операторы – абоненты», а именно – как изменяется интенсивность запросов на соединение в течение определенного интервала времени и какова вероятная продолжительность ожидания обслуживания группами абонентов.

Давайте попробуем представить себе, о какой именно системе идет речь. Итак, есть абоненты, стремящиеся общаться с другими абонентами, и есть коммутатор, где желания абонентов исполняются (это же 1909 г., времена телефонных барышень на коммутаторах, в статье Эрланга, кстати, «оператор» используется исключительно в женском роде). Абонент поднимает трубку телефона, тем самым запрашивая коммутатор, и ожидает освобождения оператора от обслуживания других абонентов. Эта простая схема что-то очень сильно напоминает, не правда ли? Много позже, в 1971 г., Эдсгер Дейкстра описал решение задачи о пяти обедающих философах, в котором использовал механизм параллелизма на основе разделяемой информации о состоянии, или «семафоры». В какой-то степени можно считать, что в системе «абонент – оператор коммутатора» семафор Дейкстры – это как раз и есть «телефонная барышня» времен Эрланга, обеспечивающая поочередный доступ к критическому ресурсу – коммутатору: один из абонентов (процессов) устанавливает семафор в состояние «занято» (в это время оператор обслуживает абонента), остальные абоненты ждут его освобождения. В 1909 г., кстати, и тот абонент, которому повезло и которого в данный момент обслуживает оператор, до наступления соединения ничего особенно полезного, кроме ожидания, делать не мог. Да и ожидающие освобождения «семафора», если нуждались в ресурсе коммутатора, должны были заниматься одним единственным делом – пытаться дозвониться до оператора. Иными словами – все абоненты-процессы блокировались. Поведение примерно такой системы и изучал Эрланг. Создатели языка Erlang именно от этого всего... отказались. В Erlang нет никакой разделяемой информации о состояниях, никакой разделяемой памяти, ничего подобного вообще.

К слову, и самих изменяемых состояний, для которых в императивных языках используются описанные переменными ячейки памяти, допускающие операции типа X = X + 1, в Erlang тоже нет. Переменные Erlang – это, если так можно сказать, допускающие раздельные необязательное объявление и инициализацию константы, значения которых после инициализации изменять невозможно. Стоит обратить внимание на слово «необязательное» – оно здесь не для красного словца. Обычно возможность использовать имена переменных без предварительного объявления их типов свойственна языкам с динамической типизацией. И Erlang – не исключение из этого правила.

Следующее, чего нет в Erlang, – операции присваивания. Ее не существует как таковой. И если даже вы впервые видите в Erlang-коде что-то очень знакомое, вроде

Pi = 3.1415926

не верьте своим глазам. И пока просто запомните – это вовсе не присваивание. Хоть бы результатом этого чего-то в данном случае будет инициализированная значением 3.1415926 неизменяемая «переменная» с именем Pi.

Еще в Erlang нет классов. Потому что не может быть. Хотя бы потому, что один из старожилов и ведущих архитекторов системы Джо Армстронг является автором весьма известного короткого текста «Почему ООП – полный отстой» (Why OO Sucks). Этот текст не во всем бесспорный, но весьма интересный. Особенно же он интересен, если принять во внимание миграцию параллельных вычислений в массовые бытовые приложения. «Выбранный создателями ООП принцип прятать изменяемые состояния [переменных класса. – Прим. автора статьи] от программиста – худший из всех возможных. Вместо того чтобы показать, что в действительности есть состояние [в параллельных программах], вместо поиска способов предотвращения негативных последствий многочисленных нюансов состояний в ООП просто решили все это спрятать» – эти слова Армстронга звучат сегодня на удивление актуально.

Развитая тщательно проработанная система типов – еще одно, чего нет в Erlang. По мнению создателей языка, система типов замечательна тем, что более или менее работоспособную версию можно «выдать на-гора» за пару месяцев, а вот на доведение ее до ума придется затратить многие годы. В это время, по сути, пользователи инструмента будут вынуждены постоянно адаптироваться к изменениям в нем, и, вполне возможно, – к существенным. А в мире не так уж много людей, которым нравится использовать отвертки с постоянно изменяющимися рукоятками и жалами.

Ну и само собой, что не менее заметно отсутствует в Erlang, – документация отдельных подсистем. В тематических форумах и группах новостей на вопросы вроде «Где найти документацию на виртуальную машину?», похоже, принято отвечать с лаконичной брутальностью Капитана Очевидность: «Ее нет». Это, к слову, далеко не так мило, как может показаться. Да, исходные тексты системы доступны, но в кризисном 2009 г. затраты времени на реинжиниринг недокументированных исходных текстов могут показаться неоправданными несколько большему количеству разработчиков, чем буквально пару лет назад.

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

Глубоко вдаваться в ее историю мы не будем (любой желающий может найти и прочесть статью Джо Армстронга «A History of Erlang»), ограничимся перечислением самых важных фактов. Эволюция системы началась во второй половине 80-х годов прошлого столетия, примерно на пять лет раньше, чем стартовал проект Java. В силу специфики – Erlang разрабатывался в лабораториях знаменитого шведского производителя телекоммуникационного оборудования Ericsson и использовался (продолжает использоваться) в реальном промышленном оборудовании – в перечень критериев, которым должна соответствовать система, было заложено одно фундаментальное требование, остающееся главным по сей день. А именно, «Erlang должен облегчать и улучшать разработку специфических для телефонии приложений – распределенных устойчивых к сбоям сильно распараллеленных с сотнями тысяч процессов». В результате эволюции система Erlang сегодняшнего дня представляет собой не что иное, как виртуализированную интегрированную среду разработки, исполнения и сопровождения именно таких приложений. Используя это определение как руководство к действию, попробуем разобрать Erlang «на детали».

Основа системы – весьма специфическая виртуальная машина nBEAM и компилятор ее команд в реальные «родные» системы команд различных целевых платформ. nBEAM (new Bogdan's Erlang Abstract Machine, новая абстрактная машина Erlang Богдана [Хаусмана, Hausman]) – продукт длительной эволюции идей, заложенных в оригинальную виртуальную машину почти забытой сегодня системы программирования Prolog (для щепетильных читателей – абстрактная машина Уорррена, Warren's Abstract Machine, давно нигде не используется, но пару документов, в том числе сканированный оригинал описания ее системы команд самим Уорреном, еще можно отыскать). Уже во второй половине 90-х годов учеными лабораторий Ericsson были проведены исследования масштабируемости виртуальной машины (VM) Erlang при исполнении компьютерами SMP-архитектуры. В результате дизайн VM был существенно изменен, показатели системы значительно возросли, и все это оказалось... невостребованным на чуть ли не 10 лет – в те годы SMP-машины были дороги, и в силу изначальной приспособленности Erlang к работе в распределенных системах было проще и выгоднее добавить пару «ящиков», чем заниматься глубинными модификациями критического программного продукта.

Сегодня nBEAM – весьма интересная сложная, плохо документированная и фактически от релиза к релизу изменяющаяся VM, в каких-то деталях архитектурных решений похожая, например, на VM системы Tcl/Tk. При исполнении вычислителями SMP-архитектуры nBEAM прозрачно для программиста разделяется на потоки исполнения на каждом процессоре, при этом каждому потоку «раздаются» отдельные множества команд VM (почти то же самое делает и VM Tcl, разве что для каждого процессора она создает не поток, а процесс-копию себя). В сочетании с адекватными механизмами выделения памяти и развитыми средствами управления такой подход позволяет добиться впечатляющих возможностей масштабирования. Главная абстракция, которой VM Erlang снабжает программиста, – сверхлегкий процесс. Раз «процесс» – значит, речь идет об уникальном собственном адресном пространстве (не следует забывать, что мы говорим не в терминах аппаратно-машинных команд и адресов памяти, а в терминах VM). А раз уникальное собственное адресное пространство, то и о механизме взаимодействия между процессами. Естественно, раз адресные пространства изолированы, то, например, предоставить одному процессу возможность доступа к данным другого передачей указателя или ссылки невозможно. Следовательно, надо прибегнуть к полноценному механизму передачи сообщений, для которого снабдить каждый процесс своим «почтовым ящиком». Механизм передачи сообщений в Erlang – полностью асинхронный. В этот момент уже пора сказать «ага!» – VM Erlang на самом деле является чуть ли не полноценной... виртуальной операционной системой. Больше того, ее API имеет удивительный «ОС-подобный характер» (ужасное сравнение, но что поделать). Например, для порождения нового процесса используется следующий очевидный вызов:

Идент_процесса = spawn(Имя_модуля, Имя_функции, Аргументы_функции)

Для передачи сообщения процессу применяется также заставляющий вспомнить командные оболочки этакий «банг-синтаксис»:

Идент_процесса ! Сообщение

Пугаться затрат ресурсов на ее «процессоориентированный» дизайн не следует – процессы Erlang легковеснее «родных» для ОС потоков.

Для высокоуровневого программирования виртуальной ОС Erlang используются трансляторы языков. И здесь уже, как и в мирах других основанных на VM систем программирования, есть выбор – основной язык, также именуемый Erlang, не одинок. Впрочем, язык Erlang был, есть и, похоже, будет основным языком для виртуальной ОС Erlang (хотя бы из-за специфики подхода к документированию проекта – большинство разработчиков «спотыкаются» просто об отсутствие элементарной документации на систему команд VM). О языке Erlang много говорить нет нужды – он на удивление прост. Кое-что мы уже знаем (например, о переменных, которые на самом деле вовсе не переменные, и о динамической типизации). Фундаментальный языковый механизм Erlang – сопоставление с шаблоном. Он используется для реализации возможностей инициализации переменных значением, управления потоком исполнения программы, выделения значений из композитных структур данных, как фундаментальная основа полиморфизма и для селекции сообщений при межпроцессном обмене. Общий синтаксис операций сопоставления с шаблоном способен ввести в заблуждение привыкших к императивным языкам программистов (именно поэтому в начале статьи читатель был об этом предупрежден):

шаблон = выражение

Как это работает? Сначала система вычисляет выражение, затем сравнивает его с шаблоном. Если результат вычисления выражения совпадает по типу с типом шаблона, а в шаблоне присутствуют неинициализированные переменные (в терминах Erlang их принято называть «несвязанными»), они инициализируются таким образом, чтобы значения шаблона и результата вычисления выражения совпали. Если это невозможно, или не выполняется условие совпадения типов, операция «не срабатывает». Таким образом, если переменные I и L никогда раньше не использовались в программе, то следующие операции приведения к шаблону будут успешно выполнены:

I = 3.1415926
L = [0, 1, 2, 3]

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

[First | Other] = L

Шаблон здесь – синтаксически легальный конструктор списка из первого элемента (Fisrt) и списка остальных элементов (Other). В данном случае типы шаблона и выражения совпадают (два списка), и для равенства значений этих списков системе необходимо инициализировать переменную First значением 0, а переменную Other – [1, 2, 3]. Список, сконструированный из этих значений, будет в точности совпадать со списком, заданным выражением. Из этого примера хорошо видно, что мы на самом деле не описывали конкретного способа инициализации переменных First и Other (на императивном языке программирования нам бы пришлось именно расписать – что, как и чему присваивать), а просто потребовали от системы конечного результата. Такой подход называется декларативным программированием, и Erlang – один из класса декларативных языков.

Типов данных в Erlang сравнительно немного – из композитных только кортежи и списки, отдельного строкового типа фактически не существует (что не мешает успешно применять Erlang при создании крупных веб-проектов, работающих со строковыми данными). Освобождение выделенной памяти – автоматическое, посредством встроенного сборщика мусора. В остальном же – предельно простой функциональный язык программирования, совершенно без «излишеств» (например, монады в Erlang просто не нужны), но и во многом без синтаксического изящества, которого добивается в своем коде профессиональный Haskell-программист.

Важнейшая часть Erlang-системы – инфраструктура разработки приложений, Erlang OTP. Естественно, это не уникальная подсистема, большинство современных разработок обладают ее аналогами (например, Ruby on Rails). Правда, в отличие от аналогов, OTP предоставляет разработчику средства создания надежных, устойчивых к сбоям приложений. Впрочем, это специфический раздел «высшего пилотажа» в Erlang, и на ознакомительном уровне приведенных сведений более чем достаточно.

Для завершения этого краткого обзора осталось сказать совсем немногое. Erlang – разработка уникальная. Пусть со своими особенностями, пусть у нее есть набирающие силу конкуренты (например, Scala на платформе JVM), пока уступающие в каких-то элементах дизайна, но быстро наверстывающие упущенное, – Erlang заслуживает внимания и изучения. Хотя бы потому, что система очень интересна.

Ready, set, buy! Посібник для початківців - як придбати Copilot для Microsoft 365

+33
голоса

Напечатать Отправить другу

Читайте также

 

Ukraine

 

  •  Home  •  Ринок  •  IТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Мережі  •  Безпека  •  Наука  •  IoT