fphistoryru

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

Часть 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 l’Aéronautique et de l’Espace) и Сорбонны. Защитил диссертацию в 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. И, что более важно для нашей истории, добавлением в виртуальную машину поддержки лексической видимости, которую требовал функциональный Лисп. Но это уже другая история.

Ржавый пояс

Рассказ о первых компиляторах функциональных языков подошел к концу. И читателю может показаться странным, что некоторые важные имплементаторы ФЯ, такие как Тернер и Дарлингтон, не сыграли в этом рассказе заметной роли. В следующей части они вернутся как, возможно, даже более важные деятели, чем они были в 70-е. Когда придуманные ими языки станут образцами для всех прочих функциональных языков и важные герои этой части начнут переделывать имплементации своих собственных языков в имплементации этих образцовых языков.
Но почему им вообще нужно возвращаться? Где они пропадали? Они не сыграли роли в этом рассказе потому, что были заняты. Играли роль в другом рассказе. Которому еще только предстоит найти своего рассказчика. Мы не претендуем на хоть сколько-нибудь полное его изложение.
История специального аппаратного обеспечения для имплементации ФЯ попала под сокращение, когда мы пытались ограничить объем нашей работы. Исключив из подробного рассмотрения совсем уж неуспешные, даже по меркам функционального программирования, направления. Да, создание специального железа для имплементации ФЯ не было успешным направлением, мы не боимся испортить сюрприз. Неуспешность слишком очевидна, сохранить интригу все равно не удалось бы.
Конечно, как и в случае с историей Лиспа, полностью исключить историю специальных машин для функционального программирования мы не можем. Во-первых, потому, что это направление дало нам множество важных имплементаторов ФЯ-компиляторов для железа обычного. Таких как Саймон Пейтон Джонс, например. Во-вторых, для того, чтоб продемонстрировать преимущества специальных машин, работающие над ними создавали и имплементации тех же языков для машин обычных. Одни из важнейших имплементаций ФЯ имеют именно такое происхождение. В-третьих, даже так и не создавшие важных имплементаций для обычных машин машинисты поучаствовали в создании современных ФЯ. Примерно как Лисп-машинисты поучаствовали в создании Common Lisp.
Так что имейте в виду, что параллельно более-менее уверенному шествию к какому-никакому но успеху компиляторов ФЯ для обычного, массового железа, тянется кладбище идей и машин, с которого мы будем иногда получать весточки. Такие, как эта глава.
Важно отметить, что в описываемые времена обитатели этого кладбища еще не знали, что это кладбище. Наоборот, как и Лисп-машины в среде лисперов, это направление считалось основным [John2004]. В особенности - имплементаторами ленивых ФЯ. Так что Августссон, Йонссон и прочие имплементаторы ФЯ на обычном железе считали нужным оправдываться [Augu89], почему они работают над такими странными вещами, а не в машинном мейнстриме.
Почему машинное направление считалось основным? Во всем виноват Джон Бэкус.

Машины освобождения

Исследовательское направление имеет право на существование, но страдает от агрессивного преувеличения его значения Бэкусом. Задолго до того, как получены убедительные результаты.
Эдсгер Дейкстра, Обзор Тьюринговской лекции Джона Бэкуса. [Dijk78]

К этому времени я получил почетное членство от IBM. Так что я мог делать все, что захочу.
Джон Бэкус [Back20]

Джон Бэкус получил премию Тьюринга 77-го года за FORTRAN и спецификацию ALGOL, но обрушился на фортраны и алголы с уничтожающей критикой в своей тьюринговской лекции “Может ли программирование быть освобождено от стиля Фон-Неймана?” [Back78]. Эта лекция наиболее известная, но не единственная попытка Бэкуса донести свое недовольство фортранами и алголами до программистов. Бэкус, например, вставил похожую критику прямо в свою статью по истории Фортрана [Back78b].
Языки программирования большие, сложные и негибкие, утверждает Бэкус. Их ограниченная выразительность не может оправдать их размер. В отличие от некоторых других авторов Алгола, Бэкус не ограничивается такой расплывчатой критикой разбухающих без особого толка фортранов и алголов и переходит к конкретике.
Фон-Неймановские компьютеры построены вокруг бутылочного горлышка: соединения между памятью и центральным процессором, пересылающего одно слово за раз. И обычные языки программирования построены вокруг этого пропихивания слова за словом через бутылочное горлышко, а не вокруг оперирования более крупными “концептуальными единицами”. Обычные ЯП - тонкие обертки абстракции вокруг Фон-Неймановской машины, := - языковое бутылочное горлышко Фон-Неймана.
Бэкус предлагает рассмотреть такой вот код на Алголе:

c := 0
for i := 1 step 1 until n do
  c := c + a[i]*b[i]

Что именно его тут не устраивает? Операторы, работающие с невидимым состоянием по сложным правилам. Код не конструирует сложные объекты из простых. Код “динамический” и многословный, нужно выполнить в уме, чтоб понять, что происходит. Код работает с одним словом за раз. Код необобщенный, работает только с вектором размером n. Аргументы имеют названия и для обобщения потребуется многословное объявление функции. Служебные действия рассыпаны по коду и не позволяют обобщения обхода векторов, которые каждый раз нужно писать заново.
И у Бэкуса даже есть решение всех этих проблем. По крайней мере, концепция решения. Конечно же, как можно догадаться уже из того, что именно Бэкус считает проблемой, решение - это “функциональное” программирование. Но не функциональное программирование Обоекембриджской ветви или Эдинбургской программы. Это довольно самобытное “функциональное” программирование происходящее из APL, в наше время представленное языком j.

Def Innerproduct ≡ (Insert +)∘(ApplyToAll ×)∘Transpose

или, если использовать укороченные варианты операций

Def IP ≡ (/+)∘(⍺×)∘Trans

что примерно соответствует такому вот коду на Хаскеле

innerproduct = foldr1 (+) . map (foldr1 (*)) . transpose

Что Бэкус называет преимуществами такого кода? Код работает только с аргументами, нет скрытого состояния со сложными правилами перехода от одного состояния к другому. Более сложная функция компонуется функциональными формами из более простых функций. Код “статический” и немногословный, структура позволяет понять его без исполнения в уме. Код оперирует “концепциями”, а не отдельными машинными словами. Код обобщенный. Машинерия для обхода векторов абстрагирована в общеполезные операции и её не нужно переписывать снова и снова. Из-за близости к Фон-Неймановской машине, утверждает Бэкус, обычные языки программирования имеют слишком негибкий “фреймворк” и потому не могут быть исправлены.
Сразу бросается в глаза, что многие конкретные претензии предъявлены к очень конкретным алголам и фортранам, очень узкому пониманию императивного программирования. Уже ко времени лекции Бэкуса эта узость была расширена и многие ограничения были отдельными алголами преодолены. Например, в обычных языках Бэкуса нельзя использовать “комбинирующие формы” потому, что результат выражения может быть только одним машинным словом.
Но Бэкус использует и вполне современные возражения функциональных программистов о том, что “Фон-Неймановским языкам” не хватает полезных свойств для рассуждения о свойствах программ.
Но что это за “комбинирующие формы”, они же “функциональные формы”, которые не возможны в обычных языках? Это композиция, Insert и ApplyToAll и некоторые другие “ФВП”, которые комбинируют существующие функции для получения новых. Обратите внимание: “функциональные формы”, а не функции.
Дело в том, что “функциональный” APL Бэкуса под названием FP не является в полной мере функциональным. Бэкус изобрел третий способ неправильной имплементации ФЯ. Программист на его языке не может объявить собственную ФВП. Все ФВП, которые когда-нибудь могут программисту понадобится уже определены Бэкусом как функциональные формы.
Бэкус знаком с функциональным программированием практически настолько хорошо, насколько можно было в 77-ом году. Бэкус ссылается статью Ландина про SECD. Ссылается на книгу Берджа. И, по видимому, Бэкус сыграл существенную роль в её популяризации. Ссылается на статьи Хендерсона и Морриса про ленивый вычислитель и Фридмана и Уайза про ленивый cons. На GEDANKEN, другие работы Рейнольдса. На МакКарти, Вюийемена, Скотта, Стрейчи, Черча и Карри. Бэкус обращался за советами и замечаниями к нашим героям Циллесу, Джеймсу Моррису, Рейнольдсу.
По видимому [Back20], Бэкус не начинал со всех этих знаний, а обнаружил их в процессе, как Келлер. Но, в отличие от Келлера, обнаружившего, что он переизобретает ISWIM, Бэкус вовсе не переизобретал ISWIM. Так что не-ISWIM Бэкуса сохранил свою самобытность более продолжительное время.
И если традиционные языки программирования по мнению Бэкуса слишком ограничены, то традиционное функциональное программирование ограничено недостаточно. Лямбда-исчисление описывается простыми правилами, но у этих простых правил сложные для понимания следствия. Эту критику ЛИ Бэкус обосновывает историей фунарг-проблем лисперов, которые сначала не поняли ЛИ, а потом долго и упорно его друг другу объясняли. Такая критика напоминает более позднюю критику монад. Если монада - это так просто, то почему так много монадных туториалов?
Бэкус сравнивает возможность писать ФВП с неограниченными операторами для контроля исполнения в обычных до-структурных языках. Буквально Стиловское и Сассмановское утверждение о том, что лямбда - это высшая ступень в развитии GOTO, но только в этот раз как будто это что-то плохое.
К счастью, эта критика ФВП не нашла особой поддержки. Это отношение разработчиков FP к ФВП поменялось и в 80-е появились расширения FP [Arvi84], разрешающие написание собственных ФВП, в том числе и от самого Бэкуса [Will88].
Келлер, по видимому, позаимствовал у Бэкуса синтаксис для применения функций в FEL. И от этого синтаксиса в языке Келлера происходит оператор : в ALFL и, затем, оператор ($) в Хаскеле и его аналоги в некоторых других языках. Лекция Бэкуса также популяризировала point-free стиль. Но только популяризировала, такой подход был известен и до того и встречается еще 60-е у Берджа и Ландина, но встречается не часто.
Спустя десятилетие, в своей ранней истории ФП [Huda89] Худак констатирует, что язык Бэкуса FP не оказал существенного влияния на то, какие фичи будут в функциональных языках.
Но, хотя авторы функциональных языков не особенно хотели делать свой язык таким как FP Бэкуса, они хотели ссылаться на его лекцию. Которая поэтому стала одной из самых цитируемых статей о ФП. Худак объясняет это тем, что это первая известная статья превозносящая ФП. Не только объясняющая, что функциональное программирование - это “хорошо”, но и то, что императивное программирование - это “плохо”. Через несколько лет, конечно, появились и другие, о которых мы расскажем в следующей части.
Критика ФП Бэкусом и сравнение ФВП с GOTO не осталась незамеченной Худаком [Huda89]. Но Худак отмечает, что обычно эта критика как раз таки остается незамеченной. И он считает, что общий посыл Бэкуса все равно в поддержку ФП. Просто в поддержку ФП с использованием небольшого набора стандартных комбинаторов.
В начале 80-х Тернер считал [Turn82] лекцию Бэкуса началом новой эпохи, но в наши дни Тернер в своей истории ФП [Turn12] уже не придает такого значения лекции Бэкуса. Зато авторы истории Хаскеля [Huda07] посчитали её достаточно важной, чтоб начать историю с неё. Благодаря поддержке “гиганта” программирования Бэкуса, пишут Худак и др., функциональное программирование стало известно как “практичный инструмент”, а не “математический курьез”. Худак утверждает [Huda89], что реклама ФП от автора Фортрана - это “лучшее, что могло случится с ФП”. Мы же в этом совсем не уверены. Потому, что вместе с функциональным программированием Бэкус популяризировал и не самую полезную для функционального программирования идею. Для некоторых ФЯ она оказалась даже смертельной.
Если своему свободному от проклятья Фон-Неймана языку Бэкус уделяет много внимания в своей версии лекции для публикации на сто страниц, то машинам, которые должны поддержать это освобождение уделяется гораздо меньше внимания. Бэкус отмечает как перспективные и анти-Фон-Неймановские машину Маго и работы Арвинда, к которым мы еще вернемся. Но читателям Бэкуса было достаточно общей идеи, машины они изобретут самостоятельно.
Идея о том, что плохие свойства языков программирования обусловлены неправильной машиной и для того, чтоб использовать хорошие языки нужны специальные правильные машины, не выглядит такой уж обоснованной из наших дней. Не выглядела она такой и для некоторых современников [Dijk78]. Но в конце 70-х она овладела умами многих функциональных программистов.
Во времена лекции Бэкуса Пейтон Джонс учился в Кембридже вместе со своим знакомым еще со школы Томасом Кларком (Thomas J. Clarke), и уже поучаствовавшим в нашей истории Джоном Фейрберном.
Программы нужно писать в функциональном стиле, пересказывает Пейтон Джонс [SPJ18] послание Бэкуса, более того, нужно создавать новые компьютеры для исполнения таких программ.
И, может быть, это не самая лучшая идея, которую можно принести из 70-х в 80-е. В десятилетие, когда массовое железо, благодаря масштабам этой самой массовости, будет с каждым годом становиться все быстрее, дешевле и, следовательно, еще более массовым.

Норман и NORMA

Хотя я не понимал математики, изложенной в Тьюринговской лекции Бэкуса, ее введение звучало смело и захватывающе: именно такая работа, в которой я хотел бы принять участие!
Уильям Стой, Имплементация функциональных языков с использованием специального аппаратного обеспечения [Stoy85].

Пришла пора рассказать, чем же занимался Тернер все эти годы. Но сначала об еще одном функциональном проекте в Кембридже. Как обычно, началось все с системы компьютерной алгебры.

Small

