1
Fork 0
mirror of https://github.com/thegeneralist01/fphistoryru synced 2026-01-10 14:10:24 +01:00
fphistoryru/compilers.md
2024-09-01 01:30:21 +05:00

310 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 и компилятору Мэттьюза мы еще вернемся.

Живые ископаемые

ML, который используется в Nuprl достаточно близок к оригиналу.
Кристоф Крейц, Система разработки доказательств Nuprl, версия 5, 2002г. [Krei2002]

До сих пор мы рассказывали о новых компиляторах, появившихся в 80-е годы. Но не смотря на то, что 70-е не были такими же успешными для имплементаций ФЯ, что-то было сделано и в те годы. Что-то, что можно доделать, улучшить и использовать. Например, имплементация LCF/ML, транслирующая ML в Kbcgs 70-х.

Насколько "мини" может быть мини-MAC?

В 1962-ом в Стенфорде появился новый профессор: Джон МакКарти. Как мы помним, он ушел из МТИ в Стенфорд как раз перед тем, как проект MAC получил много денег. В Стенфорде МакКарти сделал доклад на физическом факультете о Лиспе. В надежде на то, что физики будут использовать Лисп для символьных вычислений. И новый герой нашей истории пропустил этот доклад.
Энтони Хирн (Anthony C. Hearn), физик-теоретик, закончил Университет Аделаиды, Австралия. Защитил диссертацию в Кембриджском университете в 1962-ом. В Стенфорде он начал работать в том же году, что и МакКарти. Но пропустил его доклад про Лисп. Но один из тех, кто доклад не пропустил, посчитал что Лисп может заинтересовать Хирна и посоветовал ему поговорить с МакКарти. Что Хирн и сделал [Hear2005].
И МакКарти убедил Хирна использовать Лисп, сделав предложение, от которого Хирну было очень сложно отказаться. Если Хирн будет использовать Лисп, то МакКарти даст ему доступ к компьютеру для работы. Но если Хирн не хочет использовать Лисп - он может продолжать искать или выпрашивать машинное время самостоятельно. И как мы выяснили в предыдущей части, для многих героев нашей истории это было совсем не легко.
Так что Хирн стал писать систему компьютерной алгебры REDUCE на Лиспе. Название "REDUCE" - это (анти?) шутка. Системы компьютерной алгебры того времени производили неудобно большие результаты. Хирн посчитал, что будет смешно, если система будет называться в честь операции, которую она не делает.
Хирн описал систему в 68-ом году. Над этой первой версией он работал в Стенфорде, где он проработал до 69-го года. С перерывом в 64-65гг на год работы в лаборатории Резерфорда в Англии. С 69-го до 1980 Хирн работал в Университете Юты, где он написал REDUCE 2.
Из обстоятельств выбора Лиспа Энтони Хирном можно предположить, что он не особенно любил Лисп. И действительно, он писал REDUCE 2 на RLISP, одном из тех языков, которые должны были прикрывать лисповость Лиспа. Вроде MLISP на котором писали LCF.

EXPR PROCEDURE MAPCAR(X, FN);
    IF NULL X THEN NIL
        ELSE FN CAR X . MAPCAR(CDR X, FN);

Но Хирн, в отличие от авторов LCF, недолюбливал Лисп более обычным и менее полезным для развития ФП образом. Так что RLISP, в основном, заменял лисповый синтаксис на другой не самый удачный синтаксис, а не исправлял проблемы Лиспа того времени с типизацией и поддержкой ФП.
RLISP - это не полноценная имплементация языка, а только фронтенд для компилятора Лиспа. Какого компилятора?
REDUCE принципиально отличался от проектов по созданию систем компьютерной алгебры, которые мы обсуждали ранее. И даже от Мини-MAC Мики, создававшего систему переписывания кода Бурсталла с Дарлингтоном. Их разработчики могли себе позволить разработку специальных имплементаций Лиспа или лиспообразного языка, как авторы SCRATCHPAD, а то и выбирать или даже создавать железо для них, как авторы Macsyma. REDUCE был, по большему счету, проектом одного человека, продемонстрировав насколько "мини" может быть мини-MAC. До конца 70-х разработчик REDUCE не мог создавать или даже выбирать ничего из этого. На какой машине будет работать REDUCE? Какой компилятор Лиспа будет её собирать? Это как повезет.
И, как обычно в то время, на всех машинах Лиспы стали разными, и отличались как принципиальными фичами, так и множеством мелких деталей. Но проблему различия Лиспов в мелких деталях было решить проще, чем проблему различий ML-ей, NPL-ей или SASL-ов. Один Лисп можно сделать похожим на другой написав набор функций. Решить проблему принципиальных отличий можно было выбрав некий минимальных их набор, пересечение множеств фич многих Лиспов.
Именно это Хирн и сделал. В 69-ом и 79-ом годах он описал [Hear79] такой диалект - Standard LISP [Padg88] и написал слои совместимости со многими другими Лиспами. И использовал его для имплементации RLISP и REDUCE. Так что, в отличие от MACSYMA и SCRATCHPAD, REDUCE худо-бедно работал на многих системах.
И крах какой-то одной из этих систем не создал бы для REDUCE такого кризиса, какой создал PDP-10-апокалипсис для MACSYMA и LCF. В прошлой части мы уделили достаточно внимания лихорадочным попыткам лисперов спасти MACSYMA. И авторы LCF не могли себе позволить предпринять ни одной из них. LCF, как и прочие проекты, произошедшие от Эдинбургской исследовательской программы, был ближе к написанию REDUCE ограниченными средствами, чем к осваивающим бесконечные деньги проектам золотого века Лиспа вроде MACSYMA. И разработчики LCF могли попытаться использовать наработки прочих спасающихся с PDP-10 и не только. Тем же самым способом, который использовал создатель REDUCE Хирн. И попытались.

Жерар Юэ

Многие участники спасения LCF, правда, хотели спасти не столько LCF, сколько LCF/ML и использовать его для написания и/или скриптования своего доказателя. Новый герой нашей истории Жерар Юэ (Gérard Huet) как раз один из них.
В 1969 Юэ получил дипломы от Национальной высшей школы аэронавтики и космоса (Ecole Nationale Supérieure de lAéronautique et de lEspace) и Сорбонны. Защитил диссертацию в 1972 в Кейс-Вестерн-Резерв университете, Огайо (Case Western Reserve University) [Huet]. В том же 1972 году он начал работать в исследовательском институте Inria, название которого в те времена еще было акронимом INRIA (до 1980 - IRIA, Institut (National) de Recherche en Informatique et en Automatique).
В 1979 в INRIA получили компьютер Honeywell с MULTICS [MULTICS1]. Помните те стоящие десятки миллионов долларов 2023-го года компьютеры, про которые мы писали в первой части? Для которых и в 70-е годы существовали конфигурации, на которых могли бы работать компиляторы ФЯ. И мы точно знаем, что могли бы как раз благодаря Юэ, в числе прочих. Стараниями которых компилятор ФЯ на такой машине заработал. Правда, уже не в семидесятые.
В 70-е в INRIA поддерживали связи с Эдинбургом [MacQ15], и в 80-81 годах Юэ заинтересовался LCF/ML. Осенью 1981-го года Юэ переписал [Nuprl94] код LCF со Stanford LISP на родственный MacLISP для Multics. Тот самый Лисп, который имплементировали в 70-е для одной из неудачных попыток спасения Macsyma. И который дожил до 81-го года благодаря тому, что Бернард Гринберг написал на нем EMACS, ставший популярным у пользователей Multics. В декабре 81-го версия этой имплементации ML была 1.2, все основные изменения были впереди, в 82-ом году.

Лоуренс Полсон

В 82-ом году, не позднее марта, Юэ уже работал над слоями совместимости для того, чтоб LCF могли компилировать разные компиляторы Лиспа, не только Maclisp [LCF92]. Во-первых, в INRIA было множество разных машин. В том числе и Лисп-машины, и, конечно, VAX-11. Все это можно и нужно использовать. Так что LCF в INRIA мог повторить все три попытки MACSYMA спастись с тонущего PDP-10. Во-вторых, у нового коллеги Юэ по спасению LCF есть доступ только к VAX-11. Тот может повторить только одну из них и не ту с которой начал Юэ.
Как мы уже писали в предыдущих главах, один из авторов и первых пользователей LCF - Майкл Гордон - с 81-го года работал в Кембридже, где он хотел продолжать использовать LCF. И SERC (Science and Engineering Research Council) выделил средства на продолжение работы над LCF. Один научный сотрудник должен был работать в Эдинбурге под руководством Милнера, этим научным сотрудником были сначала Дэвид Шмидт (David Schmidt), а после него Линкольн Уоллен (Lincoln Wallen). Они не сделали существенного вклада в имплементацию LCF/ML. Второй научный сотрудник числился в Эдинбурге, но работал под руководством Гордона в Кембридже [Gord2000]. Этим научным сотрудником в 82-83гг. был очередной новый герой нашей истории - Лоуренс Полсон (Lawrence Charles Paulson) и вот он как раз существенный вклад в имплементацию LCF/ML сделал. Полсон - математик, в 77-ом закончил Калтех (California Institute of Technology) и в 81-ом защитил диссертацию по компьютерным наукам в Стенфорде [Paul22] [Paul23]. Работать над LCF в Кембридже он начал в феврале 1982.
Юэ и Полсон работали над кодом LCF совместно, но не параллельно, а последовательно. Юэ работал над кодом примерно месяц, отправлял Полсону код на магнитной ленте по почте. Тот работал примерно месяц и отправлял ленты назад и так далее. В свободные от написания кода месяцы Полсону несколько ездил во Францию, в INRIA, где совещался с Юэ. Также Полсон установил контакты с университетом Чалмерса в Гетеборге, в котором в это время работали над портированием LCF на Franz LISP и VAX-11 независимо.
В те месяцы, когда Полсон работал над кодом - он работал на единственном на весь департамент VAX-11/750. И этот дефицит машинного времени Полсон называет основной причиной для медленного написания кода [Paul22]. VAX-11/750 - это более новая, дешевая и медленная машина из той же линейки, что VAX-11/780. Но, хотя-бы, в данном случае с 4Мб памяти.
Полсон один из многих первых пользователей ML, которые описывают его фичи как "чудеса". На Полсона наибольшее впечатление произвел параметрический полиморфизм. Производительность ML, правда, совсем не была чудесной.
То, что ML не работал на современных распространенных машинах с большой памятью - было только одной из двух главных проблем ML. Разработчики доказателей хотели его использовать, но не могли из-за того, что он был имплементирован как медленный интерпретатор. Поэтому, как мы выяснили в прошлой части, 10KLOC LCF было написано на Лиспе и только 1KLOC на ML [LCF77], причем заметной долей этой тысячи строк были просто обертки для функций, написанных на Лиспе.
Поэтому в июле 82-го, в версии 2.3 [Nuprl94] [HOL88] Жерар Юэ модифицировал транслятор Локвуда Морриса [Huet15], который генерировал из ML интерпретируемый LISP. Он сделал из него генератор LISP-кода для компиляции. Как мы выяснили в прошлой части, из-за разных видов видимости, не всякий код давал одинаковые результаты в интерпретаторе и после компиляции.
Но этот код и после компиляции работал так же медленно как интерпретируемый [Paul22b]. Ведь, как мы выяснили в предыдущей части, компиляторы Лиспа того времени не могли скомпилировать лисповые "лямбды". Тело лямбды было сериализованным кодом, который интерпретировался. И транслятор ML в Лисп-код генерировал множество таких "лямбд".
Но когда пришла очередь Полсона работать над кодом - он решил эту проблему. Тут, по всей видимости, пригодились контакты с университетом Чалмерса, ведь там Йонссон с Августссоном уже придумали как избавиться от лямбд: с помощью лямбда-лифтинга. Так что Полсон применил [Paul22b] эту технику имплементации ленивых ФЯ к имплементации энергичного ФЯ. Также Полсон решил генерировать специализации для разных применений каррированных функций, чтоб лямбд от которых нужно избавляться лифтингом было еще меньше.
Улучшенный Полсоном компилятор попал в версию 3.1 [Nuprl94] [HOL88]. Эта версия, судя по заметкам в коде, [Nuprl94] еще существовала в разных вариантах для разных систем, но версия 3.2 от первого октября 82-го называют "портируемой" [Nuprl94] [HOL88].
Какое-то время систему можно было собирать Maclisp и Лиспом для Лисп-машин под названием ZetaLisp [Huet86]. Но Лисп-машины и мэйнфреймы не пользовались особой популярностью у имплементаторов доказателей и в дальнейшем остались слои совместимости для трех Лиспов для обычных машин. Все три претендовали на хорошую портируемость и поддержку нескольких платформ, но не все соответствовали этим претензиям.
Первым из трех был, естественно, Franz LISP - первая работающая имплементация Maclisp-образного Лиспа для VAX-11, появившаяся еще в 70-е, о которой мы писали в предыдущей части. Другие два Лиспа немного новее.

