1
Fork 0
mirror of https://github.com/thegeneralist01/fphistoryru synced 2026-01-10 14:10:24 +01:00
fphistoryru/compilers.md
2024-05-29 22:07:53 +05:00

171 KiB
Raw Blame History

История применения и оценки функционального программирования.

Часть 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 почти как у Бурсталла:

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 Карделли хотели что-то такое использовать, то язык бы выглядел несколько иначе. В примерах работа со списками происходит по старому:

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 не сразу и другие люди. И это уже другая история.

Редукция графов и как её избежать

Если б не Девид Тернер, я не уверен, что занимался бы функциональным программированием.
Л. Августссон [Augu23].

Первая глава истории ФЯ разворачивалась в уже знакомом нам Эдинбурге, в котором прошла значительная часть предыстории. Но в этой главе мы отправляемся в новое для истории ФЯ место, шведский город Гетеборг.

Леннарт Августссон

Леннарт Августссон (Lennart Augustsson) один из первых героев нашей истории, личная история которого уже более-менее нормальна для программиста сегодняшнего дня. Он интересовался [Augu21] компьютерами с детства, написал первую программу в школе в шестнадцать лет, выучился программировать по книге о языке BASIC и даже владел персональным компьютером после окончания университета.
Правда, компьютером, которым он пользовался в школе был PDP-8, а перед успешным изучением BASIC он безуспешно попытался научился программировать по книге с названием вроде "FORTRAN для тех, кто знает ALGOL". Но, понятно, что не все стало обычным сразу.
Августссон закончил Технический университет Чалмерса (Chalmers University of Technology) где учился на инженера-электротехника, специальностей связанных с компьютерными науками там пока-что не было. Пока Августссон был студентом, он больше интересовался системным программированием и параллельными вычислениями и мало что знал о функциональном программировании. Слышал только о языках без операции присваивания и не представлял как такое вообще возможно. Как мы знаем, практически все языки без операции присваивания на тот момент назывались SASL и Августссон слышал именно об одном из них.

SIMPLE Made Easy

Впервые увидел ФЯ Августссон только в 1980 году, начав работу над своей диссертацией. На курсе Сёрена Хольмстрёма (Sören Holmström) по денотационной семантике. Хольмстрём использовал версию SASL из Сент-Эндрюса (и не попал в список известных пользователей SASL Тернера [Turn19]). SASL и чистое, ленивое функциональное программирование вообще произвели на Августссона сильное и положительное впечатление. Он прочел статью [Turn79] Тернера про комбинаторный интерпретатор. SASL из Сент-Эндрюса использовал прокрастинирующую SECD машину. Но её описание было, скорее всего, менее доступно. И Тернер писал о комбинаторном интерпретаторе как о важном шаге вперед. Так что Августссон сделал то, что часто делали имплементаторы ФЯ в 80-е. Имплементировал еще один SASL. Как обычно, SASL не так уж сильно походил на остальные SASLы, но хотя-бы не назывался так же как они. Августссон сначала не называл язык никак, но позднее, по совету своего научрука Бенгта Нордстрёма (Bengt Nordström), решил таки дать ему название и назвал SIMPLE.
От имплементаций SASLов Тернера он отличался тем, что компилятор в комбинаторы был написан на самом SIMPLE. Сам комбинаторный интерпретатор Августссон написал на C и писал в том числе и на своем персональном компьютере. В 1980-ом году C еще не был особенно популярным языком, тем более на персональных микрокомпьютерах. Не понятно, что Августссон использовал. BDS C на CP/M? Small c?
Хотя Тернер в своей статье утверждал, что комбинаторный интерпретатор на порядок улучшает производительность ленивых ФЯ по сравнению с ленивой SECD, из измерений, которые делали как раз Августссон со своим коллегой, мы уже выяснили, что производительность комбинаторного интерпретатора оставляла желать лучшего. Да, для бутстрапа августссоновского транслятора в комбинаторный код в 100-200 строк на SASL-образном языке этого было достаточно, но и только.
Так что Августссон объединил усилия с другим важным героем нашей истории - Томасом Йонссоном (Thomas Johnsson). Они считали, что у чистых, ленивых ФЯ привлекательные свойства, но их применение сдерживает отсутствие эффективных имплементаций.

SKI не едут