Артур Норман (Arthur Charles Norman) защитил диссертацию в 73-ем году в Кембридже и остался там преподавать. Но не только.
С 60-х годов в Кембридже разрабатывалась система компьютерной алгебры CAMAL [Fitc09], которую сначала напрасно надеялись переписать на CPL, а потом смогли переписать на Algol 68C и, наконец, переписали на BCPL. Это выделяет её из ряда систем компьютерной алгебры, с которыми мы сталкивались до сих пор. MACSYMA, REDUCE и SCRATCHPAD разрабатывались на Лиспах, иногда слегка замаскированных под Алгол. И пришедшие им на смену системы написаны на языках, которые больше похожи на BCPL и Algol 68, чем на LISP. Так что можно сказать, что CAMAL опередила свое время. Но опережение своего времени редко заканчивается чем-то хорошим и определенно не закончилось хорошо в случае CAMAL. Эти неудачи, однако, не помешали Кембриджу шагнуть навстречу новым, еще большим неудачам. Во второй половине 70-х, в Кембридже захотели написать собственную систему компьютерной алгебры на Лиспе.
К 77-ому году Норман, вместе с одним из основных разработчиков CAMAL Джоном Фитчем (John P. Fitch), разработали и имплементировали Лисп для её написания - Cambridge Lisp [Norm77] [Padg88], родственный Standard Lisp. Интерпретатор написали на BCPL и использовали его для бутстрапа компилятора на основе ранней версии компилятора Standard Lisp Хирна и Грисса.
Этот Лисп не стал особенно популярным, но написание интерпретатора на портируемом BCPL, вместо чего нибудь более специфичного для машины и низкоуровневого, не осталось незамеченным авторами одного из самых популярных Лиспов - Franz Lisp - написавшими его интерпретатор на C [Fode81].
В богатом на события нашей истории 79-ом году новая система компьютерной алгебры, называемая обычно “vector-based algebra system”, разрабатывалась Норманом и Муром (P.M.A. Moore) уже шесть месяцев [Norm79] [Norm82] и существовала уже кое-как работающая версия. Не беспокойтесь, поворот к такой обычной для системы компьютерной алгебры того времени истории был скомпенсирован необычностью в другом. Ведь Артур Норман, не смотря на такую обычную для лиспера биографию, обладал необычными для лиспера предпочтениями.
Норман не хотел использовать в качестве командного и скриптового языка системы Лисп, даже и слегка замаскированный под Алгол. Он хотел более “плавный переход” из “мира алгебраических выражений” в программирование. Такое желание само по себе не очень необычно. Как мы выяснили, такие устремления были и у авторов ALGOL 60. Так что важнее, какой “переход” в данном случае посчитают достаточно “плавным”. И тут Норман пошел даже дальше авторов SCRATCHPAD: для скриптования своей системы Норман выбрал функциональный язык Тернера SASL. ФЯ чистый и позднее еще и ленивый.
Пока что “SASL” предназначался для пользователя системы, но в перспективе хотелось сделать язык подходящим для её расширения и разработки.
Как обычно, это не какой-то из многочисленных вариантов SASL Тернера, а немного отличающийся от SASL 75. Того, который уже без явного rec, но еще без уравнений [Norm79].

LET map f l = 
    l=() -> ();
    f (HD l) , map f (TL l)
LET y = 1
LET add x = x + y
IN map add (1,2,3); 

Уже в следующем году язык получит название Small и синтаксические конструкции из Algol 68, которые сделают его похожим на Ponder [Clar80] [Norm82]:

Let map f l = 
    If l Is x . xs Then f x . map f xs
    Else Nil Fi
In
map add (1 . 2 . 3)
   [ y = 1
     And add x = x + y ] Ni;

Разработчики новой системы решили, что могут себе позволить чистый ФЯ. Раз уж это скрипт для системы компьютерной алгебры и предназначен для склеивания процедур, которые исполняются заметной время и сами написаны на другом языке. И сначала посчитали, что эффективная имплементация этого скрипта не потребуется. Так что можно позволить себе работающую имплементацию функций и ленивости, которые в Лиспах того времени, как правило, отсутствовали.
Возможно, мечтал Норман, когда-нибудь в будущем свойства чистого ФЯ пригодятся для трансформации кода, доказательства его свойств и распараллеливания.
Но, как это часто бывает, нашлись пользователи скрипта, которые не стали ограничиваться склеиванием готовых процедур, а стали писать более интересный код. И оказалось, что писать программы на чистом и ленивом ФЯ легко и приятно. Есть, правда, проблема: производительность.
Small имплементирован как интерпретатор. К тому же, как не особенно быстрый интерпретатор. Производительность интерпретаторов ФЯ оставляла желать лучшего еще до появления в них поддержки ленивости. И поддержка ленивости еще больше осложнила ситуацию.
Но в 79-ом году умы студентов Кембриджа вроде Фейрберна и Пейтон Джонса будоражили свежие идеи, которые могли показаться решением для этих проблем: комбинаторный интерпретатор Тернера и лекция Бэкуса о специальном железе для ФЯ.
Фейрберна больше заинтересовало первое и чем он занялся после этого мы уже рассказали. Пейтон Джонс если и заинтересовался, то пока что без особых практических последствий для нашей истории. Получив диплом компьютерных наук, он не остался в Кембридже работать над диссертацией, а ушел работать в индустрии [SPJ18]. Одним из главных героев нашей истории он станет еще не скоро. А вот его приятель Том Кларк, сыграл одну из главных ролей в построении специальной машины для интерпретации Small.

SKIM

Машина была инициативой студенческой “Процессорной Группы” (Cambridge University Processor Group) и не имела официальной поддержки университета [Stoy85]. Над ней работали Артур Норман и Кембриджские студенты Томас Кларк (Thomas James Woodchurch Clarke), их соавторы по первой статье [Clar80] о машине Гладстон (P. J. S. Gladstone) с МакЛином (C. D. MacLean) и только упоминающиеся в статье Билл Уорцел (Bill Worzel) и Иан Китчинг (Ian Kitching), написавший эмулятор машины. Разработку финансировала Оксфордская компьютерная компания Research Machines Ltd. при некотором содействии от Кембриджской компании Acorn Computers Ltd. Позднее проект потерял большую часть из этих участников, за исключением ядра Норман-Кларк, но привлек новых. И бросается в глаза, что этот проект существенно больше проектов, разрабатывающих первые компиляторы ФЯ, которые обычно ограничивались одним - двумя участниками. Но создание специального железа и требует больше участников.
Проблема производительности не в аппликативном программировании, решили Норман и Кларк, а в том, что существующие машины более “предрасположены” для выполнения существующих языков. И нужна машина, которая “предрасположена” уже к выполнению ФЯ.
Что означает “предрасположена” к исполнению ФЯ? Наиболее очевидным примером Норман и Кларк называют поддержку параллелизма. Но они пока что не собирались делать параллельную машину. Они хотели продемонстрировать как очень небольшой объем изменений окажет существенный эффект на производительность ФЯ.
Простота комбинаторного интерпретатора Тернера дала им надежду на то, что они смогут сконструировать редуцирующую машину. Первые ФП-машинисты посчитали комбинаторный интерпретатор Тернера элегантным, и в статье 80-го года, еще называют эффективным. Так что они решили проверить, насколько хорошо Тернеровские идеи можно воплотить в железе. Так что машина имплементировала комбинаторный интерпретатор Тернера и, соответственно, называлась SKIM (the S, K, I Reduction Machine).
Разработчики SKIM, в отличие от предыдущих героев этой части, приняли и смирились с тем, что имплементация ФЯ требует интерпретатор. Решение проблемы производительности они видели в том, чтоб сделать интерпретатор быстрым. Насколько быстрым? Цель - производительность программ на ФЯ как на миникомпьютере по цене микрокомпьютера. И это не особенно высокие требования. Как оказалось, для этого и делать ничего не надо, вскоре появились достаточно быстрые микрокомпьютеры. Но разработчики специального железа обычно не ожидали такого.
Насколько специальное железо они делали? На самом деле не такое уж и специальное. SKIM была больше похожа на Лисп-машину, чем на анти-Фон-Неймановские машины, которые продвигал Бэкус, вроде машины Маго.
Машина делалась настолько простой, насколько это возможно для машины, машинным языком которой является тернеровский набор комбинаторов. Так что вся функционально комбинаторная специфика была микрокод-программой, а не специальным железом. И минимальные изменения обычной машины для поддержки ФЯ те же самые, что и изменения для поддержки Лиспа в Лисп-машинах 70-х (в 80-е были и более значительные, к ним мы еще вернемся). Как и в случае Лисп-машин особенность железа заключалась в поддержке большего объема микрокода, чем позволяли в это время машины обычные. Как и в случае Лисп-машин, поддержка железа заключается в том, что для проверки тегов и арифметики не требуется манипуляций для отделения тегов от данных.
Разработчики считают SKIM достаточно универсальной. С другим микрокодом она может быть Лисп-машиной.
В каком-то смысле SKIM бОльшая Лисп-машина, чем современные ей Лисп-машины. Потому, что пользователь машины видит только память из пар. Объекты у которых больше (или меньше) двух полей она не поддерживает. Лисп-машины же заработали уже после того, как лисперы поняли, что такая память их не устраивает, нужны рекорды и массивы.
У SKIM есть существенные отличия от Лисп-машины. Её пользователь не имеет доступа ко всяким низкоуровневым фичам вроде регистров и стека. Машинный язык ФП-машины - чистый фя, Тернеровский набор комбинаторов.
Отсутствие доступа к стеку и регистрам, по замыслам авторов SKIM, должно спасти имплементатора языка следующего уровня для неё от соблазна как-нибудь “оптимизировать” имплементацию видимости и получить неправильные лямбды, как это сделали лисперы.
Одной из важнейших, если не самой важной причиной создания Лисп-машин было желание Лисперов получить машины с большим адресным пространством и виртуальной памятью. Но в SKIM 14бит указатели на пары, так что она не поддерживает больше 64Кб памяти. Что не позволяет запустить на ней хоть сколько-нибудь серьезную программу.
В результате SKIM - это не “недорогой” миникомпьютер как Лисп-машина, а более-менее обычный микрокомпьютер, на порядок дешевле. Сотня микросхем на двух платах.
Они обошлись создателям SKIM в 500 фунтов [Stoy85], приблизительно в 4.5 тысячи долларов 24-го года. Конечно, в это время микрокомпьютер, компоненты которого стоят столько же, работал намного быстрее и поддерживал мегабайты виртуальной памяти.
Итак, вся ФП-специфика SKIM - программы. Машина поддерживает 4 тыс. слов микрокода. Из-за простоты тернеровского комбинаторного интерпретатора он - наименьшая часть микрокода SKIM. Разбор комбинаторного кода больше, принтер еще больше и сборщик мусора - больше всего остального. В 80-ом создатели SKIM посчитали, что объем микрокода оказался гораздо меньше, чем они боялись. И раза в два меньше того, который поместится в память SKIM для микрокода.
Сборщик мусора нерекурсивный, использует разворот указателей. Эту же технику для обхода графа объектов в памяти использует и комбинаторный интерпретатор. Разворот указателей позволяет ему обнаружить что он уже обходил какую-то часть и так определить зацикливание.
Для передачи параметров в строгую функцию, например арифметическую операцию, стек все еще нужен. Но SKIM не поддерживает стек, и если делать его самостоятельно, то нужно использовать односвязный список - машина же в принципе не поддерживает никакие объекты памяти кроме пар.
Работа над SKIM началась в 79-ом году и не позднее августа 80-го она уже работала.
На момент написания статьи 80-го года авторы SKIM еще как следует не знают, насколько быстро она работает, потому что работает она еще только месяц.
Они сравнивают в основном скорость работы сборщика мусора SKIM имплементированного в микрокоде с его имплементациями на ассемблере микрокомпьютеров с Z80 и M68K (работают помедленнее SKIM) и мэйнфрейма IBM 370/165 (работает побыстрее SKIM). Обращение к памяти сборщика мусора SKIM занимает процентов 60 времени работы.
Ленивый язык, по первым оценкам авторов, исполняется на SKIM со скоростью, сравнимой с интерпретатором LISP на IBM мэйнфрейме. Ну или сравнимым со скоростью BASIC на 8-бит микрокомпьютере. Раза в два побыстрее байткод-интерпретатора BASIC и раза в полтора медленнее компилятора. Раза в два медленнее, чем интерпретируемый Лисп и раз в десять медленнее, чем скомпилированный Лисп на на IBM 370/165 [Clar80].
SKIM редуцировал комбинаторный код с примерно той же скоростью, что и комбинаторный интерпретатор, написанный на машкодах мэйнфрейма IBM 370/165 [Stoy85].
Написанный на микрокоде комбинаторный интерпретатор работал достаточно быстро, чтоб скорость редукции определялась скоростью работы с памятью, что авторы SKIM посчитали успехом.
Да, это именно то, с чем Бэкус в своей лекции призывал бороться как с главной проблемой программирования. И с чем авторы компиляторов из остальных глав этой части боролись уменьшая обращения к памяти. Пока что вместо анти-Фон-Неймановской машины имплементаторы ФЯ делали машину анти-Бэкусовскую.
Создатели SKIM посчитали свои результаты вдохновляющими. Заключают, что довольно скромное аппаратное обеспечение способно предоставить хорошую поддержку для ФЯ. Они надеялись, что простота комбинаторного интерпретатора тернера позволит сделать простую, но достаточно быструю машину. И в 80-ом посчитали, что эти надежды, в основном, оправдались. Решили, что SKIM продемонстрировала, что специальное железо может хорошо помочь в имплементации необычных языков программирования. По крайней мере, для языков работы со списками даже простой компьютер может обеспечить “достойную уважения” производительность.
Авторы поздравляют себя с победой: аппликативное программирование теперь может быть практичным решением, а не только интересной, но непрактичной идеей.
Правда, создатели SKIM отмечают, что если б они начали разрабатывать SKIM еще раз и с нуля, они сделали бы машину немного посложнее и побыстрее. Посчитали, что производительность микробенчмарков страдает, в основном, от минимального АЛУ, которое не поддерживает даже умножение для целых чисел.
Какие у них планы на будущее? Помимо усложнения поддержки арифметики, машину можно существенно ускорить только ускорив работу с памятью (с чего и началась вся эта специально-аппаратная история). Можно, например, удвоить пропускную способность шины памяти. В SKIM хоть и можно адресовать только пары - дотащить пару через Фон-Неймановское бутылочное горлышко можно только в два приема.
Можно поддержать стек аппаратно или даже добавить для него специальную, более быструю память.
Создатели SKIM предполагают, что имплементировав все это, можно будет увеличить производительность раза в четыре. Ценой увеличения размера процессора в два раза.
Чтоб все-таки запускать на машине какие-то программы, хорошо бы увеличить размер слова и адресное пространство.
Еще одно очевидное авторам направление развития - имплементация комбинаторной машины на одном чипе.
Из того что напишут работающие на SKIM позднее, правда, мы знаем о том, что у SKIM были серьезные проблемы не упомянутые в статье 80-го года. SKIM падает в среднем после 20 минут работы. К тому же, с течением времени, разработчики SKIM изменили свое мнение о том, что 4K слов для микрокода хватает с запасом. У них появилось больше идей о том, что можно перенести в микрокод. Правда, от изменения микрокода до его запуска обычно проходит день, так что работать над микрокодом не особенно удобно.
Но первоначальная команда разработчиков SKIM “рассеялась” и к решению этих проблем приступили только через годы [Stoy85].

SKIM II