PSL

Долгое время проект REDUCE не мог себе позволить разработку собственной имплементации Лиспа, но в конце 70-х ситуация изменилась. Создание компилятора явно стало гораздо более осуществимой задачей и появилась масса новых компиляторов. Одним из которых была и имплементация Standard LISP - Portable Standard LISP (PSL). В университете Юты Хирн нашел единомышленника. Мартин Грисс (Martin Griss) физик, закончил Технион в 67-ом, защитил диссертацию в Иллинойсском университете в Урбане-Шампейне в 71-ом, после чего проработал десять лет в Университете Юты [Griss]. Вместе с Гриссом, Хирн стал работать над компилятором.
И хотя первые экспериментальные компиляторы для разных платформ они смогли написать еще в конце 70-х, но первый компилятор для VAX-11 появился только через годы, когда большая часть выбиравших Лисп для VAX-11 уже выбрала Franz LISP.
PSL [Gris82] компилирует сам себя. Для имплементации рантайма используется низкоуровневое подмножество языка под названием SYSLISP, оперирующее значениями машинных типов. Обратите внимание, что это не тот способ, которым решал проблему портируемости Franz LISP и родственный PSL Cambridge LISP, с которым сравнивали Ponder в предыдущих главах. Cambridge LISP был имплементирован на не связанном с Лиспом языке системного программирования. Только не на C, как Franz LISP, а на BCPL. Как мы помним, авторы Franz LISP объясняли свои успехи выбором такого подхода, но, по всей видимости, то, что они сделали работающий LISP на VAX-11 так быстро объясняется предприимчивостью Фейтмана.
В отличие от авторов Franz LISP, получивших доступ к VAX-11 осенью 78-го года, разработчики PSL получили свой VAX 11/750 только в конце лета 81-го. Серьезная работа над VAX PSL началась в октябре того же года. Первая версия PSL для VAX, которым можно было пользоваться (по крайней мере, по мнению её авторов) - V2 - была готова уже в конце ноября 81-го [Gris82].
Для этой системы и прочих систем с большим адресным пространством разработчики PSL написали копирующий сборщик мусора. Утверждают, что написали за день. Копирующий сборщик на более медленном VAX-11/750 с в четверо большей кучей работал в 2.5 раз быстрее, чем предыдущий сборщик, больше оптимизированный для того, чтоб система умещалась в кучу PDP-10 (DEC-20). Что все равно означало паузы в одну секунду.
PSL V3 был готов уже в декабре все того-же 1981-го года и генерировал код сравнимый по скорости с тем, который производил Franz LISP. В следующем году эти результаты были улучшены.
Первую рабочую станцию с MC68K разработчики PSL получили в конце 81-го и первый, еще не особенно хорошо оптимизированный кодогенератор для этого процессора был готов ко времени доклада [Gris82] о PSL на конференции в августе 82-го (вышел первоначально как отчет в мае). В этом докладе имплементаторы говорят про то, что начали распространять ленты с VAX PSL для тестирования, после которого собираются сделать первый официальный релиз.
Заявляется, что PSL быстрее, чем Franz LISP, но бенчмарки не особенно разнообразные

tak 18 12 6
PSL int 1.00
PSL generic 3.70
Franz LISP int 1.47
Franz LISP generic 14.5
C 1.88
PSL int
░░░░░░░
PSL generic
░░░░░░░░░░░░░░░░░░░░░░░░░░
Franz LISP int
░░░░░░░░░░
Franz LISP generic
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
C
░░░░░░░░░░░░░

Нам, конечно, нужно осторожнее делать далеко идущие выводы из скорости выполнения функции tak. Как обстояли дела с реальными программами? VAX PSL достиг достаточной зрелости, чтоб собрать REDUCE - серьезную программу, ради которой все это и делалось, только в мае 82-го. И на VAX её производительность не с чем сравнивать. Но есть с чем сравнить на DEC-20. Скомпилированный PSL tak на DEC-20 быстрее, чем скомпилированный MacLisp, не говоря уже про LISP 1.6. Но скомпилированный PSL REDUCE в полтора раза медленнее, чем REDUCE, скомпилированный LISP 1.6.
Но, конечно, нужно осторожнее делать далеко идущие выводы из сложностей с имплементацией систем компьютерной алгебры и их производительностью. Если требования к памяти у них похожи на требования компиляторов ФЯ того времени, то скорость работы может быть ограничена, например, плохой имплементацией больших целых чисел, которая совсем не важна для имплементации ФЯ.

Le Lisp

Третий Лисп, компилирующий новый LCF, создавался по соседству с ним. Но мог бы и не появиться, если б имплементаторы первых двух работали быстрее.
В 1981 в INRIA стартовал проект по созданию системы для разработки интегральных схем под названием LUCIFER [Chai83]. Руководил проектом Жан Вюийемен, уже поучаствовавший в нашей истории в главе про изобретение ленивости. В начале 70-х он доказывал, что корректная имплементация рекурсии в частности и ЛИ вообще требует ленивости.
Разработчики LUCIFER посчитали, что разрабатывать систему лучше на Лиспе, а точнее на каком-нибудь из Лиспов MacLisp семейства.
Но есть проблема: система должна была работать на рабочих станциях, микрокомпьютерах с процессорами Motorola 68K. А разработчики Лиспов MacLisp семейства, как мы выяснили в предыдущей части, обычно не особенно интересовались поддержкой таких машин. В этой же части выясняется, что те немногие, что интересовались, а именно разработчики Franz LISP, все продолжали интересоваться, но ничего кроме VAX-11 пока как следует не поддерживали. Да, они похвалили [Fate81] себя за выбор подходов, хорошо подходящий для портирования на новые машины, но пока не спешили портировать. Только в октябре 1982-го Фейтман напишет в рассылке Franz LISP [Franz38] о том, что Кейт Скловер (Keith Sklower) и Кевин Лэйер (Kevin Layer) работают над версией для MC68000, но первую работающую версию начнут распространять только с конца мая 1983. К этому времени разработчики Le Lisp так привыкли, что Franz LISP не портируют, что писали об этом не "до сих пор не портировали", а "так никогда ни на что и не портировали".
Да, это несколько неожиданно. Мы уже привыкли к тому, что в 80-е годы многие имплементаторы языков программирования пытаются сделать много чего. И, в отличие от годов 70-х, часто успешно. И от тех, с кого, по большому счету и начались эти перемены к лучшему, можно было бы ожидать великих свершений. Которых пока что не было. С тем, почему так вышло мы постараемся разобраться в следующей главе.
Позднее [Chai84], разработчики LUCIFER оценили и Standard LISP с PSL. Принадлежность к семейству MacLisp не была такой уж обязательной и они предполагают, что могли бы использовать PSL. Но в конце 80-го и начале 81-го годов, когда они выбирали Лисп, PSL для нужных им платформ тоже еще не был готов.
Так что в INRIA решили написать собственную имплементацию Лиспа из MacLisp семейства - Le Lisp. Написать преимущественно на Лиспе и коде виртуальной машины LLM3. Да, именно тем способом, на ошибочность которого авторы Franz LISP списывали проблемы с разработкой NIL их соперниками из МТИ.
Le Lisp использовал опыт и идеи из VLISP, предыдущей имплементации над которой работал Жером Шаю (Jérôme Chailloux). Так что уже осенью 1981 Le Lisp заработал на рабочей станции с процессором Motorola, а к осени 1982-го был готов порт на VAX-11. В последующие годы процесс портирования виртуальной машины на новый компьютер был отработан настолько, что требовал только месяц работы.
Производительность тоже получилась хорошей. Похоже, что проблемы NIL были в чем-то другом. Компилятор и интерпретатор Le Lisp даже оптимизировали хвостовую рекурсию во многих случаях. Да, это Лисп из MacLisp семейства и, следовательно, язык с "динамической" видимостью. И, как мы помним, Стил и Сассман писали, что у таких языков принципиальные проблемы с оптимизацией хвостовой рекурсией, но имплементаторы Le Lisp придумали [Sain84] несколько трюков.
В отличие от большинства лисперов, авторы Le Lisp любили измерять производительность тем же бенчмарком, что и авторы ФЯ:

fib 20
Le Lisp (opt) 1.00
Le Lisp 5.42
Franz Lisp (1) 9.17
Franz Lisp (2) 23.3
Le Lisp (opt)
░░░░
Le Lisp
░░░░░░░░░░░░░░░░░░░░░░░
Franz Lisp (1)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Franz Lisp (2)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Le Lisp (opt) - это компиляция в такой код, который уже нельзя использовать совместно с интерпретатором, все должно быть скомпилировано. Franz Lisp (1) в этом бенчмарке - результат, измеренный Августссоном и Йонссоном, а Franz Lisp (2) - имплементаторами Le Lisp. Возможно, что это разные версии. Но также возможно, что это разные настройки компиляции. То, что при разных настройках стирания проверок во время выполнения у лиспов можно получить разные результаты на таком простом коде - это нормально. Но, конечно, то, что авторы Le Lisp выбрали именно такие - подозрительно.
Естественно, такой разницы не видно при сравнении более интересного кода. А именно сематического интерпретатора, написанного на ML и интерпретирующего ФП-код, вычисляющий числа Фибоначчи и пары разных имплементаций унификации:

fib 15 fib 20 unif1 unif2
Le Lisp 1.00 1.00 1.00 1.00
Franz Lisp 1.44 1.02 1.52 1.42
Le Lisp
░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░
Franz Lisp
░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░

но превосходство над Franz LISP все равно достигнуто.

Cambridge ML

Не смотря на все эти победы над Franz LISP, он все равно остался самым популярным Лиспом для VAX-11 и для компиляции нового LCF. И немногие измерения [Maun86] производительности использовали именно Franz LISP.

fib 20
VAX ML 0.34
LML 0.63
Franz ML 1.00
Poly 1.22
LCF/ML 31.5

Franz ML - это неофициальное название сочетания нового транслятора с Franz LISP бэкендом. Кембриджцы называли новую версию эдинбургского доказателя Cambridge LCF, а компилятор, который можно было собрать отдельно от доказателя - Cambridge ML. В INRIA чаще просто ML с указанием версии, в данном случае это ML V6.2.
LCF/ML - это интерпретатор, порт LCF/ML 77 на VAX-11, сделанный в университете Чалмерса

VAX ML
░
LML
░░
Franz ML
░░░
Poly
░░░░
LCF/ML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

и график только для компиляторов:

VAX ML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░
LML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Franz ML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Poly
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Гордон пишет [Gord2000], что производительность по сравнению с интерпретатором увеличилась на неназванных бенчмарках в 20 раз. Полсон называет имплементацию "наконец-то пригодной для использования".
Итак, к осени 82 был готов еще один компилятор ML. Не первый компилятор ML, но первый компилятор LCF/ML. Его авторы, пока что, не стали делать существенных изменений в языке. В отличие от Карделли и Йонссона с Августссоном.
Гордон с Полсоном, правда, считали, что получившийся улучшенный транслятор в Лисп - это только временное решение, и ожидается, что его заменит компилятор Карделли или даже Poly. Они писали, что не хотят разрабатывать имплементации ML они хотят ML использовать [Gord82].
Но имплементаторы доказателей пока не спешили использовать VAX ML и Poly. Для чего пока что было много причин. Например, у Cambridge ML был интероп с Лиспом, а VAX ML еще не имел даже ввода-вывода и единственным видом интеропа было написание нового примопа.
Так что транслятор в Лисп будут разрабатывать и дальше, сразу несколько разработчиков доказателей. В том числе и добавлять фичи в ML. Но первый форк Cambridge ML произошел до этих первых существенных изменений языка. Осенью 1983, версия 4.3 этой имплементации ML использовали имплементаторами доказателя Nuprl (читается new pearl). Nuprl разрабатывался в Корнеллском университете как и Russell. Ни разработчики Russell, ни разработчики Nuprl не хотели делать ФЯ похожим на HOPE, так что LCF/ML в Nuprl дожил до начала XXI века практически в первозданном виде.
В отличие от них, почти все прочие наши герои хотели делать свои ФЯ похожими на HOPE, но это уже другая история.

Лисп, который покончит с Лиспами

Моя жизнь была политизирована ужасными событиями.
Ричард Столлман, Мой опыт Лиспа и разработка GNU EMACS. [Stal2002]

В апреле 1981-го ARPA организовало встречу с разработчиками Лиспов. Разработка Лиспов часто велась за счет ARPA и там хотели выяснить, как у лисперов идут дела. Все ли у них хорошо? Потому, что в ARPA возникли подозрения, что хорошо не все. И, как мы выяснили в соответствующих главах прошлой части, для таких подозрений были все основания.
За день до встречи организованной ARPA, лисперы из Interlisp-сообщества организовали свою встречу, на которой договорились выступить единым фронтом и показать свое сообщество здоровым и единым. Что им удалось. После этого прошло не так уж много времени и Interlisp благополучно вымер. История MacLisp и его сообщества сложилась иначе [Stee96].

Какой вы маклиспер?

MacLISP-сообщество не находится в "состоянии хаоса". Оно состоит из четырех хорошо определенных групп, которые движутся в четырех хорошо определенных направлениях.
приписывается Скотту Фалману [Stee82]

Упомянутым Фалманом четырем направлениям соответствовали шесть имплементаций. В прошлой части мы уже рассказывали о NIL для VAX-11, NIL для суперкомпьютера S-1 и Franz LISP для VAX-11.
Историю попыток написать практический компилятор функционального Лиспа мы оставили на том, что лисперы МТИ не успели написать его до конца 70-х. Этот компилятор MacLISP-образного языка NIL должен был обеспечить работу системы компьютерной алгебры Macsyma на новых машинах с большим адресным пространством VAX-11/780. Но новые машины машины становились все менее новыми, а компилятор NIL все не был готов. Работу Macsyma на VAX тем временем обеспечил совсем не функциональный диалект МакЛиспа под названием Franz LISP, имплементированный в Беркли.
Один из имплементаторов Franz LISP Фодераро вспоминает [Stee96b], что он с коллегами ожидал, что NIL будет готов через год после появления первой работающей версии Franz LISP, после чего о ней можно будет забыть. Этого, однако, не произошло. Фодераро думал, что Franz LISP никогда не будет использоваться за пределами Беркли, но он ошибся. Franz LISP стал самым популярным Лиспом для самого популярного мини-компьютера. Перспективы же того, что NIL будут использовать за пределами МТИ пока что не выглядели радужно.
От этого все менее перспективного проекта отделился еще менее перспективный - компилятор NIL для суперкомпьютера S-1. Амбициозной целью NIL для S-1 была эффективная компиляция, позволяющая писать имплементацию Лиспа на Лиспе вообще и эффективная компиляция арифметики в частности. Как мы выяснили в прошлой части, наработки по эффективной компиляции арифметики в MacLisp для PDP-10 не были документированы. И, по мнению разработчика компилятора MacLisp для Multics, практически неизвлекаемы из непонятного исходного кода компилятора, а потому утрачены. Поэтому NIL для S-1 должен был использовать наработки компилятора языка Bliss, аналога BCPL для машин DEC.
Как мы выяснили в прошлой главе на примерах PSL и Le Lisp, задача была не такой уж безнадежной. Другое дело, что, в отличие от PSL и Le Lisp разработка NIL для S-1 затягивалась.
Остальные три диалекта MacLisp - это Лиспы для Лисп-машин: Lisp Machine Lisp, ZetaLisp и Spice Lisp. И два из этих Лиспов - для Лисп-машин разработанных в МТИ или происходящих от них. Почему же так много Лиспов для лисп-машин?
В декабре 1980 был принят акт Бая-Доула (Bayh-Dole Act), который позволил коммерциализировать разработки университетов и лабораторий, сделанные на государственные деньги [Mose08]. В результате группы исследователей стали превращаться в коммерческие компании. Например, разработчики рабочей станции на процессоре MC68K из Стенфорда и разработчики версии UNIX для работы на таких компьютерах из Беркли, о которых мы писали в прошлой части, стали в 82-ом году компанией Sun Microsystems. Очень важной для истории ФП, но об этом позже.
Разработчики Лисп-машин из МТИ собирались стать одной из первых таких компаний. Но ограничиться одной компанией им не удалось.
В МТИ Лисп-машинистами руководил Ричард Гринблатт. Один из первых и важнейших имплементаторов MacLisp. Тот самый, что сделал Лисп языком, пользователю которого нужно больше полагаться на использование стека, а не сборщика мусора.
По воспоминаниям Столлмана [Stal2002], Гринблатт опасался, что инвесторы захватят контроль над компанией и потому не хотел привлекать серьезные инвестиции. Он рассчитывал производить по заказу Лисп-машины разработанные в МТИ. На которых будет работать софт, разработанный в МТИ. И предполагал, что все это будет и дальше разрабатываться в МТИ сотрудниками МТИ и совместителями. Часть плана, касающаяся разработки софта - вполне работающая сегодня. Но, как оказалось, не применительно к лисперам 80-х.
Поскольку Гринблатт не чувствовал, что разбирается в бизнесе, он пригласил работать в формирующейся Лисп-машинной компании старого знакомого - Рассела Нофтскера (Russell Noftsker). Тот когда-то работал в ИИ-лаборатории МТИ, но потом ушел в бизнес. И Нофтскер тоже не чувствовал, что Гринблатт разбирается в бизнесе и тоже хотел решить эту проблему. Но более радикально. Нофтскер подговорил практически всех Лисп-машинистов, и в особенности самых важных, вроде Найта (Tom Knight) и Муна, основать компанию без Гринблатта. Что они и сделали. Эта компания стала называться Symbolics, она привлекала инвестиции, собиралась разрабатывать новую Лисп-машину Symbolics 3600, новый Маклисп под названием ZetaLisp и имела совсем другие планы насчет сотрудничества с МТИ.
ZetaLisp назывался так потому, что планировался как последний, окончательный Лисп [Stee96]. По видимому, в Symbolics или не знали какая буква в греческом алфавите последняя. Или думали, что другие лисперы не знают.
Но почему окончательный? Был ли он неким улучшенным Лиспом, исправляющим исторические недостатки и ошибки Лиспов? Нет, был свалкой фич и все тех же исторически сложившихся особенностей, которую даже его сторонники называли уродливой. Окончательность достигалась другими способами.
Гринблатт не сдался, основал компанию Lisp Machines Inc. Эта компания какое-то время пыталась следовать его плану. Но возникла проблема. Бесплатно писать код в МТИ для коммерческой компании Гринблатта стал только Столлман. Всех остальных машинистов МТИ наняла Symbolics. Совмещать работу там с работой в МТИ было запрещено. Так что Лисп-машинизм в МТИ был разгромлен. Правда, удушить LMI оказалось несколько сложнее. Но не потому, что первоначальный план Гринблатта заработал, а потому что Гринблатт передумал. В 83-ем LMI привлекли венчурные инвестиции, сопоставимые с Symbolics по размерам [Phil99].
Spice Lisp - разрабатывался под руководством Скотта Фалмана (Scott Elliot Fahlman) в Университете Карнеги — Меллона с 1980 в рамках проекта SPICE (Scientific Personal Integrated Computing Environment) по созданию рабочей станции [Stee82]. Как и ZetaLisp, Spice Lisp произошел от Lisp Machine LISP, но при менее драматических обстоятельствах. Spice Lisp не был создан для того, чтоб продавать конкретную Лисп-машину. Предполагалось портировать его на рабочие станции на которых некоторые Лисп-примитивы имплементированы с помощью микрокода. И это не самая распространенная разновидность рабочих станций, так что первое время Spice Lisp был имплементирован для одной рабочей станции - PERQ, которая еще поучаствует в нашей истории. В той части, которая не касается Лиспа. Изначально планируемая портируемость Spice Lisp, правда, еще пригодится.
Это не полный перечень вариантов MacLisp. Например, Le Lisp, о котором мы рассказывали в прошлой главе, в этот перечень не включен. В перечень входят варианты MacLisp, которые попытаются собрать обратно в один Лисп. Symbolics пока-что не преуспела в борьбе с Лиспами. И по итогам совещания в ARPA могло сложится впечатление, что Маклиспов слишком много. Нужно было что-то предпринять и предпринимать что-то стал Ричард Гэбриел.

