Limbo

Поскольку меня Inferno привлекает именно как среда разработки, то помимо
архитектуры самой системы немалое значение имеет язык программирования.
По большому счёту мне давным давно пофиг, на каком языке писать
(я программирую с 1989 года, и за это время перепробовал кучу языков).
Но… всё таки на одних языках работать приятнее, чем на других — и здесь
дело не в том, что одни языки лучше других, а в том, что для разных стилей
мышления лучше подходят разные языки.
Переход от Perl к Limbo — очень контрастный. Языки совершенно разные:
Perl — не типизированный вообще, Limbo — сильно типизированный; в Perl нет
нормальной поддержки нитей и асинхронности приходится добиваться через
мультиплексирование, Limbo — чуть ли не вынуждает писать именно
многопоточные программы (если вы смотрели презентацию Роба Пайка, то там
был прикольный пример с многопоточным поиском простых чисел); etc.
И, тем не менее, Limbo мне очень понравился и писать работающий код я на
нём начал практически сразу.
Я уже не очень хорошо помню C, но попробую описать Limbo именно в плане
отличий от C — думаю, так будет проще для большей части аудитории (и ни
слова про PHP! :)).

Общая информация.


Про такие особенности Limbo как схожесть синтаксиса с C, высокая
портабельность байт-кода, заточенность под параллельное программирование,
динамическая подгрузка/выгрузка модулей, проверку типов и границ массивов
в том числе и в процессе выполнения и наличие сборщика мусора я уже
упоминал.
Ещё можно добавить, что для Limbo написано значительное кол-во
разнообразных библиотек (идут в комплекте с Inferno), облегчающих работу с
графикой, математикой, базами данных, etc.
Для понимания примеров стоит добавить, что объявление типа переменной
делается в паскалевском стиле:
  • ":" — объявление
  • "=" — присваивание
  • ":=" — объявление с одновременным присваиванием, тип определяется по типу
    присваиваемого объекта


Типы данных.


Помимо обычных числовых типов, структур и union, Limbo поддерживает строки
и несколько более специфических типов данных: списки, массивы, tuples и
каналы. (Ещё есть специальный тип «модуль», я его упоминал ранее когда
описывал интерфейсы, но с точки зрения особенностей языка он интереса не
представляет.) Все эти типы данных это first-class variables, т.е. их можно
сохранять в переменных, передавать через каналы, etc.
Обычные числовые типы можно преобразовывать друг в друга, кроме того
строки тоже можно преобразовывать в числа и наоборот. Но все
преобразования должны указываться явно, неявных преобразований типов нет.

Строки.


Строки можно преобразовывать в массивы байт, и наоборот.
Кроме этого строки поддерживают срезы, т.е. можно обратиться к конкретному
символу или последовательности символов, например: my_string[5:15].

Списки.


list это последовательность элементов одного типа оптимизированная для
стеко-подобных операций (добавить элемент в начало списка, получить первый
элемент списка, получить остаток списка (кроме первого элемента)).
Для работы со списками есть три оператора:
  • "::" (создание нового списка,
    левый операнд это один элемент, правый это список элементов того же типа)
  • «hd» (возвращает первый элемент списка не меняя сам список)
  • «tl» (возвращает список состоящий из второго и последующих элементов заданного
    списка — т.е. «выкусывает» первый элемент)

Пример:
l : list of int;
l   = 10 :: 20 :: 30 :: nil; # создаём список из 3-х элементов
l   = 5 :: l;                # добавляем в начало ещё один
i  := hd l;                  # получаем int равный 5, список не изменился
l2 := tl l;                  # получаем новый список 10 :: 20 :: 30 :: nil
l2  = tl l2;                 # удаляем из него первый элемент

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

Массивы.


array содержит фиксированное кол-во элементов одного типа.
Размер массива указывается при его создании/инициализации, а не при объявлении
типа переменной — т.е. массивы можно динамически создавать в любой момент
(когда стал известен требуемый размер массива).
Фактически в Limbo только два способа динамически выделить память: создать
array указав требуемый размер через переменную, и добавить новый элемент в
начало list.
Естественно, массивы тоже поддерживают срезы.

Tuples.


tuple (не знаю, как корректно перевести на русский) это что-то вроде списка
из 2-х и более элементов любых типов. И это не просто список, а такой же
тип данных, как и другие — тип самого tuple фактически определяется по
тому, каких типов элементы и в каком порядке он содержит. Пример:
i_s : (int, string);
i_s = (5, "five");
# тип i_r_s_s это (int, real, string, string)
i_r_s_s := (5, 0.5, "five", "comment");