Летом 82-го года Томас Кларк закончил проектирование SKIM II. Никакие изменения для ускорения работы в новый проект не попали. Так что ни поддерживающего умножение, ни кеша не было. Главной целью было создать более надежную платформу для экспериментирования и ослабить ограничения на размеры поддерживаемой памяти и микрокода [Stoy85].
Так что главными отличиями SKIM II от SKIM I были увеличенное адресное пространство, больше тег-бит в каждом слове и упрощение отладки микрокода, дла которого есть и больше места в памяти. Вместо 16-бит слов теперь 24-бит слова, в которых 4 бита для тегов [Stoy83].
Микрокод может занимать до 64K слов.
Если SKIM I была экспериментальной машиной, то SKIM II задумывалась как более стабильная платформа для экспериментирования с функциональным программированием.
И функциональное программирование теперь в принципе возможно, адресного пространства теперь хватает на миллион пар, что соответствует 6Мб памяти. Виртуальная память, правда, не поддерживается.
SKIM больше не студенческий проект, Кларк уже не студент и новый участник проекта работает над диссертацией.
Уильям Стой (William Robert Stoye) прочел лекцию Бэкуса и идея о том, что с обычными машинами не все в порядке произвела на него впечатление. Так что он решил участвовать в проекте создания машины необычной. Да, Бэкус, скорее всего, посчитал бы, что в этой необычной машине не в порядке все то же самое, что и в обычной. Но на практике лекция Бэкуса вдохновляла смело делать необычные машины, а не исправлять конкретные проблемы, которые описывал Бэкус. В детали его рассуждений Стой, по собственному утверждению, особо не вникал.
Стой приступил к воплощению идей Кларка в октябре 82-го и в июне 83-го SKIM II заработала [Stoy85].
SKIM II это 230 микросхем на четырех платах, из них 92 - память. Машина не использует все адресное пространство, установленной памяти хватает на 256 тыс. пар [Stoy83], т.е. полтора мегабайта. Машина может быть расширена еще тремя платами с памятью, но не расширена.
SKIM II, в отличие от SKIM I не падает каждые двадцать минут, исполняет тесты сутками без проблем.
Весь ввод-вывод осуществляет обычный компьютер (BBC micro), соединенный со SKIM II, который используется и для отладки микрокода.
И писать и отлаживать микрокод теперь легко, и места для него хватает. Так что оптимизации и идеи по развитию машины теперь связаны с изменениями микрокода, а не железа. И микрокода для SKIM II написано в два раза больше, чем для SKIM I. Стой занимался совершенствованием микрокода со второй половины 83-го.
Стек-список в куче и рекурсия теперь не используется и для вызова строгих комбинаторов. Это улучшило производительность на 10%.
Более важной оптимизацией была идея Кларка о разгрузке сборщика мусора. Сборка мусора занимает больше половины времени редукции, даже если занято меньше половины кучи. Объекты, обычно, короткоживущие, но алгоритм сборки не может это использовать для уменьшения времени обхода.
В ходе редукции комбинаторов часто нужно аллоцировать пары, на которые есть только одна ссылка, пока они не станут мусором. И в новом микрокоде имплементированы однобитные счетчики ссылок в указателях, которые позволяют определять такие объекты и освободить место немедленно или даже немедленно переиспользовать его, существенно уменьшая работу для сборщика мусора и аллокатора.
В микрокоде также реализованы операции, которые в ранних версиях имели наивную имплементацию. Например, структурное сравнение объектов. И микрокодовое сравнение не только сравнивает, но и переписывает дерево объектов в куче. Убирая дублирующиеся поддеревья и заменяя их на ссылки на одно и то же поддерево. После того, как сравнение установило, что поддеревья действительно одни и те же.
До оптимизации микрокода SKIM II редуцировала примерно 80000 комбинаторов в секунду [Stoy83], а в результате оптимизации микрокода производительность SKIM II увеличилась примерно в полтора раза [Stoy85].
Насколько хорошо помогают однобитные счетчики ссылок, конечно же, зависит от того, как много свободной памяти осталось и Стой даже приводит [Stoy85] соответствующие измерения

  tak 50% tak 5% sort 50% sort 20%
v1 1.23 2.28 1.35 2.09
v2 1.15 1.06 1.24 1.17
v3 1.00 1.00 1.00 1.00

Здесь v1 - первоначальная версия микрокода, v2 - оптимизация применения строгих комбинаторов и v3 - это все, что в v2 плюс однобитные счетчики. Процент рядом с бенчмарком - это процент свободной памяти.

v1
░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░
v2
░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░
v3
░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░

Также SKIM II сравнивают с комбинаторным интерпретатором, работающим на более актуальной для функциональных программистов 80-х машине - микрокомпьютере с MC68K. Описания этого интерпретатора не просто не дошли до нас, но не существовали даже в виде отчета в Кембридже, потому что Стой ссылается только на разговор со Стюартом Рэем о нем. И SKIM II работает в 30 раз быстрее этого интерпретатора. С суперкомбинаторным компилятором, над которым работали в Кембридже Рэй с Фейрберном, сравнения, правда, почему-то нет.
Параллельно с развитием микрокода Стой пишет первые функциональные программы для ФП-машины. Разработчики SKIM больше не пишут собственный компилятор ФЯ в комбинаторы и Стой не использует Small для написания программ для SKIM II. Он использует наработки другого Кембриджского функционального проекта - Ponder, компилятор которого может производить Тернеровские комбинаторы. Но функциональное программирование - тема для другой части нашей истории, к этому мы еще вернемся.
Совершенствование ПО для “простой машины” - это, конечно, хорошо. Но жаль, что создатели SKIM не нашли сил и средств сделать машину посложнее. К счастью, в августе 84-го Стой посетил Остин (Техас), где подходила к концу разработка как раз такой ФП-машины.

NORMA

С 1973 года Роберт Бартон (Robert Stanley Barton), известный разработчик мэйнфреймов, работал в исследовательском центре Burroughs в Ла-Холье (Burroughs Systems Research in La Jolla, San Diego, California) над новым компьютером той разновидности, которую Бэкус считал анти-Фон-Неймановской. Увлечение около-функциональным анти-Фон-Нейманизмом захватило Бартона под впечатлением от теоремы Черча-Россера. Но в 78-ом году, примерно тогда же, когда Бэкус рассказывал, что за такими как у Бартона машинами будущее, проект Бартона закрыли. Участники этого проекта, вроде Гамильтона Ричардса (Hamilton Richards), нашли себе занятие в новом исследовательском центре Burroughs в Остине (Burroughs Austin Research Center, (B)ARC). Где стали работать над компьютером еще более около-функциональным, но существенно менее анти-Фон-Неймановским [Hoar22].
Функциональным языком, который имплементировали в ARC был диалект SASL, называющийся ARC SASL [Rich84]. Отличие ARC SASL от SASL 83 было, наверное, минимальное из тех, что потребовали бы изменить чуть ли не каждую строку кода при переводе с одного SASL на другой. Если бы было что переводить, конечно. В ARC SASL скобки в синтаксисе списков были квадратными, как в KRC.
В отличие от большинства других имплементаторов SASL, в ARC могли себе позволить нанять Тернера консультантом в январе 80-го года. И в ARC могли многое себе позволить. Помните о том интерпретаторе SASL, который в Сент-Эндрюсе начинали писать на микрокоде мэйнфрейма, который так и не смогли купить? В ARC был и мэйнфрейм и соответствующий микрокодовый интерпретатор ARCSYS [Rich84], с помощью которого там экспериментировали с SASL, пока не была готова его основная имплементация - ФП-машина. ФП-машину делали долго, начали еще в 79-ом году, как и SKIM но заработала только в декабре 84-го [Stoy85]. Но эта машина была воплощением в жизнь многого из того, что для создателей SKIM так и осталось мечтой.
Остинская ФП-машина NORMA (Normal Order Reduction MAchine), как и SKIM была “софт-машиной” с поддержкой большого объема микрокода, тэгов и сборки мусора, а не какого-то конкретного способа реализации ФЯ. Но на микрокоде был написан все тот же комбинаторный интерпретатор Тернера. Правда, интерпретировал он не совсем тот же Тернеровский набор. Сотрудник ARC Марк Шивел (Mark Scheevel) обнаружил, что комбинатор B' и получающая его трансформация из этого набора не то что не улучшают, а даже ухудшают производительность и придумал комбинатор B*, который используется вместо B' в NORMA и позднее Тернером в интерпретаторе его следующего языка [SPJ87].
NORMA не поддерживала виртуальную память, как и SKIM. Как и в SKIM, специальное железо соединено с обычным компьютером. В случае NORMA - это микрокомпьютер с на 8086. Но обычный компьютер используется не только для ввода-вывода, сборщик мусора использует его память для сохранения корней [Sche86].
Чаще всего отличия NORMA от SKIM демонстрируют, что может позволить себе лаборатория при коммерческой компании в США, по сравнению с университетским проектом в Великобритании. В NORMA больше памяти: два блока по 512K слов DRAM и 512K слов SRAM, быстрой памяти для отметок маркирующего сборщика, в каждом. Слова 64-битные, в каждом пара из 24-бит полей и тегов. В NORMA есть быстрый кэш для “стека”, вернее для заменяющего его “хребта”, потому что используется тот же метод избегания рекурсии в интерпретаторе, что и в SKIM.
Сам процессор тоже больше, исполняющий большие инструкции в 370 бит для параллелизма на уровне команд. NORMA не собирает аргументы для очередного комбинатора в стек или какую-то заменяющую его структуру в памяти как SKIM, а использует регистры. Сборщик мусора в NORMA той же маркирующей и очищающей разновидности, что и в SKIM, но очистка происходит одновременно с редукцией.
SKIM была собрана из обычного ассортимента ТТЛ микросхем. В отличие от нее, NORMA использует помимо стандартных ТТЛ чипов и специальные чипы (ULA ASIC) [Stoy85] [Sche86].
Над железом работали Гэри Логсдон (Gary Logsdon), Брент Болтон (Brent Bolton), Фрэнк Уильямс (Frank Williams) и Майк Уинчелл (Mike Winchell). Над системным ПО, помимо Марка Шивела, работали Боб Бетке (Bob Bethke), Том Крокетт (Tom Crockett), Курт Хирн (Curt Hern) и Дэвид Доу (David Dow). Помимо непосредственных имплементаторов были еще консультанты вроде Тернера и даже Дейкстры, работавшие над доказателем для проверки свойств SASL кода Боб Бойер (тот самый, из части про уравнения) и Мэтт Кауфман (Matt Kaufmann), описавший ARC SASL Ричардс. Над SKIM работало больше, чем над отдельными компиляторами ФЯ, но над NORMA работало больше людей, чем над всеми первыми компиляторами ФЯ вместе взятыми.
В результате всего этого NORMA работала быстрее, чем SKIM II, которая с первой версией микрокода редуцировала 80 тыс. комбинаторов в секунду [Stoy83], а с последней - 100 тыс. согласно Шивелу [Sche86] или примерно 120 тыс. согласно Стою [Stoy85]. NORMA редуцировала 250 тыс. комбинаторов в секунду.
Но насколько это хорошо по сравнению с теми, кто не собирал специальное железо и не заказывал специальные чипы? В 50 раз быстрее, чем микрокодовый интерпретатор для мэйнфрейма Burroughs и в 25 раз быстрее, чем интерпретатор SASL для VAX-11/780 на C. Успех по ускорению интерпретации, конечно, впечатляющий. Но что насчет сравнения с компиляторами? В отличие от разработчиков SKIM II, разработчики NORMA сделали такое сравнение.

  7queens primes fib20 tak
NORMA 1.00 1.00 1.00 1.00
LML (VAX) 1.60 0.71 1.14 2.50
NORMA
░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░
LML (VAX)
░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░

Получается, что NORMA немного побыстрее LML на VAX-11/780. Но принципиальной разницы нет, как признает Шивел. Правда, не известно, какая разница была бы не на микробенчмарках, продолжал надеяться он, а на серьезных программах. Которые могут работать на VAX-11, но едва ли на NORMA. Виртуальной памяти-то нет.
И это сравнение со скомпилированным кодом, который работает на машине конца 70-х. В момент публикации этих результатов уже продавались микрокомпьютеры, на которых этот код будет работать в 2-4 раза быстрее.
Так что в это время даже и самые упорные ускорители комбинаторного интерпретатора делали вывод, что шаг у него слишком мелкий. К счастью, отмечают создатели SKIM [Stoy85] и NORMA [Sche86], машины не имплементируют Тернеровский интерпретатор в железе и для них можно написать микрокод, который имплементирует редукцию более крупных комбинаторов. В Остине, правда, нет своего суперкомбинаторного компилятора. А вот разработчики SKIM экспериментировали с компиляцией прямо в микрокод, что позволило бы достигнуть ускорения на десятичный порядок [Stoy85].
Но дела у создателей SKIM, видимо, пошли не очень хорошо. У разработчиков же обычного железа дела шли отлично. Так что позднее Норман пишет уже не про специальные ФП-компьютеры, а про имплементацию ФЯ на компьютерах обычных [Norm88].
А как шли дела у создателей NORMA? Дело в том, что у исследовательской работы в лабораториях при коммерческих компаниях есть и недостатки. В 86-ом году в Burroughs, во время поглощения Sperry решили, что ARC не нужен и закрыли его [Hoar22].

Скобочный потолок

Мы сидели рядом с Symbolics. У них был двухэтажный выставочный стенд из черного металла, изогнутый спереди и с рядом мониторов на втором ярусе для удобства демонстрации. Этот стенд называли “Звезда Смерти”.
Ричард Гэбриел [Gabr96]

Увы, история SKI-машины оборвалась внезапно. Вместо того, чтоб долго и мучительно проигрывать машине обычной, она стала жертвой решения, которое выглядит случайным. Но было ли оно случайным? Нам не хватает разнообразия SKI-машин, чтоб ответить на этот вопрос. Но что если добавить к ним похожих машин?
Мы выяснили, что SKI-машина - это примерно то же самое, что и Лисп-машина и отличается, в основном, микрокодом. А значит, мы можем составить представление о том, как могло бы выглядеть медленное закономерное умирание, если бы быстрое случайное не состоялось. В 80-е годы Лисп был несоизмеримо популярнее того функционального программирования, историю которого мы пишем. Так что довольно безопасно предположить, что реальные успехи Лисп-машин существенно превышают возможные успехи SKI-машин при самом удачном для них развитии событий.
И разнообразие производителей Лисп-машин позволяют нам оценить все три возможных варианта развития SKI-машин. Возможное будущее SKIM представят бывшие исследовательские группы из университета, которые стали относительно успешной и относительно неуспешной компанией производящей специальное железо. Возможное будущее NORMA - производитель обычного железа, который решил попробовать производить и специальное.
Но подождите-ка, если и Лисп-машины и SKI-машины SKIM и NORMA - это, в основном, обычные компьютеры с большой памятью для микрокода и поддержкой тегов, то почему бы не использовать Лисп-машину для того, чтоб исполнять “аппликативный” язык, написав подходящий интерпретатор на микрокоде?
Имплементаторы Пролога так и сделали [Carl84], получив ускорение в три раза и в два раза более компактный код. Такую же попытку предприняли однажды и имплементаторы ФЯ, но это уже была имплементация второго поколения, о которой мы расскажем в следующих частях нашей истории.
Да, использование Лисп-машины для имплементации ФЯ не было распространенным явлением. И на вопросы о том, насколько успешными могли бы быть SKI-машины и о том, почему Лисп-машины особо не пытались перемикропрограммировать в ФП-машины, по большему счету, один и тот же ответ.