Достаточно смертельная ловушка

Конечно, для работы одного диалекта на разных машинах были и важные для бизнеса причины. Но людей, которые разбирались в бизнесе, на встрече не было.
Гай Стил, Ричард Гэбриел. Эволюция Лиспа. [Stee96]

Ричард Гэбриел (Richard P. Gabriel) в 76-ом году портировал в Стенфорде MacLisp со специальной разработанной в МТИ ОС на обычную ОС для PDP-10. В том же году он поработал над Lisp370. C 1978 он работал над имплементацией NIL для S-1. В 1981 Гэбриел защитил диссертацию в Стенфорде, продолжая работать над S-1.
Гэбриел считал, что различия между Маклиспами не являются непреодолимыми и попытался наладить кооперацию между группами. И сделать это на деньги ARPA. Для начала он обсудил эту идею с Джоном Уайтом, разработчиком MacLisp для PDP-10 и NIL для VAX-11. Это тот самый Уайт, который писал статью про передвижной сборщик мусора. Затем Гэбриел и Уайт поговорили с Гаем Стилом. Стил уже поработал над MacLisp для PDP-10, RABBIT и, вместе с Гэбриелом, над NIL для S-1. И в обсуждаемое время работал над SPICE Lisp в КМУ. Этим троим найти общий язык было не так сложно. Они договорились подойти с предложениями к Фалману, руководителю разработки SPICE Lisp.
И через пару месяцев Гэбриел, Стил, Уайт, Фалман и еще несколько разработчиков NIL и SPICE Lisp встретились в КМУ и обсудили технические детали объединенного обратно Маклиспа. Стил работал над функциональным компилятором RABBIT, а Гэбриел с Уайтом оба работали над ранним функциональным Лиспом от IBM - Lisp370. Так что и объединенный обратно Маклисп договорились делать функциональным Лиспом с лексической видимостью по умолчанию и полноценными замыканиями.
Но уже на этом этапе поддержка функционального программирования скорее минимальная. Это не Схема. Как в важном, например оптимизация функциональных вызовов вообще и рекурсивных в частности не планируется. Так и в мелочах, вроде отдельного пространства имен для функций, что требует использовать специальный оператор для того, чтоб передавать функции в другие функции.
Особого акцента на функциональном программировании не делалось. Оно подавалась прочим лисперам как "консистентность" - одинаковое поведение между скомпилированным и исполняемым интерпретатором кодом.
После встречи в КМУ обсуждалось и название будущего стандартного Лиспа. Название Standard Lisp уже было занято. В попытках найти что-то похожее по смыслу нашли Common Lisp, но не стали пока останавливаться на этом названии. Название не всем понравилось. Так Гэбриелу не нравились ассоциации, ведь он собирался делать "элитарный Лисп".
Сформировавшееся первоначальное ядро разработчиков элитарного Лиспа пошло на контакт с другими группами лисперов. Самой важной группой посчитали Лисп-машинистов, и решили связываться с ними последними. После того, как наберут уже достаточно важности для этого.
Для начала, Гэбриел встретился с авторами Standard Lisp и разработчиками PSL, о которых мы писали в предыдущей главе. Они встретили Гэбриела доброжелательно и согласились участвовать в обсуждении стандарта. Правда, не особенно верили в то, что из этого что-то получится.
Встреча с разработчиками Franz Lisp получилась "менее сердечной". Возможно, что читатель предыдущей части удивится: почему разработчик Franz Lisp относится не особо сердечно к разработчику NIL, а не наоборот? Но не беспокойтесь, для этого уже появляются причины.
Разработчики Franz Lisp не горели желанием разрабатывать еще один Маклисп, но согласились голосовать против его фич.
Даже вечные соперники маклисперов - интерлисперы теперь относились к маклисперам лучше, как отметил Гэбриел, чем разные виды маклисперов друг к другу. И согласились предоставить из своих рядов наблюдателя для формирующегося комитета.
Связываться с разработчиками Lisp370 не стали. "Не имея на то хорошей причины", пишет Гэбриел. Не стали связываться и с европейскими лисперами, вроде авторов Le Lisp, на что они, по мнению Гэбриела и Стила, серьезно обиделись.
ARPA поддержало разработку стандарта. Но планируемая организация его разработки за счет ARPA не вполне состоялась. ARPA разрешила использовать для этого часть средств, которые уже выделило на разработку SPICE Lisp. И это не последнее несчастье SPICE Lisp в нашей истории.
Правда ARPA согласилось предоставить доступ к ARPANet для тех немногих участников стандартизации Лиспа, у которых такого доступа еще не было. Так что стандарт разрабатывался новым, невиданным доселе способом: преимущественно по электронной почте. И, обсуждая Common Maclisp по электрой почте, лисперы обнаружили, что такие обсуждения имеют тенденцию быть еще менее "сердечными", чем обсуждения при личной встрече. С другой стороны, лисперы жаловались на то, что не видя собеседника трудно определить, какой из ваших аргументов разозлил его больше всего.
Только заручившись всей этой поддержкой, в июне 81-го, Стил с Гэбриелом отважились начать переговоры с Лисп-машинистами. Машинисты заявили, что вся серьезная и важная для Лиспа работа идет на Лисп-машинах и эксперименты по имплементации Лиспа для обычных машин интересны только академии. Машинисты были не против стандартного Лиспа, и считали что их участие необходимо для того, чтоб проект оказался успешен. И если прочие лисперы хотят этого успеха, они должны слушать машинистов и делать Лисп таким, каким его хотят видеть Лисп-машинисты. Стандартный Лисп должен стремиться к тому, чтоб быть подмножеством Zetalisp. И прочие лисперы были вынуждены с ними согласится, хотя и продолжали жаловаться, что стандартный Лисп получается "наибольшим подмножеством Лиспа для Лисп-машин, который они могут забить в наши глотки" [Stee96].
К счастью для развития ФП, машинисты не были против "консистентности" (так называли минимальные функциональные фичи). К сожалению, стандартный Лисп проектировался так, чтоб для его имплементации требовалась Лисп-машина. Получался язык с дорогими вызовами функций и плохо подходящий для имплементации для обычной машины. Полная противоположность идей Стила времен RABBIT, Анти-схема.
Но бывший имплементатор MacLisp для MULTICS и ведущий мыслитель Лисп-машинистов Мун отмахивался от возражений против таких фич утверждая, что "достаточно умный компилятор" сможет справится и с этим. И Стил с Гэбриелом соглашались: ну конечно, они напишут достаточно умный компилятор. Вот только, скорее всего, Мун и совершенно точно машинисты в большинстве не верили, что "достаточно умный компилятор" возможен. Главная Лисп-машинная компания Symbolics, которая, по всей видимости, хотела остаться единственной лисповой компанией, рассмотрела идеи по созданию Лиспа для обычных машин и отказалась. Машинисты оценили, что даже на третьем поколении рабочих станций SUN Лисп будет работать по крайней мере в 17 раз медленнее, чем на Лисп-машине Symbolics 3600 [Gabr96]. Ожидали, что Лиспы для обычных машин неизбежно вымрут, уступив место окончательному Лиспу - ZetaLisp.
Позднее надежда подавляющего большинства имплементаторов Лиспа для обычных машин на "достаточно умный компилятор" рухнула и выражение стало употребляться преимущественно иронически.
На основе описания SPACE Lisp Стил с помощью других разработчиков SPACE Lisp и Гэбриела написал первый черновик под названием "Швейцарский Сыр" (много дыр). Черновик содержал множество вопросов, решения по которым должны были быть определены голосованием комитетчиков.
Тем временем, не смотря на работу над общим Маклиспом, или может быть даже благодаря ей, война маклисперов продолжалась и набирала силу.

Мэри Поппинс, до свидания

Функция возвращает время, потраченное на сборку мусора. Разумеется, в текущей версии NIL оно всегда равно нулю.
Берк и др. Справочное руководство по NIL 0.286 [Burk84]