Причём tuple можно «разбирать» на составляющие присваивая его в список
обычных переменных:
# создаёт переменные i типа int и s типа string и
# инициализирует их значениями 5 и "five"
(i, s) := i_s; 

Кстати, обмен значений двух переменных на Limbo делается примерно так:
(i, j) = (j, i);


Каналы.


Каналы (chan) позволяют организовывать IPC между локальными процессами
передавая атомарно объекты заданного типа.
Чтение/запись канала это блокирующая операция.
Операторы чтения/записи выглядят как стрелки:
c := chan of int;   # создаёт канал
c <-= 10;           # отправить в канал
i := <-c;           # принять из канала int
<-c;                # принять и проигнорировать значение
c = nil;            # уничтожить канал

Каналы бывают буферизированные (размер буфера вы указываете примерно так
же, как размер массива). Запись в буферизованные каналы не блокируется
пока не будет заполнен буфер. Буфер работает как FIFO очередь.
Для мультиплексирования каналов в Limbo есть целых два средства — можно
читать из массива каналов, а можно использовать специальный оператор alt
для выбора канала.
alt {
    i := <-inchan           =>
        sys->print("received: %d\n", i);
    outchan <-= "message"   =>
        sys->print("message sent\n");
}

Собственно каналы это единственный способ IPC в Limbo, они используются и
для передачи данных, и для синхронизации потоков, в общем полная замена
всяким mutexes, semaphores, shared memory, etc…
Что касается их производительности… я не уверен на 100%, но по-моему при
передаче чего-то через канал передаётся просто его адрес в памяти, т.е.
никакого копирования на самом деле не происходит и всё просто летает.

Составные типы.


cool : array of chan of (int, list of string);

это массив хранящий каналы, по которым передаются tuple состоящие из int и
списка строк. Размер массива здесь не определяется, он будет задан в
процессе выполнения, при инициализации массива.

Unicode.


Limbo использует UTF8 для I/O, и UTF16 для представления строк в памяти.
Т.е., например при считывании исходника модуля с диска в нём может
использоваться UTF8 в комментариях, строках и символьных константах.
Если есть массив байт (array of byte) и он конвертируется в строку, то
байты из массива обрабатываются как UTF8 и конвертируются в строке в
UTF16; а при преобразовании строки в массив байт происходит обратное
преобразование и в массиве оказывается UTF8.

Функции.


Функциям можно передавать параметрами ссылки на функции.

ООП.


Объекты симулируются через тип данных структура (adt), элементами которых
помимо обычных типов данных могут быть функции. На самом деле это,
безусловно, очень кастрированное ООП — наследования нет, ничего нет,
населена роботами. (с) :) Впрочем, вру. Полиморфизм — есть. Но немного
странный, больше напоминает templates в C++:
смотрите сами.

Нити.


Для запуска заданной функции в отдельной нити в Limbo используется
встроенный оператор spawn.

Ошибки и исключения.


Поддержка исключений есть, как обычных строковых, так и пользовательских
типов. К моему сожалению большинство системных и библиотечных функций
вместо исключений для возврата ошибок используют tuple: (errcode, result).
Безусловно, tuple это большой шаг вперед относительно POSIX-овского
возврата информации об ошибке в виде результата -1, но… хотелось бы
чтобы вместо этого использовались исключения.

Ссылки.


Ну и на закуску полное описание Limbo на русском.
По сути это примерно на 99.9% полный пересказ англоязычной доки по Limbo
своими словами и иначе структурированный (мне, как Perl-программисту,
хотелось сделать упор на типы данных и операции над ними, а то я от
типизированных языков успел отвыкнуть).
+26
27 мая 2007, 22:09
8
powerman 102,8

комментарии (31)