Персональный миникомпьютер

Самым успешным из производителей Лисп-машин была компания Symbolics. Та самая, которую основали машинисты, решившие обойтись без своего бывшего руководителя в МТИ Гринблатта. В Symbolics начали с производства разработанной в МТИ Лисп-машины CADR под названием LM-2. Но, в отличие от прочих машинистов начавших с МТИ-машины, они сделали в 83-ем году и Лисп-машину следующего поколения - 3600. Как и NORMA, эти машины использовали и специальное железо, не просто были обычными компьютерами с увеличенной памятью для микрокода и поддержкой тегов.
Машины линейки 3600 работали быстрее, и были любимы за это пользователями. Правда, быстрее в значительной степени потому, что ранние модели не имели сборщика мусора [Stee96]. Продвинутые пользователи прочих Лисп-машин, конечно, могли выбрать отключение сборщика и освобождать память способом, который мы подробно описали в прошлой части. Но в случае 3600 этот лайфхак уже действовал прямо из коробки. Позднее машины 3600 серии получили сборщик мусора нового поколения, о котором мы еще расскажем.
В 80-ом году, когда Symbolics уже продавала, но еще не поставляла свою первую Лисп-машину, эта машина стоила, ориентировочно $140,000 ($536,322 в 2024). Что можно было получить за эти $140K в 80-ом году? Процессор и 12K слов памяти для микрокода - это $79,600. Добавим $23,000 за минимальный жесткий диск и его контроллер. Все невеликое адресное пространство LM-2 поместится и на минимальный диск. 10-30 тысяч за ОС в зависимости от того, сколько Лисп-машин покупаете и хотите ли получать обновления. И мелочи вроде I/O-карты, клавиатуры и мыши - $7,900 за все и монохромного монитора за $4,800.
И сколько после всего этого остается на самый важный для нашей истории ресурс - оперативную память? Примерно на 0-750Кб. 4Мб памяти, минимальный объем для следующей модели, и, как мы выяснили в предыдущей части, минимальный совсем не без причины, обходился в $75,200 ($288,081 в 24-ом году). Так что тут, вероятно, не обошлось без скидки, которую обещают за покупку многих машин [Symb80]. Эта цена сопоставима с миникомпьютером VAX-11/750, а цена за пару Лисп-машин - с VAX-11/780 [VAX82].
Заплатив эти деньги в 80-ом году можно было в 81-ом получить персональный компьютер с достаточно большой памятью для ваших Лисповых (и функциональных) нужд. Или миникомпьютер, которые бывали персональными только в выходные ночью или вроде того. Все остальное время они обслуживали несколько (десятков) рабочих мест. Например, сотрудники Йеля обсуждали в рассылках, что на рабочее место для одного студента там готовы потратить 10-15 тысяч долларов [TMail]. И миникомпьютер с десятками терминалов один из способов обеспечить рабочее место за эти деньги. Чего не скажешь о Лисп-машине. По видимому, персональный миникомпьютер был для не самых богатых героев нашей истории слишком сумасшедшей идеей. Так что не удивительно, что неперсональные мини-компьютеры выиграли в подавляющем большинстве мест, где велась разработка ФЯ. И, в отличие от лисперов, которые не любили VAX-11 [Stee96], разработчиков ФЯ он более-менее устраивал.
В 81-86 годах Symbolics поставила две тысячи Лисп-машин [Symb86b]. Две тысячи машин это немного. И в Symbolics рассчитывали производить и продавать намного больше. Нам известно об этом не из декларируемых пожеланий, а потому что Symbolics арендовала больше производственных помещений и нанимала больше людей, чем им оказалось нужно в действительности [Phil99]. Но насколько это было немного для 80-х? Лисп-машинисты хотели соревноваться с миникомпьютерами. И, например, DEC - компания, производившая не очень много компьютеров, в 85-87 выпустила 65 тысяч миникомпьютеров MicroVAX II [Mash2006]. Довольно популярная по меркам DEC линейка VAX-11-машин на микропроцессорах. И только одна из многих линеек совместимых компьютеров.
Машинисты LMI к концу 83-его года продали только 23 CADR машины. Что выглядит довольно смешно, если сравнивать с успехами Symbolics, но ARC собрал только две [Turn12] или три [Trel87] машины NORMA. Первые лисп-машины LMI стоили примерно $100,000 ($383,000 в 24-году). Половину из этих 23-х собрали еще в 80-ом, первом году существования компании. Он же последний год, когда компания была прибыльной.
Так что в 83-ем году в LMI решили, что дела идут не особенно хорошо и решили сделать то, чего основатель LMI Гринблат делать опасался, и из-за чего машинисты ИИ Лаборатории МТИ и разделились на две компании. Пригласили менеджера из Texas Instruments Фрэнка Спитногла (Frank Spitnogle) на должности президента и COO в LMI. TI приобрел четверть компании.
И, нужно отдать Гринблатту должное, его опасения полностью оправдались. С помощью TI, LMI выпустили новую машину Lambda. Новую, но не очень - это все еще CADR-образная машина из МТИ 70-х. И, получив некоторый опыт работы с LMI, в TI пришли примерно к тем же выводам, к которым пришли и большинство бывших коллег Гринблатта по ИИ-лаборатории: Гринблатт и LMI не нужны. TI стала производить собственные Лисп-машины, лицензию на которые им в LMI не могли не дать. LMI не могла конкурировать и с Symbolics, и появление на Лисп-машинном рынке настоящего производителя железа не пошло им на пользу. Так что в апреле 87-го LMI обанкротилась [Phil99].
Что не обязательно плохо для Лисп-машин как явления. В TI могли производить больше Лисп-машин и сделать их дешевле. И могли позволить себе терять деньги на них, пока раздавят остальных Лисп-машинистов. TI раньше чем Symbolics стала продавать Лисп-машины на одном чипе, хотя и все той же старой CADR архитектуры Найта из 70-х. И Лисп-машина на чипе, конечно, имела меньше недостатков по сравнению с обычной рабочей станцией. МТИ купил для лаборатории компьютерных наук не машины Symbolics, а 400 Лисп-машин TI Explorer (пользователи этих машин и будущие герои нашей истории называли их “exploder”). Но TI пришла на рынок поздновато, в 84-ом году. Позднее лисперы напишут, что в 87-ом году “стало ясно, что все, кто хотел купить Лисп-машину - уже купили её”. Трудные времена наступили не только для SKI-машин. Что же случилось?

Персональный микрокомпьютер

Закончилось время, когда одним компьютером пользовались больше десяти лет. И даже заменив компьютер на новый, продолжали пользоваться компьютером примерно той же производительности. Рабочее место пользователя компьютера вместо этого дешевело. С самого начала предыстория функционального программирования была историей увеличения оперативной памяти. Но не историей увеличения числа операций в секунду. С 60-х годов история разворачивалась таким образом: разработчик, например, компилятора ФЯ, мог позволить себе немного машинного времени на первом компьютере в 1 MIPS (Atlas), затем побольше машинного времени на серийном мэйнфрейме примерно в 1 MIPS (PDP-10), затем еще больше времени на миникомпьютере в 1 MIPS (VAX-11/780) и, наконец, персональный микрокомпьютер примерно в 1 MIPS.
И это не было каким-то средним показателем. Это был передняя кромка практической производительности. К которой некоторые герои нашей (пред)истории могли только прикоснуться однажды, как Поплстоун, и затем отступить на годы в области меньшей производительности. Или даже вовсе только мечтать о ней, как Тернер. Но, к счастью, в начале 80-х этот вычислительный застой подошел к концу.
Перелом произошел в 83-84 годах. В 83-ом году в Symbolics еще были настолько уверены в продолжающемся превосходстве Лисп-машин, что ликвидировали отдел занимающийся имплементацией Лиспа для обычных машин [Phil99]. В 84-ом одни из главных героев предыдущей главы Ричард Гэбриел и Скотт Фалман вместе с одним из разработчиков PSL Эриком Бэнсоном (Eric Benson) основали компанию Lucid Inc., которая имплементировала Лисп для микрокомпьютеров [Stee96] [Gabr96]. В том же году разработчики Franz Lisp основали компанию Franz Inc. [Franz], которая занялась тем же.
Как мы выяснили в этой части, некоторые герои нашей истории в INRIA и Йеле уже давно готовились к появлению микрокомпьютеров в 1 MIPS, начав с тех рабочих станций, которые уже может и были “миникомпьютером на одном чипе”, но еще не VAX-11/780 на одном чипе. Вроде ранних рабочих станций Apollo DN400

  fib fibg fibp sub rev clos ASSOC tak
3670 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00
TERN 2.92 2.90 2.50 6.88 5.00 1.00 3.55 3.00
DN400 9.21 8.90 8.60 5.38 7.00 1.15 10.9 9.80
3670
░▒▒▓▓░░▒▒▓▓▓▓▓▓▓▓▓▓▓░▒
TERN
░░░░▒▒▒▒▓▓▓▓░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓░░░░▒▒▒▒
DN400
░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒

И, как видите, новые рабочие станции позволили разработчикам T2 резко сократить их отставание от новых Лисп-машин ничего не делая с имплементацией T2.
Обратите внимание на то, что бенчмарк для замыканий (clos) работает на ZetaLisp/3670 как на T2 и намного более медленных рабочих станциях. Не самая лучшая платформа для имплементации ФЯ!
Авторы Le Lisp из INRIA просто заявляют о победе над LM2 в марте 83 [Chai83] (даже для загружаемого в интерпретатор кода) и над 3600 в июле 84-го [Chai84] по результатам вычисления чисел Фибоначчи.

  fib 20
Le Lisp (opt) 1.00
Le Lisp 5.42
Symbolics 3600 1.25
Symbolics LM2 6.50
Le Lisp (opt)
░░░░░░░░░░░░░░░
Le Lisp
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Symbolics 3600
░░░░░░░░░░░░░░░░░░░
Symbolics LM2
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Что, конечно, не дает реалистичного представления о разнице в производительности, но есть основания считать что Le Lisp действительно был побыстрее T2.
И разработчики T2 признавали [TMail], что Лисп-машины пока еще работают заметно быстрее. Но скорость это еще не все.
В наше время Лисперы чаще вспоминают о Лисп-машинах как о потерянном рае, из которого они были изгнаны другими лисперами, как Столлман, или же другими безжалостными силами, к которым мы еще вернемся. Но если почитать рассылки 80-х, то обнаруживаются лисперы, которые просто не любят Лисп-машины. Конечно, легко не любить что-то, чего все равно не купят для вас на вашей работе. Но этим претензии не ограничиваются.
Есть лисперы, которым не нравится Лисп-машинный Лисп и которые хотят использовать Схему, которые хотят использовать свой любимый текстовый редактор, использовать TeX и C. Предпочитают рабочее окружение и интерфейс на обычных рабочих станциях. А машинисты смеются над тем, что кто-то хочет использовать C и свой любимый текстовый редактор.
Но надолго ли это преимущество Лисп-машин в скорости? Начиная с 84-го года, микрокомпьютер работает все быстрее и быстрее. Возможность поисполнять Лисп или поредуцировать комбинаторы немного быстрее на фоне той двадцатилетней стагнации могла выглядеть заманчиво. Но что делать Лисп/SKI-машине в новом мире? В следующем году комбинаторы будут редуцироваться заметно быстрее без всяких специальных машин. Будет ли специальная машина работать быстрее в следующем году?
В 84-ом году разработчики и пользователи Лиспов для обычных машин смотрят [TMail] в будущее с оптимизмом и уже ожидают победы над Лисп-машинами в ближайшем будущем. Motorola анонсировала новую модель процессора из 68K линейки - MC68020, который должен быть в разы быстрее.
Машинисты не верят. Пишут, что Лисп-машины-то вот они, а быстрых рабочих станций с 6Мб памяти и Motorola 68020 еще нет. В Symbolics Гэбриелу заявили, что Лисп на машинах с MC68020 будет, по крайней мере, в 17 раз медленнее, чем на 3600 [Gabr96]. Машинисты верили, что Лисп-машины станут быстрее. Ну а что им еще оставалось делать?
В середине 80-х разработка нового микропроцессора стоит десятки миллионов долларов (т.е. порядка 100 миллионов долларов 24-го года), и это уже существенная проблема для того кто продает десятки тысяч миникомпьютеров [Mash2006]. Не говоря уже о тысяче Лисп-машин. Это сумма того же порядка, что весь Лисп-машинный рынок или все инвестиции привлеченные успешной Лисп-машинной компанией за всю её историю.
Разумеется, это все несопоставимо с масштабами производства микрокомпьютеров, с которыми Лисп-машинистам теперь нужно было соревноваться. В те же 85-87 годы было выпущено, например, 14 миллионов IBM PC и клонов.
Конечно, только небольшое число микрокомпьютеров представляло реальную угрозу для Лисп-машин. Но это число ограничивалось, в основном, памятью. И стоимость памяти падала, все больше увеличивая долю стоимости процессора, выпускаемого миллионами и дешевого в случае микрокомпьютеров. В 80-ом году память стоила $18K ($69K 24-го) за мегабайт [Symb80]. В 86-ом $1K ($2,880 24-го) за мегабайт Apol86.
В начале 84-го года обычный микрокомпьютер на процессоре Motorola 68K - Apple Macintosh - имел жалкие 128K памяти, но уже осенью того же года - 512K, а в начале 86-го продавалась модель, которую можно было проапгрейдить до волшебного числа в 4Мб. Понятно, к чему все шло. И для тех, кому нужно было больше памяти, существовали микрокомпьютеры с той же линейкой процессоров, но большей памятью: рабочие станции Apollo, SUN и другие.
За шесть лет цена на Лисп-машины Symbolics упала с $140,000 до $40,000 ($115,205 в 2024). И $40,000 в 86-ом - это цена самой минимальной машины с 4Мб памяти и 190Мб жестким диском. В том же 86-ом году Symbolics продавала (пыталась продавать?) машины с памятью до 60Мб и дисками до 4120Мб. И, как мы выяснили, большие объемы памяти были очень желательны. Кроме того, 40 тыс. долларов - это временная цена, со скидкой. Без скидки такая минимальная машина стоила на $18,000 дороже [Symb86], т.е. 164 тысячи долларов 24-го года.
И, конечно же, MC68020 стал очень успешен и с максимальной частотой выдавал 10 MIPS. В 86-ом году за эти 40 тысяч еще без скидок можно было купить рабочую станцию с 16МГц MC68020 (4.8 MIPS) и 12Мб-16Мб памяти, а сравнимая с Лисп-машиной конфигурация обошлась бы тысяч в десять Apol86. Вот эти-то машины и покупали.
Но триумф микрокомпьютеров не единственная причина того, что желающих покупать Лисп-машины было мало. У тех, кто продавал Лиспы для микрокомпьютеров тоже начинались проблемы. И Лисп-машинисты придумали, со временем, как обратить триумф микрокомпьютеров себе на пользу. Что тоже им не особенно помогло. Но эти остальные причины упадка Лисп-машин в частности и Лиспа вообще - уже другая история.