NIL для S-1, будучи Лиспом для экзотической машины и далеко не самым важным языком для нее, не требовал посторонней помощи для того, чтоб уступить дорогу окончательному Лиспу. В 82-ом году Стил еще писал [Stee82], что для завершения проекта может потребоваться год, но позднее упоминания NIL для S-1 просто сходят на нет.
Успехи оставшихся в МТИ разработчиков Лиспа для обычных машин были переменными. Группа Фейтмана в Беркли могла одержать победу над разработчиками компиляторов для сборки Macsyma из МТИ потому, что Macsyma разрабатывалась на государственные деньги от ARPA и министерства энергетики и права МТИ на нее были ограничены [Stee96]. Но принятие акта Бая-Доула позволило МТИ контратаковать группу Фейтмана [Mose08]. МТИ заставил их отозвать разрешение на использование Macsyma у 50 пользователей VAX/UNIX и VAX/VMS версий Macsyma, которые группа Фейтмана в Беркли распространяла до этого [Fate2003].
Лицензирование Macsyma как общественного достояния в МТИ обсуждалось, но руководитель разработки Macsyma в МТИ Мозес успешно боролся против этого. Его подразделение получало все меньше финансирования из-за успехов Фейтмана и теряло все больше лисперов из-за разработчиков Лисп-машин. И Мозес считал, что разработку Macsyma в МТИ можно спасти только создав на основе группы Мозеса коммерческую компанию, которая будет разрабатывать и распространять Macsyma для VAX-миникомпьютеров и будущих рабочих станций с большим адресным пространством [Mose08]. Этот план он пытался осуществить с 81-го года.
Но значительная часть этой истории сегодня пересказывается в книге о Maxima [Fate2003], авторами которой являются и Мозес и Фейтман. Получилось так потому, что бывшие противники написали историю их общего поражения.
Преобразовать исследовательскую группу в коммерческую компанию хотел не только Мозес, это уже сделали Лисп-машинисты. Администрация МТИ не стала решать кому из бывших и будущих бывших сотрудников МТИ передавать лицензии. И они делегировали право решить это консультационной фирме Arthur D. Little.
И эта фирма решила отдать Macsyma не её разработчику Мозесу, а Лисп-машинистам из компании Symbolics. Что МТИ и сделал в 1982 году [Franz38] [Fate2003]. Мозес утверждает [Mose08], что Arthur D. Little принял такое решение потому, что сотрудники компании инвестировали в Symbolics. Так Лисп-машины, и до того бывшие одной из главных причин проблем NIL, нанесли NIL смертельный удар.
Группа Мозеса в МТИ прекратила свое существование, хоть и не так, как мечтал Мозес: его сотрудники ушли из МТИ работать в Symbolics. Например, поучаствовавший уже в нашей истории Джеффри Голден, работавший над нужными для Macsyma оптимизациями в компиляторах Лиспа [Mose08]. Узел ARPANET, предоставлявший доступ к Macsyma прекратил работу в 83-ем году [Fate2003].
Разработка NIL, для которой Macsyma была главным смыслом существования и источником финансирования, тоже прекратилась. Так и не имплементировав ни NIL, ни Common Lisp на который позднее переключилась. Уже в 82-ом году в DEC потеряли надежду на то, что NIL станет стандартным Липом для VAX и там стали разрабатывать свой совместно с КМУ [Stee82], на базе SPICE Lisp. Уайт оставил NIL и работу над маклиспами вообще, и успел стать интерлиспером до печального конца Interlisp.
Новое руководство проекта по написанию NIL для VAX зато, по всей видимости, считало, что нужно что-то релизить. И релизило. Первые пререлизы стали выпускать через годы после того, как NIL планировали закончить. И выпускали в декабре 82-го Release 0 [Burk83]. Выпускали в июне 83-го Release 0.259 [Burk83b]. Выпускали в январе 84-го Release 0.286 [Burk84]. И, наконец, перестали. Но в исходниках [NIL85] видны следы активности до конца 84-го. Разумеется, даже в последнем релизе не было работающего сборщика мусора.
И Мозес считает, что завоевание Macsyma Лисп-машинистами не пошло на пользу Macsyma. Ведь основным бизнесом Symbolics была продажа Лисп-машин, так что владение Macsyma использовалось в основном для недобросовестной конкуренции и удушения конкурентов-лисперов [Mose08] [Fate2003].
Акт Бая-Доула, впрочем, позволяет государству воспользоваться плодами своих инвестиций. И министерство энергетики получило от МТИ код Macsyma на NIL. Да, на неимплементированном языке [Fate2003].
Лисперам удалось отплатить группе Мозеса и MACSYMA-консорциуму за все унижения. Теперь уже не Лисп существовал ради MACSYMA, но MACSYMA стала средством для продвижения любимого детища лисперов - Лисп-машин.
К тому времени, когда разработка NIL подходила к безрезультатному к концу, разработка Common Lisp подошла к успешному завершению. На смену сырной редакции черновика летом 82-го пришел "Дуршлаг" (дыр стало больше, но их размеры уменьшились), за ним в ноябре того же года "Лазер" (предполагается когерентность). Практически все важные технические решения были приняты к началу 83-го, но работа продолжалась, пока 29-го ноября 83-го года не вышел последний черновик описания Common Lisp под названием "Мэри Поппинс", само совершенство. Описание в виде книги вышло только в следующем 84-ом году.
Все это время продолжалась работа над первоначальной, экспериментальной имплементацией Common Lisp, которой стал SPICE Lisp, но он не работал на распространенных машинах, которые были у имплементаторов ФЯ.
Да, к середине восьмидесятых готов ситуация с функциональным Лиспом принципиально не отличалась от ситуации в конце семидесятых. Что-то еще не доделано, что-то уже не будет доделано, что-то работает, только не на тех машинах.
Но подход к таким проблемам у имплементаторов ФЯ в 80-е, наоборот, принципиально отличался от 70-х. Они хотели использовать для имплементации функциональных языков все что можно. Нашли что использовать и в этом случае.

True == False

Весной 81-го Йельский университет, как и многие другие в то время, переходил с PDP-10 на новые машины. Но, в отличие от многих других совершающих этот переход, перескочил через поколение и заменял этот мэйнфрейм не на миникомпьютер VAX-11, а сразу на рабочие станции Apollo с процессором Motorola MC68K. Автору этой идеи Джону О'Доннелу очень нужно было сэкономить деньги. (Это не тот О'Доннел из первой части, который разрабатывал язык с уравнениями, это тот, который в середине 80-х пытался коммерциализировать Йельские наработки по VLIW.)
Но есть проблема, с которой уже сталкивались герои нашей истории в прошлой главе. В это время не было Лиспа, который бы работал на тех рабочих станциях, которые купил О'Доннел. И особых надежд на то, что он скоро появится не было. Так что, как и в Inria в это же время, в Йеле решили разрабатывать подходящий Лисп самостоятельно.
Но в Йеле и в Inria посчитали подходящими совсем разные Лиспы. В Йеле Лисп для рабочих станций стал писать приятель О'Доннела Джонатан Рис, который решил имплементировать "Схему". И Рису было не сложно уговорить О'Доннела, для которого не было таким уж важным, какой Лисп будет имплементирован, важным было то, что можно будет сменить мэйнфрейм на рабочие станции.
Нужно отметить, что желание имплементировать Схему в 81-ом году не было распространенным желанием. Даже знание об этом языке и серии статей Сассмана и Стила все еще не было особенно распространено [Rees2004] [Shiv2001]. В Йеле со всем этим были знакомы человека четыре. Одним из них был Дрю МакДермотт (Drew McDermott), который в 70-е был, как и Стил, студентом Сассмана в МТИ, а в 80-е преподавал в Йеле. Он и научил Риса Лиспу и идеям Стила и Сассмана.
Джонатан Рис (Jonathan A. Rees) уходил в академический отпуск и работал в МТИ над имплементацией NIL. Да, над NIL работал студент из Йеля. Потому, что все кто мог стать Лисп-машинистом в МТИ становились Лисп-машинистами. Желающих писать Лиспы для унылых обычных машин не было.
Заканчивая свое обучение в Йеле весной 81-го года, Рис прослушал курс Перлиса по функциональному программированию. Один из вспоминающих [Shiv2001] в наши дни об этом курсе утверждает, что Перлис сам узнал о функциональном программировании не так давно. Что сомнительно. Как мы помним, Перлис был одним из тех, кто понимал борьбу функциональной партии авторов ALGOL 60. Но, может быть, имеется функциональное программирование в узком "эдинбургском" смысле. Из его курса можно было узнать о HOPE и, видимо, о каком-то языке Тернера. Вспоминающий вспомнил язык, который Тернер к тому времени еще не создал.
Рис получил диплом в 81-ом. И летом того же года был готов имплементировать функциональный Лисп для О'Доннела. Работа началась в июне 1981, помимо Риса в проекте участвовали еще два человека: Норман Адамс (Norman I. Adams) и Кент Питман (Kent Pitman), один из имплементаторов Macsyma и компилятора MacLisp для PDP-10.
Питман вместе с Рисом занимались дизайном языка. Мы писали "Схема" в кавычках не просто так. Рис и другие писали имплементацию не одного из Схема-репортов, а нового диалекта Схемы. Рис работал над NIL и совершенно точно не хотел работать над еще одним. И результат отрицания NIL (False) в Лиспе будет T (True). Так новую схему и её имплементацию и назвали, тем более что программы, которые писали в Йеле традиционно получали однобуквенные названия вроде e, c, z и u. Название T иногда писали Tau, а в подготовленных для печати документов использовали заглавную букву тау Τ.
Питман написал на MacLisp для PDP-10 экспериментальный интерпретатор T1, на котором они с Рисом испытывали свои идеи. Этот исследовательский этап проекта закончился в сентябре 81-го с уходом Питмана, который вскоре после этого начал работать над Common Lisp. C сентября 81-го и, по крайней мере до лета 82-го разработка T становится проектом более привычного для разработки ФЯ размера в одну с четвертью ставки. Летом 82-го он, возможно, увеличился до двух, когда к проекту присоединился Джеймс Филбин (James Philbin).
Это не единственное сходство T с первыми ФЯ. T, как и Схема, это функциональный Лисп, о функциональности которого заявляется открыто и смело. Никто не прячет её за "консистентностью", как в NIL и Common Lisp. Первая публикация [Rees82] с описанием T повторяет доводы из статей Сассмана и Стила и даже называется по тому же шаблону "LAMBDA: The ultimate software tool". И эффективная имплементация функционального языка на обычных машинах (например, оптимизация хвостовых вызовов) имеет первостепенную важность. Можно даже сказать, что она важнее чем в Схеме. Главное отличие T от Схемы в том, что в T отказались от "антипрологовости" Схемы. Выразительность продолжений в T ограничена по сравнению со Схемой так, чтобы упростить имплементацию языка с использованием обычного стека.
Авторы T в принципе не против продолжений и даже предполагали, что в будущем улучшат их поддержку добавлением еще одного специального стека. Вероятно, что главное отличие T от Схемы продиктовано деталями имплементации компилятора. И первый компилятор T, не смотря на все отсылки к Стилу, это вовсе не RABBIT 2. Может быть T и был отрицанием NIL с точки зрения истории идей. Но, с точки зрения истории имплементаций, T это буквально NIL. Рис взял за основу и модифицировал код компилятора NIL для S-1, написанного Стилом на MacLisp. Генераторы кода для более полезных чем S-1 систем написал Адамс, позднее дописывал еще и Филбин.
Интерпретатор Питмана не использовался для бутстрапа. Компилятор, над которым работал Рис, был написан на общем подмножестве МакЛиспа и T так, чтоб его можно было с помощью слоя совместимости скомпилировать компилятором MacLisp на PDP-10 как кросс-компилятор, генерирующий код для VAX-11 и MC68K. Этот кросс-компилятор заработал в начале мая 82-го и скомпилировал интерпретатор языка T, написанного на T - T 2 и компилятор T, бывший компилятор NIL, переписанный на подмножество T - TC 1. И компилировал какое-то время, пока код не вышел за пределы поддерживаемого Маклиспом подмножества. T 2.3 была последней версией, которая еще компилировалась кросс-компилятором [TMail].
Так что первым из всех наработок функциональных лисперов использовали самый заброшенный проект. Не ожидали? Но, может быть, из-за ненужности и заброшенности компилятора NIL для S-1 его код и можно было брать и использовать. Никто не попытался превратить его в коммерческий продукт. Рис работал над NIL для VAX и использовал некоторые техники имплементации, с которыми познакомился во время этой работы, но, видимо, возможности использовать код NIL для VAX не было.
И заброшенность Лиспового проекта, конечно, относительна. Даже в NIL для S-1, по видимому, вложено больше труда, чем в типичный для нашей истории ранний компилятор ФЯ.
Правда, Рис использовал код NIL для S-1 далеко не самой последней версии. Так что значительная часть вложенных в него трудов все равно была напрасной. NIL для S-1 был достаточно недоделанным для того, чтоб Рису пришлось то ли имплементировать полностью, то ли просто доделывать имплементацию лексических замыканий. Так что даже от функциональных идей авторов NIL для S-1 особой пользы могло и не быть.
T, как это не удивительно, применили для системного программирования, да еще и собирались использовать как промежуточный язык и бэкенд для имплементации обычных императивных языков, так что разрешать мутабельность только как ссылки на изменяемые объекты в куче было неприемлемо. Поэтому замыкания нельзя представлять одним массивом. Но и списки как в RABBIT - не практичный подход, так что в T замыкания - списки массивов.
Разумеется, код не использующий ФВП обходится аллокациями только на стеке. Возможно, что даже код, который только принимает функции, как в RABBIT. По крайней мере весной 82-го это планировали сделать.
Но почему T не использовал RABBIT? Может быть просто потому, что Рис мог достать код компилятора NIL, но не мог достать код RABBIT. Может быть это еще один довод в пользу того, что RABBIT не особенно подходил для практического использования. По крайней мере, авторы, зная про RABBIT, утверждают, что пригодных для практического использования компиляторов схемы в 1982 году нет [Rees82]. Но, скорее всего, амбициозный CPS-трансформирующий RABBIT был слишком большим для ранних рабочих станций.
Как мы выяснили в предыдущей части, для того, чтоб скомпилировать себя RABBIT потребовалось бы на машине с 32бит указателями 2-3Мб памяти. И микрокомпьютерам - рабочим станциям в 81-ом году было еще далеко до очень специальной конфигурации мэйнфрейма, на котором работал RABBIT. Даже рабочие станции на которых компилятор T работал в 83-ем году - Apollo DN300 - имели только 0.5-1.5Мб памяти в зависимости от комплектации [Data83]. Эти рабочие станции поддерживали страничную память и RABBIT бы в ней кое-как уместился, частью на жестком диске. Но понятно, почему такая идея могла выглядеть не особенно хорошо.
По крайней мере, у компилятора NIL для S-1 настоящий аллокатор регистров, написанный на основе опыта мейнстримного компилятора Bliss-11. И проект разработки и имплементации T в 81-83гг. скорее напоминает разработку обычного компилятор ФЯ из этой части истории, а не обычный лисповый проект того времени. И получить от лиспового проекта хоть и недоделанный, но компилятор было очень важно для разработчиков T. Тем более, что сами они называют компилятор важнейшей частью имплементации, подчеркивая сходство с "обычными" языками и отличие от обычного Лиспа, главная часть имплементации которого - интерпретатор. Но Рис не считает, что T компилирует так же хорошо, как Bliss-11 [TMail]. Достаточно хорошо, чтоб написать большую часть имплементации T на T, включая и сборщик мусора. Но написать такой сборщик мусора, скорость которого хорошей не считают.
И по готовности сборщика мусора мы попробуем оценить готовность имплементации для использования. То, что сборщик мусора какое-то время не был имплементирован - это уже довольно привычно. Но у T был и необычный этап имплементации сборщика. В апреле 82-го, в версии 0.53 была добавлена функция GC, но автоматически сборщик не вызывался, только вручную, если нужно [TMail]. Сборщик какое-то время работал не очень хорошо. Например, в первом релизе рантайм падал в случае повторного вызова этой функции.
Сборщик стал запускаться автоматически только в мае 83-го года, в версии T 2.6 (TC 1.3). Размер кучи увеличивался тоже автоматически, и был ограничен на UNIX и Aegis (клоне UNIX, написанном в Apollo на Паскале) 3-4Мб. Сборщик был копирующим, но не сборщик Чейни, обходил дерево объектов в глубину.
После выхода этой, по видимому, первой практически полезной версии, было еще два релиза T2. В декабре 83-его вышел T 2.7 и в мае 84 T 2.8 (TC 1.4). Последняя версия T2 - 2.9 не была доступна для пользователей, её использовали только для написания T3. Уже в 84-ом году разработчикам T стали доступны гораздо более мощные рабочие станции и у них появились гораздо более амбициозные планы. Но это уже другая история.
Насколько быстро работал скомпилированный T2 код? Мы нашли один микробенчмарк [TMail], который позволяет сравнить T 2.7 с некоторыми другими компиляторами, работающими на VAX-11/780

