From a84b0cc8e05e6ba113fd2fb8b45df2705d7e46f2 Mon Sep 17 00:00:00 2001 From: klapaucius Date: Thu, 29 Feb 2024 18:20:01 +0500 Subject: [PATCH] feb. update --- compilers.md | 366 +++++++++++++++++++++++++++++++++++++++++++++++++++ index.md | 5 +- 2 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 compilers.md diff --git a/compilers.md b/compilers.md new file mode 100644 index 0000000..96a1b27 --- /dev/null +++ b/compilers.md @@ -0,0 +1,366 @@ +История применения и оценки функционального программирования. +======= + +- [История применения и оценки функционального программирования.](#история-применения-и-оценки-функционального-программирования) +- [Часть 1: Первые компиляторы.](#часть-1-первые-компиляторы) + - [От Луки](#от-луки) + - [Лука Карделли](#лука-карделли) + - [Пари PASCAL](#пари-pascal) + - [FAM](#fam) + - [Диалоги](#диалоги) + - [Сумма против Милнера](#сумма-против-милнера) + - [Ссылки больше не против](#ссылки-больше-не-против) + - [Детали](#детали) + - [Без сборщика и со сборщиком.](#без-сборщика-и-со-сборщиком) + - [Разъединение](#разъединение) + - [Новые эксперименты с VAX](#новые-эксперименты-с-vax) +- [Литература](#литература) + + +Часть 1: Первые компиляторы. +============================ + +Как мы выяснили в предыдущей - нулевой - части истории, к началу 80-х уже появился функциональный язык, похожий на современные - HOPE. Но имплементаторы ФЯ не все и не сразу захотели имплементировать языки, похожие на HOPE. Были желание и, главное, возможность имплементировать более необычные для нас функциональные языки. +Поэтому в первые годы десятилетия появилось несколько имплементаций ФЯ, которые только позднее стали имплементациями ФЯ, похожих на HOPE. Или были использованы как части таких имплементаций. Первая часть истории функционального программирования будет о них. +Наконец-то прошли времена, когда никакие идеи по имплементации функциональных языков не работали. С появлением относительно распространенных компьютеров с большой памятью заработали все. Конечно, не все одинаково быстро и одинаково хорошо. +На смену этим имплементациям придет новое поколение компиляторов, многие из которых будут написаны на языках, имплементированных с помощью первых компиляторов функциональных языков, использовать их в качестве бэкендов. Будут использовать первые имплементации для бутстрапа или, хотя-бы, прототипирования. Таким образом сформируются главные единицы нашей истории ФП - _семейства имплементаций_. В отличие от имплементаций ФЯ, большинство из которых давно заброшены, большинство семейств имплементаций живы и сегодня, хотя и могут влачить довольно жалкое существование. +Мы начинаем историю функционального программирования с компилятора, разработка которого началась в одном из важнейших мест, в которых разворачивалась предыстория. + +От Луки +------- + +> Прежде всего, следует понять, что, по крайней мере, потенциально, ML может быть скомпилирован очень эффективно. +> Л. Карделли, Компиляция функционального языка [Card84]. + +В то время, когда авторы HOPE позиционировали свой язык как едва исполняемую спецификацию, в том же Эдинбурге более известный другими своими работами Лука Карделли начал работу на более амбициозным проектом. + +### Лука Карделли + +Лука Карделли (Luca Andrea Cardelli) изучал компьютерные науки в Пизанском Университете (University of Pisa) в 73-78 годах. В том же городе, где шли работы над одним из первых языков уравнений TEL. Там Карделли получил доступ к IBM 370. Карделли хотел писать на Algol 68 [Card07], но на машине не было такого компилятора. Уже знакомый нам Algol68C, хоть и заработал на IBM 370 в конце 73-го [Birr77], но зарелизили его только в начале 80-го [ALGOL80]. Первый компилятор Algol 68 для IBM 370 со сборщиком мусора - FLACC - анонсировали в 78-ом году [Thom78]. Единственный компилятор, имплементировавший окончательный Algol 68 полностью, коммерциализированная версия компилятора FLASC, бывшего магистерской диссертацией [Thom76] как и RABBIT, генерирующий не такой быстрый код, как у компиляторов из Кембриджа и Молверна из-за большого числа проверок времени исполнения. +Все слишком поздно для Карделли. Как он говорит в 2007-ом году - "к счастью" [Card07]. Но в середине 70-х Карделли, как и Уоррену, Алгол 68 нравился. Большинство прочих доступных языков: Fortran, APL, Lisp 1.5, PL/1 Карделли не нравились вовсе. Оставался только один язык, которому, как вспоминает Карделли не было никакой альтернативы - SIMULA 67. На IBM 370 компилятор Симулы был доступен с мая 72-го [Holm98]. Как раз вовремя! Не смотря на то, какими работами Карделли наиболее известен, ООП ему не особенно понравилось. Понравилась ему сборка мусора, типизация, поддержка строк в языке. Классы он использовал для кодирования индуктивных типов и не считал особенно удобными для этого. +Карделли вспоминает, что в студенческие годы познакомился с функциональным программированием и Схемой, но не упоминает Схему как язык-кандидат, рассматриваемый им для какого-то практического использования. Как мы писали в главе про уравнения, в Пизе работали люди, которые интересовались результатами Эдинбургской исследовательской программы, но Карделли, по видимому, ознакомился с Эдинбургскими работами только в Эдинбурге. +Карделли прибыл в Эдинбург и стал работать над диссертацией в ноябре 78-го [Card21] [MacQ15] под руководством Плоткина. Это плохо согласуется с тем, что остатки бывшей Группы Экспериментального Программирования влились в Департамент Компьютерных Наук Эдинбургского Университета через год после этого, в конце 79. Может быть, Плоткин сделал это раньше Бурсталла, а может быть это событие датировано неправильно. Если это слияние двух ветвей Эдинбургской программы произошло раньше - некоторые странности и несоответствия истории HOPE исправляются, но слабое влияние HOPE на ранние работы Карделли становится еще более странным. Это слабое влияние, впрочем, делает сам вопрос не очень важным. +Также Карделли изучил Паскаль и посчитал его "приятным языком для имплементации". И это было не очень популярное мнение в Эдинбурге в частности и среди имплементаторов функциональных языков вообще. Судя по тому, что долгое время не было много желающих работать над программой, которую он писал на Паскале. Что он писал на Паскале? +Диссертация Карделли была вовсе не об имплементации компилятора языка общего назначения, как у Уоррена или Стила, а об описании и верификации железа [Card82d]. Какое он имеет отношение к нашей истории? Карделли писал программы для форматирования текста своей диссертации [MacQ14]. И для новой машины Эдинбургского Университета, VAX-11/780, нет компилятора Симулы, которой Карделли привык пользоваться, пока был студентом в Италии. Не беда. Карделли вспоминает [Card07], что в Эдинбурге "наконец-то" узнал о первом языке программирования, который он посчитал "приятным". Об ML. +К сожалению, ML - это не язык на котором можно писать какие-то программы для обработки текстов. И первоначальные авторы не собираются его таким делать [Miln93] [Miln2003]. Но Карделли решил: Почему нет? Почему бы мне не имплементировать первый компилятор для функционального языка, на котором я хочу писать код для форматирования текста моей диссертации? На Паскале - языке, приятном языке для имплементации ФЯ. И имплементировал. Потому что мог. + +### Пари PASCAL + +На самом-то деле - поясняет Карделли - ML можно компилировать в быстрый код [Card84]. У функционального языка Эдинбургской программы ряд важных свойств, облегчающих его эффективную имплементацию: + +* статические (лексические) области видимости - лексический контекст известен во время компиляции. +* статическая типизация - нет необходимости в информации о типах и ее проверке в рантайме. +* регулярность применения функций - в большинстве случаев можно использовать стек, как в Алголах. +* паттерн-матчинг позволяет оптимизировать проверки. +* описания типов данных достаточно высокоуровневые, чтобы можно было оптимизировать их расположение в памяти. +* абстрактные типы данных - позволяют использовать эффективные структуры данных вроде массивов и битмапов. + +Как и Уоррен, Карделли посчитал решениями многое из того, что его предшественники считали проблемами. +Но не все так радужно, для эффективной компиляции ФЯ есть и препятствия, основное из которых - полиморфизм. Полиморфизм требует универсального представления, боксинга (почти) всех данных, чтоб они были одинаково представлены - как указатели, что не полезно для производительности и потребления памяти. +Типы данных, которые можно описывать в LCF/ML тоже имеют плохое представление в памяти. Потому, что собираются из пар как придумал МакКарти и типизировал Моррис. +Та же проблема и у структур в памяти, которые создают имплементации ФЯ. Все эти стеки-списки, окружения-списки, замыкания-списки желательно заменить на плоские структуры. +Наконец, использовать абстрактные типы данных для представления изменяемых структур данных в LCF/ML нельзя из-за ограничений `letref`. +Нужно решить массу проблем. + +### FAM + +К октябрю 1980 Карделли описал виртуальную машину для имплементации ML и прочих ФЯ на машинах с большой памятью. Машину он называл сначала AM [Card80], а позднее FAM (Functional Abstract Machine). Это SECD, в которой используемые там для всего списки заменены на массивы и сделаны некоторые изменения для более быстрой имплементации функций. Эти изменения: + +* три отдельных стека для аргументов, для возвратов и для имплементации исключений. +* операция применения функций разделена на три: сохранение фрейма, восстановление фрейма и собственно само применение в новом окружении, создаваемом и "демонтируемом" предыдущими операциями. +* оптимизация хвостового вызова. +* "плоские" замыкания. + +Замыкание представлено в памяти массивом ссылок. То, что собирался делать Стил в RABBIT, да так и не собрался. Замыкания должны разделять между собой мутабельные ячейки и замыкания из списков (как в SECD и в RABBIT) позволяют осуществить такое. Но цена не устраивала Карделли. Поскольку он предполагал, что использоваться будут, в основном, неизменяемые переменные он выбрал имплементацию, которая делает дороже использование изменяемых переменных: значения изменяются не в массиве, представляющем замыкание, а в ячейке кучи, на которую есть указатель в этом массиве. Замыкания "плоские", но только для неизменяемых переменных. Имплементация Схемы или языков вроде обоекембриджских ISWIM-ов PAL и McG потребовала бы для этого нелокального анализа, но в LCF/ML мутабельность явная. Удобно! +Массивы доступны и для пользователя виртуальной машины. И массивы обычных объектов и специальные строки. +(F)AM использовала более компактное представление данных [Card80] [Card83], чем LCF/ML. +В слово на VAX-11 помещается только один указатель, так что, анбоксинг не требует ухищрений, которые были необходимы на PDP-10. Вместо указателя на объект в каждое слово можно поместить целое число довольно полезного размера, хотя полным 32-битным диапазоном придется пожертвовать. Разумеется, один бит нужно потратить на то, чтоб отличать указатель от числа или еще какого-нибудь значения, которое можно закодировать как целое число. +Один бит используется для этого сегодня в OCaml, но Карделли различает указатели и целые числа не так. Карделли умещает на место указателя существенно менее полезное число - только 16-бит. Все, что больше 65536 - указатель. +Дополнять эти некрупные целые должны были сначала 32-битные числа с плавающей точкой в куче [Card80]. Позднее Карделли передумал и решил имплементировать длинные целые [Card83]. Предполагалось, что значения одного и того же типа могут быть представлены как 32-бит слово с 16-бит. целым числом или как указатель на массив в куче, представляющий настолько длинное число, насколько длинным позволяет быть ограничение на размер массива. Массивы, разумеется, разрешены не очень большие - до 64Кб. +Но поддержка длинных целых не будет имплементирована до 84-го года [Card84b]. Все это время ML Карделли будет поддерживать только 16-бит целые числа и никакие другие. +Как небольшие целые числа кодируются и некоторые другие значения: пустой список, `true` и `false`, аналог хаскельного типа `()` [Card80] [Card83]. +Также машина поддерживает рекорды со многими полями, они не ограничены парами. Но представление для вариантов все еще как у МакКарти, требует лишний уровень косвенности, как и изменяемая ссылка. Это ссылка на пару из ссылки на объект кучи и тега. Почему-то именно в таком порядке [Card83]. +Описывая виртуальную машину, Карделли отмечает, что сборщик мусора надо использовать с поколениями, как у Хьюита и Либермана. И ссылается на более позднюю версию их отчета [Lieb81]. Но сначала сборщик мусора - это только планы, что уже не должно удивлять читателя. Когда же планы воплощены в реальность - сборщик все равно был не сборщиком Хьюита и Либермана. Карделли имплементировал копирующий сборщик [Card82a]. Рекурсивный, даже не алгоритм Чейни, так что сборщик падал, обходя списки в тысячи элементов [Card84b]. Сначала размер кучи не менялся. Рантайм Карделли занимал как минимум 1Мб памяти. 512Кб доступно для программы на ML, еще 512Кб резервировалось для копирования. Пользователь системы не мог установить этот размер ни при запуске, ни при компиляции программы на ML. Размер кучи - константа в рантайме на Паскале, который нужно пересобирать, чтоб изменить значение. Карделли пишет что минимальная куча, которая позволяет коду на ML работать ("но не долго") - 8Кб [Card82a]. Позднее размер кучи стал изменяться автоматически [Card83b], но рекурсивность алгоритма Карделли так и не исправил [Card84b]. +Фиксированная ML-куча была только минимальной оценкой требуемой памяти потому, что помимо ML-кучи, была еще и куча без сборки мусора, которая использовалась компилятором и постоянно росла. Почему компилятор занимает какую-то память во время исполнения? Это необычный компилятор. + +### Диалоги + +Но необычный не по тем причинам, по которым был необычным RABBIT. Компилятор Карделли совсем не похож на RABBIT, на который Карделли ссылается [Card84]. Вместо десятка проходов только четыре: парсинг, проверка типов, компиляция в стековую виртуальную машину и генерация VAX машкода. Вывод и проверка типов совмещались с вычислением свободных переменных и смещения стека. +В компиляторе Карделли вместо сложных нелокальных оптимизаций RABBIT, трансформирующих абстрактные синтаксические деревья, оптимизации напоминающие скорее компилятор Уоррена. На который Карделли не ссылается. Оптимизатор работает с последовательностью команд виртуальной машины и заглядывает в этой последовательности не очень далеко. Например, поддержка быстрого карринга реализуется как выбрасывание из последовательности операций виртуальной машины для применения функции `f x y` + +``` +сохранить фрейм, применить, восстановить фрейм, сохранить фрейм, применить, восстановить фрейм +``` + +сочетаний `восстановить фрейм, сохранить фрейм`. Оптимизация хвостового вызова делается не трансформацией всего кода как в RABBIT, а заменой набора операций + +``` +сохранить фрейм, применить, восстановить фрейм, возврат +``` + +на специализированную операцию `хвостовое применение`. Типичный подход в компиляторе Уоррена. +Как в компиляторах Пролога Уоррена, BCPL Ричардса, как в компиляторе Algol 68C, эти операции виртуальной машины разворачиваются в код по шаблону. Это довольно распространенный способ имплементации. В отличие, например, от того, который использовал Стил в RABBIT. Получающийся в результате код не особенно хороший. Например, нет хорошего распределения регистров. Карделли пишет, что в это время считается, что пользы от хорошего использования регистров для ФЯ нет вовсе. Сам Карделли несколько дистанцируется от этого мнения и не говорит, что так считает он сам. Но он считает, что пока сойдет и так. +Но то, что компилятор Карделли использует виртуальную машину, как многие другие, еще не означает, что это обычный компилятор. Есть то, что делает компилятор карделли непохожим на другие. +В отличие от компилятора Уоррена, Карделли не использует системный микро-ассемблер. Почему? Компилятор Карделли интерактивный. Для каждого вводимого пользователем или загружаемого из файла выражения производится компиляция. Компиляция целиком происходит в памяти и результат подлинковывается к коду, полученному на предыдущих шагах REPL. Каждое выражение компилируется в окружении, сформированном на предыдущих шагах - статическая видимость даже на топлевеле. +Карделли пишет, что компилятор выглядит необычно потому, что он не был знаком с тем как пишут обычные компиляторы. В качестве примера таких знаний, которые ему удавалось избегать до поры - книга зеленого дракона Ульмана и Ахо. +Но Карделли, в отличие от имплементаторов LCF/ML и HOPE, умел парсить менее экзотическим способом. Карделли написал парсер рекурсивным спуском вручную. +Зато Карделли, по его заверениям, знаком с тем как имплементировались Лиспы и Схемы. Отсюда, объясняет он [Card84] и все странности. Мы уже отмечали, что сходство с RABBIT мало. Но давайте посмотрим, что думают о компиляторе Карделли лисперы. +Имплементатор Схем Кент Дибвиг (Kent Dybvig) в 84-ом году познакомился [Dybv06] с Карделли и тот показал ему свой интерактивный компилятор ML. +Он понравился Дибвигу и тот имплементировал Chez Scheme как интерактивный компилятор. Хотя первоначально планировал другой подход, который считал обычным для Лиспов - как пару из интерпретатора и компилятора. Интерактивный компилятор он, наоборот, считает совсем необычным для Лиспа. +Также как и Карделли, Дибвиг выбрал для своей Схемы и "плоское" представление замыканий, но еще до того, как познакомился с Карделли. Этот способ организации замыканий он тоже не считает лисповым, считает схожим с алголовыми "дисплеями" и почерпнул его из книги Рассела об имплементации Алгола. +В докладе [Card07] о SIMULA карделли говорит, что ML интерактивный и потому более объектно-ориентированный, чем SIMULA. Что бы не значило это загадочное утверждение, но наверное не значит то, что Карделли позаимствовал идею об интерактивном компиляторе у симулистов. +Куча, которую использует интерактивный компилятор только растет [Card82a]. Раз компилятор интерактивный - компиляция раздельная с очень небольшой минимальной единицей компиляции, как и у Уоррена. Но функциональные программисты, как мы видели в коде на HOPE, не всегда охотно делят код на такие небольшие единицы. Карделли рекомендует писать "небольшие" функции в 10-100 строк [Card84b]. +Фичи виртуальной машины Карделли позволяют компилировать обычный код на LCF/ML, но, чтоб использовать их полностью, в ML нужно добавить некоторые фичи, доступные непосредственно программисту. Что Карделли и сделал. + +### Сумма против Милнера + +Виртуальная машина Карделли поддерживает рекорды, в которых больше двух полей. И варианты, объединяющие более двух видов объектов кучи. Для того, чтоб программист на ML мог объявлять эти новые, менее ветвистые структуры данных, Карделли добавил новые конструкторы типов и значений в ML [Card82b]. +Не смотря на то, что представление в памяти у них скорее как у типов данных, которые придумывал и пытался продвигать Хоар - наборы полей со ссылками на другие наборы полей со ссылками - как языковая фича они ближе к типам данных в Algol 68. +Как типы данных в Algol 68, но не как типы данных Хоара или алгебраические типы данных, рекорды и варианты Карделли не требуют деклараций, создающих новые типы. Они все уже существуют как наборы пар имен и типов, порядок которых не имеет значения: + +``` +- let r = (|a=3; c="1"; b=true|); +> r = (|a=3; b=true; c="1"|) : (|a:int; b:bool; c:tok list|) +``` + +Рекорды-произведения у Карделли дополняют варианты-суммы. Это существенное отличие от типов-объединений Algol 68. С этим взглядом на структуры данных как на сочетание дополняющих друг друга произведений и сумм Карделли познакомил его научрук Плоткин [Card07]. В языке Карделли можно объединять объекты одного типа и строить сложные структуры, которые не коллапсируют в алголовское одноуровневое объединение. Программисту нужно придумывать названия для каждого тега. Но для имплементатора должно быть удобно, что не нужно получать теги из типов. Тем более в языке с параметрическим полиморфизмом и стиранием типов. В первом же примере Карделли особо не раздумывает и делает названия тегов именами типов: `[|int: int; bool: bool|]`. +Попробуем сконструировать значение такого типа. + +``` +- [|int=3|]; +Unresolvable Variant Type: [|int:int|] +``` + +Да, вторая версия системы МакКарти еще хуже для вывода типов, чем первая. Естественно, такой конструктор - не конструктор значения единственного типа, а конструктор значения любого из бесчисленного числа уже имеющихся типов, даже если они вам все и не нужны. Какого именно - придется часто указывать: + +``` +- [|int=3|]:[|int: int; bool: bool|]; +``` + +Поскольку каждый раз записывать структурный тип может быть хлопотно, как и в Algol 68 можно объявить короткое имя для них: + +``` +let type R = (|a:int; b:bool; c:tok list|); +let type OneOfTwo = [|int: int; bool: bool|]; +``` + +И, скорее, даже нужно. +Как и синонимы типов в Algol 68, синонимы типов в LCF/ML и в ML Карделли не параметризованы. Как и в Algol 68, хотя и по другой причине, в ML Карделли нельзя просто так взять и написать рекурсивный тип. Рекурсию нужно разбивать "прокладкой". Если в Algol 68 рекурсия не работает из-за плоского представления, то у Карделли из-за вывода типов. К счастью, в ML, в отличие от Algol 68, есть способ объявлять новые типы и решить проблемы с рекурсией и параметризацией - абстрактные типы данных: + +``` +let type * list <=> [| cons: (| head: *; tail: * list |); + nil |] +``` + +Разумеется, представление в памяти у такого списка, даже во второй версии системы МакКарти, хуже, чем у "непосредственной имплементации" с суммами произведений. Значение `abslist [| cons = (| head = 1; tail = abslist [|nil|] |) |]` представляется в памяти так: + +``` + + ┌───┐ ┌───┬───┐ + │ ├──►│ │ 0 │ + └───┘ └─┬─┴───┘ + │ + ▼ + ┌───┬───┬───┐ ┌───┬───┐ + │ 2 │ 1 │ ├──►│ 0 │ 1 │ + └───┴───┴───┘ └───┴───┘ + +``` + +Тег варианта в отдельном объекте от объекта-рекорда. Ячейка с числом `2`, перед той, на которую указывает ссылка - это размер рекорда, информация для сборщика мусора. +Поэтому для конструкторов списков специальные объекты в FAM, и список - все еще встроенный тип. +Для конструкторов вариантов типа `.` (в Хаскеле это `()`) есть специальные сокращенные имена и конструкторы. Например, тут можно писать просто `nil` вместо `nil: .` и конструировать `[|nil|]` вместо `[|nil=()|]`. Что удобно для определения перечислений: + +``` +let type color = [|red; orange; yellow|]; +``` + +Не смотря на то, что перечисление можно представлять числом, в FAM значения такого типа - объекты в куче, и занимают там больше одного слова. Первое слово - `0` соответствующий конструктору `()`, а сразу за ним число - тег. +Обращение к полям рекордов неожиданно современное и обычное: + +``` +- r.a; + 3 : int +``` + +Если у сумм проблемы с выводом типов при конструировании, то у произведений, понятное дело, проблемы с выводом типов при разборе: + +``` +- \r. r.a; +Unresolvable Record Selection of type: (|a:*a|) +Field selector is: a +``` + +У таких функций нужно или аннотировать тип или употреблять их тут же, в однозначном контексте: + +``` +- (\r. r.a) r; + 3 : int +``` + +Карделли пытался решить проблему с выводом типов подтипированием рекордов и вариантов, но, пока что, безуспешно [Card07]. +Для разбора сумм-вариантов используется конструкция `case` почти как у Бурсталла: + +```haskell +let rec map f xs = case replist xs of + [| cons = (| head; tail |) . abslist [| + cons = (| head = f head; tail = map f tail |)|]; + nil . abslist [| nil |] + |] +``` + +Для ПМ рекордов тоже есть сокращенный синтаксис. Можно писать `(| head; tail |)` вместо `(| head=head; tail=tail |)`. +Интересно, что тут, в отличие от Algol 68 и идей Хоара, паттерн-матчинг хотя-бы вложенный. Эта новая для ML разновидность паттерн-матчинга не очень хорошо сочетается со старой. Суммы и произведения можно матчить в `let` паттерне и даже как-то сочетать с разновидностями паттернов из LCF/ML. Но не всегда. `case` на первом уровне работает только для сумм, на что недвусмысленно указывает и его синтаксис, с такими же квадратными скобками. +В месте разбора суммы тип выводится, но потому, что этот `case` обязан перечислить все теги. И только один раз, так что в этом он ближе к `case` в core, чем к `case` в Haskell. Так что, скорее всего, не получится использовать старую разновидность ПМ в `case` с помощью такого вот трюка: + +``` +case [| l = xs |]:[| l: * list |] of + [| l = h :: t . ...; + l = [] . ... + |] +``` + +Но, надо полагать, что если бы пользователи ML Карделли хотели что-то такое использовать, то язык бы выглядел несколько иначе. В примерах работа со списками происходит по старому: + +```ocaml +let rec map f l = + if null l then [] + else f(hd l)::map f (tl l); +``` + +дополним этот пример до нашего традиционного примера: + +``` +map (\x. x + y) [1; 2; 3] where y = 2; +``` + +Добавив в ML эти типы данных МакКарти второго поколения, Карделли оставил и систему МакКарти первого поколения из LCF/ML, в которой все собирается из пар. Со многими другими пережитками первого ML он поступил иначе. + +### Ссылки больше не против + +Как мы помним, `letref` не особенно часто использовался в коде на LCF/ML и мы рассмотрели одну из причин - большая часть кода и практически весь мутабельный код был написан на Лиспе и только вызывался из ML. Разумеется, в имплементированном не через компиляцию в Лисп языке не так хорошо обстоят дела с вызовом лисповых функций. +Отсутствие в LCF/ML первоклассных изменяемых ссылок - другая причина. Мутабельность в ML сделали ради изменяемых деревьев. Но делать их без первоклассных ссылок не особенно удобно. Поэтому Гордону хотелось [Gord80] добавить мутабельные структуры, такие как массивы или мутабельные списки в ML. При этом Гордон сохраняет скепсис в отношении изменяемых ссылок. Считает, что идея совсем не очевидно хорошая и может быть "тем же по отношению к `letref`, чем является `goto` по отношению к `while`". Критика, похожая на критику изменяемых ссылок Хоаром. Это довольно сложное отношение Гордона к первоклассным изменяемым ссылкам еще больше осложнялось тем, что они с Милнером просто не знали как их типизировать так, чтоб сохранить безопасность и не получить средства приведения любого типа к любому другому. +Так что Гордон поручил разобраться как типизировать конструктор типов `ref` Луишу Дамашу, пишущему в это время в Эдинбурге диссертацию под руководством Милнера. +Луиш Мануэл Мартинш Дамаш (Luís Manuel Martins Damas) придумал [Dama84] даже два способа: доработал первоначальную идею Гордона о "слабых" переменных и собственный. И оба позаимствовал Карделли для своей версии ML [MacQ15]. Не одновременно, а последовательно. +Читатель может заподозрить, что если придумано два способа сделать что-то и не так очевидно какой лучше - вопрос все еще не закрыт. Да, так и есть, эмелистам предстоит работать над этой проблемой еще много лет. Но первые практические результаты уже получены. Сам Дамаш имплементировал типо-безопасные первоклассные изменяемые ссылки для LCF/ML. +Итак, вместо `letref` как в первоначальном LCF/ML у Карделли первоклассные ссылки. Было +`letref a = 3 in a := a + 1`, а стало `let a = ref 3 in a := @a + 1`. Можно писать `let a = ref[]` на топлевеле без аннотации типа. Можно использовать для определения абстрактных типов `let type * array <=> * ref list` [Card82b]. +И хотя вполне можно было бы поддержать старую `letref` аннотацию, в ML Карделли она не попала. + +### Детали + +Некоторые отличия были продиктованы ограничениями виртуальной машины [Card82b] [MacQ15]. Например: размеры токенов, целых чисел. Отсутствие информации о типах во время выполнения изменило поведение операции структурного сравнения. +Новые фичи языка перехватили некоторые старые операторы. Конструктор списка теперь `_`. `.`, которая была в LCF/ML, в Лиспе и, первоначально, в Прологе теперь занята селекторами рекордов. Конкатенация двух списков, которая в LCF/ML была `@` у Карделли `::`. `@` - теперь операция получения значения по ссылке. +Виртуальная машина позволяет делать хвостовую оптимизацию, и циклов `if then loop` пока нет, но планируются. Полностью Карделли от них не отказался. Вот циклов, управляемых выбросами исключений, как в LCF/ML у Карделли нет и не планируется. +У абстрактных типов новый синтаксис: вместо `abstype TA = TC` у Карделли `let type TA <=> TC`. +Отличаются наборы символов из которых можно составлять имена. Вместо `-3` у Карделли `~3`. +Карделли заменил длинные ключевые слова LCF/ML вроде `letrec` на составные вроде `let rec`. +Карделли не только просто менял одни токены в декларациях на другие. Он ввел в ML конструкцию `decl1 ins decl2`. Раннюю версию современной конструкции `local decl1 in decl2`. +Одних только косметических изменений синтаксиса хватило бы для того, чтоб при переносе кода с LCF/ML на ML Карделли пришлось бы редактировать почти каждую строчку. В лучших традициях диалектов NPL и языков Тернера. Не то чтобы, правда, было много кода на LCF/ML, было что переносить и было желание переносить. Все-таки в 70-е функционального программирования не было. +Получившийся язык Карделли известен под множеством имен. В описании [Card82a] отличий от LCF/ML он называется Edinburgh ML. Карделли также называет его ML under VMS, ML under Unix, "моя имплементация для VAX". Вадсворт называет его Cardelli ML, Гордон - Luca ML. В редакционной колонке первого номера самиздат-журнала "Полиморфизм" [Card82c] Карделли и МакКвин называют язык VAX ML. Так же называет язык описывающий его историю [MacQ14] МакКвин и в наши дни. +Появление нового диалекта ML вызвало некоторое желание у эмелистов и не только стандартизировать ML. Это желание приведет к появлению языка, который мы считаем диалектом NPL. Но в названии этого диалекта NPL есть ML, так что мнение о том, что это еще один диалект ML не совсем лишены основания. Это, впрочем, уже другая история, к которой мы еще вернемся. + +### Без сборщика и со сборщиком. + +Милнер вспоминает [Miln93] [Miln2003], что Карделли имплементировал компилятор за несколько недель. Но между первым описанием виртуальной машины в октябре 80-го и первой версией компилятора прошли месяцы. +Первый релиз готов 12 июня 81-го года [Card82c]. В этой версии не полностью имплементированы абстрактные типы, но самое главное - нет сборщика мусора. +Все это было исправлено 13 октября 1981 во второй и последней версия компилятора Карделли для VMS. +Карделли не ограничивал использование в университетах и распространение в другие университеты, но использование в исследовательских лабораториях и в индустрии требовало его согласия в каждом отдельном случае [Card82a]. И некоторые университеты заинтересовались компилятором ML. Как и у Тернера, у Карделли был список [Card82c] известных пользователей VAX ML. Не очень впечатляющий список, умещающийся на одну страничку и включающий самого Карделли. Но само наличие списка уже означает выдающуюся популярность имплементации ФЯ в те времена. +Список пользователей говорит скорее о том, когда пользователь перестал получать новые версии, а не когда начал. И многие не обновляли компилятор никогда, первая версия без сборщика мусора была для них и последней. +С компилятором Карделли ознакомился Милнер, Гордон и Вадсворт. Ознакомились в важных центрах разработки ФЯ в Гётеборге, Оксфорде и в INRIA, рассказ о которых еще впереди. +Как мы выяснили в предыдущей части, VMS - стандартная ОС VAX-11 - не особенно хорошо подходила для использования требующих большой памяти программ вроде компиляторов ФЯ. По крайней мере, компилирующих что-то побольше однострочников. BSD подходила лучше и была популярна в академии. По каким-то причинам Карделли не начал имплементацию на этой системе, так что за исправление этой ошибки взялись другие. +Не позднее марта 1982 [Sait82] в Университете Карнеги — Меллона Сайто Нобуо (Saito Nobuo) [MacQ14] портировал 13-10-81 версию [Card82c] компилятора Карделли на Unix с помощью Berkeley Pascal. Того самого компилятора Паскаля, который писали в Беркли Кен Томпсон и другие [McKus]. Долгое время Berkeley Pascal был только интерпретатором байт-кода, но в 1980-ом появился компилятор [Joy86]. +Известным Карделли пользователем этой версии был Ханс Боэм (Hans Boehm), который еще поучаствует в нашей истории. +Иэн Коттам (Ian Cottam) планировал [Card82c] портировать эту версию на одну из первых рабочих станций с Motorola 68K. Рабочую станцию продавала компания Apollo, написавшая Multics/Unix-подобную ОС Aegis на Паскале. Можно предположить, что их компилятор был получше. Если б эти планы закончились успехом - название VAX ML стало бы не таким подходящим, но пока что смена названия откладывается. + +### Разъединение + +В апреле 1982 Лука Карделли покинул Эдинбург и отправился в Нью-Джерси. Но история VAX ML в Эдинбурге на этом не заканчивается. Кто-то (Милнер не помнит кто) предложил использовать компилятор Карделли для обучения второкурсников [Miln93] [Miln2003]. Вскоре для компилятора Карделли нашли в Эдинбурге и другое применение, но об этом позже. +В Лабораториях Белла в Мюррей Хилл работать Карделли уговорил МакКвин [Card12]. Там Карделли проработал с апреля 82-го по сентябрь 85-го. Научился использовать C, но язык ему совсем не понравился. Карделли не заинтересовался C++, хотя и слышал что Страуструп делает Симулу из C. Не заинтересовался потому, что из C. Хотя кабинет Страуструпа был дальше по коридору [Card07]. +В Bell Labs Карделли сам занялся портированием своего компилятора на Unix. Он использовал не только Berkeley Pascal, далеко не самую быструю имплементацию Паскаля, которая заметно хуже Паскаля для VMS [Nebe83], с которого он начинал. Если для имплементации компилятора Berkeley Pascal еще более-менее подходил, то рантайм Карделли переписал на C. Первый релиз был готов уже 13 августа 1982. +В этой версии в ML вернулись циклы. А именно конструкция `if then loop` из LCF/ML. Имплементированы массивы. +Как мы помним, в Эдинбурге Карделли не поддавался влиянию языков бывшей Группы Экспериментального Программирования. Но в Мюррей Хилл МакКвин на него, видимо, повлиял и VAX ML сделал первый небольшой шаг к "хопизации": `cons` оператор называемый в первых версиях `_` получил современное имя `::` как в HOPE и до того в POP-2. Этот шаг был не последним, но это уже другая история. В предыдущих версиях VAX ML `::` называлась конкатенация списков, которая теперь получила современное имя `@`. И, соответственно, операцию над ссылками Дамаша `@` переименовали в современную `!`. +Вскоре, 24 августа 1982 была готова следующая версия. В ней Карделли добавил функции для ввода-вывода и VAX ML, видимо, впервые стал языком общего назначения. Немного поздно для того, чтоб писать программы для форматирования диссертации Карделли. +В том же году была сделана еще одна версия, от 5 ноября 1982 с новым алгоритмом проверки типов для изменяемых ссылок. Версия вышла незадолго до судьбоносного совещания в лаборатории Резерфорда - Эплтона, которое завершило предысторию ML и начало его историю, но об этом мы расскажем в следующей части. + +### Новые эксперименты с VAX + +Насколько быстрый код генерировал компилятор Карделли? Сам Карделли в своих статьях не приводит таких измерений, только пишет, что производительность можно считать удовлетворительной для типичных применений. Что может означать довольно плохую производительность, когда такое пишет довольный пользователь Симулы. К счастью, компилятор Карделли поучаствовал в том сравнении ранних имплементаций ФЯ [Augu84] [Augu89], которым мы уже пользовались, чтоб продемонстрировать невеселые итоги 70-х: + +| | fib 20 | primes 300 | 7queens | insort 100 | tak | +| :------------ | :------: | :--------: | :------: | :--------: | :------: | +| VAX ML | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | +| LCF/ML (LISP) | 92.0 | 24.2 | 18.9 | 15.0 | 19.3 | +| SASL (LSECD) | 62.0 | 16.7 | 18.9 | 12.0 | | +| Miranda (SKI) | 144 | 10.3 | | | | +| LISP int. | 42.0 | 6.50 | 5.33 | 6.40 | 4.75 | +| LISP comp. | 2.20 | 0.92 | 0.58 | **0.80** | 0.19 | +| C | **0.92** | **0.17** | **0.04** | | **0.11** | +| Pascal | 1.84 | | 0.11 | | 0.16 | + +Как видите, компилятор Карделли легко обошел все предыдущие попытки имплементировать ФЯ, предпринятые участниками Эдинбургской исследовательской программы: + +``` +VAX ML +▒▓▓ +LCF/ML (LISP) +░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +SASL (LSECD) +░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +Miranda (SKI) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +LISP int. +░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓ +LISP comp. +░▒▓ +C +▒ +``` + +И был примерно равен Franz LISP. Не самой передовой имплементации Лиспа, но достаточно хорошо работающей для компиляции "главной" программы на Лиспе - Macsyma. + +``` +VAX ML +░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +LISP comp. +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +C +░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▓▓ +``` + +Итак, компилятор ML Карделли генерировал код, сопоставимый по производительности с кодом, который генерировал компилятор Лиспа. Через несколько лет после того, как соответствующего успеха добился Уоррен при имплементации компилятора Пролога. Соответствие этих успехов, правда, неполное. Компилятор Уоррена хорошо себя показал не только на микробенчмарках, но и при компилировании более серьезной программы - самого себя. Но, в отличие от Уоррена, Карделли не писал свой компилятор на языке, который тот компилировал. Может быть из-за того, что интерактивный компилятор слишком тесно интегрирован с рантаймом, а ML не самый подходящий язык для имплементации рантайма. Может быть потому, что не считал, что системные требования такого компилятора позволили бы им пользоваться. Попытаются использовать компилятор Карделли для имплементации ML не сразу и другие люди. И это уже другая история. + +Литература +========== + +[ALGOL80]: AB45.4.2 ALGOL 68 Implementations - ALGOL 68C - Release 1, Algol Bulletin No. 45, January 1980 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A45/P42.HTM +[Augu84]: Lennart Augustsson, A compiler for lazy ML. LFP '84: Proceedings of the 1984 ACM Symposium on LISP and functional programming August 1984 Pages 218–227 doi:10.1145/800055.802038 +[Augu89]: L. Augustsson, T. Johnsson, The Chalmers Lazy-ML Compiler. In The Computer Journal, Volume 32, Issue 2, 1989, Pages 127–141 DOI:10.1093/comjnl/32.2.127 +[Birr77]: Andrew Birrell. System Programming in a High Level Language. Ph.D. Thesis, University of Cambridge. December 1977. +[Card80]: Luca Cardelli, The ML Abstract Machine. https://smlfamily.github.io/history/Cardelli-ML-abstract-machine-1980.pdf +[Card82a]: EDINBURGH ML by Luca Cardelli, March, 1982. A README file accompanying the distribution of Cardelli's ML Compiler for VAX-VMS. https://smlfamily.github.io/history/Cardelli-Edinburgh-ML-README-1982_03.pdf +[Card82b]: Luca Cardelli, Differences between VAX and DEC-10 ML. March, 1982. https://smlfamily.github.io/history/Cardelli-mlchanges_doc-1982_03.pdf +[Card82c]: Luca Cardelli, David MacQueen. Polymorphism: The ML/LCF/Hope Newsletter I, 0. November 1982 +[Card82d]: Cardelli, Luca. "Algebraic approach to hardware description and verification." (1982). +[Card83]: Cardelli, Luca. “The Functional Abstract Machine.” (1983). +[Card83b]: Pre-Standard ML under Unix, August 1983. http://lucacardelli.name/Papers/MLUnix.pdf +[Card84]: Luca Cardelli, Compiling a Functional Language in LFP '84. 1984 DOI:10.1145/800055.802037 +[Card84b]: Luca Cardelli, ML under Unix Pose 4. 4/5/84 http://lucacardelli.name/Papers/MLUnix%20Pose%204.pdf +[Card07]: Luca Cardelli, An Accidental Simula User, ECOOP 2007, Dahl-Nygaard Senior Prize, August 2, 2007. +[Card12]: Luca Cardelli, Scientific Biography. May 2012 http://lucacardelli.name/indexBioScientific.html +[Card21]: Luca Cardelli, Curriculum Vitae. 2021 http://lucacardelli.name/indexBio.html +[Dama84]: Luís Damas. “Type assignment in programming languages.” (1984). +[Dybv06]: R. Kent Dybvig. 2006. The development of Chez Scheme. In Proceedings of the eleventh ACM SIGPLAN international conference on Functional programming (ICFP '06). Association for Computing Machinery, New York, NY, USA, 1–12. doi:10.1145/1159803.1159805 +[Gord80]: Michael Gordon, Locations as first class objects in ML. https://smlfamily.github.io/history/Gordon-ML-refs-1980.pdf +[Holm98]: Holmevik, Jan Rune. "Compiling Simula: A historical study of technological genesis." (1998) https://staff.um.edu.mt/jskl1/simula.html +[Joy86]: Joy, William N., Susan L. Graham, Charles B. Haley, Marshall Kirk McKusick, and Peter B. Kessler. "Berkeley Pascal User’s Manual Version 3.1− April 1986. +[Lieb81]: Lieberman, Henry, and Carl Hewitt. "A Real Time Garbage Collector Based on the Lifetimes of Objects" AI Memo No. 569A October, 1981. +[MacQ14]: Luca Cardelli and the Early Evolution of ML, by David MacQueen. A paper presented at the Luca Cardelli Fest at Microsoft Research Cambridge on Sept. 8, 2014. +[MacQ15]: MacQueen, David B. The History of Standard ML: Ideas, Principles, Culture https://www.youtube.com/watch?v=NVEgyJCTee4 +[McKus]: Marshall Kirk McKusick. A BERKELEY ODYSSEY: Ten years of BSD history. +[Miln93]: Frenkel, Karen A. and Robin Milner. “An interview with Robin Milner.” Commun. ACM 36 (1993): 90-97. DOI:10.1145/151233.151241 +[Miln2003]: interview with Robin Milner, held in Cambridge on the 3. September 2003 http://users.sussex.ac.uk/~mfb21/interviews/milner/ +[Nebe83]: Nebel, B. (1983). Ist Lisp Eine ‘Langsame’ Sprache?. In: Neumann, B. (eds) GWAI-83. Informatik-Fachberichte, vol 76. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-69391-5_3 +[Sait82]: N. Saito. ML System on Vax Unix. README for Saito’s Unix port of VAX ML, March 1982. +[Thom76]: Christopher Mark Thomson. The Run-Time structure of an ALGOL 68 Student Checkout Compiler. Master's thesis, Department of Computing Science, University of Alberta, 1976. +[Thom78]: C. Thomson. ALGOL 68 Compiler for IBM S/370. Announcement. SIGPLAN Notices, Volume 13, Number 11 (November 1978), page 11. diff --git a/index.md b/index.md index e7565a7..99c6472 100644 --- a/index.md +++ b/index.md @@ -1,7 +1,8 @@ История применения и оценки функционального программирования ======= -[Часть 0: что было, когда функционального программирования не было.](prehist.md) +[Часть 0: Что было, когда функционального программирования не было.](prehist.md) +[Часть 1: Первые компиляторы.](compilers.md) --------------------------- @@ -11,6 +12,8 @@ --------------------------- +[Обновление 2024-02-29](compilers.md#часть-1-первые-компиляторы) + [Обновление 2024-01-11](prehist.md#сколько-тысяч-слов--все-впустую) [Обновление 2024-01-03](prehist.md#декабрьский-апдейт)