Сон Черного Короля

Итак, события развивались не самым удачным образом для ФП-машин, похожих на Лисп-машины. Но не обязательно для идей Бэкуса. Да, разработчики SKI-машин вдохновлялись идеями Бэкуса, но не очень-то им следовали. И эти машины не были такими уж анти-фон-Неймановскими. Так что, разобравшись с ними, мы переходим к машинам, которые сами вдохновили Бэкуса и которые он сам называл как примеры машин анти-фон-Неймановских.
SKI-машины, как и Лисп-машины до (и немного после) них, требуют от пользователя разнообразных жертв и лишений ради того, чтоб функциональные языки и Лиспы работали быстро. И пользователи были не готовы к таким жертвам. Но что если подумать не о том, что вы можете сделать для функционального программирования, а о том, что функциональное программирование может сделать для вас? Что если такой подход понравится пользователям больше? Для чего вообще ФП нужно и для чего оно подходит лучше? У Бэкуса есть ранние версии ответа на этот вопрос: функциональные языки нужны и лучше подходят для программирования параллельных компьютеров. И, конечно же, нашлись желающие проверить правильность этого ответа.
И если размах проектов по созданию SKI-машин выглядит впечатляюще по сравнению с усилиями по написанию компиляторов ФЯ для обычных машин, то масштаб разработки параллельных ФП-машин затмевает уже SKI-машины.

Железное дерево

Это самое далекое отступление от архитектуры фон Неймана, которое я когда-либо видел.
Джон Бэкус, Может ли программирование быть освобождено от стиля Фон-Неймана? [Back78]

Настоящий Анти-Фон-Нейманизм предписывает бороться за локальность данных. Но разработанные к концу 70-х способы имплементировать высокоуровневые языки последовательно анти-локальны. Они сводятся к прыжкам по развесистыми деревьями ссылок на ссылки, с ударами о память при каждом прыжке. Но что если вернуться в “доисторические” времена, когда не было всех этих виртуальных машин, но на новом технологическом витке? И машина Маго (Gyula A. Magó) [Vegd84] исполняет код по большому счету так же, как человек, вооружённый ручкой и бумагой: переписывает строки. Не сам текст программы на FFP, Бэкусовском FP без синтаксического сахара, но строки байтов.
Вычислительные узлы соединяются как узлы бинарного дерева. Вычислительный узел применяет одну строку-лист к другой строке в своей локальной памяти. Результат этого применения становится одним из листов для другого вычислительного узла. На каждом уровне дерева вычислительные элементы могут работать параллельно и независимо. Все локально, никаких ссылок нет.
Понятно, что у отсутствия ссылок масса проблем. Не работает не только изобретение Вадсворта, избавляющее от необходимости перевычислять одно и то же снова и снова в ленивых языках. Появляется много работы там, где её уже давно никто не ждет. Передаете что-то в две разные функции? Копирование. Рекурсия? Копирование, копирование, копирование. Результат редукции не становится меньше по какой-то еще причине? Много, много копирования для перестраивания дерева.
И даже если машина, в отличие от Фон-Неймановской, оптимизирована для копирования, все равно копированию соревноваться с его отсутствием не так легко. Тем более всем этим копированиям соответствует очень мало полезной работы. Гранулярность распараллеливания очень “тонкая”.
Поэтому вся история развития машины Маго сводится к уменьшению копирования [Mago81] [Mago82] и укрупнению “гранул”.
И все это развитие происходит без построения реального железа. Разработчики компилятора Пролога в FFP называют [Kost84] главной проблемой машины Маго то, что никакой машины еще нет.
(F)FP не соответствует даже гораздо более широким определениям ФЯ чем наше и мы не пишем историю Пролога, но позднее бэкенд для машины Маго разрабатывали и для более релевантного для нашей истории компилятора. Так что к машине Маго мы еще ненадолго вернемся. Ненадолго потому, что разработка машины Маго и связанная с ней деятельность не оказали такого значительного влияния, какое оказали работы связанные со второй разновидностью упомянутых Бэкусом Анти-Фон-Неймановских машин.

The data must flow

Языки, разработанные для поддержки таких машин, были по сути функциональными языками. Но имели некоторые отличительные особенности, обусловленные архитектурой этих машин (так же, как и особенности императивных языков обусловлены Фон-Неймановской архитектурой): они обычно языки первого порядка, строгие и в некоторых случаях не поддерживают даже рекурсию.
Пол Худак, Зарождение, эволюция и применение функциональных языков программирования. [Huda89]

Другая упомянутая Бэкусом альтернатива машинам Фон-Неймана - это dataflow-машины. Dataflow - это направленные графы в которых вершины - это операции вроде копирования и двух видов переключателей и другие встроенные операции вроде арифметических, а ребра - неограниченные FIFO-очереди токенов (сообщений) между ними. Если для операции есть все необходимые токены во входящих очередях - она выполняется и помещает все результирующие токены в исходящие очереди. Идея развивалась с 60-х годов, но в таком виде её сформулировал в середине 70-х Жиль Кан (Gilles Kahn), работавший с такими героями нашей истории как Жерар Юэ и МакКвин, к чему мы еще вернемся.
Каждый узел делает очень немного работы и гранулярность параллелизма поэтому очень тонкая. Как и машине Маго, для работы каждого узла достаточно только локальных очередей, но, разумеется, на практике каждому из них не соответствует вычислительный элемент с очередями в локальной памяти. Вычислительные элементы ищут, для каких вершин графа уже есть все необходимые входящие токены в одной общей очереди в одной общей памяти и возвращают в эту общую очередь новые исходящие токены.
Мы не ставим себе целью написать историю dataflow-машин и языков. Она сопоставима по объему и продолжительности с историей тех ФЯ, что мы пишем, но не особенно тесно с ней связана. На некоторые dataflow-языки повлияли ранние ФЯ вроде ISWIM, но сами они как правило были языками первого порядка. Но, со временем, появились и исключения.
Пока мы рассказывали о взлете и падении ИИ-лаборатории МТИ, мы уже упоминали, что параллельно этим событиям в МТИ происходили и другие, вроде работы над MULTICS. И новый герой нашей истории Джек Деннис (Jack Bonnell Dennis) [Dennis], поработав над MULTICS, занялся другими вопросами и стал одним из основных и наиболее влиятельных dataflow-мыслителей и руководителем группы вычислительных структур, которая занималась преимущественно dataflow. С осени 73-го до января 74-го упомянутый уже нами язык CLU, от которого происходили абстрактные типы в LCF/ML, разрабатывался совместно с группой Денниса и должен был стать dataflow-языком [Lisk93]. Но проект этого единого языка вскоре разделился на два, и группа Денниса стала разрабатывать свой собственный язык VAL. Как и CLU, это был Алгол, идущий против функционального течения, определившего историю Алгола того времени. Анти-функциональность CLU, языка со сборщиком мусора, была обусловлена, в основном, неприязнью, которую Лисков испытывала к Algol 68. Но для VAL не так-то просто было быть ФЯ по техническим причинам. Статические dataflow Денниса не позволяли поддержать даже рекурсию.
Это ограничение было преодолено с изобретением динамического dataflow, которыми занимался более важный герой нашей истории - Арвинд.
Арвинд (Arvind Mithal, но практически всегда используется только мононим Arvind) с 74-го по 78-ой год работал в Калифорнийском университете в Ирвайне (University of California, Irvine) Arvind над динамическим dataflow и языком ID (Irvine Dataflow). Эту работу он продолжил в МТИ в группе Денниса, где язык стал просто Id, без расшифровки.
ID имел кое-какие функциональные фичи, вроде напоминающих POP-2 частичных применений Arvi78

g <- procedure (n,f) (if n=0 then 1 else n*f(n-1))
h <- compose(g,<<2,g>>)

Но до того, как Id стал одним из тех ФЯ, историю которых мы пишем, прошло немало времени.
Поработав три года с Деннисом, Арвинд стал руководителем собственной группы. И если бы мы решали о чем больше писать в этой истории не на основании полученных результатов, а на основании потраченных ресурсов, то история функционального программирования была бы историей группы Арвинда. Писать историю ФЯ как историю группы Арвинда было бы очень удобно потому, что отсканированы практически все их отчеты, в том числе ежегодные, в которых описано что сделано и что планируется сделать. И название у группы было как раз подходящее.

Группа функциональных языков