tak 18 12 6
Franz(local) 0.66
T2(local) 0.74
C 0.79
T2(unsafe) 1.00
C(lml) 1.06
Franz 1.25
NIL 1.59
Franz(lml) 1.76
T2 2.00
LML 5.88
VAX ML 9.41
Franz(local)
░░░░░░░
T2(local)
░░░░░░░░
C
░░░░░░░░
T2(unsafe)
░░░░░░░░░░░
C(lml)
░░░░░░░░░░░
Franz
░░░░░░░░░░░░░
NIL
░░░░░░░░░░░░░░░░░
Franz(lml)
░░░░░░░░░░░░░░░░░░░
T2
░░░░░░░░░░░░░░░░░░░░░
LML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
VAX ML
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Обратите внимание, что замеры, сделанные разработчиками T 2 и LML (отмечены (lml)), отличаются даже для одних и тех же компиляторов. Хотя и сделаны на одной и той же модели компьютера. Но все равно видно, что производительность T2 выглядит неплохо. Наконец-то, через два десятка лет после появления первого компилятора Лиспа, появился компилятор функционального Лиспа, который работает на распространенной машине и который можно использовать как бэкенд для функционального не-лиспа. И его использовали.

Туда и обратно

Пол Худак (Paul Raymond Hudak) защитил магистерскую диссертацию по "моделированию понимания музыки" в МТИ в 74-ом. Там он познакомился с антипрологами Planner и Conniver, но предпочитал использовать Лисп, в котором антипролог мог быть EDSL.
После получения степени магистра, Худак поработал в Watkins-Johnson Company. В академию он вернулся пять лет спустя в 79-ом.

FEL

С 79-го по 82-ой год Худак писал диссертацию в Университете Юты [Huda2012] [Hugh15] под руководством Роберта Келлера. Там, где разрабатывали Standard Lisp и PSL. Но не только. Университет был также одним из тех немногих, в которых разрабатывали ленивый ФЯ. Для нашей истории довольно важно, что Худак оказался в таком особом месте, но это, по видимому, произошло случайно. По крайней мере Питерсон утверждает [Hugh15], что Худак выбрал Университет Юты из-за увлечения горнолыжным спортом.
Научрук Худака Келлер (Robert M. Keller) с 79-го года разрабатывал FGL (Function-Graph Language), который первоначально был нетекстовым языком графов. Но в ходе работы Келлер обнаружил, что писать текст удобнее. Так что появился "текстовый FGL". Раз уж Келлер работал в Университете Юты, где Хирн использовал Лисп с алголоподобным синтаксисом для реализации Reduce 2, то и текстовый FGL получил похожий синтаксис, который несколько адоптировали для ФП [Kell80]. Получился язык в котором функции выглядят так:

FUNCTION foo(a, b)
IMPORT bar
LET x BE baz(a),
    y BE baz(b)
RESULT bar(x,y)
WHERE 
FUNCTION baz(x) RESULT x 

Тела этих LET вычисляются, когда потребуются.
Но пока шла работа над FGL закончились 70-е в которые авторы ФЯ, в основном, переизобретали все сами, и наступили 80-е, когда автор ФЯ знает про книгу Берджа, ISWIM, SASL и HOPE. Вот и Келлер узнал про все это и обнаружил, что по собственным же словам "переизобретает ISWIM" и разрабатывает язык родственный SASL [Kell82]. Так что текстовый FGL далее эволюционировал в предсказуемом направлении, но сохранил некоторую самобытность.
Келлер заменил LET и WHERE конструкции одной {decls, RESULT expr, decls} в которой декларации могут быть до и/или после выражения, в котором они используются [Lind85].

FUNCTION foo([a, b]) = 
{ x = baz(a),
  y = baz(b),
  RESULT bar(x,y),
  FUNCTION baz(x) = x }

Этот язык получил название FEL (Function-Equation Language) [Kell82]. На RESULT-конструкции идеи Келлера о том, как улучшить ISWIM не закончились. Он добавил в FEL три типа применения функций:

f:g:h:x === f(g(h x))
f|g|h|x === ((f g)h)x
x;h;g;f === f(g(h x))

и сделал синтаксис еще полегче:

foo|a:b = 
{ x = baz:a
  y = baz:b
  RESULT bar|x:y
  baz:x = x }

Наш обычный пример выглядит на FEL так:

{ map|f:l =
    if l = [] then []
              else { [h,t] = l
                     RESULT f:h ^ map|f:t }
  RESULT map|(x => x + y):(1 ^ 2 ^ 3 ^ []) 
  y = 2 }

Также как и большинство авторов ФЯ этого времени, Келлер собирался типизировать FEL. Причем с выводом типов как в ML и сделать язык более похожим на HOPE, но пока не собрался. Мы вернемся к работе Келлера в следующей главе.

ALFL

Пол Худак ознакомился с FGL/FEL, защитил диссертацию по сборке мусора в 82-ом году и отправился работать в Йель. Там он создал ALFL - ФЯ, который имел пару значительных отличий от FEL. И, как обычно в то время, множество незначительных, которые потребовали бы исправить каждую строку кода при переписывании с одного языка на другой. Если бы было что переписывать, конечно.
ALFL в большей чем FEL степени SASL и потому выглядит привычнее и более похожим на современный ФЯ. В применении и декларации каррированных функций больше нет самобытности. Но result-конструкция никуда не делась:

foo a b == 
{ x == baz a;
  y == baz b;
  result bar x y;
  baz x == x }

Применение функций все же не в точности как в SASL или, скажем, в Haskell. Применение обладает не самым высоким приоритетом. И оператор (:) который используется в ALFL для замены FEL-применения отличается от современного ($) использованием этой возможности: twice f x == f f:x. ALFL также один из первых ФЯ с Хаскельным оператором композиции (.).
Более существенные отличия ALFL от FEL тоже из SASL. В ALFL есть не только паттерн-матчинг для вложенных кортежей как в LCF/ML, но и уравнения:

{ map f [] == [];
  map f (x^L) == f x ^ map f L; 
  result map (@ x == x + y) [1, 2, 3]; 
  y == 2 }

Да, Худака, как и Келлера, в ненужности лямбд Ландин с Тернером не убедили.
Худак пишет, что паттерн матчинг похож на SASL, HOPE, Prolog и придает коду "логический стиль". Сходство с HOPE преувеличено, но утверждение про SASL и Prolog соответствуют действительности. Как и в SASL и как в Prolog, порядок уравнений имеет значение, а паттерны нелинейные, одинаковые имена в паттернах означают проверку на равенство:

member x (x^L) == true;

В ALFL можно не повторять имена функций в группах уравнений и писать

map f [] == [];
 '  f (x^L) == f x ^ map f L;

и есть отсутствующие в SASL (да и в прочих ФЯ) паттерн-выражения (pattern expressions):

{ k == 5;
  foo x #(x+k) == true;
   '  x y      == false;
  foo 5 10 } % true

Это не n+k паттерны, паттерн-выражения не вводят имена а используют их. Это скорее более компактные, но менее выразительные гарды:

k = 5
foo x y | y == x+k  = True
        | otherwise = False
foo 5 10 -- True