+1
anortorn #
Отлично! Я знаю, что концепт Inferno очень захватывающий, но я не мог предположить, что он может побудить человека донести до хаброаудитории столько информации за относительно короткий промежуток времени ;) Спасибо за все эти краткие описания и наблюдения, читать приятно.
Из вышенаписанного могу отметить использование utf-8 для работы со строками - это пошло еще с 1992 года, когда оную кодировку применили в Plan9. Очень логичный выбор, который позволяет избежать возни с кодировками сразу на системном уровне.
0
semka #
О. Списки. Списки это прекрасно, они таки взяли лучшее из lisp.
Tuples — может быть «кортеж»?
Похоже, в принципе.
+1
anortorn #
Tuple - и есть кортеж.
Как по мне, так в Limbo многое взято из python (или наоборот?): кортежи, срезы, использование юникода для представления строк на внутреннем уровне, etc.
0
semka #
А вот поди пойми что откуда взято, все хорошее уже придумали. Теперь комбинируют (:
0
123 #
Быть может Tuple и означает "кортеж", но по сути - это обычная структура (struct языка С или record Паскаля).
0
semka #
Нифига себе...
0
tom #
Насколько я понимаю, доступ к элементам кортежа осуществляется по индексу, а к элементу структуры/записи — по имени. Этим, видимо, и отличаются.
0
semka #
http://en.wikipedia.org/wiki/Tuples

Они, мягко говоря, не только этим отличаются.
0
gribozavr #
Но ведь компилятор все равно имя преобразовывает в смещение, поэтому имя — просто shortcut, по сути тот же индекс.
0
semka #
Нет, если вы когда программы пишете «играете в компилятор» — безусловно.
А если учесть, что все данные всё-равно проходят через регистры процессора, то между string и list тоже особой разницы нет.
0
gribozavr #
Если ваш string это не что-то умное типа std::basic_string, а простой char[], то действительно — для меня это тоже массив. Разница между массивом char и допустим int — особый синтаксис инициализации: "строка" vs {1, 2, 3}.
0
diamant #
похоже, ага, но как бы более обще.
0
Kpoxa #
Спасибо за очень интересные заметки, да еще и в таком объеме :)
Уже поставил, играюсь - ковыряюсь
0
asgard #
огромное спасибо за ссылки, будем смотреть.
0
shmel #
Большое спасибо за такие интересные посты.

Как я понял, что кроме него есть разве что sh и собственно все. Этот список планируют расширять или дорабатывать Limbo как язык? Вообще насколько динамично развивается вся платформа Inferno?
0
semka #
Проблема с компиляторами действительно есть, в принципе в стандартную поставку инферны входит yacc, генерирующий limbo-код, то есть можно, можно написать компилятор чего угодно (в теории).
Но почему-то никто этого не делает. Или замалчивает (-;
Платформа, увы, довольно инертная, развитие идет централизовано из Vita Nuova. Мало кто пользуется этой платформой в коммерческих целях и никого мощного сообщества разработчиков нет (пока?).
0
dv5ife #
Платформа, увы, довольно инертная, развитие идет централизовано из Vita Nuova. Мало кто пользуется этой платформой в коммерческих целях и никого мощного сообщества разработчиков нет (пока?).


К сожалению, да. Вообще, мне кажется, единственный способ сделать Inferno популярной — это позволить любому желающему принять участие в разработке. Тогда года через три это будет более чем серьзная система. Кстати, за примерами далеко ходить не надо — Линукс разрабатывался именно так.
0
powerman #
До недавнего времени у Inferno была более закрытая и коммерческая лицензия. Сейчас этот барьер убрали, так что...
0
dv5ife #
Хм.. А можно в общих чертах, что она (лицензия) из себя представляет?
0
powerman #
Я не очень разбираюсь в юридических документах, тем более на английском, но суть примерно такова: лицензия Inferno позволяет то же самое что и все открытые лицензии а-ля GPL, но помимо этого есть дополнительная возможность купить коммерческую лицензию и делать даже то, что GPL запрещает.

If you distribute Inferno with changes or additions to sections of Inferno that are under GPL or LGPL, and you will distribute (or otherwise make available) the source code to those changes or additions, you can choose the Free Software Scheme, otherwise choose the Commercial Developer Licence.
0
dv5ife #
То есть, как я понял, она частично под GPL, частично коммерческая? И свободные части можно распространять как угодно, а коммерческие — только под CDL?
0
powerman #
Нет, она вся либо под открытой лицензией, либо под коммерческой - выбираете вы.
0
dv5ife #
А, точно, то я в переводе не разобрался. Спасибо :)

"changes or additions to sections of Inferno that are under GPL or LGPL" <--бубух
0
Longman #
По поводу применения Inferno.
На Inferno работает семейство VPN Brick FirewallVPN Brick Firewall. Абсолютно секьюрная штука. Только на комп не поставишь. :)
0
Longman #
Блин, с тегами как-то не разобрался еще. http://www1.alcatel-lucent.com/enterpris…
0
robot12 #
По поводу применения, был выпущен телефоный аппарат Lucent фото которого можно найти на http://www.tuxscreen.net/. Модель увы не помню на пямять, но если поискать, в Сети можно найти оригинальную прошивку для него в формате archfs.
0
robot12 #
Вспомнил !!! Philips IS2630 ;) WebPhone :)
0
Constantine #
Таких бы статей по-больше на Хабре. Читать действительно и интересно, и полезно. Спасибо.
0
ulfurinn #
В целом основательно так напоминает Erlang.
0
powerman #
Угу. В maillist-е Inferno довольно регулярно и с уважением упоминают Erlang. :)
0
semka #
Еще бы компилятор сподобили (:

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