Новая группа функциональных языков и архитектуры (Functional Languages and Architecture, FLA) создана в МТИ в январе 81 [LCS81], а точнее выделена из группы вычислительных структур Дэнниса (Computation Structures Group). С первого же года Арвинд руководил десятком научных сотрудников, студентов и аспирантов и количество участников группы год от года росло.
В 81-ом ссылки победили очередную анти-Фон-Неймановскую локальность. Dataflow-машина “потребляет” пакеты, а значит построение на них долгоживущей структуры данных будет требовать постоянного её копирования. Так что в Id добавили I-структуры - ссылки на массивы, элементам которых можно присвоить значения один раз (отложить инициализацию), как логическим переменным в Прологе.
Тогда же Пингали (Keshav Pingali) изучал преимущества и недостатки вычисления по требованию по сравнению с вычислением, управляемым данными (data-driven) и решил, что вычисление по требованию хоть и предотвратит ненужные вычисления, но удвоит передачу пакетов между узлами. Так что Id не будет ленивым по умолчанию языком.
Тем временем он с Винодом Катаилом (Vinod Kathail) работают над двумя бэкендами компилятора Id: в dataflow-графы и в MacLisp.
В том же 81-ом году вместо dataflow-интерпретатора Арвинда с растущими (в циклах) тегами токенов, стали разрабатывать что-то более похожее на реальную машину с токенами фиксированного размера TTDA (Tagged Token Dataflow Architecture).
Каждый вычислительный элемент TTDA имеет 64К памяти для программы и 64К памяти для I-структур. Вычислительные элементы соединены сетью, пересылающей токены (пакеты фиксированного размера) между ними. Для выборки подходящих токенов из очереди, должно применяться специализированное железо для сопоставления тэгов этих пакетов. В группе Арвинда предполагают спроектировать со временем VLSI чип собственного дизайна, но для начала планируют по два MC68K процессора на каждый вычислительный узел. Прототип поможет определить для чего специальное железо действительно будет лучшим выбором, а что можно оставить обычным широкодоступным компонентом. Не нужно торопиться и вкладывать сразу много усилий в разработку специального железа. В прототипе до 256 вычислительных элементов, чтоб определить насколько хорошо все это масштабируется. Симулятор этой машины сначала написали на MacLisp.
В октябре 81-го [LCS82] группа функциональных языков и архитектуры совместно с группой вычислительных структур Денниса организовали конференцию по функциональным языкам программирования и компьютерной архитектуре (FPCA). По видимому, ту самую, на которой состоялся судьбоносный разговор Йонссона, Августссона и Тернера, который подтолкнул Йонссона к изобретению G-машины.
На конференции выступили с докладами представители основных обсуждаемых в этой главе разработчиков параллельных ФП-машин.
Языки, разрабатываемые группой функциональных языков, сначала были языками первого порядка, то есть функциональными только в соответствии с очень широким определением функциональности, но около 82-го года там заинтересовались ФЯ в более привычном смысле. Для начала, добавили ФВП в Бэкусовский FP [Arvi84].
Не позднее середины 82-го Катаил с Пингали закончили транслятор пока еще старого недофункционального Id в MacLisp, часть IdSys, компилятора/интерпретатора/отладчика Id, работающего на DEC-20 (поздняя модификация PDP-10). Компилятор в команды разрабатываемой параллельной машины пока еще не готов.
Теперь симулятор машины пишут на Паскале. Этот код на Паскале еще и называют исполняемой спецификацией машины.
Тем временем, количество вычислительных элементов в будущем прототипе понизили с 256 до 64. Первый вычислительный элемент планируется построить к концу 83-го года, но особого прогресса пока нет, использовать микропроцессоры Motorola передумали и решили собирать ЦПУ из нескольких АЛУ AMD как создатели SKIM. Но специальное железо для сопоставления тэгов спроектировано и даже заказано.
Прототип вычислительного элемента в 83-ем году так и не построили [LCS83], не смотря на то, что к середине 83-го года группа Арвинда уже удвоилась, в ней больше двадцати человек. Хотя построение железа осталось в долгосрочных планах, пока решили обойтись симуляцией.
К середине 83-го написана, как ожидают, половина симулятора TTDA, работающий на мэйнфрейме IBM. Этот мэйнфрейм IBM 4341 с 16Мб оперативной памяти и 2.4Гб диска арендован на два года специально для симуляции будущей параллельной машины.
Симулятор написан на Паскале Пингали, Дэвидом Куллером (David Culler) и Морисом Довеком (Moris Dovek) с некоторым участием на раннем этапе Арвинда и Роберта Томаса (Robert Thomas). Надеются, что можно будет использовать для моделирования машины до конца лета 83-го.
Группа Арвинда сотрудничает с уже поучаствовавшей в нашей предыстории ФЯ IBM Research в Йорктаун Хайтс, которые запускают симулятор на более мощной машине.
И более мощная машина нужна. Потому, что симуляция 20 миллионов dataflow-инструкций (несколько машинных секунд) требует 24 часа машинного времени на их мэйнфрейме.
Конечно, для запуска более-менее реальных программ такая детализированная симуляция не подходит, так что ведется работа над менее детальной, которую называют “эмуляцией”. Эмулировать проектируемую параллельную машину будет MEF (Multiprocessor Emulation Facility). Эмулятор машины из 64-ех вычислительных элементов планируют построить как 64 Лисп-машины Symbolics 3600, соединенные сетью с 64-мя 8x8 свитчами собственной разработки. Размах впечатляет? Это вам не разработка компилятора ФЯ для обычных машин по ночам в выходные на университетском компьютере.
Эмулятор пишут на Лиспе Ричард Солей (Richard Soley) с помощью еще пары лисперов. В МТИ, конечно же, хотят писать на Лиспе, а не на Паскале. Предполагая, что на Лисп-машинах-то Лисп будет работать достаточно быстро. Ожидают, что код эмулятора будет готов в декабре 83-го года. И если закупка оборудования состоится, то в начале 84-го года заработает эмулятор из 4-8 Лисп-машин. Предполагается, что полномасштабный эмулятор из 64-х Лисп-машин заработает до конца 85-го. Ожидается, что MEF будет исполнять 64 - 640 тысяч dataflow-инструкций в секунду, на порядки больше чем симулятор.
Первые две Symbolics 3600 Лисп-машины группа функциональных языков и архитектуры получила в мае 83-го, до того разрабатывая эмулятор на старых университетских MIT CADR.
К середине 83-го кодогенератор для TTDA компилятора Id закончен. К этому же времени заинтересовались добавлением в Id системы типов как в ML.
К середине 84-го группа Арвинда удвоилась еще раз - в ней теперь почти пятьдесят человек [LCS84].
Эмулятор работает пока что на пяти Symbolics 3670 Лисп-машинах (по 6Мб памяти). И ожидается, что до конца 84-го года их будет 16. Лисп-машины пока что соединены 10Мбит Ethernet. Над быстрой сетью для соединения 64-х машин работают три инженера в IBM и один в Ericsson.
Правда, эмулятор на Лиспе эмулирует только 200 инструкций в секунду. Да, примерно столько же, сколько и симулятор. В группе Арвинда надеются, что 64 машины вытянут аж 13тыс. инструкций. Но желаемая производительность для эмулятора-то 64-640тыс. операций. Разработчики эмулятора еще надеются достигнуть этого результата оптимизациями и частичным переписыванием Лисп-кода на микрокод для Лисп-машин.
О трансляторах Id в Лисп и в команды для параллельной машины теперь последовательно пишут как о двух отдельных компиляторах, хотя раньше упоминали как минимум общий код парсера. Оба переписаны на общее подмножество MacLisp и Лиспа для Лисп-машин потому, что все еще используются на PDP-10. В Йорктаун Хайтс их тем временем переписывают на какой-нибудь Лисп, работающий на машинах IBM.
Ведутся работы над оптимизациями вроде CSE и слияния блоков. Добавление ФВП в язык Бэкуса не привело ни каким важным для нашей истории последствиям. Но к середине 84-го Арвинд с разработчиками компилятора Id Катаилом и Пингали решили переделать Id для “элегантной” работы с ФВП. Планируют сделать это за два года.
Тем более, что их коллеги из группы Дэнниса, под влиянием посещавшего их Джо Стоя, соавтора Стрейчи из Оксфорда, пересмотрели анти-функциональность своего Алгола Val. Теперь этот язык из тех Алголов, которые как-то пытаются имплементировать рекурсию и ФВП. Но, как и у группы Денниса, у этого ФЯ нет будущего. А вот у Id оно пока есть.
И код на Id с его недо-функциональными фичами вроде compose производил на Арвинда и др. плохое впечатление. Особенно после знакомства с разработками Тернера и книгой Берджа. Языком, из которого нужно черпать идеи по улучшению Id, называют SASL. Ссылаются на статьи Тернера и его доклад на той самой конференции по ФЯ 81-го года.
Заинтересовались даже имплементацией параллельной редукции с помощью специальных машин, раз уж у редуцирующих функциональщиков такие успехи. Но пока как побочным проектом.
Функционализация Id набрала обороты только с появлением в группе Арвинда нового заметного героя нашей истории - Ришиюра Нихила (Rishiyur Sivaswami Nikhil) [LCS85]. Нихил до этого не имел никакого отношения к dataflow, но был знаком с функциональными языками и сам разрабатывал ФЯ как языки запросов к базам данных. О чем сделал доклад на организованной Арвиндом конференции в 81-ом. Свой вклад в модернизацию Id Нихил начал с работы над проверкой типов с поддержкой полиморфизма.
Хеллер (Steven Heller) и Троб (Kenneth R. Traub) закончили переписывание компилятора Id Винода Катаила (теперь называют Version 0) на Лисп для Лисп-машин и его отладку. Релиз Id Compiler (Version 1) состоялся в январе 85-го. О трансляторе Id в Лисп уже не вспоминают.
Этот компилятор, правда, не достаточно легко расширять и модифицировать, так что началось написание нового компилятора Version 2, который планируют закончить к июню 86-го.
Тем временем, МТИ заключил упоминавшуюся уже в этой главе сделку с TI и вместо Symbolics 3670 группа Арвинда стала получать более дешевые и медленные TI Explorer. К середине 85-го в MEF восемь 3670 и два Explorer. Платы расширения для прототипов собственной высокоскоростной сети не влезают в Explorer и их нужно перепроектировать.
Оптимизация же кода позволила пока ускорить эмуляцию на одной машине только в 5 раз перед тем как работа над ним была приостановлена весной 85-го. Пока не перешли полностью на серийные Эксплореры - нет смысла дальше оптимизировать или микрокод писать. Те две, что уже получены - прототипы.
Ну, зато машину на которой работает симулятор планируют заменить на более мощный мэйнфрейм IBM 4381.

Конец группы функциональных языков

До середины 86-го года группа функциональных языков уже не дожила [LCS86]. Но только потому, что поглотила свою родительскую группу вычислительных структур, все это время кое-как занимавшейся другой разновидностью dataflow, и помимо Денниса и нескольких его аспирантов получила и название. Теперь под началом Арвинда больше шестидесяти человек.
Ирония в том, что рассталась со своим “функциональным” названием группа Арвинда примерно тогда же, когда наконец-то серьезно занялась разработкой первого своего ФЯ. Не в широком смысле, а более-менее такого, историю которых мы пишем.
В июне и июле 85-го года Нихил и Арвинд полностью перепроектировали Id, создав Id/83s. 83 тут не год описания языка, а часть номера курса в МТИ, для которого язык разрабатывали - “6.83s: Functional Programming and Dataflow Architectures”. По крайней мере к середине 86-го Id/83s имплементирован как фронтенд для компилятора старого Id, того компилятора, который Version 1 [Nikh86].
Система типов для Id “как в ML” уже упоминалась раньше, но только года с 85-го система типов для Id обсуждается серьезно. В 86-ом Арвинд и другие уже уверены, что система типов нужна. Им нравится, что система типов - это “мощный инструмент отладки”. И нужно заметить, что это пишут не обычные функциональные программисты с их обычными отношениями с отладчиком, а чаще - его отсутствием. Это пишут лисперы, которые отладчиком пользуются регулярно. К тому же, они столкнулись с рядом проблем с производительностью. Помимо обычных арифметических, есть и более специфические проблемы. Или, по крайней мере, их формулировки. Например, для эффективности сборки мусора нужно знать какие ребра dataflow-графа передают I-структуры, т.е. ссылки на массивы ссылок.
Но в 86-ом году Арвинд и “имеющий большой опыт работы с системой типов Милнера” Нихил уже не уверены, что хотят систему типов как в ML. Или как в CLU, который тоже упоминается. Теперь им хочется чего-то вроде системы Жерара-Рейнольдса. Они понимают, что полного вывода типов не будет, но надеются обойтись небольшим количеством аннотаций.
Возможно, что это влияние новоприобретенных остатков группы Денниса, которые уже добавили вывод типов в свой dataflow-язык Val. Вывод отличающийся от Милнеровского поддержкой ad-hoc полиморфизма для арифметики рекурсивных типов. Для такого вот кода на языке Денниса, который теперь назывался VimVal [Kusz84]

function Y(f)
  function f1(x)
    f(x(x))
    endfun
  f1(f1)
  endfun

выводились вот такие типы:

Ytype = Functype(Ytype) returns(Ytype)
function Y(f:Ytype) returns(Ytype)
  function f1(x:Ytype) returns(Ytype) 

Так или иначе, но в Id/83s типов пока нет, но язык проектируется так, чтоб типы можно было проверять Хиндли-Милнером.
Если не считать некоторое сопротивление Милнеру, все прочее в новом Id делается так, как в языках эдинбургской программы. Нихил ссылается [Nikh85] на Тернера и Бурсталла, Августссона и Карделли, делая что-то вроде SASL с массивами (I-структурами). МТИ долго сопротивлялся эдинбургскому влиянию, но вот, в самом сердце альтернативной ФП-программы теперь делают очередной “эдинбургский” язык, просто с задержкой на годы.
Группа Арвинда теперь пишет на Common Lisp и Троб пишет вторую версию компилятора Id на Common Lisp. Это его магистерская диссертация. Ожидалось, что первую программу он скомпилирует в августе 86-го, но Троб справился раньше - в июле 86-го. Но имплементированы не все оптимизации, которые задуманы [Trau86]. Компилятор транслирует функции со свободными переменными в комбинаторы с помощью лямбда-лифтинга и есть ссылка на Йонссона.
Группа Арвинда получила 38 Лисп-машин TI Explorer, каждая с 8Мб памяти и 140Мб диском, и MEF использует 32 из них, 64-машинный эмулятор больше не планируется. Программа-эмулятор, которая теперь называется GITA (Graph Interpreter for the Tagged-Token Architecture) работает на одном Эксплорере со скоростью 1000 инструкций в секунду. На Symbolics 3600 - 2400 инструкций в секунду.
А сколько на 32-х Эксплорерах? По какой-то причине этой скоростью хвалиться не спешат. Вместо этого пишут, что делать выводы из результатов полученных на MEF нужно с осторожностью, на реальной машине ожидаются другие результаты.
Симулятор, который теперь называется SITA (Simulator for the Tagged-Token Architecture) теперь работает на IBM 4381, который в два с половиной раза быстрее предыдущего. 80 инструкций в секунду. Но подождите-ка, на первой машине было 230. Видимо, симулятор стал детальнее.
В 87-ом году новый функциональный Id/83s называется Id Nouveau [LCS87]. После интеграции чисто функционального ядра с I-структурами, синтаксис “заморожен” в январе 87-го. Работа над системой типов Id и проверкой типов в компиляторе Id все продолжается, но ничего пока что не готово. Релиз Id World, IDE для Id состоялся в апреле 87-го.
Как и в императивном ФЯ VAX ML, в Id Nouveau есть циклы и массивы. Но Id не является императивным языком, так что и те и другие отличаются от своих ML-ных аналогов [Nikh86].

let
    x,y = 1,1
in 
    for j from 1 to 20 do
        new x, new y = y, x+y
    return x

x и y тут не изменяемые значения, как в императивном ML, а константы и new x просто способ определить эквивалент параметра хвосторекурсивной функции.
Циклы в Id описываются как “удобный способ” записать функции с хвостовым вызовом, но компилятор не делает такой трансляции и специальным образом компилирует циклы, сохраняющееся в промежуточном представлении [Trau86]. Массивы (I-структуры) больше похожи на логические переменные, чем на ML-ные массивы. Они не изменяемые, но с отложенной инициализацией:

let 
  a = array(1..3);
  a[1] = 25;
  a[2] = 6;
  a[3] = 4
in a

Попытка прочесть неинициализированное значение из I-структуры откладывает это вычисление до тех пор, пока инициализация не будет сделана. Попытка инициализировать I-структуру второй раз прекращает исполнение программы.
Конструкций для управления распараллеливанием нет. Предполагается, что оно полностью автоматическое. Программист не должен ничего аннотировать.
Все прочее выглядит знакомо [Nikh86]:

def map f l = if l = nil then nil
              else let hd, tl = l
                   in f hd, map f tl ;

let 
    y = 1 ;
    add x = x + y
in map add (1, (2, (3, nil)))

Короче говоря, Id Nouveau выглядит как ФЯ 77-го года, а не 87-го. Но таким анахронизмом он был недолго. В 88-ом году Id будет выглядеть как ФЯ 88-го года. Но это уже другая история.

Алиса в ржавом поясе