ALFL не позаимствовал из SASL строки как ленивые списки символов. В SASL это специальный объект кучи. Не позаимствовал из SASL и стандартную библиотеку. Функции работы со списками в ALFL как в FEL. Оператор свертки [f,init]//list, zip-n оператор f\\listOflists. Оператор map f||list, который обрабатывает и вложенные списки. Оператор ::, работающий как sequence для []-аппликатива.
Не смотря на паттерн-матчинг, в библиотеке ALFL есть аналоги лисповых составных селекторов, вроде hhd и httl.
Не позднее 84-го года в ALFL добавили нотацию для построения списков с удалением дубликатов, "упорядоченные множества" {* ... *} и без, "упорядоченные мультимножества" [* ... *]. Как в языках Тернера, но не совсем:

[* [a,b] ! ["left", a] <- as ; ["right", b] <- bs ! a <> b *]

Отличие от современной такой нотации в том, что "генераторы" и "фильтры" не могут чередоваться: [* результат ! генератор ... ; генератор ! фильтр ; фильтр ... *]. Сходство с современной нотацией и отличие от первых и опубликованных к этому времени экспериментов Тернера в том, что слева от <- паттерн, а не только имя. Это нововведение может быть независимым изобретением, но может и не быть.

Компиляция ALFL

ALFL "вырос" из "игрушечного" языка Mini-FPL для курса по разработке компиляторов [Huda84]. Нам не известно, в чем было отличие между ними. Можно предположить, что в основном в "игрушечности". Имплементацию ALFL делали более практичной.
Простота комбинаторного интерпретатора Тернера привлекательна для имплементатора ленивого ФЯ. И многие оптимизации свертывания констант и вынесения вычисления из "цикла" просто работают по мере выполнения кода. Тривиальна и межпроцедурная оптимизация, границы процедур просто исчезают в получающемся комбинаторном супе. Но что толку от всех этих оптимизаций, если размер комбинаторов слишком мал и накладные расходы интерпретации доминируют? Комбинаторный интерпретатор работает, как пишет Худак, "невыносимо" медленно. Получается не очень практично.
Но Худак не попытался изменить соотношение между полезной работой и накладными расходами на интерпретацию как Хьюз, Йонссон и другие. Худак узнал о не особенно хороших результатах Хьюза, но, по видимому уже после того, как у него самого появилась другая идея.
Что если отказаться от интерпретации во время исполнения вовсе? Пусть комбинаторный интерпретатор работает во время компиляции, оптимизируя код частичным его вычислением, пока не останется то, что можно вычислить только во время выполнения, пока не исчерпает свою полезность. Получившиеся в результате комбинаторы конвертируются обратно в лямбды и компилируются. Ленивость реализуется самомодифицирующимися санками. И анализ строгости позволяет использовать эти санки только там, где без них не обойтись. И Худак считает, что чаще всего без них можно будет обходиться. Анализ разделения также позволяет сохранять как граф в куче только разделяемые значения, а не все промежуточные результаты.
Тернер не убедил Худака, что имплементация лямбд с помощью окружений такая уж серьезная проблема. Худак прочитал у Стила, что проблема решена. Так что Худак начинает компиляцию с лямбды и заканчивает ей. Комбинаторы - только промежуточное представление для оптимизатора.
Но не все составные части уже готовы, так что их остается только собрать в компилятор ФЯ. Алгоритм Тернера по преобразованию лямбд в комбинаторы дает плохой комбинаторный код для частичного выполнения. Так Тернер смело вставляет Y для всех групп объявлений, даже если нет рекурсии и он не нужен. И частичный вычислитель Худака не производит редукцию Y. Так что Худак изменил алгоритм.
Разумеется, подход Худака сохраняет не все плюсы комбинаторного интерпретатора. Интерпретатор оптимизирует только код, который выполняется. В отличие от него, компилятор ALFL оптимизирует даже неиспользуемое и генерирует много кода.
Худак обсуждает то, что частично выполняющий компилятор может зациклится во время компиляции. Да, он не редуцирует Y, но ALFL - бестиповый язык и отсутствующий тайпчекер не остановит зацикливающийся код вроде такого:

{ f x == x x;
  result f f }

Но Худак не особенно опасается этого, не считает, что программист часто такое будет писать.
Первый компилятор ALFL написан Худаком и Дэвидом Кранцом (David Kranz) на T в 83-ем году. По крайней мере его описание [Huda83] сделано в 83-ем и опубликовано в январе 84-го.
Парсинг, преобразование из лямбд в комбинаторы и их частичная редукция, а также исключение повторяющихся выражений объединены в один проход. Не совсем понятно почему. Может это осталось от более "игрушечной" имплементации, которая могла быть просто комбинаторным интерпретатором и должна была работать используя меньше памяти. Может быть это сделано потому, что дерево комбинаторов до оптимизации часто больше соответствующего дерева лямбд и имплементаторам не хотелось держать его в памяти целиком. Может быть комбинаторная часть компилятора написана так потому, что ее смогли так написать из-за достаточной для этого простоты комбинаторного подхода.
Остальная часть компилятора написана совсем не так. Полученные в результате первого прохода комбинаторы транслируются в машкод еще в четыре прохода.
Второй проход это трансляция в некаррированные лямбды со многими параметрами - "макро-комбинаторы". Трансляция в макро-комбинаторы это независимо переизобретенная Худаком неполная трансляция в супер-комбинаторы Хьюза.
На третьем проходе - анализ строгости и разделения. Первоначально анализ строгости и разделения узлов производился над тернеровскими комбинаторами, но позднее решили, что делать его для лямбд удобнее.
На четвертом проходе эскейп-анализ определяет какие окружения для лямбд можно размещать на стеке, а какие придется размещать в куче. Как это делают RABBIT и TC.
На пятом проходе генерируется код, ленивые аргументы и ленивые разделяемые узлы имплементируются как самомодифицирующиеся санки. Самомодифицирующийся санк не создает дополнительной косвенности ссылок, как это часто бывает в таких имплементациях ленивости, ссылка на санк переписывается ссылкой на результат. Компилятор генерирует код для PDP-10.
Насколько быстрый код? Судя по всему, не особенно быстрый. Худак с Кранцом не приводят измерений, но называют результаты "вдохновляющими". Утверждают, что намного быстрее, чем комбинаторный интерпретатор. И в это легко поверить. Но вот сравнивать производительность с Лиспом "сложно". Первый компилятор - это только демонстрация идеи, оправдывается Худак. Анализы, которые делает компилятор ALFL пока что слишком консервативны. Но это, утверждает Худак, может быть исправлено.
К тому же, в компиляторе ALFL не используются "обычные оптимизации" вроде аллокации регистров и оптимизации хвостового вызова. Ну, не все первые компиляторы ФЯ сносно аллоцируют регистры. Но можно только порадоваться тому, что в 83-ем году оптимизация хвостового вызова уже считается обычной.
Ну а какой компилятор делает эти "обычные" оптимизации? Правильно, TC. И следующий логичный шаг, который делает Худак - отказаться от собственного кодогенератора и использовать TC в качестве бэкенда для компилятора ALFL. Этот второй компилятор ALFL назывался Alfa-Tau заработал не позднее октября 84-го [Huda84]. Над компилятором помимо Худака работали Фред Дуглис (Fred Douglis) и сменившая его в 84-ом Эдриенн Блосс (Adrienne G. Bloss), работали Джонатан Янг (Jonathan Young) и Лорен Смит (Lauren Smith). Смит имплементировала нотацию для построения списков, которой в первом компиляторе ALFL еще не было.
Правда, сравнений производительности так пока и не появилось.

Этого имени я не слышал уже давно

Итак, в начале 80-х лисперы сделали движение в сторону общего Маклиспа, и даже общего Лиспа. И, к тому же, Лиспа функционального. И это усилие, конечно, оказало влияние на те языки, на которые Лисп обычно оказывал влияние. На Лиспы для тех, кто недолюбливает Лисп, такие как POP.
После конца бывшей группы экспериментального программирования в Эдинбурге, там уже не находили сил или даже интереса для спасения POP-2 с PDP-10. Но POP-2 - не последняя версия POP.
Аарон Сломан (Aaron Sloman) ознакомился [Slom89] с POP-2 в начале 70-х, когда работал над распознаванием образов в Эдинбурге и хотел использовать язык для преподавания в Университете Сассекса (University of Sussex). Для этого, правда, существовало серьезное препятствие. Университет был слишком бедным для того, чтоб позволить себе мэйнфрейм, на котором бы работала какая-нибудь из имплементаций POP-2.
Какое-то время в Сассексе использовали WPOP на Эдинбургском PDP-10 дистанционно, но такое использование оставляло желать лучшего. В Сассексе могли себе позволить миникомпьютер и в 75-ом году Стив Харди (Steve Hardy) выбрал для университета компьютер PDP-11/40, популярный миникомпьютер DEC. За полгода, к январю 76-го, Харди написал на ассемблере байткод-интерпретатор урезанного POP-2, который назвал POP-11. Для неурезанного POP у PDP-11 было слишком мало памяти. Но это ограничение было временным. В DEC не собирались закрывать успешную линейку машин, к которой принадлежал PDP-11 и продолжили её, выпустив компьютер с большим адресным пространством.
Первые VAX11/780, сначала с 2.5Мб памяти, появились в Университете Сассекса в 81-ом году. Летом Сломан принял на работу своего бывшего студента Джона Гибсона (John Gibson), одного из дистанционных пользователей POP, и тот написал компилятор POP-11 на POP-11 для VAX-11. Для бутстрапа использовали интерпретатор Харди и получили первую работающую версию за несколько часов до первого занятия курса Сломана, на котором тот хотел использовать POP.
Увеличение памяти позволило вернуть урезанные в свое время фичи POP-2, вроде динамических списков и рекордов. POP-11 прошел через игольное ушко PDP-11 сначала потеряв многие фичи POP-2, а потом вернув их на более подходящей машине, так что существенно отличался от POP-2. И, в добавок к этим исторически обусловленным отличиям, были, конечно же, сделаны множество мелких изменений. Например, ключевые слова для объявлений и лямбд.

define map(list, proc);
    if null(list) then []
    else
        proc(hd(list)) :: map(tl(list), proc)
    endif
enddefine;

z = 2;
map([1 2 3], procedure(x,y); x + y endprocedure(%z%))