Августссон с Йонссоном поставили перед собой цель: сделать ленивые ФЯ практичным инструментом, позволяющим писать "настоящие" программы, такие как компилятор. Которые могут быстро работать на обычных современных компьютерах.
Правда, известные им способы имплементации ленивых ФЯ пока что такого не позволяли. По оценкам Августссона с Йонссоном комбинаторный интерпретатор Тернера работает примерно в сто раз медленнее, чем энергичный код [John95]. Все потому, что шаг интерпретации слишком маленький. Комбинаторы из тернеровского набора делают слишком мало работы по сравнению с прочими издержками интерпретатора, который их применяет.
Не смотря на то, что решение Тернера не выдерживало проверки, Йонссон с Августссоном продолжали считать, что саму проблему он обозначил верно. Проблема с производительностью у функциональных языков из-за имплементации свободных переменных. И если нет свободных переменных - нет проблем. Так что они продолжили работать над комбинаторным подходом, но Йонссон решил, что комбинаторы должны быть существенно больше.
Так что, для начала, Йонссон с Августссоном добавили к набору интерпретируемых комбинаторов большие комбинаторы. Например, функции для работы со списками, такие как map и fold. Поскольку код, пока что, в основном состоит из работы со списками, Августссон с Йонссоном оценили, что это может увеличить скорость на десятичный порядок.
Это не особенно согласуется с их же собственными, сделанными годы спустя, замерами производительности интерпретатора Миранды, в котором Тернер использует ту же идею. Может быть у Тернера имплементировано недостаточно списочных функций, может быть - речь о разных наборах микро-бенчмарков.
Так или иначе, результаты были недостаточно хороши для наших героев-имплементаторов. К счастью, у Йонссона появилась идея получше.
В октябре 1981-го года в городе Портсмут, Нью-Гэмпшир состоялась важная конференция по функциональному программированию (FPCA '81), к которой мы еще вернемся.
В один из дней, после докладов Йонссон с Августссоном обсуждали с Тернером комбинаторные интерпретаторы [Augu23]. Может быть, как раз во время этого разговора (или нет) Йонссон решил, что каждую определяемую пользователем функцию-комбинатор нужно добавлять в интерпретатор как правило перезаписи графов. Пусть компилятор конструирует специализированный комбинаторный интерпретатор для каждой программы [John95].
По воспоминаниям Августссона, Тернеру идея понравилась. Почему же он сам ей не воспользовался? Об этом мы еще расскажем позднее. Августссон с Йонссоном же решили воспользоваться этой идеей.
Что еще лучше, чем комбинаторы, которые проделывают много работы по редукции графа? Вовсе граф не редуцировать. Не нужно для каждого этапа вычисления арифметического или логического выражения создавать и изменять в куче соответствующий граф объектов. Можно использовать для вычисления только стек с машинными числами. И не нужно пытаться снова редуцировать граф, редукция которого уже была закончена.
Эти ранние идеи Йонссон описал в неотсканированном отчете "Генерация кода для ленивых вычислений" 1981-го года. А Августссон на основе этих идей написал на C компилятор функционального языка. В наши дни Августссон рассказывает [Augu21], что язык не имел названия. Но в названии его описания 1982-го года, автором которого является Августссон, язык назван FC. Йонссон в своих статьях [John84] [John95] называет язык fc.
Описание языка не отсканировано и не дошло до нас. Код на нем не сохранился. Известно [John95], что это был небольшой нетипизированный ФЯ без локальных функций и паттерн-матчинга. Никаких свободных переменных. Программист на FC мог определять только комбинаторы. Из которых компилятор этого языка и собирал специализированный комбинаторный интерпретатор на VAX-11 ассемблере. Сначала у имплементации не было даже сборщика мусора. Кто бы мог подумать, насколько это обычная вещь.
Воспоминания Августссона разных лет [Augu21] [Augu23] несколько противоречат друг другу, так что сложно датировать появление этого компилятора точно. Может он был написан в летние каникулы 82-го, а может и 81-го. В этом случае Йонссон придумал, а Августссон имплементировал идеи задолго до конференции и только познакомили Тернера с ними на ней. Ссылки на неотсканированные отчеты не добавляют ясности. Отчет Августссона с описанием FC хоть и датирован 82-м годом, имеет меньший порядковый номер, чем отчет Йонссона 81-го года. Может быть просто более поздней редакцией, как отчеты Тернера с описанием SASL, имевшие много версий и редакций.
Августссон, начавший свою карьеру имплементатора ФЯ с написания очередного SASL на самом себе, не переписал компилятор FC с C на FC. Но, в отличие от тернеровских SASL-ов, на этом нетернеровском "SASL" написали какое-то заметное количество функционального кода. Мы не знаем сколько именно, но предполагаем, что тысячи строк. Августссон с Йонссоном использовали его для написания компилятора другого функционального языка. И, в отличие от Карделли, свои диссертации они напишут про разработку этого компилятора.

Ленивый ML

FC не поддерживал вложенные функции. Но Августссон с Йонссоном не хотели довольствоваться компилируемым KRC. Стремление Тернера к ФП-минимализму они не разделяли и ограниченный набор фич FC был только вынужденным результатом стремления как можно скорее начать писать компилятор ФЯ на ФЯ, а не на C. Они хотели в языке полноценное вложение функций. И не только.
Особенно после того, как Сёрен Хольмстрём и Кент Петерссон (Kent Petersson) привезли из Эдинбурга ленту с ML. По всей видимости, LCF/ML. В Гетеборге LCF портировали на VAX-11 c BSD и разрабатывали на его основе доказатель для интуиционистской теории типов Мартин-Лёфа.
Компилятор Карделли тоже попал в Гетеборг. Карделли записал [Card82c], что первую же его версию получил Лассе Остергаард (Lasse H. Ostergaard). Хольмстрём и Йонссон были подписаны [Card82c] на почтовую (еще не электронно-почтовую) рассылку компилятора Карделли. И Августссон с Йонссоном сравнили с его UNIX-версией свои результаты.
Августссон вспоминает [Augu21] как набирал в терминале map функцию или что-то вроде неё и ML REPL выводил для неё тип. Это "выглядело как магия".
Впечатление "магии" производил вывод типов, а не REPL. Не смотря на знакомство с несколькими интерактивными имплементациями ФЯ (SASL 76, LCF/ML, VAX ML) и не только (Franz LISP), Йонссон с Августссоном, по видимому, не имели особого желания делать свой компилятор интерактивным и совершенно точно не сделали его таким.
Они стали писать компилятор для UNIX так, как было принято в это время писать компилятор на UNIX. И это было куда возможнее, чем утверждал Карделли. Как мы помним, обосновывая странность своего компилятора, Карделли утверждал, что компилятор такого необычного языка как ML, очевидно, должен и сам быть необычным. Но вот Йонссон с Августссоном стали писать компилятор еще более необычного языка как портабельный компилятор C Джонсона с парсером на YACC, использованием системного BSD UNIX-инструментария и без всякой интерактивности. Они считали, что и программы на чистом ФЯ, читающие потенциально бесконечную строку и печатающие свой результат как потенциально бесконечную строку, отлично впишутся в юниксовое рабочее окружение.
Августссона с Йонссоном не привлекли абстрактные типы данных и мутабельные ссылки. Они все равно собирались имплементировать чистый ленивый ФЯ как у Тернера, но синтаксически - подмножество LCF/ML и с выводом типов Хиндли-Милнера. Lazy ML.
В отличие от Карделли, они не внесли множества мелких изменений. map написанный на Lazy ML будет работать и на LCF/ML:

letrec map f l = if null l then [] 
                           else f(hd l).map f (tl l)
in
let y = 2
in map (\x. x + y) [1; 2; 3]

Все выглядит, на первый взгляд, [Augu84] как в LCF/ML. Но язык ленивый, так что можно писать и код, который в LCF/ML не заработает:

letrec from n = n.from(succ n) in from 0

Но самый большой дошедший до нас сниппет кода на первой, минимальной версии ленивого ML работает и в ML-е строгом, хотя и происходит от примера для ленивого SASL:

let mod x y = x - (x/y*y) in
let rec filter p l = if null l then []
                     else 
                        if mod (hd l) p = 0 then
                                filter p (tl l)
                        else    hd l . filter p (tl l)
and     count a b = if a > b then []
                    else a . count (a+1) b
in
letrec sieve l = if null l then nil
                 else hd l . sieve (filter (hd l) (tl l))
in 
let primesto n = sieve (count 2 n)
in
primesto 300

Видимо потому, что это один из бенчмарков, который должен был минимально отличаться от версий на других ФЯ с которыми производилось сравнение.
Со временем Йонссон с Августссоном еще расширят это минимальное подмножество LCF/ML. Но если Карделли расширил его не самыми типичными для современных ФЯ самобытными фичами, то LML расширят как раз теми самыми фичами, которые делают ФЯ типичными языками Эдинбургской программы и мы расскажем об этом в специальной части про то, как это делали (почти) все имплементаторы ФЯ 80-х.

Неполная ленивость

Для компилятора LML написанного на FC, Йонссон доработал свои идеи по имплементации ленивости редукцией графов (и её избеганием) и описал их как G-машину.
В апреле 1983-го Йонссон выступил с докладом о ней на коллоквиуме по декларативному программированию в Университетском колледже Лондона. Из этого доклада о G-машине узнали другие разработчики ФЯ, которые выработали похожие идеи независимо. До наших дней запись доклада не дошла. Первое и последнее сохранившееся описание компилятора Lazy ML на FC - доклад [John84] Йонссона с симпозиума по конструированию компиляторов, состоявшегося в июне 1984-го года.
В G-машине есть окружение, но только глобальное. Как мы помним, сложную манипуляцию окружениями авторы LML, вслед за Тернером считают проблемой для производительности.
У G-машины три стека. На один больше чем в FAM потому, что Йонссон не совмещает, как Карделли, указатели с числами. Для неуказателей вроде чисел и булевых значений предназначен отдельный стек. Но G-машина "виртуальнее", чем FAM, структуры и команды которой были воспроизведены достаточно бесхитростно. И три стека в ней не соответствуют трём стекам в рантайме. Там есть один стек в куче для ссылок, который обходит сборщик мусора. Для сборки мусора используется алгоритм Фенихеля-Иохельсона, модифицированный для поддержки объектов кучи, отличающихся от пар. V-cтек неуказателей и D-стек отображаются на обычный системный. Значения из V-стека могут не попасть и на системный стек, компилятор старается разместить их в регистрах, пока они есть. Да, в отличие от Карделли, Йонссон с Августссоном стараются использовать регистры. Сначала они хотели подставлять для команд виртуальной машины шаблоны кода, как у Карделли и Уоррена. И затем оптимизировать получившийся машинный код, находить в нем не особенно хорошие сочетания команд и исправлять их на те, что получше. Карделли рассматривал такую возможность, но не стал с ней связываться. Не стали связываться с ней и Августссон с Йонссоном, но вместо того, чтоб ничего не делать как Карделли, они решили писать более сложный генератор кода, не просто подставляющий код по шаблонам для команд. Как и компиляторы Карделли и Уоррена компилятор LML находит сочетания команд ВМ и выбрасывает ненужные или заменяет группы на специализированные. Но на этом локальном уровне трансформации не ограничиваются. Не могут ограничиваться.
Как имплементировать ML с помощью G-машины? G-машина исполняет только функции без свободных переменных. Поэтому, в отличие от компиляторов Карделли или Уоррена, компилятор Lazy ML осуществляет нетривиальные трансформации кода. ISWIM-подобная расширенная лямбда ML-кода трансформируется с помощью преобразования под названием лямбда-лифтинг.
Йонссон рассматривает [John85] по крайней мере три способа такого преобразования. Не каждый лямбда-лифтинг подходит для имплементации ФЯ одинаково хорошо. Йонссону нужно, чтоб трансформация отвечала ряду требований. Она не должна производить небольшие комбинаторы - иначе все труды по замене интерпретатора тернера были бы напрасными. Она не должна избавляться от рекурсии, заменяя её Y-комбинатором - G-машина эффективно имплементирует рекурсивные комбинаторы. Она должна производить код в котором больше комбинаторов применены полностью или, хотя-бы, к большому числу аргументов за раз - чтоб вместо развесистых деревьев применений можно было бы использовать "векторные применения", аналог плоских замыканий в FAM.
Что Йонссона не особенно сильно беспокоило - это сохранение такого свойства комбинаторного интерпретатора, которое Тернер называл "самооптимизацией", а сегодня называют "полной ленивостью". И Йонссон его не сохранил. Насколько правильным было это решение? Это мы выясним в главе о тех, кто совершенствовал комбинаторный интерпретатор Тернера независимо от героев этой главы.
Из-за более сложных трансформаций и кодогенератора в компиляторе LML больше стадий и проходов, чем в компиляторе Карделли. И будет еще больше.
Внимание к генерации более эффективного исполняемого кода принесло плоды. Результаты микро-бенчмарков еще лучше, чем у VAX ML Карделли.

fib 20 primes 300 insort 100
LML (FC) 1.00 1.00 1.00
VAX ML 0.54 2.40 2.70
LCF/ML (LISP) 50.0 58.0 40.5
SASL (LSECD) 33.7 40.0 32.4
Miranda (SKI) 78.3 24.6
LISP int. 22.8 15.6 17.3
LISP comp. 1.20 2.20 2.16
C 0.50 0.40
Pascal 1.00

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

LML (FC)
░▓
VAX ML
▒▒▓▓
LCF/ML (LISP)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
SASL (LSECD)
░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
LISP int.
░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓
LISP comp.
░▒▓▓

В очередной раз удалось воспроизвести успех Уоррена и Карделли и генерировать код быстрее, чем получается у компилятора Лиспа, не смотря на десятки лет опыта компиляции у лисперов.
И если между имплементацией первого интерпретатора PAL и первого интерпретатора SASL 76 прошло десятилетие, первый компилятор ленивого ФЯ отстал от компилятора строгого только на пару лет. Еще одна демонстрация того, как мало пользы от многолетних "наработок" 60-х и 70-х и как мало было наработано за эти лета.

LML (FC)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓
VAX ML
░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
LISP comp.
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓

Компилятор Уоррена, как мы помним, не только показывал приличную производительность на микробенчмарках, но и был достаточно практичным, чтоб компилировать самого себя. Компилятор Карделли сам себя не компилировал, но Йонссон с Августссоном с самого начала собирались повторить и этот успех Уоррена и повторили, переписав компилятор LML на LML. Это, правда, уже другая история.
На этом закончилась история FC, первого ленивого ФЯ, на котором написали компилятор ленивого ФЯ, но история LML только начинается.
Комплекс идей Августссона и Йонссона оказался успешным, но, к сожалению, они не публиковали абляционный анализ, который продемонстрировал бы вклад каждой идеи в отдельности и их сочетаний. Но это сделали другие разработчики компиляторов ленивых ФЯ.

Еще ортогональнее

Вернемся в другое, уже хорошо знакомое нам место действия истории ФП - Кембридж - где уже предпринимали пару неудачных попыток имплементировать ФЯ. В 80-е в Кембридже смело двинулись навстречу новым неудачам, начав сразу несколько таких проектов. В них поучаствовал и один из наших старых знакомых - Майкл Гордон. В 1981 Гордон начал работу в Кембриджском Университете, где стал научным руководителем Джона Фейрберна (Jon Fairbairn), автора языка Ponder [Fair82] [Fair85] [Fair86].
Если вы хаскелист и фамилия Фейрберн кажется вам смутно знакомой, то это возможно потому, что Кметт какое-то время популяризировал выражение "порог Фейрберна". И да, это тот самый Фейрберн.
В начале 80-х в Кембридже как раз подходили к концу попытки имплементировать Algol 68, и Ponder связывает с этим языком некоторое идейное родство. Чего же не хватало в Algol 68? Нет, не того, о чем вы подумали. В нем не хватало "ортогональности". Не смотря на все желание Вейнгаардена и его союзников, "ортогональность" языка, который так и не смогли сделать удобным функциональным и даже не собирались делать ленивым - существенно ограничена. В Ponder эти недостатки исправлены.
Ponder спроектирован в соответствии с принципами ссылочной прозрачности и "ортогональности" (даже Фейрберн употребляет это слово в кавычках). Ponder задуман быть простым функциональным языком с нормальным порядком вычисления и предназначен для написания больших программ.
Насколько простым? Это типизированная лямбда (не сильно) расширенная средствами группировки функций, типами данных и мощной системой объявления операторов. Все остальное предполагается описывать на самом Ponder.
Даже конструкторы составных типов - рекорды и объединения, которые были в Algol 68 частью языка, в Ponder нужно кодировать как функции. Кодируются с помощью функций и списки

CAPSULE RECTYPE LIST [T] == !R. (BOOL -> T -> LIST [T] -> R) -> R;

LET nil == !T. LIST [T]: !R. (BOOL -> T -> LIST [T] -> R) f -> R:
           f true abort abort;

LET cons == !T. T new_head -> LIST [T] tail -> LIST [T]:
            !R. (BOOL -> T -> LIST [T] -> R) f -> R:
            f false new_head tail;

LET head == !T. LIST [T] l -> T:
            l (BOOL null -> T h -> LIST [T] tail -> h);

LET tail == !T. LIST [T] l -> LIST [T]:
            l (BOOL null -> T h -> LIST [T] tail -> tail);

LET null == !T. LIST [T] l -> BOOL:
            l (BOOL null -> T h -> LIST [T] tail -> null);
           
SEAL LIST;

То же и с управляющими конструкциями. Например, алгол-68-образный if

CAPSULE TYPE IF_FI [T] == T;
CAPSULE TYPE BOOL == !T. T -> T -> T;
CAPSULE TYPE TE [T1, T2] == PAIR [T1, T2];
BRACKET IF FI == !T. IF_FI [T] if_fi -> T: if_fi;

PRIORITY 9 THEN ASSOCIATES RIGHT;
INFIX THEN == BOOL b -> !T. TE [T, T] te -> IF_FI [T]:
              BEGIN LET then_part, else_part == te;
                    b then_part else_part
              END;

PRIORITY 9 ELSE ASSOCIATES RIGHT;
INFIX ELSE == !T1. T1 then -> !T2. T2 else -> TE [T1, T2]:
              pair then else;

SEAL IF FI;
SEAL BOOL;
SEAL TE;

И вон те BEGIN с END, разумеется, определены тем же способом.
PAIR хоть и объявлен в библиотеке как три функции, все-таки имеет поддержку компилятора, чтоб можно было разбирать пары в LET a, b == ....
Этот разбирающий пары LET используется и для кодирования абстрактных типов, которыми конструкция для группировки функций не является. Не смотря на название ключевого слова SEAL, конструкция CAPSULE ничего не инкапсулирует, все объявления видны и спрятать их можно только в локальном LET. Но CAPSULE позволяет решить проблемы проверки типов оборачиванием, как и АТД в LCF/ML.
Все это торжество "ортогональности" невозможно без исправления еще одной проблемы Algol 68 - отсутствия параметризованных типов. И выводимые параметризованные типы ML для этого недостаточно выразительны, так что Фейрберн пожертвовал выводом типов для параметров, но не для возвращаемых значений. Получившаяся система выглядит похожей на систему Рейнольдса, но на самом деле родственна работе МакКвина и др. [MacQ82]. Так что часть аннотаций типов обязательны, но меньшая часть, чем в Algol 68 и HOPE. В примерах кода !T. означает forall t..
В Ponder можно перегружать операторы, но не обычные декларации функций. Как и в Алгол 68. Как в HOPE перегрузка плохо взаимодействует с параметрическим полиморфизмом, но в отличие от авторов HOPE, Фейрберн не собирается ничего с этим делать. Параметрические типы с ограниченным полиморфизмом, как планировали делать в HOPE и других языках, один из которых как раз тоже разрабатывают в Кембридже, показались ему слишком сложными.
Перегрузка в Ponder позволяет объявить cons-оператор, с помощью которого можно сконструировать список без использования nil.

INFIX :: == !T. T h -> T t -> cons h (cons t nil);
INFIX :: == cons;

Наш обычный пример [Fair85]:

LET map == !T1, T2. (T1 -> T2) f ->
          BEGIN LET_REC mapf == LIST [T1] l -> LIST [T2]:
                              IF null l
                              THEN nil
                              ELSE f (head l) :: mapf (tail l)
                              FI;
                mapf
          END;

LET y == 2; map (INT x -> x + y) (1 :: 2 :: 3)

В стандартной библиотеке Ponder, которая называется, как и в Algol 68, "стандартная прелюдия", помимо этого map есть и другие ФВП. Помимо прочих ФВП для разбора типов данных, есть foldr, называющийся right-gather и foldl под названием left-gather и с современным порядком аргументов (b -> a -> b) -> b -> [a] -> b, в отличие от HOPE и SASL. Есть filter и compose и как функция и как оператор, но трудно сказать какой символ использовался в реальном коде, в статьях вместо него используется .
Всю эту "ортогональность" Фейрберн планирует использовать для написания "больших" программ, как и авторы LML. Это требует эффективной имплементации. Первоначально, Ponder был имплементирован наиболее известным в то время способом имплементации ленивого ФЯ - комбинаторным интерпретатором Тернера. И, также как Августссон с Йонссоном, Фейрберн не доволен SKI-интерпретатором. Интерпретатор Тернера тратит только один процент времени на переписывание графа, отмечает [Fair85] Фейрберн, а все остальное время только готовится к следующему переписыванию.
К этому времени, как мы выяснили, у Йонссона в Гетеборге уже были идеи о том, как имплементировать ленивый ФЯ лучше, но эти идеи пока что не распространились за пределами Гетеборга.
Но распространились похожие идеи других исследователей, так что мы покидаем Кембридж, чтоб не надолго вернуться в два других центра развития функционального программирования.

Джон Хьюз и великие комбинаторы

Мы оставили Оксфорд вместе с Тернером, после его неудачной попытки написать компилятор PAL. Но история ФП в Оксфорде на этом не закончилась. И наш новый герой этой истории, но, вероятно, уже известный читателям - Джон Хьюз (Robert John Muir Hughes).
В 1975 [Hugh2005], между окончанием школы и поступлением в университет, Хьюз прочел книгу о Лиспе. В наши дни он вспоминает [Hugh23], что книга была не особенно хорошая, но в то время произвела на Хьюза серьезное впечатление и Лисп ему очень понравился. У Хьюза, правда, не было доступа к имплементации Лиспа, так что он написал её самостоятельно в оксфордской Группе по изучению программирования.
Эти события Хьюз считает своим знакомством с функциональным программированием. Как мы выяснили в предыдущей части, программирование на Лиспе в 75-ом году было не особенно функциональным, но трудно с уверенностью сказать это о Лиспе Хьюза.
Лисп понравился потому, что Хьюз, когда писал на нем, чувствовал себя продуктивнее, чем когда писал на других (неназванных) языках.
Когда Хьюз стал студентом в Оксфорде, он принял участие в имплементации компилятора языка Рейнольдса GEDANKEN. И если судить по следам, которые оставил этот компилятор, а точнее по их отсутствию - имплементация компилятора GEDANKEN не была намного успешнее, чем компилятора PAL до того. Хьюз знаком с PAL и пользовался им. Удивительно, но Хьюз не пользовался SASL, но использовал KRC, который применяли в Оксфорде для преподавания в начале 80-х.
Статья Тернера о комбинаторном интерпретаторе [Turn79] произвела на Хьюза впечатление, как и на Августссона. И как и Августссон, Хьюз имплементировал собственный язык с помощью этой техники.
И Хьюз хотел [Hugh2005] сделать программирование проще, чтоб программы были "короткими, красивыми и элегантными". И программы на ленивых ФЯ он посчитал как раз такими. Только вот программы на ФЯ писать не будут, если имплементации останутся такими медленными.
Так что Хьюзу пришлось придумывать как сделать их быстрыми под руководством Бернарда Суфрина (Bernard Sufrin). Как и Йонссон, Хьюз начал с интерпретатора Тернера и решил продолжить, делая комбинаторы больше. Для больших комбинаторов он придумал название "суперкомбинаторы".
Хьюз написал на BCPL [Hugh82] комбинаторный интерпретатор, который мог использовать не только комбинаторы из тернеровского набора, но и загружать любые комбинаторы, написанные на BCPL.
Хьюз написал также компилятор, который компилирует ФЯ через лямбда-образный промежуточный код в BCPL-комбинаторы, которые компилируются компилятором BCPL и используются комбинаторным интерпретатором. По паре некрупных примеров корда вроде такого:

el n s = if n = 1 then hd s else el (n - 1) (tl s) fi

видно, что язык, который Хьюз компилирует - это язык выражений, а не уравнений, вроде LCF/ML, но с больше характерной для Тернера легкостью синтаксиса. Тот ли это язык, первоначально имплементированный Хьюзом с помощью техники Тернера, о котором Хьюз вспоминает выше? Мы не знаем.
В своей диссертации [Hugh83] Хьюз описывает алгоритм фрагментами кода на Паскале, Прологе и функциональном языке. Или может только функциональном псевдокоде. Хьюз называет его "SASL-образная нотация с некоторыми расширениями". Да, наши дни Хьюз вспоминает, что не пользовался SASL. И это объясняет то, что нотация не особенно SASL-образна. Мы еще рассмотрим её подробнее, но не в этой главе.
В отличие от Гетеборгских имплементаторов, больше прочих тернеровских идей Хьюза захватила идея "само-оптимизации", для которой Хьюз придумал её современное название - "полная ленивость". Разумеется, он не собирался отказываться от полной ленивости, как отказались авторы LML, и собирался при переходе к большим комбинаторам сохранить её любой ценой.
Помните, как Йонссон отверг несколько способов лямбда-лифтинга из-за того, что они дают неподходящие комбинаторы, скомбинированные неподходящим образом? Может быть Йонссон решил, что они не подходят из общих соображений, но если и проверил на опыте, то результаты не описал. Ну а Хьюз компилировать в комбинаторы похожим неподходящим способом попробовал совершенно точно. И его результаты опубликованы [Hugh82].

ack hanoi ack(nc) fac append prim era uni e
Turner 1.00 1.14 1.00 1.03 1.00 1.27 1.12 1.20 1.82
Hughes 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00
Turner
░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓
Hughes
░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓
x100
▓

Некоторое ускорение заметно, но до ускорения в сто раз еще далеко.
Когда работа над диссертацией Хьюза уже подходила к концу, в апреле 83-го, на организованном [SERC83] Саймоном Пейтон-Джонсом коллоквиуме по декларативному программированию в Университетском колледже Лондона, Йонссон выступил с докладом о G-машине. Так мир узнал, как быстро можно редуцировать графы, а избегать их редукции - еще быстрее, если полную ленивость не сохранять. G-машина успела попасть в обзор похожих работ Хьюзовской диссертации. Но, на момент её завершения в июле 1983-го года, Хьюз утверждает, что "реалистичной" имплементации ФЯ с помощью суперкомбинаторов пока нет. Но в Кембридже Фейрберн собирается использовать суперкомбинаторы для имплементации Ponder.
А из Оксфорда важного центра имплементации ленивых ФЯ пока не получилось и, защитив диссертацию, Хьюз отправился работать в Гетеборг.

Полная ленивость

Алгоритм, разработанный Хьюзом, игнорирует некоторые прагматические свойства вычисления. Джон Фейрберн, Разработка и имплементация простого типизированного языка, основанного на лямбда-исчислении [Fair85].

Делать комбинаторы для переписывания графов побольше еще не достаточно. Нужно еще и избегать работы с ними где это возможно. А где это возможно? На это дает ответ анализ строгости.
В конце 70-х наш с вами старый знакомый Бурсталл посетил Гренобль, где узнал [Cousot] о работах Патрика и Радии Кузо (Patrick Cousot, Radhia Cousot) над абстрактной интерпретацией.
В Эдинбурге, под руководством Бурсталла и Милнера, выпускник Кембриджа Алан Майкрофт защитил диссертацию в 81-ом и работал в Эдинбурге до 83-го года [Mycr16]. Майкрофт продолжал работы Кузо, применив их наработки к функциональным языкам и изобретя анализ строгости [Clac85].
"Функциональными" языки в данном случае можно называть только с большой натяжкой потому, что анализ строгости Майкрофта работал только для языка первого порядка, без поддержки ФВП. И для "плоского" домена, так что списки не поддерживались тоже, что в это время считалось едва ли не большей проблемой.
Клек (Clack) и Пейтон Джонс пишут [Clac85], что Йонссон изобрел анализ строгости независимо, описав его в очередном не дошедшем до нас отчете октября 81-го года, который Клек с Пейтон Джонсом (мы надеемся) читали. И утверждают, что метод Йонссона был еще хуже, чем метод Майкрофта. Но в 83-84 Майкрофт работал в университете Чалмерса [Mycr16] в Гетеборге и обсуждал с Йонссоном его работы [John84].
Что будет, если улучшить метод Хьюза анализом строгости Майкрофта?

quicksort tak 18 12 6 nfib 20
Hughes 1.00 1.00 1.00
Hughes+Mycroft 0.99 0.83 1.00
Hughes
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
Hughes+Mycroft
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓

Ох.
Суперкомбинаторы, которые получаются в результате работы алгоритма Хьюза оставляют желать лучшего. Они могут быть слишком малы, комбинироваться в более ленивый код, чем раньше, могут быть недостаточно оптимально применены, неэффективно имплементируют рекурсию. На некоторые проблемы указал уже сам Хьюз в своих работах [Hugh82] [Hugh83], но не исправил. Исправлять пришлось Фейрберну, который пытался сделать это сохранив, в отличие от Йонссона, свойства "полной ленивости".
Диссертация Фейрберна и новый, суперкомбинаторный компилятор Ponder были готовы к декабрю 84-го года, что делало компилятор Ponder одним из последних первых компиляторов ФЯ. Он написан на Algol68c. Собираются ли его переписать на Ponder? К этому мы вернемся в части про написания компиляторов ФЯ на ФЯ.
Фронтенд компилятора Ponder, состоящий из парсера, тайпчекера и принтера выдает текст, код на промежуточном языке - ЛИ с аннотациями. Этот вывод получают бэкенды для разных платформ, для важнейших для разработчиков ФЯ 80-х платформ: VAX, Motorola 68K и некоторых других. Бэкенд разбирает код на промежуточном языке, трансформирует лямбду в суперкомбинаторы, делает анализ строгости получившихся суперкомбинаторов и генерирует машкод нужной платформы. Место разделения на фронтенд и бэкенд может показаться странным, но скорее всего объясняется наличием SKI-бэкенда, с которого имплементация началась, но который не стал ненужным после добавления суперкомбинаторной машинерии. Почему? Это другая история, к которой мы еще вернемся.
Технически, компилятор Ponder пока что не позволяет выяснить, насколько плохо влияет на производительность решение (не) имплементировать полную ленивость. Дело в том, что генератор кода в основных бэкендах хуже, чем кодогенератор компилятора LML и похож скорее на генератор кода в VAX ML, с теми же подстановками кода инструкций ВМ макроассемблером и оптимизациями, заменяющими некоторые сочетания таких инструкций ВМ. Именно так для PAM (Ponder Abstract Machine) имплементирована и оптимизация хвостового вызова.
И кодогенератор в компиляторе Ponder плохой потому, что техники написания хорошо известны, объясняют имплементаторы Ponder, и потому их применение в исследовательском компиляторе не оправдано из-за недостаточной новизны.
Но не все имплементаторы бэкендов придерживались такого мнения. Тиллотсон (Mark Tillotson) имплементировал бэкенд для мэйнфрейма IBM 3081 и этот бэкенд делал обычные для того времени оптимизации. Но разработчиков ФЯ больше интересовали миникомпьютеры и рабочие станции на VAX или Motorola 68K, а не малодоступные мэйнфреймы. И работы Тиллотсона, по видимому, действительно не были так уж интересны имплементаторам ФЯ и не отсканированы, до нас не дошли.
Фейрберн сравнил свои наработки с алгоритмом Хьюза, а также с еще несколькими результатами неудачных попыток имплементировать ФЯ, которые уже знакомы нам из части 0 этой истории.

quicksort tak 18 12 6 nfib 20
Hughes 2.34 21.3 4.20
Hughes+Mycroft 2.31 17.6 4.20
Fairbairn 1.14 4.95 2.91
Fairbairn+Mycroft 1.00 1.00 1.00
Cambridge Lisp 0.98
BCPL 0.52
Algol 68c 0.24
Hughes
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
Hughes+Mycroft
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
Fairbairn
░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
Fairbairn+Mycroft
░░░░░░░░░░░░░░▒▒▓▓▓▓▓▓▓▓

Это результаты на Motorola 68K. Насколько работы Тиллотсона позволяли компилятору Ponder сравниться с компилятором LML - точно не известно. Фейрберн пишет [Fair85], что эти оптимизации ускорили код раз в пять, что, по всей видимости, могло делать бэкенд сравнимым с бэкендами Algol68c и LML, авторы которых так или иначе заботились о качестве генерируемого кода. Но непосредственные сравнения не были сделаны или до нас не дошли.
Как видите, Ponder - первый из первых компиляторов ФЯ, который не смог обойти Лисп, даже на собственных бенчмарках и такой как Cambridge Lisp, далеко не из самых лучших.
Фейрберн отмечает, что в случае программы, в основном оперирующей со списками, пользы от анализа строгости нет и для суперкомбинаторов, полученных его улучшенным методом. Алгоритм Майкрофта бессилен перед ленивыми структурами данных. Но у коллеги Фейрберна были кое-какие идеи о том, как решить эту проблему.
Усовершенствованный алгоритм анализа строгости для компилятора Ponder разрабатывал Стюарт Рэй (Stuart Charles Wray), тоже работавший в это время над диссертацией под руководством Гордона. Клек с Пейтон Джонсом считают [Clac85], что алгоритм Рэя не происходит от алгоритма Майкрофта.
Рэй утверждает [Wray86], что его анализ строгости быстрее анализа Майкрофта и может работать на любой программе, выраженной как суперкомбинаторы. Анализ определяет не только ленивые и строгие параметры, но и неиспользуемые и "опасные", вычисление которых остановит или зациклит программу.
Для выявленных анализом Рэя неиспользуемых параметров не генерируется код, но подавляющее большинство таких параметров и так не переживают трансляцию в суперкомбинаторы. Выявленные тем же анализом "опасные" параметры не пытаются вычислять, вместо этого во время выполнения выводится информация об ошибке.
Рэй называет улучшение от анализа строгости стоящими затраченных усилий, но не такими потрясающими, как можно было надеяться. Посмотрим, насколько они не потрясающие.

nfib 20 ins.sort qsort dragon 10
Hughes 9.92 2.74 1.64 1.57
Fairbairn 2.68 1.03 1.08 1.21
F + Wray 1.00 1.00 1.00 1.00
BCPL 0.51 0.21
Hughes
░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░
Fairbairn
░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░
Fairbairn + Wray
░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░

Поспорить сложно, результат не особенно впечатляет.
Рэй пишет, что промежуточный код для nfib "идеальный" и вся разница с BCPL объясняется плохим кодогенератором в компиляторе Ponder, который и в имплементации BCPL, как мы помним, оставляет желать лучшего.
Но главной целью было, как мы помним, адоптировать к ФЯ анализ строгости, который у Майкрофта применялся к языку не особенно функциональному. Рэй расширяет свой метод, чтоб находить больше возможной строгости в коде с ФВП. Анализатор строгости первого порядка работает быстро. Даже с программами больше 2KLOC справляется всего за несколько секунд. Анализатор языка второго порядка, анализирующие недоступные методу Майкрофта ФВП, работает примерно в десять раз медленнее.
Какие были результаты у этого десятикратного роста вычислительных затрат? Рэй не приводит даже таблицу с результатами. Потому, что они идентичны результатам анализатора, который поддерживает только язык первого порядка.
Такой результат удивил Рэя и он сравнил коды виртуальной машины получаемые с анализаторами первого и второго порядка. Из десятка тысяч команд ВМ отличий нашлось только с десяток. Таких, что влияли хоть как-то на производительность - только пять.
Но специализированные версии ФВП в зависимости от свойств передаваемых в них функций в компиляторе Ponder не генерировали и не собирались. Из-за того, что вырастет объем кода.
Анализ строгости, работающий со списками, Рэй не имплементировал вовсе. Тут тоже нужно будет специализировать код для получения каких-то результатов, и делать это никто не собирается.
Ну что ж. В 80-е, конечно, наступило время успехов ФП, но, похоже, не для всех.
Над чем еще работали в Кембридже?

Чайники Рассела

К сожалению, некоторые хорошие идеи из этого и родственных языков так и не попали в мейнстрим. К счастью, плохие идеи тоже не попали.
Х. Боэм [Boeh12]

В предыдущих главах мы выяснили, что невозможные в 60-х и 70-х годах имплементации функциональных языков, написанные с нуля, стали очень даже возможны в 80-х. И, конечно, в это время возможностей и еще одна разновидность неудач 70-х стала разновидностью успехов: переделывание имплементации языка со сборщиком мусора в имплементацию функционального языка.
Мы начнем рассказ о таких имплементациях не с Лиспов, а с языков, принадлежащих к другим, не обоекембриджским ветвям потомков ALGOL 60. Из истории того из них, который мы рассматривали подробнее в прошлых главах - ALGOL 68 - видно, что облегчение синтаксиса и мечты о функциональных фичах сделали его настолько похожим на существенно менее известный CPL, что некоторые алголисты стали (ошибочно) считать, что типизированное функциональное программирование вообще и ML в частности происходят от ALGOL 68.
ALGOL 68 произвел тяжелое впечатление на авторов CLU, так что первый "язык 80-х" CLU был анти-Алголом 68 и, следовательно, языком анти-функциональным. Хотя, благодаря обязательному сборщику мусора, мог позволить функциональные фичи. К счастью, не все языки этого алгольно-паскального семейства были такими и авторы некоторых хотели воплотить в жизнь самые смелые мечты функциональной партии авторов Алгола 68.
Типизация, лексическая видимость и легкий синтаксис делали их настолько похожими на ML-и, что можно бы было и не выделять их в отдельную категорию от тех компиляторов, которые мы рассмотрели в предыдущих главах. Но мы сделали это. В меньшей степени из-за незначительной общей истории с ML-ями. А главным образом из-за различий в том, как из компилятора такого ФЯ будут делать компилятор ФЯ современного вида. Но это уже другая история.

Russell

Одним из этих языков был Russell. В конце семидесятых о Russell говорили как о языке, похожем на CLU и как о примере того, что в язык, похожий на Паскаль можно добавить "мощные языковые конструкции". Эту похожесть на ALGOL 60/Pascal еще можно как-то разглядеть в ранних примерах [Deme78] :

proc Update(
    type Ta with(
             function f (Ta) : integer;
                var field Cnt : integer;
          );
    var A : array 1..n of Ta
  );
  for i : 1..n loop
    A[i].Cnt := A[i].Cnt + f(A[i])
  end
end Update

Но усилия по облегчению и "функционализации" синтаксиса вскоре стерли большую часть этого сходства, как и в случае прочих производных Алгола вроде CPL или Algol 68, вступивших на этот путь.
Russell разрабатывали [Boeh12] в Корнеллском университете (Cornell University) [Deme78] Алан Демер и Джеймс Донахью. Демер (Alan J. Demers) защитил докторскую диссертацию в Принстоне в 75 [Demers], а Донахью (James E. Donahue) в том же 75-ом в университете Торонто [Modula3]. Донахью ушел из Корнелла в 81-ом в лабораторию Xerox в Пало-Альто, но продолжил работать над Russell и там.
Третьим и, по всей видимости, наиболее известным сегодня автором языка был Ханс Боэм (Hans-Juergen Boehm), выпускник университета Вашингтона, он в 1978-1982 работал в Корнелле над диссертацией под руководством Демера [Boeh12]. Боэм впоследствии работал над описанием семантики и имплементацией Russell в 1982-84 в университете Вашингтона и в 1984-89гг. в Университете Райса (Rice University).
В 1980-ом Демер с Донахью пишут [Deme80], что Russell "скоро будет имплементирован". Но имплементация Russell не задалась. Почему? В предыдущих главах мы уже выяснили, что, в отличие от 70-х, 80-е это время возможностей и успехов для имплементаторов ФЯ. Но в случае Russell к ФЯ добавились дополнительные осложнения.
Как нам напомнила история Ponder, авторы Алгола 68 мечтали не только об имплементации ФЯ, но и об "ортогональности". Но можно ли сделать язык еще "ортогональнее", чем Ponder?
Может и не во всем, но и в Ponder есть средства, которые можно исключить, заменив уже имеющимися и непервоклассные средства, которые можно сделать первоклассными. В то время, когда в Эдинбурге хотели добавить параметризованные модули в язык с параметризованными типами и параметризованные типы в язык с параметризованными модулями, появилась идея языка в котором на два способа параметризовать меньше.
Но как можно исключить из ФЯ и параметры типов и параметры модулей, сохранив нужный для ФЯ полиморфизм? В функциональном языке и так уже есть средство параметризовать все что нужно - функция. Пусть функция принимает одни типы и возвращает другие [Boeh80] [Deme80] :

List == func[ T : type{} ]
  type L {
    Nil    : func[] val L ;
    empty? : func[ val L ] val Boolean ;
    first  : func[ val L ] val T;
    rest   : func[ val L ] val L;
    ||     : func[ val L; val T ] val L }
  { let
      ConsNode == 
        record{ hd: T; tl: union{ ConsNode; Void }}
          with CN{
            Mk == func[x: val T; y: val ConsNode]{ Mk[ x, FromConsNode[y] ] };
            Mk == func[x: val T; y: val Void]{ Mk[ x, FromVoid[y] ] }
          }
    in union{ ConsNode; Void }
      with L{
        Nil == func[] { L$FromVoid[ Void$IsNull ] };
        || == func[ x : val T; y : val L ]
            { if
              IsConsNode?[y] ==> FromConsNode[ Mk[ x; ToConsNode[y] ] ]
              | else ==> FromConsNode[ Mk [ x; ToVoid [y]] ]
              fi };
        empty? == func[ x : val L ]{ IsVoid?[x] };
        first == func[ x : val L ]{ hd[ ToConsNode[x] ] }; 
        rest == func[ x : val L ]{ tl[ ToConsNode[x] ] } }
    ni }
let map == func[ T, R : type{}; 
                 f : func[ val T ] val R;
                 L : List[T] ] val List[R]
           { if 
                empty?[L]  ==> Nil[];
                | else ==> append[map[T;R;f;rest[L]];
                                      f[first[L]] 
                                      ];
             fi } in
let y == 2 in map [Integer;Integer;func[ x : Integer ]{ x + y };
                   1 || 2 || 3 || Nil[]]

И, как оказалась, проверить, что функция примененная с одному и тому же типу возвращает один и тот же тип не так-то легко.

Poly

И пока авторы Russell все планировали и планировали его имплементировать, в Кембридже Дэвид Мэттьюз (David Charles James Matthews) имплементировал [Matt83] его упрощенную версию - язык Poly. Упрощения состояли в том, что создать тип с помощью функции в Poly можно:

let ilist == list(integer); 

но применить функцию справа от : нельзя, l : list(integer) - некорректная аннотация типа. И, в отличие от Russell, такие let нельзя использовать в сигнатуре абстрактного типа [Boeh86].
Автор Poly Мэттьюз собирался в дальнейшем преодолеть это ограничение, наложив ограничения на функции возвращающие типы. Авторы Russell поступили именно так, и по словам Мэттьюза, читавшего неотсканированные репорты о Russell не дошедшие до нас, в 79-ом году собирались ограничить вообще все функции, например, запретив в них свободные переменные. Но передумали. В 1980 и далее Russell задумывался как полноценный ФЯ, на котором можно писать функции вроде такой:

Y == func[ f : func[ func[ val T ] val T ] func[ val T ] val T ]
       { func[ x : val T ] { (f[Y[f]]) [x] } }

и такой

compose ==
    func[f: func[val t2]val t3;
          g: func[val t1]val t2;
          t1,t2,t3: type{}]{
            func[x: val t1]val t3{f[g[x]]} 
    }

Разумеется, Poly и Russell отличались не только этими важными вещами, но получили и множество мелких различий, как и у большинства уже рассмотренных нами родственных языков и диалектов. Пока функционального программирования не было, не было и кода, который можно переиспользовать. Но код уже начинает появляться и скоро с этим поверхностным разнообразием начнут бороться. Это уже, правда, другая история.
На языке Poly можно описать [Matt85] список как и на Russell, не в самых лучших традициях поздней системы МакКарти и ранних языков описания спецификаций:

let list ==
    proc(base: type end) 
        type (list)
            car : proc(list)base raises nil_list;
            cdr : proc(list)list raises nil_list; 
            cons: proc(base; list)list; 
            nil : list; 
            null: proc(list)boolean 
        end
    begin
        type (list)
            let node == record(cr: base; cd: list);
            extends union(nl : void; nnl : node); 
            let cons == proc(bb: base; ll: list)list
                (list$inj_nnl(node$constr(bb, ll)));
            let car ==
                proc(ll: list)base
                begin
                    node$cr(list$proj_nnl(ll)) 
                    catch proc(string)base (raise nil_list)
                end;
            let cdr ==
                proc(ll: list)list
                begin
                    node$cd(list$proj_nnl(ll))
                    catch proc(string)list (raise nil_list)
                end;
            let nil == list$inj_nl(void$empty);
            let null == list$is_nl
        end
    end;

Но написать на Poly map как на Russell нельзя из-за того, что в сигнатуре потребуются применения функций. К счастью, в Poly, как и в Russell, но в отличие от ML и HOPE, уже решена проблема ограниченного полиморфизма, так что можно и даже нужно написать map принимающий и возвращающий любой тип с интерфейсом списка:

letrec map == proc[a: type end; b: type end;
    alist : type (l)
            car: proc(l)a raises nil_list;
            cdr: proc(l)l raises nil_list; 
            cons: proc(a; l)l;
            nil : l;
            null: proc(l)boolean 
         end;
    blist : type (l)
            car: proc(l)b raises nil_list;
            cdr: proc(l)l raises nil_list; 
            cons: proc(b; l)l;
            nil : l;
            null: proc(l)boolean 
        end]
    (f: proc(a)b; xs: alist) blist 
    ( if alist$null(xs) 
        then blist$nil
        else blist$cons(f(alist$car(xs)), map(f, alist$cdr(xs))) );

let y == 2;
let ilist == list(integer); 
map[integer,integer](proc(x:integer)integer(x+y),
                     ilist$cons(1, ilist$cons(2, ilist$cons(3, ilist$nil)))); 

Автор Ponder Фейрберн был знаком с этими победами "ортогональности", но посчитал слишком сложными и решил с этим не связываться. Что, возможно, лишило нас самого "ортогонального" языка из возможных. Но проблема с применением "функций", генерирующих типы, существует в некоторых ФЯ и сегодня, хотя сегодня это уже не проблема обычных функций, раз уж разнообразие способов параметризации в них победило.
В отличие от Russell, в Poly аргументы, которые могут выводиться и которые не могут, отличаются синтаксически - видом скобок.
Авторы этого подхода к параметризации знали о ML и были недовольны ограничениями его системы типов, как и Фейрберн. Они понимали, что все выводить как в ML, конечно, не получится, но рассчитывали на то, что смогут выводить многое. И сложности с этим их неприятно удивили [Boeh85]. Вывод даже того немногого, что получилось выводить имплементаторы Russell называют самой сложной частью имплементации [Boeh86].

Воспроизведение

Имплементация Poly, написанная Мэттюзом, в значительной степени воспроизводит работу Карделли над компилятором VAX ML. Это воспроизведение не было независимым. Мэттьюз работает над компилятором через пару лет после основной части работы Карделли, знает об этой работе и ссылается на нее. И этот контакт, установленный через перешедшего в Кембридж из Эдинбурга Гордона, работал в обе стороны. О Poly пишут в почтовой рассылке ML.
До имплементации Poly у Мэттьюза уже был опыт работы над компилятором Zurich Pascal.
Также как и компилятор Карделли, компилятор Poly интерактивный. Также, как и компилятор Карделли, компилятор Мэттьюза написан на Паскале для VAX. Но с самого начала на UNIX. Имплементация Poly разрабатывалась итеративно, сначала это был наивный интерпретатор АСТ, затем интерпретатор байт-кода, наконец компилятор, генерирующий машкод для VAX.
Мэттьюз парсит рекурсивным спуском, считает такой подход конвенциальным. Компилятор строит дерево, которое потом преобразует в машкод вторым проходом. Дерево представляет довольно низкоуровневый язык - команды стековой виртуальной машины, большая часть работы компилятора в первом проходе. Так что высокоуровневых оптимизаций, трансформирующих абстрактное синтаксическое дерево нет.
Как и Карделли, Мэттьюз имплементирует параметрический полиморфизм с помощью универсального представления, размещая в куче все, что не помещается в слово. Для того, чтоб отличать указатель от числа он использует не схему Карделли, ограничивающую числа половиной слова, а два бита тегов. Два бита чтоб отличать также указатель на одно слово в куче (01) и на объект из нескольких слов с одним из них, использованных для хранения длины объекта (10). 11 и 00 используются для чисел, так что они теряют только один бит точности.
Мэттьюз использует компактные замыкания-массивы, как и Карделли. Так что, как и в VAX ML, все локальные значения - неизменяемые, изменять можно только ячейку в куче. Функциональные значения - указатели на эти компактные замыкания, что требует двойной косвенности ссылок на функцию, что Мэттьюз считает существенным недостатком. Но ничего лучше пока что не придумал.
Главное отличие имплементаций, по видимому - сборщик мусора. Сборщик мусора у имплементации Poly на Паскале не копирующий, как у VAX ML, и вообще не перемещающий, как в первых имплементациях Лиспа. Собирает фрагменты кучи в список свободных. С не самыми лучшими последствиями для скорости аллокации и сборки.
В отличие от Карделли, Мэттьюз не считает свой интерактивный компилятор необычным. Но, наверное, на эту оценку повлияло то, что по крайней мере один такой компилятор уже был.
Как и в компиляторе Карделли, в компиляторе Мэттьюза делается не много оптимизации, но Мэттьюз, как и Карделли, заявляет, что все работает "приемлемо быстро" для интерактивной работы. И не приводит никаких измерений производительности. Что "приемлемо быстро" значило в случае VAX ML мы знаем благодаря Йонссону с Августссоном. Но что это значило в случае имплементации Poly на Паскале мы не знаем точно. Наш старый знакомый Гордон и будущий важный герой нашей истории Паульсон оценивают [Gord82] его производительность как высокую. Но это по сравнению с LCF/ML. Не очень высокая планка.
Оптимизатор (для начала - инлайнер) Мэттьюз решил писать на Poly после того, как перепишет компилятор на Poly. Да, в отличие от Карделли, Мэттьюз собирался делать компилятор, компилирующий самого себя. Причем компилятор должен был стать функцией, доступной в REPL, как в Лиспах или POP-2. Он даже начал это делать, переписав на Poly лексер и генератор кода, но не успел переписать до того, как закончил работать над своей диссертацией [Matt83] в 83-ем году. Работать над компилятором он, правда, не прекратил, но это уже другая история.
Смогли ли имплементировать Russell? В своей диссертации Мэттьюз пишет, что еще нет. Но существенно позднее Russell был таки имплементирован [Boeh86]. По видимому, на C [Boeh88]. Слишком поздно, чтоб оказаться в числе первых компиляторов ФЯ.
Эту имплементацию авторы тоже сравнивают с имплементацией ML Карделли (Боэм один из пользователей это компилятора), но сходство между этими компиляторами меньше, чем между компилятором Карделли и компилятором Мэттьюза. Например, имплементаторы Russell не используют более современный подход с компактными замыканиями-массивами, которые планировал использовать Стил в Rabbit и которые использовал Карделли. Вместо этого имплементаторы Russell размещают в куче фреймы стека, как делали имплементаторы Interlisp в 70-е.
На имплементацию ФЯ компилятор Russell не оказал значительного влияния. Разве что через использование консервативного сборщика мусора Боэма-Демера-Уизера [Boeh88], который происходит от рантайма Russell для Motorola 68K и является той самой работой, которой Боэм как раз известен. Сборщик Боэма подходит для имплементации ФЯ еще меньше, чем сборщик в имплементации Мэттьюза, но, как не странно, будет использоваться для имплементации ФЯ. Так что на этом мы прощаемся с языком Russell и его имплементацией. А к Poly и компилятору Мэттьюза мы еще вернемся.

ПРОДОЛЖЕНИЕ СЛЕДУЕТ

Литература

[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 218227 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 127141 DOI:10.1093/comjnl/32.2.127
[Augu21]: The Haskell Interlude 02: Lennart Augustsson https://www.buzzsprout.com/1817535/9286902
[Augu23]: Lennart Augustsson - MicroHaskell https://www.youtube.com/watch?v=Zk5SJ79nOnA
[Birr77]: Andrew Birrell. System Programming in a High Level Language. Ph.D. Thesis, University of Cambridge. December 1977.
[Boeh80]: Boehm, Hans-J., Alan J. Demers, and James E. Donahue. An informal description of Russell. Cornell University, 1980.
[Boeh85]: Boehm, H.-J. (1985). Partial polymorphic type inference is undecidable. 26th Annual Symposium on Foundations of Computer Science (sfcs 1985). doi:10.1109/sfcs.1985.44 
[Boeh86]: Hans-Juergen Boehm and Alan Demers. 1986. Implementing RUSSELL. In Proceedings of the 1986 SIGPLAN symposium on Compiler construction (SIGPLAN '86). Association for Computing Machinery, New York, NY, USA, 186195. doi:10.1145/12276.13330
[Boeh88]: Boehm, H.-J., & Weiser, M. (1988). Garbage collection in an uncooperative environment. Software: Practice and Experience, 18(9), 807820. doi:10.1002/spe.4380180902 
[Boeh12]: Ханс Боэм https://archive.is/20121203003033/http://www.hpl.hp.com/personal/Hans_Boehm/
[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
[Clac85]: Clack, C., Peyton Jones, S.L. (1985). Strictness analysis — a practical approach. In: Jouannaud, JP. (eds) Functional Programming Languages and Computer Architecture. FPCA 1985. Lecture Notes in Computer Science, vol 201. Springer, Berlin, Heidelberg. doi:10.1007/3-540-15975-4_28
[Cousot]: Cousot, Patrick. “The Contributions of Alan Mycroft to Abstract Interpretation.”
[Dama84]: Luís Damas. “Type assignment in programming languages.” (1984).
[Deme78]: Alan Demers, James Donahue, and Glenn Skinner. 1978. Data types as values: polymorphism, type-checking, encapsulation. In Proceedings of the 5th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '78). Association for Computing Machinery, New York, NY, USA, 2330. doi:10.1145/512760.512764
[Deme80]: Alan J. Demers and James E. Donahue. 1980. The Russell Semantics: An Exercise in Abstract Data Types. Technical Report. Cornell University, USA.
[Demers]: Alan J. Demers https://www.cs.cornell.edu/annual_report/99-00/Demers.htm
[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, 112. doi:10.1145/1159803.1159805
[Fair82]: Fairbairn, Jon. Ponder and its type system. Technical Report Number 31 University of Cambridge, Computer Laboratory UCAM-CL-TR-31 November 1982 DOI: 10.48456/tr-31 https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-31.pdf
[Fair85]: Fairbairn, Jon. Design and implementation of a simple typed language based on the lambda-calculus. No. UCAM-CL-TR-75. University of Cambridge, Computer Laboratory, 1985. doi:10.48456/tr-75
[Fair86]: Jon Fairbairn, A new type-checker for a functional language, Science of Computer Programming, Volume 6, 1986, Pages 273-290, doi:10.1016/0167-6423(86)90027-4
[Gord80]: Michael Gordon, Locations as first class objects in ML. https://smlfamily.github.io/history/Gordon-ML-refs-1980.pdf
[Gord82]: Mike Gordon, Larry Paulson, 1982-11-03 in Polymorphism Vol 1 part 1 Jan 83
[Holm98]: Holmevik, Jan Rune. "Compiling Simula: A historical study of technological genesis." (1998) https://staff.um.edu.mt/jskl1/simula.html
[Hugh82]: Hughes, John. Graph reduction with super-combinators. PRG28. Oxford University Computing Laboratory, Programming Research Group, 1982.
[Hugh82b]: R. J. M. Hughes. 1982. Super-combinators a new implementation method for applicative languages. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 110. DOI:10.1145/800068.802129
[Hugh83]: Hughes, Robert John Muir. "The design and implementation of programming languages." Ph. D. Thesis, Oxford University (July 1983) (published as Technical Monograph PRG-40 September 1984).
[Hugh2005]: John Hughes, My Most Influential Work https://www.cse.chalmers.se/~rjmh/citations/my_most_influential_papers.htm
[Hugh23]: The Haskell Interlude #36 John Hughes, Recorded 2023-09-21. Published 2023-10-31 https://haskell.foundation/podcast/36/
[John84]: Efficient Compilation of Lazy Evaluation. Thomas Johnsson SIGPLAN '84: Proceedings of the 1984 SIGPLAN symposium on Compiler construction June 1984 Pages 5869 doi:10.1145/502874.502880
[John85]: Johnsson, T. (1985). Lambda lifting: Transforming programs to recursive equations. In: Jouannaud, JP. (eds) Functional Programming Languages and Computer Architecture. FPCA 1985. Lecture Notes in Computer Science, vol 201. Springer, Berlin, Heidelberg. doi:10.1007/3-540-15975-4_37
[John95]: Thomas Johnsson. Graph reduction, and how to avoid it. In Electronic Notes in Theoretical Computer Science Volume 2, 1995, Pages 139-152 DOI:10.1016/S1571-0661(05)80191-4
[Joy86]: Joy, William N., Susan L. Graham, Charles B. Haley, Marshall Kirk McKusick, and Peter B. Kessler. "Berkeley Pascal Users 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.
[MacQ82]: D. B. MacQueen and Ravi Sethi. 1982. A semantic model of types for applicative languages. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 243252. doi:10.1145/800068.802156
[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
[Matt83]: Matthews, David Charles James. Programming language design with polymorphism. No. UCAM-CL-TR-49. University of Cambridge, Computer Laboratory, 1983.
[Matt85]: David C. J. Matthews. 1985. Poly manual. SIGPLAN Not. 20, 9 (August 1985), 5276. doi:10.1145/988364.988371
[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/
[Modula3]: MODULA-3 authors https://www.cs.purdue.edu/homes/hosking/m3/reference/authors.html
[Mycr80]: Mycroft, A. (1980). The theory and practice of transforming call-by-need into call-by-value. In: Robinet, B. (eds) International Symposium on Programming. Programming 1980. Lecture Notes in Computer Science, vol 83. Springer, Berlin, Heidelberg. doi:10.1007/3-540-09981-6_19
[Mycr16]: Alan Mycroft, Mini CV https://www.cl.cam.ac.uk/~am21/minicv.txt
[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 Saitos Unix port of VAX ML, March 1982.
[SERC83]: DISTRIBUTED INTERACTIVE COMPUTING NOTE 781 https://www.chilton-computing.org.uk/acd/pdfs/dic/dic781.pdf
[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.
[Turn79]: Turner, D. A. (1979). A new implementation technique for applicative languages. Software: Practice and Experience, 9(1), 3149. doi:10.1002/spe.4380090105 
[Wray86]: Wray, Stuart Charles. Implementation and programming techniques for functional languages. No. UCAM-CL-TR-92. University of Cambridge, Computer Laboratory, 1986. DOI: 10.48456/tr-92