Поскольку в работах Денниса, Арвинда и других над dataflow-машинами уделялось достаточно много внимания общеполезным вопросом вроде тех, как лучше пересылать сообщения между вычислительными элементами, не только чему-то dataflow-специфическому, они оказали влияние на строителей машин, которые не были dataflow-машинами.
Производить новые пакеты в вычислительном элементе можно и другим способом, например вычислять по требованию, редуцируя графы. Тем более, что и Жиль Кан рекомендовал поступать так. Скомпенсировать увеличение числа передаваемых пакетов можно увеличив “зернистость” параллелизма.
Такие машины должны были имплементировать обычные функциональные языки, так что их создатели уже поучаствовали в нашей истории. Помните Роберта Келлера, научрука Худака в университете Юты, который разрабатывал FGL/FEL? Имплементировать эти языки он хотел с помощью параллельной машины AMPS (Applicative Multi-Processing System) [Kell79].
Вычислительные элементы AMPS соединялись в дерево, как у машины Маго, но на этом сходство и заканчивалось. Вычислительные элементы в узлах балансировали нагрузку для вычислительных элементов в листьях дерева. AMPS редуцировала не строки, а графы. Точнее не редуцировала, конечно, потому, что не была построена. В 79-ом году параллельная редукция эмулировалась программой на Паскале. И строить реальную машину в ближайшее время не собирались. Писали только еще один симулятор на Simula 67.
К 84-му году на смену этой симулируемой машине пришла другая симулируемая машина - Rediflow (REDuctIon, dataFLOW) [Kell85]. Машина должна была состоять из вычислительных элементов, соединенных быстрой сетью с коммутацией пакетов. Каждый элемент - Келлер называет его Xputer - состоит из соединенных шиной микропроцессора (рассматривается Motorola 68020), памяти (примерно мегабайт) и свитча.
Новая машина утратила древесное своеобразие своей предшественницы. Свитч соединяет Xputer с четырьмя соседями в прямоугольной сетке. С ближайшими, или, в случае конфигурации “бабочка” с соседями во все более удаленных “столбцах” с ростом номера “строки” сетки.
Гранулярность параллелизма более “грубая” по сравнению с AMPS. Вместо подсчета ссылок - сборщик мусора, придуманный Худаком.
Другой ФП-машинист, который уже поучаствовал в нашей истории работал над уже упоминавшимся в нашей истории проектом в Великобритании. В Имперском колледже Лондона, независимо от Келлера, но под влиянием идей таких разработчиков dataflow-машин как Арвинд и Деннис, наш старый знакомый Джон Дарлингтон со своими коллегами разрабатывал машину ALICE (Applicative Language Idealized Computing Engine) [Darl81].
В 70-е Дарлингтон разрабатывал NPL вместе с Бурсталлом в Эдинбурге. В разработке HOPE он принять участия, по всей видимости, не успел, но использовал эту новую версию NPL в своем проекте как имплементируемый язык и как язык для имплементации.
ФЯ, например HOPE и KRC, решают многие проблемы разработки программ, но не получили широкого распространения из-за неустранимой неэффективности на обычных, Фон-Неймановских машинах, утверждают Дарлингтон и его соавтор Рив (Mike Reeve).
Но не все потеряно: железо становится дешевле и многопроцессорные машины - осуществимее. И вот для них-то функциональные языки подходят “идеально”. Они “свободны от побочных эффектов” и потому подвыражения можно вычислять независимо.
Дарлингтон и Рив знают о разработке параллельных машин для императивных языков, “взаимодействующих последовательных процессов”, например, в Университете Карнеги — Меллона. И отмечают, что при программировании таких машин нужно программировать конкурентность и синхронизировать доступ к разделяемым данным вручную. При программировании ФП-компьютера на ФЯ ничего этого делать не придется, ожидают они.
Дарлингтон и Рив не позднее октября 81-го года пишут, что ожидают впечатляющие успехи VLSI, которые произведут массу дешевых блоков для строительства компьютеров. Дешевых благодаря своей массовости. И эти строительные блоки - микропроцессоры. Не ТТЛ микросхемы, из которых в это время строят SKI и Лисп-машины. Позднее лисперы будут писать, что в это время это было еще совсем не очевидно. Но, как видите, очевидно для Дарлингтона и Рива: не делайте сами центральные процессоры, стройте компьютер из готовых центральных процессоров на одном чипе, которые будут все лучше и дешевле. И цель строителя компьютера наладить передачу данных между этими микропроцессорами и памятью.
В 81-ом году предполагается, что ALICE будет состоять из блоков, соединенных быстрой сетью с коммутацией пакетов. Каждый из этих блоков состоит из нескольких микропроцессоров соединенных друг с другом и с памятью шиной.
Такой блок из 20 процессоров и памяти, рассуждают Дарлингтон и Рив, может быть настольной рабочей станцией. Похоже, что у них все же слишком оптимистичные ожидания успехов VLSI. Такая машина должна, по результатам моделирования, совершать 150 тыс. редукций. Редукций более крупных, чем 250 тыс. редукций, которые через несколько лет будет совершать (почти) однопроцессорная NORMA. И только на два десятичных порядка быстрее, чем интерпретатор HOPE на DEC-10, что выглядит не очень впечатляюще для двадцати процессоров. Другими словами, эта двадцатипроцессорная рабочая станция в мечтах Дарлингтона и Рива исполняла код на HOPE с примерно той же скоростью, с какой исполнялся реальный код на VAX-ML на реальном миникомпьютере в то время, когда Дарлингтон докладывал на конференции о своих мечтах.
“Крупномасштабная” машина, в свою очередь, может состоять из 4096 таких двадцатипроцессорных элементов и совершать 150 миллионов редукций. И если читатели не ожидают скорого воплощения такой грандиозной системы в жизнь, то правильно делают, что не ожидают.
Пока что, как и у прочих разработчиков параллельных машин, работает только симулятор. Симулятор написан еще в 81-ом. И в этом случае тоже на Паскале.
“Симулятором” в данном случае называется не то же самое, что в случае TTDA машины. Этот симулятор не только интерпретирует программы, скомпилированные компилятором HOPE, но и позволяет оценивать их производительность. Правда, симулятор все еще слишком абстрактный для хорошей оценки. Так что ведется работа над более низкоуровневым симулятором. Конечно же, опять на Паскале, но в этот раз на Path Pascal с расширениями для системного программирования и параллельности.
Какие-то из этих эмуляторов не позднее 82-го года работали на новом мэйнфрейме IBM 4331. Как видите, даже в Великобритании разработчики ФП-железа могли получить доступ к машинам, об использовании которых создатели компиляторов ФЯ для обычных машин могли только мечтать. Для того, чтоб разрабатывать и запускать код для будущего параллельного компьютера быстрее и на более слабых машинах, в 82-ом году планировали написать быстрый интерпретатор ФП-машинного целевого языка CTL (Compiler Target Language).
CTL описывает правила перезаписи, по которым вычислительный элемент переписывает одни пакеты в создание новых пакетов или их обновление. Пакетов, передаваемых между вычислительными элементами и памятью, как в dataflow-машинах. Но в ALICE пакеты крупнее чем таких машинах. Пакет описывает применение функции. Так что гранулярность распараллеливания больше, чем в dataflow-машинах, но меньше, чем во второй машине Келлера.
Этот процесс обработки пакетов соответствует редукции графов, пакеты можно переписать на месте. Это равномощно одиночному присваиванию и позволяет имплементировать еще и логические и dataflow-языки. В сочетании с копированием, императивный язык они тоже позволяют эмулировать, но неэффективно.
Применения конструкторов - обычные пакеты, не требуется строить структуры данных из I-массивов как в Id. Пока еще сборку мусора планируют делать подсчетом ссылок, но уже беспокоятся из-за того, что циклические графы при этом не поддерживаются.
Предполагается, что в воплощенной в железе ALICE идентификаторы пакетов - просто их адреса. Никакая машинерия для хеширования и прочего ускорения сопоставления тэгов в dataflow-машинах не нужна.
Именно в язык исполняемый этими симуляторами и эмуляторами транслирует код компилятор HOPE на HOPE [Moor82], о котором мы уже рассказывали в прошлой части. Этот компилятор в Имперском колледже Лондона начали разрабатывать не позднее октября 81-го.
В Лондоне не могут себе позволить ускорение эмуляции с помощью покупки множества Лисп-машин, например. Так что для того, чтоб разрабатывать нужные им программы на HOPE, разработчики ALICE сделают более ранний и существенный вклад в развитие имплементаций ФЯ для обычных машин, чем разработчики ФП-машин в МТИ. Но эта история уже больше подходит к теме следующей части.

Вообразите машины

Итак, в отличие от SKI-машин, которые удалось построить достаточно быстро, параллельные ФП-машины все продолжали оставаться только медленными интерпретаторами. “Главный недостаток” машины Маго совсем не уникален. Может у Пролог-машин все было иначе, сложно сказать, мы не пишем историю Пролога.
Но родственные обсуждаемым в этой главе проектам не-совсем-ФП-машины были имплементированы в начале 80-х.
Разновидность dataflow-машин, а которых у токенов есть теги (tagged-token dataflow) была независимо от Арвинда [Arvi86] изобретена в Университете Манчестера и там построили такую машину в октябре 81-го года [Gurd85]. Она имплементировала происходящий от до-функциональной версии языка VAL Денниса язык первого порядка с рекурсией SISAL.
Но даже “предварительную” оценку производительности опубликовали только в 83-ем. И даже в 85-ом оценка была вот такой:

  RSIM/1 RSIM/2 RSIM/3 RSIM/4
SISAL(1FU) 34.0 82.6 76.5 31.0
SISAL(12FU) 4.00 8.90 8.50 3.14
C(VAX11/780) 1.00 1.00 1.00 1.00
SISAL(1FU)
░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░
SISAL(12FU)
░░░▒▒▒▓▓▓░░
C(VAX11/780)
░░

Авторы предлагают не спешить с выводами и подождать, когда появится компилятор SISAL для VAX11/780 и сравнивать с ним. Видимо, предполагается, что он будет компилировать хуже компилятора C и параллельная машина будет выглядеть лучше.
А пока выглядит довольно плохо. Так что Арвинд считает нужным упомянуть [Arvi86], что машина медленная из-за плохих АЛУ и микрокода вместо железа. А вовсе не потому, что dataflow - это плохая идея. Другими словами, манчестерская машина - это такая dataflow-SKIM.
Для сопоставления токенов она использовала хеширование, что также не способствовало быстрой работе, но на тот момент и Арвинду было нечего ответить на такую критику. Да, авторы ALICE отмечали отсутствие такой проблемы у их машины как плюс, по сравнению с машиной Арвинда.
Арвинду зато было что ответить на другой недостаток манчестерской машины - она не имела первоклассной поддержки структур, в отличие от машины Арвинда. Массивы в ней кодировались как циклы - каждый элемент был токеном (пакетом) и метка номера итерации в таких пакетах кодировала индекс. Да, авторы ALICE отмечали наличие такой проблемы и у их машины как плюс, по сравнению с машиной Арвинда.
Сумеют ли ФП-машинисты продемонстрировать, что проблема не в идее, а в имплементации? К этому мы еще вернемся.

Голатак

На этом первая часть нашей истории окончена. Её итоги мы подведем с помощью популярного в описываемое время микро-бенчмарка - nfib. В 80-е годы имплементаторы ФЯ обычно используют его, и в ряде случаев только его, для того чтоб познакомить своих коллег с производительностью своих имплементаций. И если вдруг имплементатор еще не опубликовал результат сам - о нем спрашивают в рассылках. Лисперы используют в таком качестве варианты функции Такеучи. Но те из них, кто больше связаны с ФЯ Эдинбургской программы, например в INRIA (Le Lisp) или в Йеле (T), готовы запустить и nfib.
Вот код этой функции на LML:

let rec nfib n = if n < 2 then 1 else nfib(n-1) + nfib(n-2) + 1

Вместо n-го числа Фибоначчи функция возвращает количество вызовов функций, которые потребовались для вычисления n-го числа Фибоначчи. В начале 80-х n обычно равен 20, но для того, чтоб легче было сравнивать с результатами, полученными при других n, число вызовов обычно делят на число секунд, понадобившихся для того, чтоб все эти вызовы сделать. Это число известно или может быть вычислено для большинства основных имплементаций, обсуждающихся в этой части и для пары важных имплементаций из предыдущей. Понятно, что чем больше число - тем лучше. Это не единственное отступление от обычного формата бенчмарков в нашей истории. Обычно результаты относительные, но тут мы воспользуемся случаем и покажем насколько небольшими были результаты абсолютные. Среди этих имплементаций есть те, на которых написали реальные программы. Давайте сравним их с самой медленной, но все еще пригодной для использования имплементацией ФЯ, с которой, мы надеемся, знакомы большинство читателей. Байткод-интерпретатор ghci на современном компьютере выдает nfib порядка миллиона. Первым компиляторам ФЯ на первых пригодных для их работы машинах до этого далеко:

  nfib
Le Lisp [Chai84] 182425
ZetaLisp (3600) [Chai84] 145940
Algol68C ? [Fair85] [Gris82] 60000
C [Augu84] 47589
VAX ML [Augu84] 43782
T2 ? [TMail] [Chai84] 33940
ARC SASL (NORMA) [Sche86] 31271
LM Lisp (LM-2) [Chai83] 28065
LML [Augu84] 26375
Franz Lisp [Augu84] 19901
Ponder (SKIM II) ? [Stoy85] [Sche86] 15011
ML V6.2 Franz Lisp [Maun86] 15000
Ponder ? [Fair85] [Gris82] 14741
Poly [Maun86] 12300
SASL [Augu84] 706
LCF/ML [Augu84] 476
FEL (Rediflow) 0
FFP (Mago) 0
Id (TTDA) 0
HOPE (ALICE) 0
Le Lisp [Chai84]
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_ZetaLisp (3600)_ [Chai84]
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Algol68C ? [Fair85] [Gris82]
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
C [Augu84]
░░░░░░░░░░░░░░░░░░░░░░░░░░
VAX ML [Augu84]
░░░░░░░░░░░░░░░░░░░░░░░░
T2 ? [TMail] [Chai84]
░░░░░░░░░░░░░░░░░░░
_ARC SASL (NORMA)_ [Sche86]
░░░░░░░░░░░░░░░░░
_LM Lisp (LM-2)_ [Chai83]
░░░░░░░░░░░░░░░
LML [Augu84]
░░░░░░░░░░░░░░
Franz Lisp [Augu84]
░░░░░░░░░░░
_Ponder (SKIM II)_ ? [Stoy85] [Sche86]
░░░░░░░░
ML V6.2 Franz Lisp [Maun86]
░░░░░░░░
Ponder ? [Fair85] [Gris82]
░░░░░░░░
Poly [Maun86]
░░░░░░░

Большинство измерений сделаны на VAX-11/780, очевидное исключение составляют специальные машины, выделенные курсивом. ? - обозначает другую категорию исключений - эти результаты или получены на Motorola 68000 и пересчитаны так, чтоб получить оценку результата на VAX-11/780. Или, в случае SKIM II, получены из результата на NORMA и соотношения в скоростях редукции. Все эти оценки оптимистические, реальные результаты могли быть заметно хуже. Но оптимистичны не только оценки. Для ряда имплементаций известны несколько результатов и мы всегда выбирали самый лучший из них.
Параллельные ФП-машины в описываемое время еще не существуют, так что применяют функции 0 раз. Имплементаторы ALFL по какой-то причине пожелали, чтоб производительность его ранних имплементаций осталась неизвестной.
Какие выводы можно сделать? Первые компиляторы 80-х уже по крайней мере на порядок или даже на порядки быстрее имплементаций 80-х. И все равно производительности большинства еще есть куда расти. Тем, кто не хочет имплементировать ФЯ с нуля, определенно есть смысл использовать Le Lisp или T (жаль, что для кембриджского компилятора Algol 68 так и не сделали рантайм со сборщиком мусора). Единственная быстрая специальная машина - это Symbolics 3600 (правда, в этом сравнении не видно, что на бенчмарках, проверяющих производительность кода с ФВП она совсем не так хороша). Остальные машины вроде Lambda и Explorer ближе к LM-2, а прочие еще хуже. Ну и в INRIA, похоже, уже выгудхартили весь и без того невеликий смысл из этого микро-бенчмарка.
Эти результаты, правда, не очень хорошо предсказывают судьбу как самих этих имплементаций, так и семейств имплементаций, которым они положили начало.
Так, не смотря на очевидное превосходство результатов, которых добились разработчики компиляторов для обычных машин, в середине 80-х у специальных ФП-машин откроется второе дыхание и они останутся важным и престижным направлением имплементации ФЯ. Да, произойдет это потому, что в 70-е годы посещавший SRI Фурукава Коити нашел там ксерокопию ксерокопии ксерокопии распечатки FORTRAN-исходников Марсельского интерпретатора Пролога. Но это уже другая история.

Литература