Некоторый синтаксис из POP-2 пока что оставался для совместимости, но едва ли можно было легко использовать POP-11 для спасения с PDP-10 имплементации HOPE на POP-2. Даже если бы у кого-то и было желание это сделать.
Как вы поняли, имплементаторы POP-11 - из той разновидности имплементаторов ФЯ, которые имплементируют бодро, весело и много. И POP-11 для VAX - имплементация, которая оптимизирована для этого. В POP-11 были макросы, которые раскрываются не только в синтаксически правильный POP-11, но и в инструкции виртуальной машины. Это должно было позволить легко писать фронтенды для других языков.
И в начале 80-х все больше и больше имплементаторов ФЯ хотели имплементировать не древний и неудобный язык вроде POP, а современный, с уравнениями и паттерн-матчингом. Вот и разработчики и пользователи POP-11 решили использовать его для имплементации такого языка. Для имплементации Пролога.
В 82-ом году Крис Меллиш (Chris Mellish) и Стив Харди придумали как имплементировать Prolog с помощью инструментария для расширения POP-11. Меллишу удалось это сделать, только скорость исполнения кода получилась не такая хорошая, как у передовых имплементаций Пролога. Одна из причин - то, что можно было аллоцировать на стеке в специализированной имплементации Пролога, приходилось размещать в куче в виде объектов POP-11. Использование таких объектов было необходимо для прозрачного интеропа между языками. Пользователи POP-11 хотели писать программы на нескольких языках и приоритизировали такой интероп. Другая причина - виртуальная машина просто не имела нужных для эффективной имплементации фич.
Так что Гибсон добавил в виртуальную машину стек продолжений и прочие нужные для имплементации Пролога вещи. Новый компилятор назвали POPLOG. Название больше не менялось но языки продолжили добавлять. Сначала новые языки имплементировались с помощью макросистемы. Затем некоторые из них получали более качественную поддержку компилятора и ВМ и попадали в базовый комплект поставки.
Следующим после Пролога языком, который имплементировал POPLOG, стал Лисп. Джон Каннингем (Jon Cunningham) имплементировал подмножество MacLisp. Но эпоха MacLisp подходила к концу, начиналась эра Common Lisp. И в 83-ем году, не дожидаясь окончания работы над описанием Common Lisp, имплементаторы POPLOG начали работу над имплементацией Common Lisp. И, что более важно для нашей истории, добавлением в виртуальную машину поддержки лексической видимости, которую требовал функциональный Лисп. Но это уже другая история.

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

Литература

[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/
[Burk83]: Glenn Burke. Introduction to NIL. Laboratory for Computer Science, Massachusetts Institute of Technology, March 1983. https://www.softwarepreservation.org/projects/LISP/MIT/Burke-Introduction_to_NIL-1983.pdf
[Burk83b]: Glenn S. Burke, George J. Carrette, and Christopher R. Eliot. NIL Notes for Release 0.259, Laboratory for Computer Science, Massachusetts Institute of Technology, June 1983. https://www.softwarepreservation.org/projects/LISP/MIT/Burke_et_al-NIL_Notes_Release_0259-1983.pdf
[Burk84]: Glenn S. Burke, George J. Carrette, and Christopher R. Eliot. NIL Reference Manual corresponding to Release 0.286. Report MIT/LCS/TR-311, Laboratory for Computer Science, Massachusetts Institute of Technology, January 1984. https://www.softwarepreservation.org/projects/LISP/MIT/Burke_et_al-NIL_Reference_Manual_0286-1984.pdf
[Chai83]: J. Chailloux, J.M. Hullot, Jean-Jacques Levy, J. Vuillemin. Le systeme LUCIFER d'aide a la conception de circuits integres. RR-0196, INRIA. 1983.
[Chai84]: Jérome Chailloux, Matthieu Devin, and Jean-Marie Hullot. 1984. LELISP, a portable and efficient LISP system. In Proceedings of the 1984 ACM Symposium on LISP and functional programming (LFP '84). Association for Computing Machinery, New York, NY, USA, 113122. doi:10.1145/800055.802027
[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).
[Data83]: Datapro report M11-075-10 8311 http://bitsavers.informatik.uni-stuttgart.de/pdf/datapro/datapro_reports_70s-90s/Apollo/M11-075-10_8311_Apollo_Domain.pdf
[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
[Fate81]: Fateman RJ. Views on transportability of lisp and lisp-based systems. InProceedings of the fourth ACM symposium on Symbolic and algebraic computation 1981 Aug 5 (pp. 137-141).
[Fate2003]: Souza, Paulo Ney de, Richard J. Fateman, Joel Moses and Clifford W Yapp. “The Maxima Book.” (2003). https://maxima.sourceforge.io/docs/maximabook/maximabook-19-Sept-2004.pdf
[Franz38]: Franz Lisp Opus 38.93 https://github.com/krytarowski/Franz-Lisp-Opus-38.93-for-4.3BSD-Reno
[Gabr96]: Gabriel, Richard P. Patterns of software. 1996.
[Gris82]: Martin L. Griss, Eric Benson, and Gerald Q. Maguire. 1982. PSL: A Portable LISP System. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 8897. doi:10.1145/800068.802139
[Griss]: Martin Griss, Biography https://martin.griss.com/bio.htm
[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
[Gord2000]: Gordon M. From LCF to HOL: a short history. In Proof, language, and interaction 2000 Jul 24 (pp. 169-186).
[Hear79]: J. Marti, A. C. Hearn, M. L. Griss, and C. Griss. 1979. Standard LISP report. SIGPLAN Not. 14, 10 (October 1979), 4868. doi:10.1145/953997.953999
[Hear2005]: Hearn, Anthony C.. “REDUCE: The First Forty Years.” Algorithmic Algebra and Logic (2005).
[HOL88]: HOL88 https://github.com/theoremprover-museum/HOL88
[Holm98]: Holmevik, Jan Rune. "Compiling Simula: A historical study of technological genesis." (1998) https://staff.um.edu.mt/jskl1/simula.html
[Huda83]: Paul Hudak and David Kranz. 1984. A combinator-based compiler for a functional language. In Proceedings of the 11th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '84). Association for Computing Machinery, New York, NY, USA, 122132. doi:10.1145/800017.800523
[Huda84]: Hudak, Paul. ALFL Reference Manual and Programmer's Guide. Yale University, Department of Computer Science, 1984.
[Huda2012]: Paul R. Hudak, CV https://www.cs.yale.edu/homes/hudak/Resumes/vita.pdf
[Huet]: Gérard Huet, biography https://gallium.inria.fr/~huet/biography.html
[Huet86]: Huet, G. (1986). Theorem proving systems of the Formel project. In: Siekmann, J.H. (eds) 8th International Conference on Automated Deduction. CADE 1986. Lecture Notes in Computer Science, vol 230. Springer, Berlin, Heidelberg. doi:10.1007/3-540-16780-3_138
[Huet15]: Gérard Huet, Thierry Coquand and Christine Paulin. Early history of Coq, September 2015 https://coq.inria.fr/refman/history.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
[Hugh15]: John Hughes, John Peterson. Tribute to Paul Hudak, ICFP 2015 https://www.youtube.com/watch?v=Hivzs-LUmU0
[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.
[Kell80]: Robert M. Keller. 1980. Divide and concer: Data structuring in applicative multiprocessing systems. In Proceedings of the 1980 ACM conference on LISP and functional programming (LFP '80). Association for Computing Machinery, New York, NY, USA, 196202. doi:10.1145/800087.802806
[Kell82]: Keller, RobertM. "FEL: An Experimental Applicative Language." 情報処理学会研究報告プログラミング (PRO) 1982, no. 49 (1982-PRO-003) (1982): 73-79.
[Krei2002]: Kreitz, Christoph. "The Nuprl Proof Development System, Version 5: Reference Manual and Users Guide." Department of Computer Science, Cornell University (2002)
[LCF77]: Code for LCF Version 5, Oct 1977 https://github.com/theoremprover-museum/LCF77
[LCF92]: cambridge lcf 1.5 (25-AUG-92) https://github.com/kohlhase/CambridgeLCF
[Lieb81]: Lieberman, Henry, and Carl Hewitt. "A Real Time Garbage Collector Based on the Lifetimes of Objects" AI Memo No. 569A October, 1981.
[Lind85]: Gary Lindstrom. 1985. Functional programing and the logical variable. In Proceedings of the 12th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '85). Association for Computing Machinery, New York, NY, USA, 266280. doi:10.1145/318593.318657
[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
[Maun86]: Mauny, M., & Suárez, A. (1986). Implementing functional languages in the Categorical Abstract Machine. Proceedings of the 1986 ACM Conference on LISP and Functional Programming - LFP 86. doi:10.1145/319838.319869
[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
[Mose08]: Moses, J. (2012). Macsyma: A personal history. Journal of Symbolic Computation, 47(2), 123130. doi:10.1016/j.jsc.2010.08.018 
[MULTICS1]: https://www.multicians.org/benchmarks.html#INRIA
[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
[NIL85]: Jon L White et al. VAX NIL source code, Release 0.286. Massachusetts Institute of Technology. Provided by Josh Dersch. https://www.softwarepreservation.org/projects/LISP/MIT/nil.zip
[Nuprl94]: Nuprl 3.2 (26-MAY-94) https://github.com/owo-lang/nuprl-3 https://web.archive.org/web/20220630143027/http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/areas/reasonng/atp/systems/nuprl/0.html
[Padg88]: Padget, Julian. "Three Uncommon Lisps." In First International Workshop on Lisp Evolution and Standardization. 1988.
[Paul22]: Lawrence Paulson. Memories: Edinburgh LCF, Cambridge LCF, HOL88 https://lawrencecpaulson.github.io/2022/09/28/Cambridge_LCF.html
[Paul22b]: Lawrence Paulson. Memories: Edinburgh ML to Standard ML https://lawrencecpaulson.github.io/2022/10/05/Standard_ML.html
[Paul23]: Lawrence Paulson. Curriculum Vitae https://www.cl.cam.ac.uk/~lp15/Pages/vita.pdf
[Phil99]: Phillips, Eve Marie. "If it works, it's not AI: a commercial look at artificial intelligence startups." PhD diss., Massachusetts Institute of Technology, 1999.
[Rees82]: Jonathan A. Rees and Norman I. Adams IV. 1982. T: a dialect of Lisp or LAMBDA: The ultimate software tool. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 114122. doi:10.1145/800068.802142
[Rees2004]: Jonathan Rees, The T Project http://mumble.net/~jar/tproject/
[Sain84]: Emmanuel Saint-James. 1984. Recursion is more efficient than iteration. In Proceedings of the 1984 ACM Symposium on LISP and functional programming (LFP '84). Association for Computing Machinery, New York, NY, USA, 228234. doi:10.1145/800055.802039
[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
[Shiv2001]: Olin Shivers, History of T https://paulgraham.com/thist.html
[Slom89]: Sloman, Aaron. "The Evolution of Poplog and Pop-11 at Sussex University." POP-11 Comes of Age: The Advancement of an AI Programming Language (1989): 30-54.
[Stal2002]: Richard Stallman. My Lisp Experiences and the Development of GNU Emacs. https://www.gnu.org/gnu/rms-lisp.html
[Stee82]: Guy L. Steele. 1982. An overview of COMMON LISP. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 98107. doi:10.1145/800068.802140
[Stee96]: Guy L. Steele and Richard P. Gabriel. 1996. The evolution of Lisp. Uncut draft.
[Stee96b]: Guy L. Steele and Richard P. Gabriel. 1996. The evolution of Lisp. History of programming languages---II. Association for Computing Machinery, New York, NY, USA, 233330. doi:10.1145/234286.1057818
[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.
[TMail]: T mailing list archive. http://people.csail.mit.edu/riastradh/t/tmail.tar.bz2
[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