[Arvi84]: Arvind, Brock, J. D. and Pingali, K. “FP1.5: Backus’ FP with higher order functions”, M.I.T. Lab. for Computer Science, (Unpublished 1984)
[Arvi86]: Arvind and David E. Culler. “Dataflow architectures.” (1986). https://dspace.mit.edu/handle/1721.1/149103
[Augu84]: Lennart Augustsson, A compiler for lazy ML. LFP ‘84: Proceedings of the 1984 ACM Symposium on LISP and functional programming August 1984 Pages 218–227 doi:10.1145/800055.802038
[Augu89]: L. Augustsson, T. Johnsson, The Chalmers Lazy-ML Compiler. In The Computer Journal, Volume 32, Issue 2, 1989, Pages 127–141 DOI:10.1093/comjnl/32.2.127
[Augu21]: The Haskell Interlude 02: Lennart Augustsson https://www.buzzsprout.com/1817535/9286902
[Augu23]: Lennart Augustsson - MicroHaskell https://www.youtube.com/watch?v=Zk5SJ79nOnA
[Back78]: John Backus. 1978. Can programming be liberated from the von Neumann style? a functional style and its algebra of programs. Commun. ACM 21, 8 (Aug. 1978), 613–641. doi:10.1145/359576.359579
[Back78b]: John Backus. 1978. The history of FORTRAN I, II, and III. SIGPLAN Not. 13, 8 (August 1978), 165–180. doi:10.1145/960118.808380
[Back20]: Turing Awardee Clips. Backus on functional programming. https://www.youtube.com/watch?v=OxuPZXXiwKk
[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, 186–195. doi:10.1145/12276.13330
[Boeh88]: Boehm, H.-J., & Weiser, M. (1988). Garbage collection in an uncooperative environment. Software: Practice and Experience, 18(9), 807–820. 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
[Carl84]: Mats Carlsson. LM-Prolog - The Language and its Implementation. Thesis for Licentiat of Philosophy in Computer Science at Uppsala University. UPMAIL Technical Report 30, October 1984.
[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, 113–122. 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
[Clar80]: T. J. W. Clarke, P. J. S. Gladstone, C. D. MacLean, and A. C. Norman. 1980. SKIM - The S, K, I reduction machine. In Proceedings of the 1980 ACM conference on LISP and functional programming (LFP ‘80). Association for Computing Machinery, New York, NY, USA, 128–135. doi:10.1145/800087.802798
[Cousot]: Cousot, Patrick. “The Contributions of Alan Mycroft to Abstract Interpretation.”
[Dama84]: Luís Damas. “Type assignment in programming languages.” (1984).
[Darl81]: John Darlington and Mike Reeve. 1981. ALICE a multi-processor reduction machine for the parallel evaluation CF applicative languages. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA ‘81). Association for Computing Machinery, New York, NY, USA, 65–76. doi:10.1145/800223.806764
[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, 23–30. 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
[Dennis]: Jack B. Dennis https://csg.csail.mit.edu/Users/dennis/index.htm
[Dijk78]: Dijkstra, Edsger Wybe. A review of the 1977 Turing Award Lecture by John Backus https://www.cs.utexas.edu/~EWD/transcriptions/EWD06xx/EWD692.html
[Dybv06]: R. Kent Dybvig. 2006. The development of Chez Scheme. In Proceedings of the eleventh ACM SIGPLAN international conference on Functional programming (ICFP ‘06). Association for Computing Machinery, New York, NY, USA, 1–12. doi:10.1145/1159803.1159805
[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
[Fitc09]: Fitch, J. (2009). CAMAL 40 Years on – Is Small Still Beautiful?. In: Carette, J., Dixon, L., Coen, C.S., Watt, S.M. (eds) Intelligent Computer Mathematics. CICM 2009. Lecture Notes in Computer Science(), vol 5625. Springer, Berlin, Heidelberg. doi:10.1007/978-3-642-02614-0_8
[Fode81]: Foderaro JK, Fateman RJ. Characterization of VAX Macsyma. InProceedings of the fourth ACM symposium on Symbolic and algebraic computation 1981 Aug 5 (pp. 14-19).
[Franz]: History of Franz Inc. https://franz.com/about/company.history.lhtml
[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, 88–97. 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).
[Gurd85]: J. R Gurd, C. C Kirkham, and I. Watson. 1985. The Manchester prototype dataflow computer. Commun. ACM 28, 1 (Jan. 1985), 34–52. doi:10.1145/2465.2468
[Hear79]: J. Marti, A. C. Hearn, M. L. Griss, and C. Griss. 1979. Standard LISP report. SIGPLAN Not. 14, 10 (October 1979), 48–68. doi:10.1145/953997.953999
[Hear2005]: Hearn, Anthony C.. “REDUCE: The First Forty Years.” Algorithmic Algebra and Logic (2005).
[Hoar22]: Krzysztof R. Apt and Tony Hoare (Eds.). 2022. Edsger Wybe Dijkstra: His Life,Work, and Legacy (1st. ed.). ACM Books, Vol. 45. Association for Computing Machinery, New York, NY, USA. https://doi.org/10.1145/3544585
[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, 122–132. doi:10.1145/800017.800523
[Huda84]: Hudak, Paul. ALFL Reference Manual and Programmer’s Guide. Yale University, Department of Computer Science, 1984.
[Huda89]: Paul Hudak. 1989. Conception, evolution, and application of functional programming languages. ACM Comput. Surv. 21, 3 (Sep. 1989), 359–411. DOI:10.1145/72551.72554
[Huda07]: Paul Hudak, John Hughes, Simon Peyton Jones, and Philip Wadler. 2007. A history of Haskell: being lazy with class. In Proceedings of the third ACM SIGPLAN conference on History of programming languages (HOPL III). Association for Computing Machinery, New York, NY, USA, 12–1–12–55. DOI:10.1145/1238844.1238856
[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, 1–10. 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 58–69 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
[John2004]: Thomas Johnsson. 2004. Efficient compilation of lazy evaluation. SIGPLAN Not. 39, 4 (April 2004), 125–138. doi:10.1145/989393.989409
[Joy86]: Joy, William N., Susan L. Graham, Charles B. Haley, Marshall Kirk McKusick, and Peter B. Kessler. “Berkeley Pascal User’s Manual Version 3.1− April 1986.
[Kell79]: KELLER, R. M., LINDSTROM, G., & PATIL, S. (1979). A loosely-coupled applicative multi-processing system. 1979 International Workshop on Managing Requirements Knowledge. doi:10.1109/mark.1979.8817294 
[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, 196–202. doi:10.1145/800087.802806
[Kell82]: Keller, RobertM. “FEL: An Experimental Applicative Language.” 情報処理学会研究報告プログラミング (PRO) 1982, no. 49 (1982-PRO-003) (1982): 73-79.
[Kell85]: Keller, Robert M. “Rediflow architecture prospectus.” In TR UUCS-85-105. Dept of Computer Science, University of Utah USA, 1985.
[Kost84]: Alexis Koster. 1984. Compiling prolog programs for parallel execution on a cellular machine. In Proceedings of the 1984 annual conference of the ACM on The fifth generation challenge (ACM ‘84). Association for Computing Machinery, New York, NY, USA, 167–178. doi:10.1145/800171.809619
[Krei2002]: Kreitz, Christoph. “The Nuprl Proof Development System, Version 5: Reference Manual and User’s Guide.” Department of Computer Science, Cornell University (2002)
[Kusz84]: Kuszmaul, Bradley C. “Type checking in vimval.” (1984).
[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
[LCS81]: Laboratory for Computer Science Progress Report 18, July 1980-June 1981 https://apps.dtic.mil/sti/tr/pdf/ADA127586.pdf
[LCS82]: Laboratory for Computer Science Progress Report 19, 1 July 1981-30 June 1982 https://apps.dtic.mil/sti/pdfs/ADA143459.pdf
[LCS83]: Laboratory for Computer Science Progress Report 20, July 1982 - June 1983 https://apps.dtic.mil/sti/tr/pdf/ADA145134.pdf
[LCS84]: Laboratory for Computer Science Progress Report 21, July 1983-June 1984 https://apps.dtic.mil/sti/pdfs/ADA158299.pdf
[LCS85]: Laboratory for Computer Science Progress Report 22, July 1984-June 1985 https://apps.dtic.mil/sti/tr/pdf/ADA227278.pdf
[LCS86]: Laboratory for Computer Science Progress Report 23, July 1985-June 1986 https://apps.dtic.mil/sti/tr/pdf/ADA227277.pdf
[LCS87]: Laboratory for Computer Science Progress Report 24, July 1986-June 1987 https://apps.dtic.mil/sti/tr/pdf/ADA207705.pdf
[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, 266–280. doi:10.1145/318593.318657
[Lisk93]: Liskov, B. (1993). A history of CLU. ACM SIGPLAN Notices, 28(3), 133–147. doi:10.1145/155360.155367
[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, 243–252. 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
[Mago81]: Gyula Magó. 1981. Copying operands versus copying results: A solution to the problem of large operands in FFP’S. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA ‘81). Association for Computing Machinery, New York, NY, USA, 93–98. doi:10.1145/800223.806767
[Mago82]: Gyula Magó. 1982. Data sharing in an FFP machine. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP ‘82). Association for Computing Machinery, New York, NY, USA, 201–207. doi:10.1145/800068.802151
[Mash2006]: John Mashey, A Historical Look at the VAX: The Economics of Microprocessors, 01-24-2006 http://www.bitsavers.org/pdf/dec/vax/VAX_Retrospective_20060124.pdf
[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), 52–76. 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
[Moor82]: Ian W. Moor. 1982. An applicative compiler for a parallel machine. SIGPLAN Not. 17, 6 (June 1982), 284–293. DOI:10.1145/872726.807002
[Mose08]: Moses, J. (2012). Macsyma: A personal history. Journal of Symbolic Computation, 47(2), 123–130. 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
[Nikh85]: R. S. Nikhil, Practical Polymorphism, CSG-Memo-249 https://csg.csail.mit.edu/pubs/memos/Memo-249/Memo-249.pdf
[Nikh86]: Rishiyur S. Nikhil, Keshav Pingali, Arvind. Id Nouveau. Computation Structures Group - Memo No. 265, July 23, 1986
[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
[Norm77]: Fitch, J. P., & Norman, A. C. (1977). Implementing LISP in a high-level language. Software: Practice and Experience, 7(6), 713–725. doi:10.1002/spe.4380070606 
[Norm79]: Norman, A.C., Moore, P.M.A. (1979). The initial design of a vector based algebra system. In: Ng, E.W. (eds) Symbolic and Algebraic Computation. EUROSAM 1979. Lecture Notes in Computer Science, vol 72. Springer, Berlin, Heidelberg. doi:10.1007/3-540-09519-5_78
[Norm82]: Norman, A. C. (1982). The development of a vector-based algebra system. Computer Algebra, 237–248. doi:10.1007/3-540-11607-9_28 
[Norm88]: A. C. Norman. 1988. Faster combinator reduction using stock hardware. In Proceedings of the 1988 ACM conference on LISP and functional programming (LFP ‘88). Association for Computing Machinery, New York, NY, USA, 235–243. doi:10.1145/62678.62716
[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, 114–122. doi:10.1145/800068.802142
[Rees2004]: Jonathan Rees, The T Project http://mumble.net/~jar/tproject/
[Rich84]: H. Richards. 1984. An overview of ARC SASL. SIGPLAN Not. 19, 10 (October 1984), 40–45. doi:10.1145/948290.948294
[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, 228–234. doi:10.1145/800055.802039
[Sait82]: N. Saito. ML System on Vax Unix. README for Saito’s Unix port of VAX ML, March 1982.
[SERC83]: DISTRIBUTED INTERACTIVE COMPUTING NOTE 781 https://www.chilton-computing.org.uk/acd/pdfs/dic/dic781.pdf
[Sche86]: Mark Scheevel. 1986. NORMA: a graph reduction processor. In Proceedings of the 1986 ACM conference on LISP and functional programming (LFP ‘86). Association for Computing Machinery, New York, NY, USA, 212–219. doi:10.1145/319838.319864
[Shiv2001]: Olin Shivers, History of T https://paulgraham.com/thist.html
[Symb80]: Symbolics LM-2 Price List, Oct 1980 https://bitsavers.org/pdf/symbolics/LM-2/LM-2_Price_List_Oct1980.pdf
[Symb86]: Symbolics Overview https://bitsavers.org/pdf/symbolics/history/Symbolics_Overview_1986.pdf
[Symb86b]: Symbolics Press Release, 1986 https://vtda.org/docs/computing/Symbolics/Symbolics_Press_Release1986.pdf
[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.
[SPJ87]: Peyton Jones, Simon L. The implementation of functional programming languages (prentice-hall international series in computer science). Prentice-Hall, Inc., 1987.
[SPJ18]: People of Programming Languages. An interview project in conjunction with POPL 2018. Interview with Simon Peyton-Jones http://www.cs.cmu.edu/~popl-interviews/peytonjones.html
[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, 98–107. 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, 233–330. doi:10.1145/234286.1057818
[Stoy83]: Stoye, William. “The SKIM microprogrammer’s guide.” (1983).
[Stoy85]: Stoye, William. “The implementation of functional languages using custom hardware.” (1985).
[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
[Trau86]: Traub, Kenneth R.. “A COMPILER FOR THE MIT TAGGED-TOKEN DATAFLOW ARCHITECTURE.” (1986).
[Trel87]: Treleaven, P.C., Refenes, A.N., Lees, K.J., McCabe, S.C. (1987). Computer architectures for artificial intelligence. In: Treleaven, P., Vanneschi, M. (eds) Future Parallel Computers. Lecture Notes in Computer Science, vol 272. Springer, Berlin, Heidelberg. doi:10.1007/3-540-18203-9_15
[Turn79]: Turner, D. A. (1979). A new implementation technique for applicative languages. Software: Practice and Experience, 9(1), 31–49. doi:10.1002/spe.4380090105 
[Turn82]: Turner, D.A. (1982). Recursion Equations as a Programming Language. In: Darlington, John, David Turner and Peter B. Henderson. “Functional Programming and its Applications: An Advanced Course.”
[Turn12]: Turner DA. Some history of functional programming languages. In International Symposium on Trends in Functional Programming 2012 Jun 12 (pp. 1-20). Springer, Berlin, Heidelberg.
[VAX82]: VAX Product Sales Guide EG-21731-18, Apr. 82
[Vegd84]: Vegdahl. (1984). A Survey of Proposed Architectures for the Execution of Functional Languages. IEEE Transactions on Computers, C-33(12), 1050–1071. doi:10.1109/tc.1984.1676387 
[Will88]: J. H. Williams and E. L. Wimmers. 1988. Sacrificing simplicity for convenience: Where do you draw the line? In Proceedings of the 15th ACM SIGPLAN-SIGACT symposium on Principles of programming languages (POPL ‘88). Association for Computing Machinery, New York, NY, USA, 169–179. doi:10.1145/73560.73575
[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