From 62e2eb051a49c6dd3cd63be73bb4c61a625a4947 Mon Sep 17 00:00:00 2001 From: TheGeneralist <180094941+thegeneralist01@users.noreply.github.com> Date: Thu, 8 Jan 2026 22:45:53 +0100 Subject: [PATCH] start 1-to-1 translation --- AGENTS.md | 28 + index.md | 100 +- prehist.md | 665 +++-- ru/compilers.md | 2218 +++++++++++++++++ ru/hopes.md | 1677 +++++++++++++ ru/index.md | 99 + ru/prehist.md | 6194 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 10598 insertions(+), 383 deletions(-) create mode 100644 AGENTS.md create mode 100644 ru/compilers.md create mode 100644 ru/hopes.md create mode 100644 ru/index.md create mode 100644 ru/prehist.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..05d7aa7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Documentation lives in the repository root as Markdown files (`index.md`, `prehist.md`, `compilers.md`, `hopes.md`). +- Original Russian sources are stored in `ru/` with the same filenames. +- Other files (e.g., `t(cdr(J),K,diff(K))`) should be treated as historical artifacts; do not rename or remove unless explicitly requested. + +## Build, Test, and Development Commands +- No build or test tooling is defined in this repo. +- Use standard Markdown tooling if you need previews (e.g., `pandoc` or a local Markdown viewer), but do not introduce new build scripts without discussion. + +## Coding Style & Naming Conventions +- Keep content in Markdown with clear headings and stable anchor links. +- Preserve filenames and the document order when translating or editing. +- Maintain a consistent tone with existing narrative prose; avoid adding extra section headers unless necessary. +- Keep line wrapping consistent with the file you edit (current files are mostly long lines). + +## Testing Guidelines +- No automated tests are present. +- Validate changes by checking internal links and anchors in the modified Markdown files. + +## Commit & Pull Request Guidelines +- Commit messages in history are short and lowercase (e.g., `dec. upd.`, `citations`). Follow that pattern. +- PRs should describe which documents changed and why, and call out any renamed headings or anchor changes. + +## Translation Notes +- English translations live in the repo root; Russian originals stay under `ru/`. +- When translating headings, update the TOC entries to match the new anchor text. diff --git a/index.md b/index.md index bcacd33..7552e65 100644 --- a/index.md +++ b/index.md @@ -1,99 +1,99 @@ -История применения и оценки функционального программирования +History of the Use and Evaluation of Functional Programming ======= -[Часть 0: Что было, когда функционального программирования не было.](prehist.md) -[Часть 1: Каталог компиляторов.](compilers.md) -[Часть 2: Великое уравнивание.](hopes.md) +[Part 0: What existed when functional programming did not.](prehist.md) +[Part 1: Compiler catalog.](compilers.md) +[Part 2: The great equalization.](hopes.md) --------------------------- -[текст на github](https://github.com/klapaucius/fphistoryru) -[обсудить на github](https://github.com/klapaucius/fphistoryru/discussions) -[обсудить в telegram](https://t.me/hateHaskellers) -[поддержать проект](https://boosty.to/fphistory/donate) +[text on GitHub](https://github.com/klapaucius/fphistoryru) +[discuss on GitHub](https://github.com/klapaucius/fphistoryru/discussions) +[discuss on Telegram](https://t.me/hateHaskellers) +[support the project](https://boosty.to/fphistory/donate) --------------------------- -[Обновление 2026-01-01](hopes.md#день-зависимости) +[Update 2026-01-01](hopes.md#день-зависимости) -[Обновление 2025-12-13](hopes.md#негативное-пространство) +[Update 2025-12-13](hopes.md#негативное-пространство) -[Обновление 2025-11-16](hopes.md#граф-0) +[Update 2025-11-16](hopes.md#граф-0) -[Обновление 2025-10-30](hopes.md#новая-надежда) +[Update 2025-10-30](hopes.md#новая-надежда) -[Обновление 2025-09-06](hopes.md#big-in-japan) +[Update 2025-09-06](hopes.md#big-in-japan) -[Обновление 2025-08-09](hopes.md#le-ml) +[Update 2025-08-09](hopes.md#le-ml) -[Обновление 2025-06-30](hopes.md#fasadrenovering) +[Update 2025-06-30](hopes.md#fasadrenovering) -[Обновление 2025-05-31](hopes.md#любой-принц-в-янтаре) +[Update 2025-05-31](hopes.md#любой-принц-в-янтаре) -[Обновление 2025-04-30](hopes.md#карделли-против-карделли) +[Update 2025-04-30](hopes.md#карделли-против-карделли) -[Обновление 2025-03-31](hopes.md#standard-ml-836) +[Update 2025-03-31](hopes.md#standard-ml-836) -[Обновление 2025-02-28](hopes.md#лука-карделли-и-поздняя-эволюция-npl) +[Update 2025-02-28](hopes.md#лука-карделли-и-поздняя-эволюция-npl) -[Обновление 2025-01-31](hopes.md#часть-2-великое-уравнивание) +[Update 2025-01-31](hopes.md#часть-2-великое-уравнивание) -[Обновление 2025-01-08](compilers.md#сон-черного-короля) +[Update 2025-01-08](compilers.md#сон-черного-короля) -[Обновление 2024-12-02](compilers.md#скобочный-потолок) +[Update 2024-12-02](compilers.md#скобочный-потолок) -[Обновление 2024-11-01](compilers.md#норман-и-norma) +[Update 2024-11-01](compilers.md#норман-и-norma) -[Обновление 2024-09-30](compilers.md#ржавый-пояс) +[Update 2024-09-30](compilers.md#ржавый-пояс) -[Обновление 2024-08-31](compilers.md#true--false) +[Update 2024-08-31](compilers.md#true--false) -[Обновление 2024-08-08](compilers.md#лисп-который-покончит-с-лиспами) +[Update 2024-08-08](compilers.md#лисп-который-покончит-с-лиспами) -[Обновление 2024-06-30](compilers.md#живые-ископаемые) +[Update 2024-06-30](compilers.md#живые-ископаемые) -[Обновление 2024-05-29](compilers.md#чайники-рассела) +[Update 2024-05-29](compilers.md#чайники-рассела) -[Обновление 2024-04-29](compilers.md#еще-ортогональнее) +[Update 2024-04-29](compilers.md#еще-ортогональнее) -[Обновление 2024-03-31](compilers.md#редукция-графов-и-как-её-избежать) +[Update 2024-03-31](compilers.md#редукция-графов-и-как-её-избежать) -[Обновление 2024-02-29](compilers.md#часть-1-каталог-компиляторов) +[Update 2024-02-29](compilers.md#часть-1-каталог-компиляторов) -[Обновление 2024-01-11](prehist.md#сколько-тысяч-слов--все-впустую) +[Update 2024-01-11](prehist.md#сколько-тысяч-слов--все-впустую) -[Обновление 2024-01-03](prehist.md#декабрьский-апдейт) +[Update 2024-01-03](prehist.md#декабрьский-апдейт) -[Обновление 2023-11-30](prehist.md#у-меня-нет-памяти-а-я-должен-аллоцировать) +[Update 2023-11-30](prehist.md#у-меня-нет-памяти-а-я-должен-аллоцировать) -[Обновление 2023-10-31](prehist.md#lambda-the-ultimate-misunderstanding) +[Update 2023-10-31](prehist.md#lambda-the-ultimate-misunderstanding) -[Дополнения 2023-09-12](https://gist.github.com/klapaucius/ab5a543401248b6f517b4755fd4bb017) +[Addendum 2023-09-12](https://gist.github.com/klapaucius/ab5a543401248b6f517b4755fd4bb017) -[Обновление 2023-08-26](prehist.md#надежда-умирает-последней) +[Update 2023-08-26](prehist.md#надежда-умирает-последней) -[Обновление 2023-07-19](prehist.md#ни-тот-ни-этот-имена-запретны) +[Update 2023-07-19](prehist.md#ни-тот-ни-этот-имена-запретны) -[Обновление 2023-06-30](prehist.md#две-диссертации) +[Update 2023-06-30](prehist.md#две-диссертации) -[Обновление 2023-05-31](prehist.md#спецификация---это-имплементация) +[Update 2023-05-31](prehist.md#спецификация---это-имплементация) -[Обновление 2023-04-26](prehist.md#s-0) +[Update 2023-04-26](prehist.md#s-0) -[Обновление 2023-03-31](prehist.md#уравнения-и-неравенства) +[Update 2023-03-31](prehist.md#уравнения-и-неравенства) -[Обновление 2023-02-28](prehist.md#возвращение-резолюционизма) +[Update 2023-02-28](prehist.md#возвращение-резолюционизма) -[Обновление 2023-01-31](prehist.md#типо-теоретическая-альтернатива) +[Update 2023-01-31](prehist.md#типо-теоретическая-альтернатива) -[Обновление 2022-12-24](prehist.md#следующие-700-изобретений-продолжений) +[Update 2022-12-24](prehist.md#следующие-700-изобретений-продолжений) -[Обновление 2022-12-12](prehist.md#%D0%B7%D0%B0-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%B5%D1%89%D0%B5-%D0%BD%D0%B8%D0%BA%D0%BE%D0%B3%D0%BE-%D0%BD%D0%B5-%D1%83%D0%B2%D0%BE%D0%BB%D1%8C%D0%BD%D1%8F%D0%BB%D0%B8) +[Update 2022-12-12](prehist.md#%D0%B7%D0%B0-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%B5%D1%89%D0%B5-%D0%BD%D0%B8%D0%BA%D0%BE%D0%B3%D0%BE-%D0%BD%D0%B5-%D1%83%D0%B2%D0%BE%D0%BB%D1%8C%D0%BD%D1%8F%D0%BB%D0%B8) -[Обновление 2022-11-14](prehist.md#%D1%8D%D0%B4%D0%B8%D0%BD%D0%B1%D1%83%D1%80%D0%B3%D1%81%D0%BA%D0%B0%D1%8F-%D0%B8%D1%81%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%B0%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0) +[Update 2022-11-14](prehist.md#%D1%8D%D0%B4%D0%B8%D0%BD%D0%B1%D1%83%D1%80%D0%B3%D1%81%D0%BA%D0%B0%D1%8F-%D0%B8%D1%81%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%B0%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0) -[Обновление 2022-10-10](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5-%D1%81%D0%B0%D0%BC%D1%8B%D1%85-%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D1%85-%D0%B8%D0%BC%D0%BF%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8%D0%B9) +[Update 2022-10-10](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5-%D1%81%D0%B0%D0%BC%D1%8B%D1%85-%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D1%85-%D0%B8%D0%BC%D0%BF%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8%D0%B9) -[Обновление 2022-09-29](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5%D0%BF%D0%BE%D0%BF%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D1%85-%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2) +[Update 2022-09-29](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5%D0%BF%D0%BE%D0%BF%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D1%85-%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2) -[Обновление 2022-08-26](#%D0%BF%D1%81%D0%B5%D0%B2%D0%B4%D0%BEcpl) +[Update 2022-08-26](#%D0%BF%D1%81%D0%B5%D0%B2%D0%B4%D0%BEcpl) diff --git a/prehist.md b/prehist.md index 2c03066..c945083 100644 --- a/prehist.md +++ b/prehist.md @@ -1,36 +1,36 @@ -История применения и оценки функционального программирования +History of the Use and Evaluation of Functional Programming ======= -- [История применения и оценки функционального программирования](#история-применения-и-оценки-функционального-программирования) -- [Часть 0: что было, когда функционального программирования не было.](#часть-0-что-было-когда-функционального-программирования-не-было) - - [Когда _чего_ не было?](#когда-чего-не-было) - - [Для чего нужна эта работа и в чем ее отличия от других](#для-чего-нужна-эта-работа-и-в-чем-ее-отличия-от-других) - - [Лямбда-криптозой](#лямбда-криптозой) - - [Лямбда-фанерозой](#лямбда-фанерозой) -- [Ветвь обоих Кембриджей](#ветвь-обоих-кембриджей) - - [В Кембридже](#в-кембридже) +- [History of the Use and Evaluation of Functional Programming](#history-of-the-use-and-evaluation-of-functional-programming) +- [Part 0: What existed when functional programming did not.](#part-0-what-existed-when-functional-programming-did-not) + - [When was _what_ not there?](#when-was-what-not-there) +- [Why this work is needed and how it differs from others](#why-this-work-is-needed-and-how-it-differs-from-others) + - [Lambda Cryptozoic](#lambda-cryptozoic) + - [Lambda Phanerozoic](#lambda-phanerozoic) +- [The Branch of Both Cambridges](#the-branch-of-both-cambridges) + - [In Cambridge](#in-cambridge) - [CPL](#cpl) - - [псевдоCPL](#псевдоcpl) - - [Следующие 700 непопулярных языков](#следующие-700-непопулярных-языков) - - [Следующие 700 исписанных листов](#следующие-700-исписанных-листов) - - [Следующие 700 вариаций семи фич](#следующие-700-вариаций-семи-фич) - - [больше не фичи](#больше-не-фичи) - - [отсутствие лямбд](#отсутствие-лямбд) - - [выражение `where`](#выражение-where) - - [тернарный оператор ветвления](#тернарный-оператор-ветвления) - - [пока еще фичи](#пока-еще-фичи) - - [конструкция `let`](#конструкция-let) - - [аннотация рекурсии](#аннотация-рекурсии) - - [отсутствие аннотаций рекурсии](#отсутствие-аннотаций-рекурсии) - - [отсутствие аннотаций типов](#отсутствие-аннотаций-типов) - - [обозначение блоков отступами](#обозначение-блоков-отступами) - - ["одновременные"/"параллельные" декларации](#одновременныепараллельные-декларации) - - [обсуждавшиеся фичи](#обсуждавшиеся-фичи) - - [параметрический полиморфизм](#параметрический-полиморфизм) - - [Изобрел ли Ландин АлгТД?](#изобрел-ли-ландин-алгтд) - - [Следующие 700 не самых быстрых имплементаций](#следующие-700-не-самых-быстрых-имплементаций) - - [В другом Кембридже](#в-другом-кембридже) - - [Мартин Ричардс](#мартин-ричардс) + - [pseudoCPL](#pseudocpl) + - [The Next 700 Unpopular Languages](#the-next-700-unpopular-languages) + - [The Next 700 Written Pages](#the-next-700-written-pages) + - [The Next 700 Variations of Seven Features](#the-next-700-variations-of-seven-features) + - [No Longer Features](#no-longer-features) + - [Absence of Lambdas](#absence-of-lambdas) + - [`where` expression](#where-expression) + - [ternary branching operator](#ternary-branching-operator) + - [Still Features](#still-features) + - [`let` construct](#let-construct) + - [recursion annotation](#recursion-annotation) + - [absence of recursion annotations](#absence-of-recursion-annotations) + - [absence of type annotations](#absence-of-type-annotations) + - [block delimiting by indentation](#block-delimiting-by-indentation) + - ["simultaneous"/"parallel" declarations](#simultaneousparallel-declarations) + - [Discussed Features](#discussed-features) + - [Parametric Polymorphism](#parametric-polymorphism) + - [Did Landin Invent ADTs?](#did-landin-invent-adts) + - [The Next 700 Not-So-Fast Implementations](#the-next-700-not-so-fast-implementations) + - [In the Other Cambridge](#in-the-other-cambridge) + - [Martin Richards](#martin-richards) - [BCPL](#bcpl) - [PAL](#pal) - [McG](#mcg) @@ -179,155 +179,154 @@ - [Литература](#литература) -Часть 0: что было, когда функционального программирования не было. +Part 0: What existed when functional programming did not. =================================== -Когда _чего_ не было? +When was _what_ not there? ------------------- -> Функциональное программирование - это инженерная ветвь конструктивной математики. - Олег Нижников. +> Functional programming is an engineering branch of constructive mathematics. + Oleg Nizhnikov. -Мы не ставим перед собой цели дать определение функциональному программированию. Мы, однако же, вынуждены определить какие-то практические рамки для нашего исследования. Выбрать историю _чего именно_ мы пишем, и выбрать что-то обозримое. -И многие определения ФП просто непрактичны с этой точки зрения. История языков с первоклассными функциями в наши дни фактически равна истории языков программирования, ведь даже редкие языки в которых их до сих пор нет - вроде C++ или Rust - обычно связаны разными историческими отношениями с языками где первоклассные функции есть. -Не годится также и не такая необъятная, но все еще неподъемная группа языков "по каким-то историческим причинам считающиеся функциональными", о которых обычно и пишут те, кто пишут историю ФП. Главная причина того, почему это определения ФЯ для нас не годится - в этой группе языков есть Лисп, и мы не хотим писать историю Лиспа. В основном потому, что история Лиспа слишком громадная тема, чтоб рядом с ней было вообще можно заметить историю прочих языков этой "традиционной" группы. Мало того, сейчас есть проблема, которой во времена выбора "исторически сложившегося перечня ФЯ" просто не было: сегодня Лисп - типичный представитель огромной категории языков. Так же как и в какую-нибудь Java, первоклассные функции были добавлены в Лисп после того, как он уже долгое время существовал и это не такая уж значительная и важная деталь истории языка. И написание истории функционализации Лиспа потребовало бы сравнения с другими языками, прошедшими через тот же процесс. А именно почти со всеми существующими сейчас языками программирования. -Мы бы хотели поставить перед собой реальную цель и ограничиться функциональными языками в более узком смысле, историю которых мы рассмотрим немного более глубоко, чем можно позволить себе рассмотреть историю всех языков. -Чтоб сузить группу языков мы добавим к первой фиче +We do not aim to define functional programming. However, we are forced to set some practical boundaries for our study: to choose the history of _what exactly_ we are writing, and to choose something manageable. +And many definitions of FP are simply impractical from this point of view. The history of languages with first-class functions today is effectively the history of programming languages, because even the rare languages that still do not have them, such as C++ or Rust, are usually tied by various historical relationships to languages that do. +The group of languages "considered functional for some historical reasons" is also unsuitable, even though it is not so vast, because it is still unmanageable. These are the languages that people usually write about when they write the history of FP. The main reason this definition of functional languages does not work for us is that this group includes Lisp, and we do not want to write the history of Lisp. Mainly because the history of Lisp is a topic so huge that next to it you would hardly notice the history of other languages in this "traditional" group at all. Moreover, there is now a problem that did not exist when the "historically formed list of functional languages" was chosen: today Lisp is a typical representative of a huge category of languages. Just like in, say, Java, first-class functions were added to Lisp after it had already existed for a long time, and this is not such a significant or important detail in the language's history. Writing the history of Lisp's functionalization would require comparisons with other languages that went through the same process, namely almost all programming languages that exist today. +We would like to set ourselves a realistic goal and limit ourselves to functional languages in a narrower sense, whose history we will consider a bit more deeply than one can afford when looking at the history of all languages. +To narrow the group of languages, we will add some features to the first one: -* Первоклассные функции +* First-class functions -еще какие-нибудь. +and a few more. -* Параметрический полиморфизм и вывод/реконструкция типов +* Parametric polymorphism and type inference/reconstruction -позволяют существенно сократить предмет исследования, но все еще недостаточно. Так что мы добавим +reduce the subject significantly, but still not enough. So we will add -* Алгебраические типы и паттерн-матчинг +* Algebraic data types and pattern matching -Вот эта последняя фича, наконец-то, дает нам семейство языков подходящего размера. -К сожалению, объединение фич выглядит довольно произвольным. Кроме того, если когда-нибудь АлгТД и ПМ станут так же распространены как первоклассные функции (мы бы не стали на это особо рассчитывать), проблема истории всех языков вернется, чтоб преследовать наших последователей, если они у нас, конечно, будут. +This last feature finally gives us a family of languages of a suitable size. +Unfortunately, this bundle of features looks rather arbitrary. Moreover, if algebraic data types and pattern matching ever become as widespread as first-class functions (we would not count on it), the problem of the history of all languages will return to haunt our successors, if we have any. -Языки с этим набором фич, бывает, называются "Эмелеподобными языками". -Только вот люди не достаточно часто соглашаются какие языки ML-подобны. Не все ML-и так уж подобны другим ML-ям, а что уж говорить про не ML-и. +Languages with this set of features are sometimes called "ML-like languages." +Only people do not often agree on which languages are ML-like. Not all MLs are that similar to other MLs, let alone non-MLs. -Попробуем найти первый язык с такими фичами и определить семейство через него. -Хорошие новости: существует один такой язык - Hope, никто, насколько нам известно, не изобретал еще раз такое же сочетание независимо. Он получился из "слияния" языков, в каждом из которых был неполный набор интересных нам фич. +Let us try to find the first language with these features and define the family through it. +The good news: there is one such language, Hope; as far as we know, no one independently invented the same combination again. It resulted from a "merging" of languages, each of which had an incomplete set of the features we care about. -Плохие новости: не все языки, историю которых мы хотели бы писать, происходят от этого языка, так что наши надежды на "Hope-образные языки" не оправдались так же, и по тем же причинам, что и на "ML-образные". "Слияние", вероятно, не самая подходящая перспектива в этом случае. Отношения между языками не хотят принимать вид удобных деревьев или графов. +The bad news: not all languages whose history we would like to write derive from that language, so our hopes for "Hope-like languages" did not come true for the same reasons that our hopes for "ML-like languages" did. "Merging" is probably not the best perspective here. Relationships between languages do not want to take the shape of convenient trees or graphs. -Правильнее будет говорить про набор языков, каждый из которых, развиваясь, позаимствовал недостающие до нашего определяющего наборы фичи из остальных, а Hope - просто первый результат этого процесса, который мы будем далее называть "хопизацией". ML как единый язык этот процесс не проходил, его прошли по отдельности по крайней мере три языка с "ML" в названии. +It is more accurate to speak about a set of languages, each of which, in its development, borrowed the missing pieces of our defining feature set from the others, and Hope is simply the first result of this process, which we will later call "hopization." ML as a single language did not go through this process; at least three languages with "ML" in their name did so separately. -В те времена такое взаимодействие языков, проектов и их авторов было бы затруднено, если бы участники находились далеко друг от друга. Они и не находились далеко. Исследовательская программа, историю которой мы будем писать, начиналась в Эдинбурге и соседнем с ним городе Сент-Эндрюсе, так что мы, наконец-то, нашли рамки которые нам подходят, пусть и географические, что, конечно, не лучший вариант, но что есть - то есть. +At that time, such interaction of languages, projects, and their authors would have been difficult if the participants had been far apart. They were not. The research program whose history we will write began in Edinburgh and the neighboring town of St Andrews, so we finally found boundaries that suit us, even if they are geographic, which is not the best option, but it is what it is. -Почти все языки, происходящие от первоначальной группы языков Эдинбургской программы, сохранили обсуждаемые свойства, а если мы сделаем еще шаг назад и рассмотрим то, от чего они произошли - назовем эту, предыдущую программу "ветвью обоих Кембриджей", то такой однородности всех происходящих от нее языков не будет. +Almost all languages derived from the initial group of the Edinburgh program kept the properties under discussion, but if we step back once more and look at what they came from - let us call that earlier program the "branch of both Cambridges" - then such uniformity among all of its descendants will not hold. -Мы пишем историю "Эдинбургской исследовательской программы". Но это звучит длинновато, так что далее мы обычно будем называть группу языков, которую мы выбрали для написания истории функционального программирования, просто "функциональные языки", как делали и наши великие предшественники. В этом смысле наша работа ничем не отличается от других работ по истории функционального программирования. -А в чем отличается? +We are writing the history of the "Edinburgh research program." But that sounds a bit long, so from now on we will usually call the group of languages we chose to write the history of functional programming simply "functional languages," as our great predecessors did. In this sense our work is no different from other works on the history of functional programming. +And how does it differ? -Для чего нужна эта работа и в чем ее отличия от других +Why this work is needed and how it differs from others ----------- -> История развития теории типов, которая в последствии привела к системе типов Standard ML, насчитывает более ста лет. -> Д. МакКвин, Р. Харпер, Дж. Реппи, История Standard ML [MacQ20] +> The history of the development of type theory, which later led to the Standard ML type system, spans more than a hundred years. +> D. MacQueen, R. Harper, J. Reppy, History of Standard ML [MacQ20] -Не то чтобы литературы по истории ФП было мало. Уже существуют как обзорные материалы по ФП вообще [Huda89] [Turn12], так и истории отдельных языков или их семейств [Hud07] [MacQ20], биографии исследователей [Camp85] [MacQ14]. Зачем нужна еще одна? +It is not that there is little literature on the history of FP. There are already survey materials on FP in general [Huda89] [Turn12], histories of individual languages or families [Hud07] [MacQ20], and biographies of researchers [Camp85] [MacQ14]. Why do we need another one? -### Лямбда-криптозой +### Lambda Cryptozoic -Когда, в очередной раз, не хватает десятков гигабайт памяти для компиляции кода на Хаскеле, естественным образом возникает вопрос: в каком же _смысле_ функциональное программирование существовало в каком-нибудь 1973-ем году? К сожалению, материалы по истории ФП обычно не уделяют этому особого внимания. Для историй функционального программирования в них часто слишком мало истории программирования. -Мы не ставим перед собой цели дать определение "программированию", но в этой работе предполагаем, что это процесс написания программ. И наши великие предшественники, в своих работах по истории функционального программирования, не особенно любят писать какого размера программы получались в результате этого процесса. -Более того, часто историк программирования уходит в такие глубины прошлого, про существование программирования в которых можно говорить только с большой натяжкой. Например, предыстория Standard ML начинается аж с 1874 года [MacQ20]. -Понятно, что в 1874 функционального программирования не было, но было ли оно, например, в 1974-ом? Какие программы к этому году были написаны на функциональных языках? Какие имплементации были доступны и для кого? До какого года ФЯ могло существовать только так же, как могло и в 1874-ом году: как нотация в книгах, тетрадях, на досках и так далее? -Например, часто утверждается, что ML появился в 1973 году, но что именно произошло в этом году? Упрощенно говоря, в этом году у Робина Милнера появилось желание писать интерактивный решатель теорем не на Лиспе. И в нашей работе мы покажем в каком году Милнер написал часть интерактивного решателя теорем на ML. Какая это была часть, сколько в ней было строк кода. Кто и через сколько лет написал интерактивный решатель теорем полностью на ML. И кто и когда написал интерактивный решатель теорем полностью на ML, имплементация которого, в свою очередь, и сама написана на ML. И сколько строк кода было в этой имплементации. -Между мечтой и возможностью большая разница и существенный временной интервал и мы считаем, что такая перспектива может быть полезной, если кто-то не согласен, что история чего-то начинается с мечты, и на ней же и заканчивается, ведь имплементация идеи тривиальна и не интересна. -Нельзя сказать, что вопрос применимости имплементаций ФЯ для программирования вовсе не поднимается, но он не особенно интересен нашим великим предшественникам. В некоторых обзорах перечисляют имплементации, которые авторы (обзора, но нередко автор обзора является и автором одной из этих имплементаций) считают "неигрушечными", "эффективными" [Huda89], "быстрыми" [SPJ87], но критерии не сформулированы четко, и вполне возможно, что у вас бы сложилось другое впечатление, если б вы узнали об этих имплементациях больше. У нас определенно сложилось другое впечатление. В таких списках через запятую перечислены компиляторы, которые компилировали себя и другие проекты в десятки тысяч строк вместе с компиляторами, которые этого не делали. -Поэтому мы постараемся установить, какого размера программы писали с помощью имплементаций ФЯ, какая была производительность у этих программ. +When, once again, tens of gigabytes of memory are not enough to compile Haskell code, a natural question arises: in what _sense_ did functional programming exist in, say, 1973? Unfortunately, materials on the history of FP usually pay little attention to this. In histories of functional programming there is often too little history of programming. +We do not aim to define "programming," but in this work we assume it is the process of writing programs. And our great predecessors, in their works on the history of functional programming, do not especially like to write about what size programs resulted from this process. +Moreover, the historian of programming often goes so deep into the past that one can speak of the existence of programming there only with great stretch. For example, the prehistory of Standard ML begins as far back as 1874 [MacQ20]. +Clearly, there was no functional programming in 1874, but was there, for example, in 1974? What programs had been written in functional languages by that year? What implementations were available and to whom? Until what year could functional languages exist only in the same way they could in 1874: as notation in books, notebooks, on blackboards, and so on? +For example, it is often claimed that ML appeared in 1973, but what exactly happened that year? Roughly speaking, in that year Robin Milner wanted to write an interactive theorem prover not in Lisp. And in our work we will show in which year Milner wrote a part of the interactive theorem prover in ML. What part it was, how many lines of code it contained. Who and how many years later wrote an interactive theorem prover entirely in ML. And who and when wrote an interactive theorem prover entirely in ML whose implementation, in turn, is itself written in ML. And how many lines of code were in that implementation. +Between a dream and a possibility there is a big difference and a significant time interval, and we believe such a perspective can be useful if someone does not agree that the history of something begins with a dream and ends with it, since implementing the idea is trivial and uninteresting. +It cannot be said that the question of the applicability of functional-language implementations for programming is not raised at all, but it is not very interesting to our great predecessors. In some surveys they list implementations that the authors (of the survey, and often the author of the survey is also an author of one of these implementations) consider "non-toy," "efficient" [Huda89], "fast" [SPJ87], but the criteria are not clearly formulated, and it is quite possible you would have a different impression if you learned more about these implementations. We certainly did. Such lists lump together compilers that compiled themselves and other projects of tens of thousands of lines with compilers that did not. +Therefore we will try to determine what size programs were written with functional-language implementations and what performance those programs had. -Если что-то может помешать историку ФП все больше и больше углубляться в прошлое - то это название. Некоторые авторы так любят какое-то название, что продолжают использовать его для все новых и все менее схожих вещей. Синтаксисы многих функциональных языков в 80-х более похожи друг на друга, чем на свои версии с теми же названиями из 70-х. К счастью, первый раз назвали что-то ML очень давно, и нет препятствий для того чтоб начать историю ML с начала, или даже задолго до его начала. -К сожалению, многие любят называть одно и то же по-разному. Да, первый компилятор Хаскеля был получен всего за пару месяцев из компилятора Нехаскеля, что явно указывает на то, что уже была проделана большая работа, которую вполне обоснованно можно считать работой по созданию Хаскеля. Три из пяти первых компиляторов Хаскеля разрабатывались долгие годы до того, как появилась сама идея спроектировать этот язык. Увы, Нехаскель не назывался "Хаскель", так что ничего не поделаешь - в истории Хаскеля [Hud07] уделить этому больше нескольких строк нельзя. Да, авторы Хаскеля выбрали другой Нехаскель как основу для первого Хаскель-репорта. И этот Нехаскель похож на Хаскель 1.0 больше чем ML в 82-ом на ML в 84-ом. Извините, название было не "Хаскель" - наши великие предшественники не могут писать историю этого. +If anything can prevent the historian of FP from going deeper and deeper into the past, it is the name. Some authors love a name so much that they keep using it for newer and less similar things. The syntaxes of many functional languages in the 1980s are more similar to each other than to their own versions with the same names from the 1970s. Luckily, the first time something was called ML was very long ago, and there are no obstacles to starting the history of ML from the beginning, or even long before its beginning. +Unfortunately, many people like to call the same thing by different names. Yes, the first Haskell compiler was obtained in just a couple of months from a Nehaskell compiler, which clearly indicates that a great deal of work had already been done, which can reasonably be considered work on creating Haskell. Three of the first five Haskell compilers were developed for many years before the very idea of designing this language appeared. Alas, Nehaskell was not called "Haskell," so there is nothing to be done - in the history of Haskell [Hud07] it is impossible to devote more than a few lines to this. Yes, the authors of Haskell chose another Nehaskell as the basis for the first Haskell Report. And that Nehaskell resembles Haskell 1.0 more than ML in 1982 resembles ML in 1984. Sorry, the name was not "Haskell" - our great predecessors cannot write its history. -Все это, в основном, последствия того, что наши великие предшественники писали в первую очередь истории идей. И они делали это не потому, что это легко. Одна из причин того, что мы не собираемся писать историю идей - проследить историю идей очень сложно. Так что мы пишем историю имплементаций в первую очередь, и только потом историю идей. -Для каждой идеи можно найти ту, от которой она произошла, и каждая тащит все дальше в глубины веков, там идеи до самого низа. Так историк функционального программирования и оказывается в 1874-ом году. История имплементаций легко решает эту проблему (решает даже слишком хорошо, но об этом позднее). -Идеи оставляют меньше следов, и следов менее удобных для историка. Имплементация оставляет после себя разные версии кода, отметки в нем, репорты, анонсы и описания релизов, из которых понятно что заработало и когда. -Идея оставляет после себя статью, которая может быть опубликована через годы и "приведена в порядок" каким-то стирающим историю способом. Что это значит? Обзор родственных работ в статьях документирует отношение идей, но не их историю. Влияние одних идей на другие может декларироваться, но на самом деле "повлиявшая" идея может быть обнаружена только после того, как идея на которую она "повлияла" была переоткрыта самостоятельно. -Бывает, что автор прямо говорит об этом. Например Ксавье Леруа пишет что переизобрел некоторые идеи из машины Кривина не зная о ней и добавил ссылки только после того, как другие люди обратили его внимание на сходство. Часть идей по имплементации Chez Scheme ее автор открыл самостоятельно, а потом только обнаружил их в VAX ML, а часть действительно позаимствовал. Но не все считают нужным сообщить об этом. Хуже того, такие свидетельства могут противоречить другим. Например, мнение авторов языка о том, на какие языки они повлияли может не совпадать с мнением авторов этих языков о том, какие языки повлияли на них. -Тут нет ничего удивительного, идеи о том, как делать языки программирования, нередко возникают независимо. Даже алгоритм Хиндли-Милнера - нетривиальная и точно специфицируемая идея - был независимо переизобретен, но никто не написал независимо два одинаковых компилятора. -Дополнительная сложность тут в том, что добавлять ссылки на статьи сильно легче, чем использовать код компиляторов, так что в графе получается слишком много ребер, если мы пишем историю идей. -Для объединения имплементаций в семейства мы будем использовать более редкие и надежно устанавливаемые отношения: общий код, общие авторы, использование одной для бутстрапа другой. +All of this is mostly a consequence of our great predecessors writing primarily histories of ideas. And they did so not because it is easy. One of the reasons we are not going to write a history of ideas is that tracing the history of ideas is very difficult. So we write a history of implementations first, and only then a history of ideas. +For each idea you can find the one it came from, and each one drags you further into the depths of the centuries, where there are ideas all the way down. That is how the historian of functional programming ends up in the year 1874. The history of implementations easily solves this problem (even too well, but more on that later). +Ideas leave fewer traces, and traces that are less convenient for the historian. An implementation leaves different versions of code, marks in it, reports, announcements, and release descriptions from which it is clear what worked and when. +An idea leaves an article that may be published years later and "cleaned up" in some history-erasing way. What does this mean? A related-work review in articles documents relationships among ideas, but not their history. The influence of one idea on another may be declared, but in fact the "influencing" idea may only be discovered after the idea it "influenced" was rediscovered independently. +Sometimes the author says this directly. For example, Xavier Leroy writes that he reinvented some ideas from the Krivine machine without knowing about it and added references only after other people drew his attention to the similarity. Some ideas in the implementation of Chez Scheme were discovered independently by its author and only later found in VAX ML, and some were actually borrowed. But not everyone feels the need to report this. Worse, such testimony can contradict others. For example, the authors' view of which languages they influenced may not coincide with the view of the authors of those languages about which languages influenced them. +There is nothing surprising here: ideas about how to make programming languages often arise independently. Even the Hindley-Milner algorithm - a nontrivial and precisely specifiable idea - was independently reinvented, but no one independently wrote two identical compilers. +An additional difficulty here is that adding references to articles is much easier than using compiler code, so the graph ends up with too many edges if we write a history of ideas. +To group implementations into families we will use rarer and more reliably established relationships: shared code, shared authors, using one to bootstrap another. -### Лямбда-фанерозой +### Lambda Phanerozoic -Что представляет из себя наша история имплементаций? В первую очередь - это история компиляторов в код обычных машин, в меньшей степени - история интерпретаторов и специальных аппаратных имплементаций. Мы выбираем компиляторы для обычных машин как наиболее успешное направление имплементации ФЯ. Раз уж они дожили до наших дней (в отличие от специального железа) и больше применялись для написания нетривиальных программ (в отличие от специального железа и интерпретаторов) про них просто больше известно, есть о чем писать. С другой стороны, компилятор достаточно масштабный проект, чтоб их было не слишком много (в отличие от интерпретаторов), так что у нас получается вполне обозримый набор достаточно подробных историй, а не бесконечные списки с немногословными описаниями. Мы, конечно же, опишем и часть истории интерпретаторов и специальных машин в той степени, в какой это необходимо для понимания истории компиляторов, но не ставим перед собой цели сколько-нибудь полно осветить этот вопрос. +What is our history of implementations? First of all, it is a history of compilers into the code of ordinary machines, and to a lesser extent a history of interpreters and special hardware implementations. We choose compilers for ordinary machines as the most successful direction of implementing functional languages. Since they have survived to our day (unlike special hardware) and were more often used to write nontrivial programs (unlike special hardware and interpreters), there is simply more known about them, and more to write about. On the other hand, a compiler is a sufficiently large-scale project that there are not too many of them (unlike interpreters), so we get a fairly manageable set of fairly detailed histories rather than endless lists with terse descriptions. We will, of course, describe part of the history of interpreters and special machines to the extent that it is necessary for understanding the history of compilers, but we do not aim to cover this question fully. -Но и у истории имплементаций есть проблемы. История программирования не очень богата событиями, когда функциональные программисты не так уж много пишут нетривиальные программы. Что такое нетривиальные программы? Например, код компилятора этого или другого функционального языка, код интерактивного решателя теорем. -Планка не выглядит высокой, но не для ФЯ, конечно. Причем, не то что-бы был какой-то выбор того где должна находится эта планка, потому что кроме того как быстро работают микробенчмарки и как быстро компилирует компилятор написанный на каком-нибудь ФЯ, мало что можно узнать. -Можно прочесть про одни имплементации, что числа Фибоначчи вычисляются не сильно медленнее чем на C, и что на языке написан компилятор, компилирующий что-то за приемлемое время, а про другие прочесть то, что компилятор на них не написан, а производительность не так-то и важна. -Поскольку история программирования и имплементаций сильно сдвинута к концу истории идей, она плохо ложиться на типичную для истории идей структуру "прогресса" и "формаций", когда все начинается с Лиспа, продолжается строгими ФЯ и завершается ленивыми. Вместо этого у нас после периода, когда ничего не работает, наступает период, когда заработало все и везде. Так что нам нужно как-то дополнительно структурировать "еще не работает" и "уже начинает работать". -Нашу историю структурирует жизненный цикл имплементаций: -Если имплементация достаточно старая, она могла долгие годы существовать как имплементация языка, который не похож на то, что мы определили как ФЯ в предыдущей главе. Например, Chez Scheme была использована как бэкенд для имплементации языка, который мы считаем ФЯ - Idris 2 - примерно после 35-илетней истории (если не считать один эксперимент). -С течением времени, необходимые фичи в этот язык добавлялись, или для имплементации вовсе писался фронтенд для другого языка. Более новые имплементации уже могли с самого начала быть имплементациями ФЯ Эдинбургской программы. -Если имплементация существует достаточно долго, то она используется для бутстрапа другой имплементации, написанной с самого начала уже на этом ФЯ. Если имплементация недостаточно успешна, то этого может и не произойти. -У этого простого цикла "предыстория-хопизация-бутстрап" могут быть и другие осложнения, разветвления и т.д. но общая структура обычно видна. +But the history of implementations also has problems. The history of programming is not very rich in events where functional programmers write many nontrivial programs. What are nontrivial programs? For example, the code of a compiler for one functional language or another, or the code of an interactive theorem prover. +The bar does not look high, but not for functional languages, of course. And it is not as if there was any real choice about where this bar should be, because apart from how fast microbenchmarks run and how fast a compiler written in some functional language compiles, there is little to learn. +You can read about some implementations that Fibonacci numbers are computed not much slower than in C, and that the language has a compiler that compiles something in acceptable time, and about others that no compiler was written in them and that performance is not that important. +Because the history of programming and implementations is heavily shifted toward the end of the history of ideas, it fits poorly with the typical history-of-ideas structure of "progress" and "formations," where everything starts with Lisp, continues with strict functional languages, and ends with lazy ones. Instead, after a period when nothing works, we have a period when everything works everywhere. So we need to further structure "not working yet" and "already starting to work." +Our history is structured by the life cycle of implementations: +If an implementation is old enough, it could for many years exist as an implementation of a language that does not look like what we defined as functional languages in the previous chapter. For example, Chez Scheme was used as a backend to implement a language we consider a functional language - Idris 2 - after roughly 35 years of history (if we do not count one experiment). +Over time, the necessary features were added to that language, or a frontend for another language was written for the implementation. Newer implementations could already, from the very beginning, be implementations of functional languages of the Edinburgh program. +If an implementation exists long enough, it is used to bootstrap another implementation written from the start in that functional language. If an implementation is not successful enough, this may not happen. +This simple "prehistory-hopization-bootstrap" cycle can have other complications, branching, and so on, but the overall structure is usually visible. -И последнее, но не наименее важное, история имплементаций ставит перед нами вопрос, ответ на который требует описывать и историю обычных машин и операционных систем, которую мы, разумеется, тоже будем описывать без каких-то претензий на полноту: -Если идея функционального программирования, как это выяснили наши великие предшественники, существовала столько же, сколько и программирование или даже дольше, то почему функционального программирования не было? Когда оно могло бы уже появиться? И что было тогда, когда функционального программирования не было? +And last, but not least, the history of implementations poses a question whose answer requires us to describe the history of ordinary machines and operating systems, which we will of course also describe without any claim to completeness: +If the idea of functional programming, as our great predecessors established, existed as long as programming itself or even longer, then why was there no functional programming? When could it have already appeared? And what was there at that time when functional programming did not exist? -И, наконец-то, мы покончили с сильно затянувшимся предисловием к предисловию и у нас впереди сильно затянувшееся предисловие про сильно затянувшуюся эпоху, когда идея функционального программирования была, а функционального программирования не было, ведь первый компилятор функционального языка Эдинбургской исследовательской программы появился только в 1981-ом году. +And finally, we are done with the overly long preface to the preface, and ahead of us is an overly long preface about an overly long era when the idea of functional programming existed, but functional programming did not, because the first compiler for a functional language of the Edinburgh research program appeared only in 1981. -Ветвь обоих Кембриджей +The Branch of Both Cambridges ====================== -> Было сказано, в частности Морисом Уилксом, что этот проект был полным провалом и его не следовало и начинать. - Мартин Ричардс, Кристофер Стрейчи и Кембриджский компилятор CPL [Rich2000] +> It was said, in particular by Maurice Wilkes, that this project was a complete failure and should not have been started. + Martin Richards, Christopher Strachey and the Cambridge CPL compiler [Rich2000] -> "Различные формы факториалов", рукопись 6 страниц, без даты - Каталог статей и писем Кристофера Стрейчи [Strachey] +> "Various forms of factorials," manuscript, 6 pages, undated + Catalogue of the papers and correspondence of Christopher Strachey [Strachey] -История функциональных языков отделилась от истории всех прочих языков, когда в одном Кембридже задумали сделать практичный функциональный язык общего назначения, а затем в другом Кембридже удалось имплементировать два языка: один из них был практичным языком общего назначения, а другой - функциональным. +The history of functional languages separated from the history of all other languages when, in one Cambridge, they conceived a practical general-purpose functional language, and then in another Cambridge they managed to implement two languages: one was a practical general-purpose language and the other was functional. -Нужно заметить, что история языков с первоклассными функциями отделялась от истории всех прочих еще два или три раза, но Эдинбургская Исследовательская Программа произошла именно от этого, двойного кембриджского ответвления, которое называется так потому, что семейство языков разрабатывалось в основном исследователями, которые сначала поработали в одном Кембридже, а потом в другом Кембридже. +It should be noted that the history of languages with first-class functions separated from the history of all other languages two or three more times, but the Edinburgh Research Program came precisely from this double Cambridge branch, which is so called because the family of languages was developed mainly by researchers who first worked in one Cambridge and then in the other Cambridge. -В Кембридже +In Cambridge ----------- -До начала истории имплементаций ФЯ была история ошибочных представлений о том, что такая имплементация уже может быть написана. И одним из первых их носителей был Кристофер Стрейчи (Christopher Strachey). В начале 60-х он работал консультантом и в этом качестве участвовал в проектировании компьютеров и разработке ПО. По-видимому, с переменным успехом, но мы не изучали этот вопрос глубоко. Его единственным сотрудником и вторым программистом компании с января 1960 был Питер Ландин (Peter Landin), который часть рабочего времени (согласованно со Стрейчи) тратил на исследования, которые имеют непосредственное отношение к истории ФП [Camp85], а именно: формальную семантику ЯП, трансляцию ЯП в ЛИ и виртуальную машину для интерпретации лямбда-выражений [Land64]. -Знакомый Стрейчи Морис Уилкс (Maurice Vincent Wilkes) руководил кембриджской математической (позднее - компьютерной) лабораторией (University Mathematical (Computer) Laboratory, Cambridge). Вместе со Стрейчи Уилкс критиковал ALGOL 60 [Stra61] за отсутствие синтаксических различий между чистыми и прочими, рекурсивными и нерекурсивными функциями. В этой их работе появились первые наброски CPL [Rich13] +Before the history of implementations of functional languages there was a history of mistaken ideas that such an implementation could already be written. One of the first bearers of these ideas was Christopher Strachey. In the early 1960s he worked as a consultant and in that capacity took part in computer design and software development. Apparently with mixed success, but we did not study this question deeply. His only colleague and the company's second programmer from January 1960 was Peter Landin, who spent part of his working time (by agreement with Strachey) on research directly related to the history of FP [Camp85], namely formal semantics of programming languages, translation of programming languages into the lambda calculus, and a virtual machine for interpreting lambda expressions [Land64]. +A colleague of Strachey, Maurice Vincent Wilkes, headed the Cambridge Mathematical (later Computer) Laboratory (University Mathematical (Computer) Laboratory, Cambridge). Together with Strachey, Wilkes criticized ALGOL 60 [Stra61] for the lack of syntactic distinctions between pure and other functions, recursive and non-recursive functions. In this work the first sketches of CPL appeared [Rich13]. ### CPL -Летом 62-го Уилкс пригласил Стрейчи работать в свою лабораторию для участия в разработке языка и компилятора для нового компьютера Titan (позднее Atlas 2 [TITAN]), который лаборатория должна была получить через два года [Camp85]. Переход Стрейчи из консультантов в академики плохо сказывался на его доходах, но это должна была скомпенсировать уникальная возможность поучаствовать в проектировании и имплементации опередившего свое время языка. Именно поучаствовать в проектировании и имплементации, а не спроектировать и имплементировать, потому что, забегая вперед, этого так и не произошло. Язык опередит и существующие в то время возможности его имплементировать. +In the summer of 1962 Wilkes invited Strachey to work in his laboratory to participate in developing a language and compiler for a new computer, Titan (later Atlas 2 [TITAN]), which the laboratory was to receive two years later [Camp85]. Strachey's move from consulting to academia hurt his income, but this was supposed to be compensated by the unique opportunity to participate in the design and implementation of a language ahead of its time. To participate in the design and implementation, not to design and implement it, because, to anticipate, that never happened. The language would outrun the existing capabilities of its time to implement it. -Первый пропозал CPL (Cambridge Programming Language) был написан Стрейчи, Девидом Барроном (David W. Barron) и Девидом Хартли (David F. Hartley) (которого Стрейчи при первой встрече за несколько лет до того отговорил имплементировать ALGOL 60 [Hart2000]) в августе 62-го. Но уже осенью, по инициативе Баррона и Хартли [Hart2000] [Hart13], начинается сотрудничество с университетом Лондона (London University Computer Unit), который ожидал старшую версию компьютера из той же линейки - Atlas [ATLAS]. К авторам присоединяются Эрик Никсон (Eric Nixon) и Джон Бакстон (John N. Buxton), а CPL становится Combined Programming Language. О Питере Ландине вспоминают как о участнике разработки языка [Rich2000], хотя официально он над языком не работал. -В феврале 63 готова статья “The Main Features of CPL” [Barr63]. -Не смотря на схожесть компьютеров, каждый университет пишет свою имплементацию CPL [Rich13]. Лондон - более традиционную, а Кембридж - более экспериментальную, основанную на идеях Ландина, видимо о компиляции через промежуточный язык "аппликативных выражений". -Но с октября 63-го обе команды имплементаторов совещаются каждый месяц. -Встречи комитета частые, долгие и запоминающиеся [Rich2000]. -В Кембридже имплементацию планируют закончить в начале 64-го, ко времени получения Titan. +The first CPL (Cambridge Programming Language) proposal was written by Strachey, David W. Barron, and David F. Hartley (whom Strachey had talked out of implementing ALGOL 60 at their first meeting several years earlier [Hart2000]) in August 1962. But already in the fall, on the initiative of Barron and Hartley [Hart2000] [Hart13], collaboration began with the London University Computer Unit, which was expecting a higher-end computer from the same line, Atlas [ATLAS]. Eric Nixon and John N. Buxton joined the authors, and CPL became the Combined Programming Language. Peter Landin is recalled as a participant in the language's development [Rich2000], although officially he did not work on the language. +In February 1963 the paper "The Main Features of CPL" was ready [Barr63]. +Despite the similarity of the computers, each university wrote its own CPL implementation [Rich13]. London wrote a more traditional one, and Cambridge a more experimental one, based on Landin's ideas, apparently about compiling via an intermediate language of "applicative expressions." +But from October 1963 both teams of implementers met every month. +The committee meetings were frequent, long, and memorable [Rich2000]. +In Cambridge they planned to finish the implementation in early 1964, by the time Titan arrived. -Но Стрейчи все больше переключался на формальное описание семантики, которым заинтересовался под влиянием Ландина. Баррон, один из самых активных участников сначала, тоже переключился на другие дела, хотя какое-то время участвовал в совещаниях, но компилятором уже не занимался. Над компилятором работали аспиранты, года по три, причем последний из трех в основном над своей диссертацией [Rich13] [Hart13]. +But Strachey increasingly switched to formal semantic description, which he became interested in under Landin's influence. Barron, one of the most active participants at first, also switched to other matters, although he participated in meetings for some time, but no longer worked on the compiler. Graduate students worked on the compiler for about three years each, and the last of the three mainly worked on his dissertation [Rich13] [Hart13]. -Летом 64-го Уилкс, недовольный медленной работой над компилятором, назначил руководителем команды имплементаторов Дэвида Парка (David Park), работавшего в Оксфорде и МТИ и имевшего опыт имплементации ЯП. Это не помогло. Вклад Стрейчи все сокращался, ему было не интересно работать над практичной имплементацией, да она и не получалась: предварительная версия компилятора работала медленно, в чем обвинили новаторский подход. -Только вот лондонская имплементация тоже не была завершена, удалось имплементировать только непрактичный компилятор подмножества CPL. -Фактически, над обеими имплементациями более-менее постоянно работало по одному человеку: компилятор в Лондоне писал Джордж Кулурис (George Coulouris) [Coul], а кембриджский компилятор писал Мартин Ричардс (Martin Richards) - важный герой нашей истории, к которому мы еще вернемся. -В 65 Стрейчи уходит из Кембриджа в МТИ на год, а потом в апреле 66-го в Оксфорд. Дэвид Парк тоже уходит из Кембриджа работать со Стрейчи в Оксфорде. -Все это время, впрочем, Стрейчи работает над описанием CPL, что идет не очень хорошо, потому что он годами может взаимодействовать с прочими авторами только по переписке. -В июне 66 комитет назначает Стрейчи редактором мануала, после чего большинство участников уходят из комитета официально потому, что уже и так занимались другими вещами. -Этот мануал не был опубликован, но циркулировал как самиздат [Stra66b]. Проект в Кембридже завершился в декабре 66-го [Rich13]. Тогда же, в декабре 1966, состоялась последняя встреча комитета, на которой договорились прекратить работу. [Hart13] -Джордж Кулурис в Лондоне написал компилятор подмножества CPL под названием CPL1 к осени 67-го [Coul68]. Этот компилятор описывают как более-менее законченный, но компилирующий только небольшие программы и непригодный для практического использования [Hart13]. Кембриджский компилятор описывают как непрактичный в лучшем случае [Rich13] или как вовсе не законченный [Hart13], как полный провал по мнению директора лаборатории [Rich2000]. Те, кому не нравится считать CPL "полным провалом", находили утешение в том, что CPL хоть и был неудачей по обычным стандартам, вроде известности, соблюдения сроков и эффективности имплементаций, но повлиял на другие языки [Camp85] [Rich2000]. Утешение в этом будут находить и следующие поколения дизайнеров и имплементаторов ФЯ. -Участники проекта в своих воспоминаниях объясняют провал проекта потерей интереса: Баррон и Хартли занялись ОС для Titan/Atlas 2, а Стрейчи и Парк - формальной семантикой ЯП [Hart13]. Только вот это была не первая и не последняя неудачная попытка сделать компилятор ФЯ. Что если не прогресс остановился из-за потери интереса, а интерес был потерян из-за остановки прогресса по другим причинам? К выяснению того, что это были за причины мы еще вернемся. -CPL задумывался как единый универсальный язык общего назначения [Hart13], но комитет, не смотря на практически полное согласие со Стрейчи [Hart2000] [Rich2000], мало что функционального специфицировал, а имплементировано было еще меньше. И CPL распался на диалекты: тот, что удалось имплементировать в Кембридже, тот, что удалось имплементировать в Лондоне. Для написания имплементации в Кембридже в 65-66гг. выделили самое простое подмножество из всех - практически виртуальную машину [Rich13]. Наконец, самый известный и влиятельный CPL - тот, что имплементировать было непонятно как, да и не стали пытаться. +In the summer of 1964 Wilkes, dissatisfied with the slow work on the compiler, appointed David Park, who had worked in Oxford and MIT and had experience implementing programming languages, as the head of the implementation team. This did not help. Strachey's contribution kept shrinking; he was not interested in working on a practical implementation, and it was not working out: the preliminary version of the compiler ran slowly, which was blamed on the innovative approach. +But the London implementation was not completed either; they only managed to implement an impractical compiler for a subset of CPL. +In fact, each implementation had more or less one person working on it continuously: the compiler in London was written by George Coulouris [Coul], and the Cambridge compiler was written by Martin Richards - an important hero of our story whom we will return to later. +In 1965 Strachey left Cambridge for MIT for a year, and then in April 1966 for Oxford. David Park also left Cambridge to work with Strachey in Oxford. +All this time, however, Strachey worked on the CPL description, which went poorly because for years he could interact with the other authors only by correspondence. +In June 1966 the committee appointed Strachey editor of the manual, after which most participants left the committee officially because they were already engaged in other things. +This manual was not published, but circulated as samizdat [Stra66b]. The Cambridge project ended in December 1966 [Rich13]. That same December 1966 saw the last committee meeting, where they agreed to stop work [Hart13]. +George Coulouris in London wrote a compiler for a CPL subset called CPL1 by autumn 1967 [Coul68]. This compiler is described as more or less finished, but compiling only small programs and unsuitable for practical use [Hart13]. The Cambridge compiler is described as impractical at best [Rich13] or not finished at all [Hart13], and as a complete failure in the opinion of the laboratory director [Rich2000]. Those who dislike calling CPL a "complete failure" found consolation in the fact that CPL, though a failure by usual standards such as fame, deadlines, and implementation efficiency, influenced other languages [Camp85] [Rich2000]. Later generations of functional-language designers and implementers would find consolation in this too. +Project participants in their recollections explain the project's failure by loss of interest: Barron and Hartley took up the OS for Titan/Atlas 2, and Strachey and Park took up formal semantics of programming languages [Hart13]. But this was not the first and not the last failed attempt to make a functional-language compiler. What if progress did not stop because of lost interest, but interest was lost because progress stopped for other reasons? We will return to finding out what those reasons were. +CPL was conceived as a single universal general-purpose language [Hart13], but the committee, despite near-complete agreement with Strachey [Hart2000] [Rich2000], specified little that was functional, and even less was implemented. CPL split into dialects: the one they managed to implement in Cambridge, and the one they managed to implement in London. To write the Cambridge implementation in 1965-66 they selected the simplest subset of all - practically a virtual machine [Rich13]. Finally, the most famous and influential CPL is the one that was unclear how to implement, and they did not even try. -### псевдоCPL +### pseudoCPL -Летом 63-го года Стрейчи, Баррон и Филип Вудвард (Philip Woodward) из Королевского института радиолокации прочли несколько лекций по программированию с примерами на "CPL" [Wood66] [Stra66]. Эти лекции были изданы в сборнике в 1966 [Fox66]. +In the summer of 1963 Strachey, Barron, and Philip Woodward of the Royal Radar Establishment gave several lectures on programming with examples in "CPL" [Wood66] [Stra66]. These lectures were published in a collection in 1966 [Fox66]. -Важно уточнить, что код и в этих статьях, и даже в статье, называющейся "Основные фичи CPL" [Barr63] не является кодом на какой-то версии CPL, которая была описана комитетом и тем более имплементирована. Интересно, что и автор лондонской имплементации Кулурис посчитал нужным явно написать, что нет, написать примеры с помощью CPL из мануала [Stra66b] или имплементированного в Лондоне CPL1 нельзя [Coul68]. +It is important to clarify that the code in these articles, and even in the article titled "The Main Features of CPL" [Barr63], is not code in any version of CPL that was described by the committee, let alone implemented. Interestingly, the author of the London implementation, Coulouris, also found it necessary to explicitly write that no, it is not possible to write the examples using the CPL from the manual [Stra66b] or the CPL1 implemented in London [Coul68]. -Это был гораздо более впечатляющий CPL, который был настолько хорошим языком, насколько Стрейчи хотел, чтоб он был, без всяких рамок которые могла накладывать имплементация или даже непротиворечивость самого языка, который мы будем называть _псевдоCPL_. -Стрейчи просто писал `map` и `foldr` (который называл `Lit`) +This was a much more impressive CPL, as good a language as Strachey wanted it to be, without any constraints that implementation or even internal consistency could impose, which we will call _pseudoCPL_. Strachey simply wrote `map` and `foldr` (which he called `Lit`) ```haskell let Map[f, L] = Null[L] -> NIL, @@ -341,11 +340,11 @@ let Lit[F, z, L] = Null[L] -> z, Lit[F1, 1, (1, 2, 3)] where F1[x,y] = x * y ``` -не зная как его можно имплементировать и можно ли вообще. -Как, сделать так, чтоб в коде типизированного языка не было аннотаций типов? Стрейчи даже не написал, что знает как это сделать, но алгоритм не поместился на полях. Что поделать, выхода у будущих имплементаторов ФЯ не было, пришлось придумать способ и не один. -И тот, кто хотел аннотировать типы и рекурсию и тот, кто не хотел, могли помнить о псевдоCPL как о языке, который им нравился, и который они хотели бы повторить. Со временем, так научатся делать и с имплементированными языками. Но до этого пока далеко. +without knowing how it could be implemented or whether it could be implemented at all. +How do you make it so that code in a typed language has no type annotations? Strachey did not even write that he knew how to do this, but the algorithm did not fit in the margins. There was no way out for future functional-language implementers; they had to invent a way, and more than one. +Both those who wanted to annotate types and recursion and those who did not could remember pseudoCPL as a language they liked and wanted to reproduce. In time, they would learn to do this with implemented languages as well. But that is still far away. -Эта идея не была новой. За три года до того уже выходила статья с примерами кода на языке, название которого совпадало с названием языка, который в это время имплементировался. Разумеется, эта имплементация также не поддерживала код из статьи: +This idea was not new. Three years earlier there was already an article with code examples in a language whose name matched the language being implemented at the time. Of course, that implementation also did not support the code from the article: ```haskell maplist[x; f] = [null[x] -> NIL; @@ -354,12 +353,12 @@ maplist[x; f] = [null[x] -> NIL; maplist[(1,2,3); λ[[x]; x + y]] ``` -(пример в статье отличается отсутствием вызова car) +(the example in the article differs by the absence of a `car` call) -Мечты о ФП коде в лекциях Стрейчи и д.р. - итерация похожих мечт из этой статьи про Лисп [McCa60], которой оказалось для многих достаточно, чтоб считать Лисп первым ФЯ. -Псевдокод не похож на Лисп? Ну да, Лисп-первый-ФЯ не состоялся. Планировалось, что Лисп будет выглядеть так. Это M-выражения, которые не были ни имплементированы, ни даже специфицированы потому, что имплементация фронтенда все затягивалась, и лисперы привыкли писать на промежуточном коде - S-выражениях. В результате, написание транслятора из M в S выражения было заброшено. Но для имплементации ФЯ важнее то, что не заработала как надо передача функций в функции [McCa78]. К этой проблеме мы еще вернемся. +Dreams of FP code in Strachey's lectures and others are an iteration of similar dreams from this article on Lisp [McCa60], which was enough for many to consider Lisp the first functional language. +Pseudocode does not look like Lisp? Yes, the Lisp-as-first-functional-language story did not happen. It was planned that Lisp would look like this. These are M-expressions, which were neither implemented nor even specified because the frontend implementation kept being delayed, and Lispers got used to writing in intermediate code - S-expressions. As a result, writing a translator from M to S expressions was abandoned. But for implementing functional languages, the more important thing is that passing functions to functions did not work properly [McCa78]. We will return to this problem. -Можно было бы сказать, что основная новация CPL-ной итерации в том, что работать со списками теперь собирались в типизированном языке. Но сложно назвать инновацией одно только намерение это сделать. Ни во время чтения этих лекций, ни во время их издания еще толком не понимали как такой код типизировать. Если посмотреть на расширения ALGOL 60, которые в то время имплементировали в Королевском институте радиолокации [Wood66] +One might say that the main innovation of the CPL iteration was that list processing was now intended to be done in a typed language. But it is hard to call it an innovation to merely intend to do so. Neither when these lectures were delivered nor when they were published did they really know how to type such code. If you look at the ALGOL 60 extensions that were being implemented at the Royal Radar Establishment at that time [Wood66] ```pascal list procedure Append(x, y); list x, y; @@ -367,36 +366,36 @@ Append := if Null(x) then y else Cons(Hd(x), Append(Tl(x), y)) ``` -то видно, что тип списка просто `list` без параметра. Но во времена ALGOL 60 не знали и как функции нормально типизировать, там и тип функции параметризован только по возвращаемому типу, например `real procedure`. Правда, в 1967-ом кое-какие идеи на этот счет у Стрейчи появились. +you can see that the list type is just `list` without a parameter. But in the days of ALGOL 60 they also did not know how to type functions properly; the function type was parameterized only by the return type, for example `real procedure`. It is true that in 1967 Strachey had some ideas about this. -К сожалению, не на всех производит впечатление псевдокод в статьях, многие важные имплементаторы ФЯ вспоминают, что на них произвели впечатление работающие вещи. [Turn12][TODO Леннарт Августссон, Марк Джонс, Ксавье Леруа] Но эти работающие вещи для них сделали те, кто просто не мог смириться с тем, что такой фантастический язык остается фантастическим. И позднее они придумали и имплементировали языки, на которых можно писать `map` именно так, а можно и лучше. +Unfortunately, not everyone is impressed by pseudocode in papers; many important functional-language implementers recall that what impressed them were working things. [Turn12][TODO Lennart Augustsson, Mark Jones, Xavier Leroy] But those working things were made for them by people who simply could not accept that such a fantastic language remained fantastic. And later they invented and implemented languages in which you could write `map` exactly like that, or even better. -И писать так `map` они хотели достаточно долго, чтоб атавизмы этого стиля вроде `head` и `tail` попали в ФЯ и дожили до наших дней, хотя сам стиль устарел еще до первой практической имплементации. +And they wanted to write `map` like that for long enough that atavisms of this style, such as `head` and `tail`, entered functional languages and survived to our day, even though the style itself became obsolete before the first practical implementation. -### Следующие 700 непопулярных языков +### The Next 700 Unpopular Languages -> <Я> надеялся <...> сдержать создание сомнительных языков программирования, называемых (как группа) OWHY, что означает "Or What Have You". Никто не понял шутки, и попытка была обречена. -> Дана Скотт, A type-theoretical alternative to ISWIM, CUCH, OWHY, 1993 [Scot93] +> hoped <...> to restrain the creation of dubious programming languages, called (as a group) OWHY, which means "Or What Have You." Nobody got the joke, and the attempt was doomed. +> Dana Scott, A type-theoretical alternative to ISWIM, CUCH, OWHY, 1993 [Scot93] -CPL разрабатывался в первую очередь как практичный императивный ФЯ с добавленными функциональными фичами, в реальность которого верили и имплементацию ждали [Wood66], но фантазии про который сильно опередили его имплементацию. -Параллельно с ним, в обоекембриджской программе развивался второй язык - ISWIM, который двигался в противоположном направлении. +CPL was developed primarily as a practical imperative functional language with added functional features, a language whose reality people believed in and whose implementation they expected [Wood66], but the fantasies about it far outpaced its implementation. +In parallel, in the two-Cambridge program, a second language was developing - ISWIM, which moved in the opposite direction. -#### Следующие 700 исписанных листов +#### The Next 700 Written Pages -ISWIM (или YSWIM в черновиках [Landin]), что означало "If you See What I Mean", описывал в серии статей Питер Ландин. -Сначала как чисто функциональный псевдокод для описания спецификаций, транслируемый в лямбда-исчисление [Land64]. Затем как императивный псевдокод, транслируемый в лямбда-исчисление, расширенное добавлением мутабельности и средствами захвата продолжения для имплементации произвольного потока исполнения [Land98]. И все еще для описания спецификации, теперь уже ALGOL 60 [Land65a] [Land65b]. -Наконец, как "попытку сделать систему общего назначения для описания одних вещей в терминах других вещей, которая может быть проблемно-ориентированной при подходящем выборе примитивов." [Land66]. Неостановимую фабрику ФЯ, следующие 700 языков программирования. -Эти встречные движения привели к тому, что псевдоCPL и ISWIM, а точнее их функциональные подмножества оказались примерно в одном и том же месте и разбор отдельных фич для каждого был бы слишком повторяющимся, так что собран в одной главе. -На ISWIM написаны, по видимому, более крупные фрагменты ФП псевдокода, чем на CPL. -Самый крупный из них - парсер ALGOL 60 в абстрактное синтаксическое дерево, его описание, компиляция этого дерева в императивные аппликативные выражения IAE. Всего ~500LOC функционального псевдокода [Land65b]. -Предполагалось, что такое описание семантики можно "исполнять" вручную, с помощью ручки и бумаги, но на практике это, конечно, едва ли осуществимо. -Такой подход к описанию семантики - как наивного интерпретатора написанного на ФЯ - имеет смысл только при механизации этой "семантики", когда интерпретатор действительно работает, как у авторов Clean в восьмидесятые или, например, у Россберга в десятые. -Так что, по крайней мере попытка имплементации была неизбежна. И именно от этой неизбежной механизации "семантики" останется литература по имплементации ФЯ, но о ней позже. -Сложно точно установить что на что повлияло в случае функциональных подмножеств CPL и ISWIM. -Ландин написал статьи в первом приближении еще во время работы у Стрейчи [Land65b], т.е. в 60-62 годах [Camp85], прочитал по ним лекции в 63-ем [Fox66], но опубликовал позднее, когда работал в Univac подразделении Sperry Rand и МТИ. -Функциональное подмножество ISWIM может быть старее CPL, но выглядит современнее и с меньшими странностями. Но современнее выглядит просто то, что больше понравилось авторам современных языков. -Очередная функция `map`, на этот раз на ISWIM 65 [Land65b] +ISWIM (or YSWIM in drafts [Landin]), which meant "If you See What I Mean," was described in a series of articles by Peter Landin. +First as purely functional pseudocode for describing specifications, translated into the lambda calculus [Land64]. Then as imperative pseudocode translated into the lambda calculus extended with mutability and continuation-capture facilities to implement arbitrary control flow [Land98]. And still for describing a specification, now ALGOL 60 [Land65a] [Land65b]. +Finally, as "an attempt to make a general-purpose system for describing some things in terms of other things, which can be problem-oriented with an appropriate choice of primitives" [Land66]. An unstoppable factory of functional languages, the next 700 programming languages. +These opposing movements led to pseudoCPL and ISWIM - or rather their functional subsets - ending up in roughly the same place, and analyzing individual features for each would be too repetitive, so they are gathered in one chapter. +ISWIM contains, apparently, larger fragments of FP pseudocode than CPL. +The largest of them is an ALGOL 60 parser into an abstract syntax tree, its description, and compilation of that tree into imperative applicative expressions (IAE). In total about 500 LOC of functional pseudocode [Land65b]. +It was assumed that such a semantic description could be "executed" by hand, with pen and paper, but in practice this is, of course, hardly feasible. +This approach to describing semantics - as a naive interpreter written in a functional language - makes sense only when that "semantics" is mechanized, when the interpreter actually works, as with the authors of Clean in the 1980s or, for example, Rossberg in the 2010s. +So at least an attempt at implementation was inevitable. And it is from this inevitable mechanization of "semantics" that the literature on functional-language implementation remains, but more on that later. +It is hard to determine exactly what influenced what in the case of the functional subsets of CPL and ISWIM. +Landin wrote the articles in a first approximation while still working with Strachey [Land65b], i.e., in 1960-62 [Camp85], lectured from them in 1963 [Fox66], but published them later when he worked at the Univac division of Sperry Rand and at MIT. +The functional subset of ISWIM may be older than CPL, but it looks more modern and with fewer oddities. But what looks more modern is simply what the authors of modern languages liked more. +Another `map` function, this time in ISWIM 65 [Land65b] ```haskell rec map f L = null L -> () @@ -405,92 +404,92 @@ rec map f L = null L -> () map f (1, 2, 3) where f x = x * y and y = 1 ``` -В Univac к ISWIM проекту присоединился Уильям Бердж (William H. Burge), описывающий типизированные аппликативные выражения TAE [Burg64] [Burg66]. +At Univac, William H. Burge joined the ISWIM project, describing typed applicative expressions (TAE) [Burg64] [Burg66]. -#### Следующие 700 вариаций семи фич +#### The Next 700 Variations of Seven Features -CPL разрабатывался на основе ALGOL 60 [Rich13] [Hart13]. В описании ISWIM ссылаются на псевдоLISP 60-го года и ALGOL 60 [Land66]. -"Основа Алгола" в это время - лексическая видимость и поддержка рекурсии. То, что обычно авторы языков и заимствовали из Алгола. Для заимствования этих идей не нужно знать ALGOL 60, если знать про лямбда-исчисление, но многие изобретатели ФП ссылаются на влияние и ЛИ и ALGOL 60. Почему так происходило нам не понятно. Свойства ALGOL 60 по всей видимости не являются изобретенными независимо от ЛИ, а являются следами частично успешной борьбы меньшинства комитета за то, чтоб сделать ALGOL ФЯ [Naur78]. Эта борьба не закончилась на ALGOL 60 и к ней мы еще вернемся. Возможно, ссылающимся на ALGOL была важнее доказанная практикой возможность имплементации. -Синтаксические решения из ALGOL 60 в ISWIM и CPL в основном не попали, о некоторых исключениях - ниже. Не попали и непопулярные, среди заимствующих из Алгола, фичи вроде передачи параметров по имени. -CPL - язык со множеством императивных фич, которые оказали серьезное влияние на мейнстрим. Также, со множеством странных фич. Вроде двух разновидностей имен - однобуквенных из строчной буквы и нуля и более праймов и многобуквенных, начинающихся с заглавной и могущих включать в себя числа. Для чего? Чтоб умножение могло быть не оператором, а отсутствием оператора. +CPL was developed based on ALGOL 60 [Rich13] [Hart13]. The ISWIM description refers to pseudo-LISP from 1960 and ALGOL 60 [Land66]. +The "core of ALGOL" at that time was lexical scope and support for recursion. These are the things language authors usually borrowed from ALGOL. To borrow these ideas you do not need to know ALGOL 60 if you know lambda calculus, but many FP inventors refer to the influence of both lambda calculus and ALGOL 60. Why this happened is not clear to us. The properties of ALGOL 60 apparently were not invented independently of lambda calculus, but are traces of a partially successful struggle by a minority of the committee to make ALGOL a functional language [Naur78]. This struggle did not end with ALGOL 60 and we will return to it. Perhaps for those citing ALGOL, the proven possibility of implementation in practice was more important. +Syntactic solutions from ALGOL 60 largely did not make it into ISWIM and CPL; we will discuss some exceptions below. Unpopular features among those borrowing from ALGOL, such as call-by-name parameters, also did not make it. +CPL is a language with many imperative features that had a serious influence on the mainstream. It also has many strange features. For example, two kinds of names: single-letter ones consisting of a lowercase letter and zero or more primes, and multi-letter ones starting with a capital and allowed to include numbers. Why? So that multiplication could be not an operator, but the absence of an operator. ``` Square[x] = xx ``` -Но не все странные фичи CPL остались в CPL, некоторые присутствовали и в ISWIM и оказали влияние на Эдинбургскую исследовательскую программу. +But not all of CPL's strange features remained in CPL; some were also present in ISWIM and influenced the Edinburgh research program. -##### больше не фичи +##### No Longer Features -Этот класс странных фич как раз демонстрирует происхождение Эдинбургских языков от CPL/ISWIM, странными они стали только исчезнув из этих языков в 80-х и 90-х годах. Какой еще язык претендует на то, что от него произошли языки эдинбургской программы, и эта претензия более-менее правдоподобна без учета этих странных фич? Не все сразу! +This class of strange features demonstrates the origin of the Edinburgh languages from CPL/ISWIM; they became strange only after disappearing from these languages in the 1980s and 1990s. What other language claims that the Edinburgh program languages descended from it, and that this claim is more or less plausible without taking these strange features into account? Not all at once! -###### отсутствие лямбд +###### Absence of Lambdas -Отсутствие лямбды довольно неожиданная особенность для ФЯ, и за пределами круга разработчиков CPL/ISWIM идея захватила только еще одного автора ФЯ. -И не смотря на то, что это автор нескольких влиятельных языков, и многие имплементаторы ФЯ хотели делать языки как у него, всего этого влияния не хватило для того чтоб популяризировать ФЯ без лямбд. И едва ли для кого-то такой результат покажется неожиданным. -Нам не удалось установить почему лямбда не попала в CPL/ISWIM. Возможно, это наследие ALGOL 60. Возможно потому, что авторы увлеклись эквивалентной конструкцией, которую считали одной из основных фич обоекембриджских языков и хотели использовать только ее. Эта фича - +The absence of lambda is a rather unexpected feature for a functional language, and outside the circle of CPL/ISWIM developers the idea captured only one more functional-language author. +And despite the fact that this author created several influential languages, and many functional-language implementers wanted to make languages like his, all of that influence was not enough to popularize functional languages without lambdas. And this result will hardly seem surprising to anyone. +We were unable to determine why lambda did not make it into CPL/ISWIM. Perhaps it is a legacy of ALGOL 60. Perhaps because the authors became fascinated with an equivalent construct that they considered one of the main features of the two-Cambridge languages and wanted to use only it. That feature is - -###### выражение `where` +###### `where` expression -Что делать, если лямбд нет, а хочешь написать +What do you do if there are no lambdas, but you want to write ```haskell Quad [a, b, \x -> G[x, y]] ``` -? Нужно писать [Barr63] +? You need to write [Barr63] ```haskell Quad [a, b, F] where F[x] = G[x, y] ``` -Выражение `where` - полный аналог современного `let` `in`, а не производная и гораздо более новая фича `where`-как-часть-других-конструкций, известная по Haskell. -В наши дни выражение `where` полностью заменено на `let` и `where` как часть других конструкций. И хотя уже в 66-ом в описании [Stra66b] обсуждается какая-то смутная неудовлетворенность `where`-выражением и неопределенные планы сделать `where`-как-часть-декларации, фича существовала десятилетиями. Причем `where`-выражение не осталось только в мертвых языках, не попав в современные, а успело побывать в дизайне и имплементациях Haskell, Standard ML и Caml. Редкий пример фичи, которая попала в эти языки и не осталась в них навсегда. -Авторы CPL не претендовали на изобретение `where`, утверждая, [Barr63] что позаимствовали конструкцию из калькулятора GENIE [Ilif61], но не в точности. В GENIE вместо ключевого слова `where` использовалась запятая. +The `where` expression is a full analogue of the modern `let` `in`, not the derivative and much newer feature `where`-as-part-of-other-constructs known from Haskell. +Today the `where` expression has been completely replaced by `let` and by `where` as part of other constructs. And although already in 1966 the description [Stra66b] discusses some vague dissatisfaction with the `where` expression and undefined plans to make `where`-as-part-of-declarations, the feature existed for decades. Moreover, the `where` expression did not remain only in dead languages, never making it into modern ones; it appeared in the design and implementations of Haskell, Standard ML, and Caml. A rare example of a feature that entered these languages and did not stay forever. +The authors of CPL did not claim to have invented `where`, stating [Barr63] that they borrowed the construct from the GENIE calculator [Ilif61], though not exactly. In GENIE a comma was used instead of the `where` keyword. -###### тернарный оператор ветвления +###### ternary branching operator -попал в мейнстрим, но в ФЯ вытеснен конструкциями из ключевых слов и частью других конструкций - гардами, на синтаксис которых он, вероятно повлиял, но не так сильно как синтаксис ветвления в псевдолиспе из [McCa60], возможно через синтаксис ISWIM 64. +It entered the mainstream, but in functional languages it was displaced by keyword-based constructs and by parts of other constructs - guards - whose syntax it likely influenced, but not as strongly as the branching syntax in the pseudo-Lisp of [McCa60], possibly via the syntax of ISWIM 64. ```haskell let rec fact(n) = n = 0 -> 1, n * fact(n-1) ``` -В первом коде на ML (LCF 77) почти всегда используется тернарный оператор, а не `if then else` конструкция. +In the first code in ML (LCF 77) the ternary operator is used almost always, rather than the `if then else` construct. -##### пока еще фичи +##### Still Features -###### конструкция `let` +###### `let` construct -В CPL `let` это часть синтаксиса деклараций, и `where` определяется через декларацию в возвращающем ссылку на значение блоке [Stra66b]. +In CPL, `let` is part of the declaration syntax, and `where` is defined through a declaration in a value-returning block [Stra66b]. ```haskell E where D === ref of § let D; result is E § ``` -Но в ISWIM `let` - выражение [Land65a], вводится как сахар для `where` [Land66] +But in ISWIM, `let` is an expression [Land65a], introduced as sugar for `where` [Land66] ```haskell let x = M; L === L where x = M ``` -###### аннотация рекурсии +###### recursion annotation -Одна из первых идей для CPL еще из критики ALGOL 60 [Stra61], которая повлияла на языки Эдинбургской программы - аннотация рекурсии. В своей конечной и наиболее популярной форме - с помощью ключевых слов `rec` и `and`. -Для ALGOL 60 такие аннотации рассматривали, но отклонили с небольшим перевесом [Naur78] +One of the early ideas for CPL, already from the critique of ALGOL 60 [Stra61], which influenced the languages of the Edinburgh program, was recursion annotation. In its final and most popular form, it uses the keywords `rec` and `and`. +For ALGOL 60 such annotations were considered but rejected by a small margin [Naur78]. -###### отсутствие аннотаций рекурсии +###### absence of recursion annotations -Не смотря на то, что Стрейчи критиковал ALGOL 60 за отсутствие аннотаций рекурсии, в работах, в которых "CPL" используется как псевдокод, он эти аннотации обычно пропускал [Stra66]. -Так что не важно, есть в вашем ФЯ аннотация рекурсии или нет - избежать влияния CPL не удалось. Как и влияния ALGOL 60, разумеется. +Although Strachey criticized ALGOL 60 for the absence of recursion annotations, in works where "CPL" is used as pseudocode he usually omitted those annotations [Stra66]. +So it does not matter whether your functional language has recursion annotations or not - avoiding CPL's influence was not possible. Neither was avoiding ALGOL 60's influence, of course. -###### отсутствие аннотаций типов +###### absence of type annotations -Не аннотировать типы в псевдокоде легко, в имплементированном языке уже сложнее. Самое сложное, конечно, когда типы есть и их не надо аннотировать, но можно сделать и проще, если аннотировать нечего или если замести аннотации куда-нибудь не на очень видное место. В каждом из трех первоначальных языков Эдинбургской программы будет один из трех основных способов: хороший, плохой и отвратительный. +Not annotating types in pseudocode is easy; in an implemented language it is harder. The hardest case, of course, is when types exist and you do not have to annotate them, but you can also make things easier if there is nothing to annotate or if you hide annotations somewhere not very visible. In each of the three original languages of the Edinburgh program there will be one of the three main approaches: good, bad, and ugly. -###### обозначение блоков отступами +###### block delimiting by indentation -В ISWIM [Land64] [Land65a] +In ISWIM [Land64] [Land65a] ```haskell let x = M; L @@ -503,49 +502,49 @@ let x = M L ``` -###### "одновременные"/"параллельные" декларации +###### "simultaneous"/"parallel" declarations -Зачатки паттерн-матчинга, пока без вложений и ветвлений. +The beginnings of pattern matching, still without nesting or branching. -В ISWIM [Land64] +In ISWIM [Land64] ``` (u,v) = (2*p + q, p - 2*q) ``` -В псевдоCPL [Stra66] и ISWIM 65 [Land65b] +In pseudoCPL [Stra66] and ISWIM 65 [Land65b] ```haskell let u, v = 2*p + q, p - 2*q ``` -##### обсуждавшиеся фичи +##### Discussed Features -За считанные месяцы до окончательного прекращения всех работ по CPL Стрейчи делает доклад [Stra67] в котором рассуждает о системе типов, о полиморфизме и различиях между параметрическим и ad-hoc, о том как, возможно, будут сделаны составные типы и что будет, возможно, решено делать ли в CPL параметрические типы и если да, то как. Никто это не решал и не имплементировал, но, благодаря этим анонсам и рассуждениям, Стрейчи стали считать одним из изобретателей полиморфизма. +Just months before the final cessation of all work on CPL, Strachey gave a talk [Stra67] in which he reasoned about type systems, about polymorphism and the differences between parametric and ad hoc, about how composite types might be done, and whether CPL would decide to have parametric types and if so, how. No one decided this and no one implemented it, but thanks to these announcements and reflections, Strachey came to be considered one of the inventors of polymorphism. -###### параметрический полиморфизм +###### Parametric Polymorphism -В докладе Стрейчи наконец-то представляет тип всех тех псевдокодовых `map` [Stra67]: +In the talk, Strachey finally presents the type of all those pseudocode `map`s [Stra67]: ``` (α ⇒ β, α list) ⇒ β list ``` -Лекции были опубликованы только в 2000 году, так что префиксная форма полиморфных типов может быть более поздней нотацией "как в ML". Но, по видимому, происходит от постфиксных аннотаций вроде `string ref`, которые были имплементированы [Coul68] и вероятно происходят от типов из ALGOL 60. Таких как `integer array`. -В типизированном ISWIM Берджа [Burg64] эта сигнатура выглядела бы +The lectures were published only in 2000, so the prefix form of polymorphic types may be a later notation "as in ML." But it apparently comes from postfix annotations like `string ref`, which were implemented [Coul68] and likely descend from ALGOL 60 types such as `integer array`. +In Burge's typed ISWIM [Burg64] this signature would look like ``` map ∈ (A -> B) -> (A-list -> B-list) ``` -но именно такой сигнатуры мы в его работах не видели. -Эдинбуржцы ссылаются на работу Стрейчи по полиморфизму [Miln78], но в такой тривиальной форме идея параметрического полиморфизма была, по всей видимости, переоткрыта независимо. Например авторами языка CLU, на который эдинбуржцы также ссылаются. Возможно, переоткрыта даже в рамках Обоекембриджской программы. -Интересно, что автор языка CLU Барбара Лисков (Barbara Liskov) пишет [Lisk93], что параметризованные типы в CLU появились из идеи сделать пользовательские типы вроде упомянутых уже выше массивов или функций ALGOL 60. Т.е. как очевидный шаг, которому не уделяется особого внимания в статьях по CLU и его истории. В отличие от ограниченного "интерфейсами" параметрического полиморфизма, который описывается как интересная и сложная идея и проблема, потребовавшая долгие годы для дизайна и имплементации. -И для интересных проблем, касающихся полиморфизма, от обоекембриджцев решений эдинбуржцам не осталось. +but we did not see exactly such a signature in his works. +The Edinburgh researchers refer to Strachey's work on polymorphism [Miln78], but in such a trivial form the idea of parametric polymorphism was apparently reinvented independently. For example by the authors of the CLU language, which the Edinburgh researchers also cite. Possibly it was reinvented even within the two-Cambridge program. +Interestingly, the author of CLU, Barbara Liskov, writes [Lisk93] that parameterized types in CLU arose from the idea of making user-defined types such as the arrays or functions of ALGOL 60 mentioned above. That is, as an obvious step that receives little attention in papers on CLU and its history. In contrast to the parametric polymorphism constrained by "interfaces," which is described as an interesting and difficult idea and problem that required many years of design and implementation. +And for interesting problems related to polymorphism, the two-Cambridge researchers left no solutions for the Edinburgh researchers. -###### Изобрел ли Ландин АлгТД? +###### Did Landin Invent ADTs? -Тернер утверждает, что да [Turn12], и на первый взгляд он прав. Уже в статье [Land64] 64-го года и, вероятно, в лекциях для летней школы 63-го года [Fox66] Ландин использует неформальную нотацию для определения структур данных: +Turner claims yes [Turn12], and at first glance he is right. Already in the 1964 paper [Land64] and, probably, in the 1963 summer school lectures [Fox66], Landin uses an informal notation for defining data structures: ``` A list is either null @@ -553,7 +552,7 @@ A list is either null and a tail (t) which is a list. ``` -Описание у Ландина достаточно неформальное, чтоб не связываться с проблемами не изобретенного еще как следует полиморфизма и вообще избегать указывать что представляет из себя поле `head` (`h` там не параметр, а сокращенное имя поля). Бердж описывает список как параметрический [Burg64], или, может быть, как конвенцию по именованию списков +Landin's description is informal enough to avoid dealing with the problems of polymorphism not yet properly invented, and to avoid specifying what the `head` field actually is (`h` there is not a parameter, but an abbreviated field name). Burge describes the list as parametric [Burg64], or perhaps as a naming convention for lists ``` An A-list is either null and is a nullist @@ -561,7 +560,7 @@ or has a head (or h) which is an A and a tail (or t) which is an A-list ``` -Если на первый взгляд Тернер прав, то на второй и следующие взгляды сходство уже не так очевидно. Такая декларация объявляет не конструкторы АлгТД, которые могут использоваться и как паттерны. Паттерн-матчинга в ISWIM еще нет. Декларация объявляет предикаты и селекторы, имена которых указывает программист, а также конструкторы, имена которых формируются по правилам из имен предикатов и имени всей структуры: +If at first glance Turner is right, then on second and subsequent looks the similarity is not so obvious. Such a declaration does not declare the constructors of ADTs, which can also be used as patterns. There is no pattern matching in ISWIM yet. The declaration defines predicates and selectors whose names are specified by the programmer, as well as constructors whose names are formed by rules from the predicate names and the name of the whole structure: ``` null(constructnullist()) = true @@ -570,9 +569,9 @@ h(constructlist(x, L)) = x constructlist(h L, t L) = L ``` -В данном случае имя для `cons` вообще не написано (понятно, что такой предикат избыточен, хватит и `null`), но в более поздних статьях списки определены с `cons` [Land66]. -Ладно, паттерн-матчинга нет, но хотя-бы общая идея сумм произведений-то была изобретена? Вроде бы, со вторым, более узким определением Тернер прав. Идея сумм произведений ясна. -Но нам эта идея ясна потому, что мы уже знаем АлгТД как суммы произведений. Посмотрим на более сложную структуру, например описание абстрактного синтаксического дерева ISWIM [Land66]: +In this case the name for `cons` is not written at all (obviously such a predicate is redundant; `null` is enough), but in later articles lists are defined with `cons` [Land66]. +Okay, there is no pattern matching, but was at least the general idea of sums of products invented? With the second, narrower definition, Turner seems to be right. The idea of sums of products is clear. +But this idea is clear to us because we already know ADTs as sums of products. Let us look at a more complex structure, for example the description of the ISWIM abstract syntax tree [Land66]: ``` an aexpression (aexp) is @@ -587,8 +586,8 @@ an aexpression (aexp) is or one-armed ... ``` -Так, что тут у нас, сумма произведений сумм произведений ... -Нотация для псевдокода, для которого пока особо не придумывали как объявлять типы, а декларации АлгТД должны ведь и типы объявлять. И суммы произведений позволяют это удобно делать. А нотация Ландина - не позволяет, по крайней мере в неизменном виде. В современном ФЯ это выглядело бы как-то так: +So what do we have here, a sum of products of sums of products ... +This is notation for pseudocode, for which they had not yet really figured out how to declare types, while ADT declarations must declare types. Sums of products allow this conveniently. Landin's notation does not, at least not unchanged. In a modern functional language it would look something like this: ```haskell data AExp @@ -600,9 +599,9 @@ data AExp ... ``` -Если посмотреть на то что получилось, когда обоекембриджцы попытались перейти от нестрогого описания к чему-то более точному и типизировать это, то тут-то появляются уже серьезные основания сомневаться в том, что суммы произведений уже были изобретены. -Отступив от бесконечно вложенных сумм и произведений слишком далеко назад они получили отдельные декларации для произведений и для сумм. Или не отступили, а просто позаимствовали эту систему из другого языка, к которому мы еще вернемся. -Ричардс вспоминает, что авторы CPL много обсуждали как сделать композитные типы но так и не остановились ни на чем до того, как он покинул проект [Rich13]. Этот процесс, по всей видимости, не документирован. Известен только конечный результат из того самого доклада Стрейчи [Stra67], в котором он рассказывал про полиморфизм. +If we look at what happened when the two-Cambridge researchers tried to move from a loose description to something more precise and to type it, this is where there are serious grounds to doubt that sums of products had already been invented. +Backing away too far from infinitely nested sums and products, they ended up with separate declarations for products and for sums. Or they did not back away, but simply borrowed this system from another language, which we will return to. +Richards recalls that the CPL authors discussed for a long time how to make composite types but never settled on anything before he left the project [Rich13]. This process is apparently undocumented. Only the final result from that very Strachey talk [Stra67], in which he spoke about polymorphism, is known. ``` node Cons is LispList : Car @@ -616,69 +615,69 @@ element LispList is Atom ``` -(да, тип и имя поля на неправильных сторонах `:`) +(yes, the type and the field name are on the wrong sides of `:`) -Декларация вводит три новых типа `Cons`, `Atom` и `LispList` -и их конструкторы с селекторами. -`element` объявляет не тип сумму, а или-тип, конструктор `LispList` перегружен, параметр может принимать и объекты типа `Atom` и `Cons`. Тип, а не имя конструктора определяет что за значение конструируется. -Конструктор для пустого списка не определен потому, что в этом пропозале для CPL есть специальные конструктор `NIL` и предикат `Null`. Да, для того чтоб делать то, что обожают делать в мейнстримных языках, а вот в ФЯ Эдинбургской программы - не особенно. -Схожая "одноуровневая" система будет и в ISWIM. Далее мы увидим, что один из изобретателей ПМ по АлгТД уже в Эдинбурге понимал типизированную версию Ландинской нотации так же, как и авторы CPL. +The declaration introduces three new types, `Cons`, `Atom`, and `LispList`, +and their constructors with selectors. +`element` declares not a sum type but an either-type; the constructor `LispList` is overloaded, its parameter can take objects of both type `Atom` and `Cons`. The type, not the constructor name, determines what value is constructed. +The constructor for the empty list is not defined because in this CPL proposal there is a special constructor `NIL` and predicate `Null`. Yes, for doing what mainstream languages love to do, but not so much in the functional languages of the Edinburgh program. +A similar "single-level" system will also be in ISWIM. Later we will see that one of the inventors of pattern matching for ADTs already in Edinburgh understood the typed version of Landin's notation in the same way as the CPL authors. -### Следующие 700 не самых быстрых имплементаций +### The Next 700 Not-So-Fast Implementations -> Мы здесь рассматриваем возможность, а не практичность. Тем не менее, то, что написано ниже, может быть принято за отправную точку для разработки эффективной имплементации. -> Питер Ландин, ЛИ подход [Land66b] +> We are considering possibility here, not practicality. Nevertheless, what is written below may be taken as a starting point for developing an efficient implementation. +> Peter Landin, The lambda calculus approach [Land66b] -Основная литература по имплементации ФЯ от обоекембриджской программы осталась от ISWIM - языка спецификации, который предполагалось "выполнять" с помощью ручки и бумаги. Это ставило реальную цель. Цель была достигнута и первые доклады сделаны Ландином в 63-ем и статья опубликована в 64-ом [Land64]. -CPL был амбициозной попыткой имплементировать ФЯ общего назначения, но имплементация функциональной части не особенно продвинулась. -Про имплементацию CPL в Кембридже мало что опубликовано. Раз уж попытка закончилась неудачей, публиковать было нечего. -Мы бы не хотели создать впечатление, что цель Ландина была скромной, или что ее достижение было незначительным результатом. Цель была просто реалистичнее, чем цель CPL-щиков. Которые планировали имплементировать за два года ФЯ не только на самом этом ФЯ, но и как единственный язык для новой машины. Единственный потому, что должен был подходить для любых задач. -Сомнительно, что такую цель кто-то достиг бы и сегодня, при том, что мы знаем как делать ФЯ. А в 62-ом не знал никто. Но, правда, были уже две школы ФЯ-строения, которые либо не знали, что они не знают, либо не очень и хотели делать ФЯ. Причем не знали/не хотели они разные вещи. К этим направлениям недо-ФП-мысли мы вернемся позже. -Ландин всего-навсего был первым, кто придумал работающую идею. Вернее потенциально работающую. В предисловии мы определили практическую имплементацию как компилятор, способный компилировать хотя-бы компилятор ФЯ или что-то схожее по сложности. И наработки Ландина никогда не достигли этого уровня практичности. Но базирующиеся на них - достигли. -До Ландина механизация вычисления лямбда-выражений, которые действительно вычисляются как лямбда-выражения, была только в форме переписывания последовательностей символов или деревьев, как, например, оптимизатор компилятора переписывает код [Gilm63]. -Ландин описал стековую машину, что гораздо практичнее, но не без проблем. -Стековая машина - это то, что до Ландина уже позволяло имплементировать язык с рекурсивными функциями [Dijk60] первого порядка, и даже решить половину проблем с функциями высшего порядка, а именно передавать функции в функции [Rand64]. -Оставалось решить проблему с возвращением функций из функций. -Для этого Ландин использовал замыкания, сам термин введен им. Он, правда, не изобрел замыкание как структуру данных. Похожие структуры из ссылки на функцию и полей для её свободных переменных использовались, только на стеке, для имплементации передачи аргументов по имени в ALGOL 60 и назывались PARD [Dijk62]. Ландин применил их для возврата функций из функций, аллоцируя в куче со сборщиком мусора, который к тому времени уже применили в Лиспе [McCa60]. -У Ландина получилась машина с четырьмя регистрами, названная SECD по именам регистров: Stack, Environment, Control, Dump. -C - программа, список аппликативных выражений, которые машина редуцирует. -S - стек, нужен для имплементации функций. -D - хранит снапшот полного состояния машины (S,E,C,D) и нужен для имплементации лямбд. -E - окружение. Нужно для того, чтоб у лямбд могли быть свободные переменные. -Первая опубликованная версия SECD-машины имплементировала лямбда-исчисление, в которое транслировалась чисто функциональная версия ISWIM [Land64]. -В 64-ом Ландин расширил язык, добавив к ЛИ присваивание и операцию захвата продолжения. Это расширенное ЛИ он назвал IAE - императивные аппликативные выражения, а новую версию SECD-машины - sharing machine [Land65a]. Для имплементации захвата продолжения в этой машине добавлена еще одна разновидность замыкания "программа-замыкание", которое добавляет к функции не только окружение E, но все состояние машины D. -Версия статьи про SECD [Land64] 66-го года в сборнике [Fox66] уделяла больше внимания имплементации SECD с помощью компьютера, а не ручки и бумаги [Land66b]. -Понятно, что имплементация рекурсии с помощью стека - не самый практичный способ имплементировать ФЯ. Обязательное использование сборщика мусора, практичной имплементации которого еще не существовало, было даже большей проблемой. Но эти проблемы были решаемы, и были решены позже, что мы в дальнейшем рассмотрим подробнее. -Пытались ли имплементаторы CPL использовать идеи Ландина? В воспоминаниях участников упоминается, что для имплементации используются "аппликативные выражения" [Camp85] как у Ландина. Но это скорее всего не означает трансляции в лямбда-исчисление. В описании имплементации нефункционального языка BCPL [Rich69] тоже говорится об аппликативных выражениях, но в данном случае это точно не лямбда - просто промежуточное представление в виде дерева. -Во время работы в Univac Ландин и Уильям Бердж имплементировали прототип SECD [Land66] для Univac 1107 [Burg64]. Эта попытка имплементации по всей видимости не была успешной, потому что от нее осталось только пара упоминаний в статьях и перечне документов из личного архива Ландина [Landin]. -И Ландин и Бердж предпримут еще по одной попытке, но уже не в Univac. +The main literature on functional-language implementation from the two-Cambridge program remained with ISWIM - a specification language intended to be "executed" with pen and paper. This set a real goal. The goal was achieved, and the first reports were made by Landin in 1963 and the paper published in 1964 [Land64]. +CPL was an ambitious attempt to implement a general-purpose functional language, but the implementation of its functional part did not advance much. +Little was published about the CPL implementation in Cambridge. Since the attempt ended in failure, there was nothing to publish. +We would not like to give the impression that Landin's goal was modest or that its achievement was an insignificant result. The goal was simply more realistic than the goal of the CPL group, who planned to implement in two years a functional language not only in that functional language itself, but also as the only language for a new machine. The only language because it was supposed to fit any task. +It is doubtful that anyone would achieve such a goal even today, given that we know how to make functional languages. In 1962, no one knew. But there were already two schools of functional-language building that either did not know that they did not know, or did not really want to make functional languages. And what they did not know or did not want was different. We will return to these directions of pseudo-FP thought later. +Landin was simply the first to come up with a working idea. Rather, a potentially working one. In the preface we defined a practical implementation as a compiler capable of compiling at least a compiler for a functional language or something similar in complexity. Landin's work never reached that level of practicality. But those based on it did. +Before Landin, the mechanization of evaluating lambda expressions that actually evaluate as lambda expressions existed only in the form of rewriting sequences of symbols or trees, as, for example, a compiler optimizer rewrites code [Gilm63]. +Landin described a stack machine, which is much more practical, but not without problems. +A stack machine is what, before Landin, already allowed implementing a language with first-order recursive functions [Dijk60], and even solving half the problems with higher-order functions, namely passing functions to functions [Rand64]. +It remained to solve the problem of returning functions from functions. +For this, Landin used closures, a term he introduced himself. He did not, however, invent the closure as a data structure. Similar structures consisting of a reference to a function and fields for its free variables were used, only on the stack, to implement call-by-name argument passing in ALGOL 60 and were called PARD [Dijk62]. Landin used them to return functions from functions, allocating them on the heap with garbage collection, which by then had already been used in Lisp [McCa60]. +Landin ended up with a machine with four registers, named SECD after the registers: Stack, Environment, Control, Dump. +C - the program, a list of applicative expressions that the machine reduces. +S - the stack, needed for implementing functions. +D - stores a snapshot of the full machine state (S,E,C,D) and is needed for implementing lambdas. +E - the environment. It is needed so that lambdas can have free variables. +The first published version of the SECD machine implemented the lambda calculus, into which the purely functional version of ISWIM was translated [Land64]. +In 1964 Landin extended the language by adding assignment and a continuation-capture operation to the lambda calculus. He called this extended lambda calculus IAE - imperative applicative expressions - and the new version of the SECD machine the sharing machine [Land65a]. To implement continuation capture, this machine adds another kind of closure, a "program closure," which adds to the function not only the environment E but the entire machine state D. +The 1966 version of the SECD paper [Land64] in the collection [Fox66] paid more attention to implementing the SECD with a computer rather than with pen and paper [Land66b]. +It is clear that implementing recursion with a stack is not the most practical way to implement a functional language. The mandatory use of garbage collection, for which a practical implementation did not yet exist, was an even bigger problem. But these problems were solvable, and were solved later, which we will consider in more detail. +Did CPL implementers try to use Landin's ideas? The recollections of participants mention that "applicative expressions" were used for implementation [Camp85], as in Landin. But this most likely does not mean translation into lambda calculus. The description of the implementation of the non-functional language BCPL [Rich69] also speaks of applicative expressions, but in that case it is definitely not lambda - just an intermediate representation as a tree. +While working at Univac, Landin and William Burge implemented a SECD prototype [Land66] for the Univac 1107 [Burg64]. This implementation attempt was apparently not successful, because only a couple of mentions of it remain in papers and in the list of documents from Landin's personal archive [Landin]. +Both Landin and Burge would make one more attempt each, but no longer at Univac. -В другом Кембридже +In the Other Cambridge ------------------ -Возможно, что вклад CPL в историю языков программирования пока не выглядит выдающимся. Но, как это не удивительно, уже описанного хватило для того, чтоб впечатлить некоторых участников Эдинбургской исследовательской программы. Чтобы впечатлить остальных, а сверх того, еще и сделать CPL непосредственным предком многих популярных современных языков, понадобились еще и труды исследователей и имплементаторов в другом Кембридже. Точнее, в основном, одного имплементатора - Мартина Ричардса. +Perhaps CPL's contribution to the history of programming languages does not yet look outstanding. But, surprisingly, what has been described so far was enough to impress some participants of the Edinburgh research program. To impress the rest, and in addition to make CPL a direct ancestor of many popular modern languages, it also took the work of researchers and implementers in the other Cambridge. More precisely, mostly one implementer - Martin Richards. -### Мартин Ричардс +### Martin Richards -Мартин Ричардс (Martin Richards) с 1959 изучал математику в том самом Кембридже. В октябре 1963 он приступил к работе над диссертацией, повстречался со Стрейчи, и его затянуло в CPL проект [Comp17]. Стрейчи не мог быть его научруком официально, и им стал Баррон, а после того, как тот потерял интерес к CPL через год - Дэвид Парк [Rich2000], интересы которого тоже со временем поменялись. -Три года, до декабря 1966, Ричардс был практически единственным человеком в Кембридже работавшем только над CPL, занимаясь имплементацией его минимального подмножества Basic CPL, фактически виртуальной машины, которое должно было быть использовано для имплементации полнофункционального компилятора. Который, как мы помним, не имплементировали. -Вырвавшись из трясины обреченного проекта, которым никто кроме него не занимался, Ричардс отправился в МТИ. -Перед этим он заверил Стрейчи, что уж там-то он развернет работу над имплементацией CPL по настоящему. -Или, по крайней мере, Стрейчи заверял что план был именно такой [Strachey]. Еще осенью 67-го он рассказывал в докладе [Stra67], что портабельный компилятор CPL скоро будет готов. -Казалось бы, после того как, брошенный своими научруками, Ричардс провалил написание кембриджского компилятора, можно ли было ждать от него успехов в МТИ? Но успехи не заставили себя ждать. +Martin Richards studied mathematics in that very Cambridge starting in 1959. In October 1963 he began work on his dissertation, met Strachey, and was drawn into the CPL project [Comp17]. Strachey could not be his official supervisor, so Barron became one; after Barron lost interest in CPL a year later, David Park took over [Rich2000], whose interests also changed over time. +For three years, until December 1966, Richards was practically the only person in Cambridge working solely on CPL, implementing its minimal subset Basic CPL, effectively a virtual machine intended to be used to implement a full compiler. Which, as we remember, was never implemented. +Escaping the mire of a doomed project that no one but him was working on, Richards went to MIT. +Before that he assured Strachey that there he would really ramp up work on implementing CPL. +Or at least Strachey assured that this was the plan [Strachey]. As late as fall 1967 he said in a talk [Stra67] that a portable CPL compiler would soon be ready. +One might think that after being abandoned by his supervisors, Richards had failed to write the Cambridge compiler, and there could be no success at MIT. But the successes were not long in coming. ### BCPL -> BCPL - это просто CPL из которого удалены все сложные части. -> Мартин Ричардс, Как BCPL эволюционировал из CPL [Rich13] +> BCPL is just CPL with all the complex parts removed. +> Martin Richards, How BCPL evolved from CPL [Rich13] -> Ряд синтаксических и лексических механизмов BCPL элегантнее и последовательнее, чем в B и C. -> Деннис Ритчи, позаботившийся об этом [Ritc93] +> A number of BCPL's syntactic and lexical mechanisms are more elegant and consistent than in B and C. +> Dennis Ritchie, who saw to that [Ritc93] -В МТИ Ричардс работал вместе с Ландиным под началом Джона Возенкрафта (John "Jack" Wozencraft) [Rich2000] с декабря 66-го по октябрь 68-го. [Rich2000] [Comp17] -В МТИ Ричардс написал компилятор Basic CPL на AED-0 [Rich2000], похожем на ALGOL 60 языке, но с указателями и прочими фичами для системного программирования [Ross61]. Затем он написал компилятор BCPL на BCPL. Начальная версия компилятора в 1K строк была готова в начале 67-го года [Rich2000] для CTSS на IBM 7094 [Rich13]. За следующие 10 лет компилятор вырос до 5K строк и еще столько же строк тулинга вроде отладчика и редактора [Atki78]. -Получившийся язык не был уже минимальным подмножеством CPL или ВМ для его имплементации. Это был достаточно большой язык со многими фичами позволяющими делать одно и то же разными способами - управляющими структурами и т.д. [Rich74], который сам использовал стековую виртуальную машину Ocode для удобства портирования компилятора [Rich69]. BCPL отличался от CPL тем, что все что в CPL было сложно имплементировать в BCPL не попало. -И все что нам, как историкам ФП, интересно - было сложно имплементировать. Понятно, что в BCPL нельзя было вернуть замыкание, и, соответственно, не требовался сборщик мусора. Но даже ограниченной ФП-функциональности ALGOL с передачей замыкания вниз по стеку не было. Свободные переменные могли быть только статическими [Rich74]: +At MIT, Richards worked with Landin under John "Jack" Wozencraft [Rich2000] from December 1966 to October 1968 [Rich2000] [Comp17]. +At MIT Richards wrote a Basic CPL compiler in AED-0 [Rich2000], a language similar to ALGOL 60 but with pointers and other system-programming features [Ross61]. He then wrote a BCPL compiler in BCPL. The initial 1K-line compiler was ready in early 1967 [Rich2000] for CTSS on the IBM 7094 [Rich13]. Over the next 10 years the compiler grew to 5K lines and another 5K lines of tooling such as a debugger and editor [Atki78]. +The resulting language was no longer a minimal subset of CPL or a VM for its implementation. It was a fairly large language with many features that allowed doing the same thing in different ways - control structures and so on [Rich74] - and it used a stack virtual machine, Ocode, to make the compiler easier to port [Rich69]. BCPL differed from CPL in that everything that was hard to implement in CPL did not make it into BCPL. +And everything that is interesting to us as FP historians was hard to implement. It is clear that in BCPL you could not return a closure, and therefore no garbage collector was required. But even the limited FP functionality of ALGOL with passing a closure down the stack was absent. Free variables could only be static [Rich74]: ```f# let a, b = 1, 2 @@ -692,24 +691,24 @@ static $( a = 1; b = 2 $) let f (x) = a*x + b ``` -Ричардс оставил в BCPL один тип - слово. Не стал имплементировать выражение `where`, опередив на четверть века моду на выкидывание выражения `where` из ФЯ. Убрал аннотации рекурсии. -Не смотря на все эти урезания, BCPL все равно сложный язык, со вложенными функциями, возвращающими значения блоками-выражениями. Компилятор BCPL строил абстрактное синтаксическое дерево [Rich69] и потому имел серьезные для того времени требованиями к памяти. Поэтому Кен Томпсон не мог его использовать на той машине, на которой писал Unix и сделал еще более урезанную версию языка без всех этих вложенностей: B [Ritc93]. Томпсон сделал и некоторые изменения не связанные с экономией памяти. B - один из ранних примеров декембриджизации. Это процесс изменения синтаксиса происходящего от обоекембриджской ветви на любой другой, лишь бы только иначе выглядящий, широко практиковался в наши дни авторами Scala, Rust, Swift и др. -От B произошел C и далее большая часть ЯП-мейнстрима, каким мы его знаем и любим. -Сам BCPL использовался до 80-х годов. В том числе и некоторыми имплементаторами ФЯ. -В апреле 68-го Стрейчи получил письмо от Ричардса о том, что тот передумал имплементировать CPL, а решил вместо этого дальше развивать BCPL [Strachey]. +Richards left only one type in BCPL - the word. He did not implement the `where` expression, anticipating by a quarter century the fashion of throwing the `where` expression out of functional languages. He removed recursion annotations. +Despite all these cuts, BCPL is still a complex language, with nested functions and value-returning block expressions. The BCPL compiler built an abstract syntax tree [Rich69] and therefore had serious memory requirements for its time. Therefore Ken Thompson could not use it on the machine on which he wrote Unix and made an even more stripped-down version of the language without all these nestings: B [Ritc93]. Thompson also made some changes not related to saving memory. B is one of the early examples of decambridgization - the process of changing the syntax descended from the two-Cambridge branch into something else, anything that just looks different, widely practiced today by the authors of Scala, Rust, Swift, and others. +From B came C and then most of the mainstream programming languages as we know and love them. +BCPL itself was used until the 1980s, including by some functional-language implementers. +In April 1968 Strachey received a letter from Richards saying that he had changed his mind about implementing CPL and decided to continue developing BCPL instead [Strachey]. ### PAL -> Поскольку никто не пишет реальные программы на PAL, мы можем позволить себе неэффективную имплементацию, которая иначе была бы неприемлемой. -> Артур Эванс, PAL - язык, спроектированный для преподавания. [Evan68] +> Since no one writes real programs in PAL, we can afford an inefficient implementation that would otherwise be unacceptable. +> Arthur Evans, PAL - a language designed for teaching [Evan68] -Создав первый практический (он же первый нефункциональный) язык обоекембриджской программы, Ричардс не остановился на достигнутом и приступил к имплементации функционального языка с ограниченной практичностью. -Этим языком ограниченной практичности был PAL - педагогический алгоритмический язык [Evan68b], разрабатываемый специально для курса 6.231 МТИ "Programming Linguistics". -Первыми его имплементаторами были теперь работающий в МТИ Ландин и Джеймс Моррис (James H. Morris, Jr.). Они написали на Лиспе то, что конечно же было ISWIM-ом с незначительными изменениями. Но имплементация, видимо, получилась слишком непрактичной даже для языка, непрактичность которого пытались представить как фичу, позволяющую ему имплементировать всякие сумасшедшие вещи вроде лямбда-исчисления. Поэтому вторую версию в 68 году имплементировали Мартин Ричардс и Томас Баркалоу (Thomas J. Barkalow) на BCPL для IBM 7094 под CTSS. Компилятор в байт-код к 70-му году был 1.3KLOC, а интерпретатор 1.5KLOC. -PAL имплементирован с помощью SECD машины, которую авторы PAL почему-то называют CSED-машиной. -Проблема неуказывания типов в псевдокодах решена тем, что язык "динамически" типизирован. -Описывающие PAL говорят, что вторая имплементация отличалась от ISWIM несколько больше [Evan68] [Rich13], но не говорят чем. -Мы рискнем предположить, что это за отличие, тем более, что это отличие практически единственное заметное: в ISWIM добавили лямбду. +Having created the first practical (and also the first non-functional) language of the two-Cambridge program, Richards did not stop there and moved on to implementing a functional language with limited practicality. +That limited-practicality language was PAL, a pedagogical algorithmic language [Evan68b] developed specifically for MIT course 6.231 "Programming Linguistics." +Its first implementers were Landin, now working at MIT, and James H. Morris, Jr. They wrote in Lisp what was, of course, ISWIM with minor changes. But the implementation apparently turned out to be too impractical even for a language whose impracticality was presented as a feature, allowing it to implement crazy things like the lambda calculus. Therefore, in 1968 the second version was implemented by Martin Richards and Thomas J. Barkalow in BCPL for the IBM 7094 under CTSS. By 1970 the bytecode compiler was 1.3 KLOC and the interpreter 1.5 KLOC. +PAL was implemented with the SECD machine, which the PAL authors for some reason call the CSED machine. +The problem of not specifying types in pseudocode was solved by making the language "dynamically" typed. +Those describing PAL say that the second implementation differed from ISWIM somewhat more [Evan68] [Rich13], but they do not say how. +We will venture to guess what the difference was, especially since this difference is practically the only noticeable one: a lambda was added to ISWIM. ```haskell def rec map f L = Null L -> nil @@ -718,8 +717,8 @@ def rec map f L = Null L -> nil map (ll x. x + y) (1, (2, (3, nil))) where y = 1 ``` -Имплементация поддерживалась до 70-го года и язык несущественно менялся. -С 69-го он выглядел так [Woze71]: +The implementation was maintained until 1970 and the language changed only insignificantly. +From 1969 it looked like this [Woze71]: ```haskell def rec map f L = Null L -> nil @@ -728,10 +727,10 @@ def rec map f L = Null L -> nil map (fn x. x + y) (1, (2, (3, nil))) where y = 1 ``` -Помимо уже перечисленных имплементаторов, в авторы языка записаны Артур Эванс (Arthur Evans), который написал большую часть статей, репортов и мануалов, Роберт Грэм (Robert M. Graham) и Джон Возенкрафт. Просто поразительно, конечно, сколько авторов может быть у идеи добавить лямбду в функциональный язык. -Следующая по очевидности, после добавления лямбды в ФЯ, идея, которая пришла бы в голову современному человеку, конечно, такая: у нас есть непрактичный, медленный ФЯ PAL и практичный язык на котором он имплементирован BCPL. Что если мы будем "склеивать" в коде PAL-скрипта вызовы быстрых функций, написанных на BCPL? Но нет, никаких следов такого рода идей в 1968 году мы не видели. Такие идеи начнут завоевывать умы только к концу 70-х. -В Австралии 90-х годов именно эта идея будет воплощена в жизнь владельцем софтверной компании Lennox Computer Дугласом Ленноксом. Он использует сочетание придуманных и имплементированных им клонов ISWIM/PAL называемого GTL (General Tuple Language), сильно отставшего от своего времени, и C называемого (не тот)D [Lennox]. Это только первый из описываемых нами здесь, но не последний из ряда случаев, когда какой-то ФЯ или его имплементация находят неожиданное продолжение жизни у антиподов. -В PAL пытались имплементировать Ландинскую аннотацию для описания структур, со всеми особенностями вроде неограниченной степени вложенности +Besides the implementers already listed, the language's authors are given as Arthur Evans, who wrote most of the papers, reports, and manuals, Robert M. Graham, and John Wozencraft. It is remarkable, of course, how many authors an idea can have for adding lambda to a functional language. +The next obvious idea, after adding lambda to a functional language, that would come to a modern person's mind is this: we have an impractical, slow functional language, PAL, and a practical language it is implemented in, BCPL. What if we "glue" calls to fast functions written in BCPL into PAL script code? But no, we saw no traces of such ideas in 1968. Such ideas would start to win minds only by the end of the 1970s. +In 1990s Australia, this idea would be realized by Douglas Lennox, owner of the software company Lennox Computer. He uses a combination of his invented and implemented ISWIM/PAL clones called GTL (General Tuple Language), badly out of date, and C called (not that)D [Lennox]. This is only the first described here, but not the last in a series of cases where some functional language or its implementation finds an unexpected continuation of life among the antipodes. +In PAL they tried to implement Landin's notation for describing structures, with all the features such as unlimited nesting ``` def rec LIST which @@ -740,7 +739,7 @@ def rec LIST which else IsNIL ``` -и сложный правил для именования конструкторов и предикатов, которые генерировались по описанию. +and complex rules for naming constructors and predicates that were generated from the description. ``` def LIST which is @@ -748,15 +747,15 @@ def LIST which is else IsNIL ``` -поскольку тут только один "конструируемый объект" `HEAD also TAIL` именем его тега будет имя всей структуры, т.е. `LIST`, а именем его конструктора `MakeLIST` [Zill70]. Но эта работа не была завершена. -На PAL было написано кода не сильно больше, чем псевдокода на ISWIM. Это были в основном учебные интерпретаторы и другие примеры из курса для которого PAL и создавался [Woze71]. Для каких-то других целей он, судя по отсутствию следов, не использовался ни в МТИ, ни где-то еще. На то, что делали в МТИ язык оказал минимальное влияние. Другой Кембридж не принял ФП в этот раз. -Имплементация PAL самый старый артефакт в этой истории, для которого сейчас доступен исходный код [PAL360]. Исходный код был доступен с самого начала, как и для многого другого разработанного в МТИ на госденьги и являющегося поэтому общественным достоянием. Кроме того, это был код на языке, компилятор которого был переносим на другие машины и был перенесен. Что было редкостью в то время вообще, и особенно в МТИ, где практически все остальные имплементации языков писали на ассемблере, а потом заново, когда меняли машины. Не смотря на все эти качества которые, казалось бы, делали PAL хорошей платформой для разработки ФЯ, он в качестве такой платформы не использовался. -За одним исключением. -Как мы помним, Стрейчи после CPL-проекта стал работать в Оксфорде. И там он был научруком у человека, который попытался имплементировать PAL эффективно. Чтоб PAL можно было использовать как язык общего назначения. Это ему не удалось. Не то чтоб ему с тех пор хоть раз удалось эффективно имплементировать ФЯ. Но ФЯ, которые он разработает будут хотеть эффективно имплементировать другие люди и он будет далее одним из главных героев этой истории функционального программирования. +since there is only one "constructed object" `HEAD also TAIL`, the name of its tag will be the name of the whole structure, i.e., `LIST`, and the name of its constructor will be `MakeLIST` [Zill70]. But this work was not completed. +Not much more code was written in PAL than the pseudocode in ISWIM. These were mostly educational interpreters and other examples from the course for which PAL was created [Woze71]. For any other purposes, judging by the absence of traces, it was not used either at MIT or elsewhere. The language had minimal influence on what was done at MIT. The other Cambridge did not adopt FP this time. +The PAL implementation is the oldest artifact in this history for which source code is available today [PAL360]. The source code was available from the beginning, like much other software developed at MIT with government money and therefore in the public domain. In addition, it was code in a language whose compiler was portable to other machines and was ported. This was rare at the time in general, and especially at MIT, where almost all other language implementations were written in assembler and then rewritten when machines changed. Despite all these qualities that might have made PAL a good platform for FP development, it was not used as such a platform. +With one exception. +As we recall, after the CPL project Strachey went to Oxford. There he supervised a person who tried to implement PAL efficiently so that PAL could be used as a general-purpose language. He failed. It is not that he ever since succeeded in implementing functional languages efficiently. But the functional languages he would develop would be ones other people wanted to implement efficiently, and he would become one of the main heroes of this history of functional programming. ### McG -Уильям Бердж, работавший с Ландином в Univac, после этого работал в IBM Research в Йорктаун Хайтс. Там он вместе с Бартом Левенвортом (Burt Leavenworth) в 1968 году имплементировал на основе SECD-машины еще один язык мало отличимый от ISWIM - McG [Emde06] [Burg71]: +William Burge, who worked with Landin at Univac, later worked at IBM Research in Yorktown Heights. There, together with Burt Leavenworth, in 1968 he implemented another SECD-based language little different from ISWIM - McG [Emde06] [Burg71]: ```haskell def rec map f x @@ -766,49 +765,49 @@ def rec map f x let y = 1; let f x = x + y; map f (1,2,3); ``` -И вот он-то повлияет на отношение к ФП в Йорктаун Хайтс. И Бердж использует его для ФП исследований в 70-е годы. +And it was this language that would influence attitudes toward FP in Yorktown Heights. Burge used it for FP research in the 1970s. ------------------- -Пришла пора прощаться с основными действующими лицами обоекембриджской истории. Впереди 70-е годы, такой важной роли в истории ФП они уже играть не будут. Ландин занялся административной работой. Ричардс забросил ФП и сконцентрировался на развитии BCPL. Стрейчи умер. -Мы отправляемся на север, в новую основную локацию и знакомимся с новыми главными героями: аспирантом Стрейчи, знакомым Ландина и знакомым знакомого Стрейчи. +It is time to say goodbye to the main actors of the two-Cambridge story. The 1970s are ahead, and they will no longer play such an important role in the history of FP. Landin moved into administrative work. Richards abandoned FP and focused on developing BCPL. Strachey died. +We are heading north, to a new main location, and meeting new main heroes: Strachey's graduate student, Landin's acquaintance, and the acquaintance of an acquaintance of Strachey. -Эдинбургская исследовательская программа +Edinburgh Research Program ======================================== -## От исследовательского программирования к экспериментальному. +## From research programming to experimental. -Мартин ван Эмден как-то раз вычитал в журнале New Scientist сумасшедшую историю. В Эдинбурге некие Бурсталл и Поплстоун бросили вызов МТИ, написали многопользовательскую ОС на собственном языке для интерактивного программирования! И как раз в этом, 1968-ом году в Эдинбурге проходит конференция IFIP. Конференция не по функциональному программированию и даже не по программированию вообще, а по всему что связано с использованием компьютеров. Связано с этим пока не очень много, так что одной конференции достаточно для всего. Ван Эмден пока еще только сдавал программы на Алголе на бумажной ленте, чтоб их когда-нибудь запустили и очень хотел увидеть интерактивное программирование и более продвинутый чем Алгол язык. И Математический Центр Амстердама отправил ван Эмдена на конференцию. -Конечно же, на конференции было запланировано демо эдинбургской системы. Только кроме ван Эмдена на него никто не пришел. И демо не состоялось из-за проблем с телефонным соединением. Ну что ж. Тем больше времени у разработчиков чтоб рассказать ван Эмдену о проекте. Путь к этому (частичному) успеху, запустившему Эдинбургский университет в TOP4 центров по разработке ИИ, не был прямым [Emde06]. +Martin van Emden once read a crazy story in New Scientist. In Edinburgh, certain Burstall and Popplestone challenged MIT and wrote a multi-user OS in their own language for interactive programming! And in that year, 1968, an IFIP conference was held in Edinburgh. The conference was not about functional programming, or even about programming at all, but about everything related to the use of computers. There was not much related to that yet, so one conference was enough for everything. Van Emden was still submitting Algol programs on paper tape to be run someday and very much wanted to see interactive programming and a language more advanced than Algol. The Amsterdam Mathematical Centre sent van Emden to the conference. +Of course, a demo of the Edinburgh system was planned for the conference. But apart from van Emden, no one came. And the demo did not take place due to problems with the telephone connection. Oh well. All the more time for the developers to tell van Emden about the project. The path to this (partial) success, which propelled the University of Edinburgh into the top 4 AI development centers, was not straightforward [Emde06]. -### Род Бурсталл +### Rod Burstall -Род Бурсталл (Rodney Martineau Burstall) - физик [Burstall] сначала работавший кем-то вроде тех, кого сейчас называют "квант", а затем программистом [Ryde2002]. Бурсталл как-то раз разговорился с другим покупателем в книжном магазине. Этот другой покупатель - Мервин Прагнелл (Mervyn Pragnell) - организовывал подпольный семинар по логике в Лондоне. Подпольный потому, что что официально Лондонский Университет не имел к нему никакого отношения. На этих семинарах в 61-ом году Бурсталл познакомился с Ландином, а Ландин научил его лямбда-исчислению и функциональному программированию в пабе "Герцог Мальборо". -Связи в функциональном подполье не обошлись без последствий. Несколько лет спустя, в 1964-ом году Бурсталлу позвонил некий Дональд Мики (Donald Michie) и предложил работу в "Группе Экспериментального Программирования", или может быть в экспериментальной группе программирования в Эдинбургском университете. Бурсталл согласился. -Вскоре выяснилось, что кандидатуры рекомендовал Стрейчи, имея в виду, что первые месяцы будущий сотрудник будет работать у него над CPL [Burs2000], тем более, что у экспериментальной группы пока что не было компьютера, так что экспериментальное программирование в ней откладывалось. -Работа Бурсталла над CPL, как это обычно бывало с работой над CPL, не имела особо хороших последствий для CPL. Но прежде чем отправиться работать над CPL в октябре 65-го, Бурсталл познакомился со своим коллегой Поплстоуном. +Rodney Martineau Burstall was a physicist [Burstall] who first worked as something like what we now call a "quant," and then as a programmer [Ryde2002]. Burstall once struck up a conversation with another buyer in a bookshop. The other buyer, Mervyn Pragnell, organized an underground logic seminar in London. Underground because officially the University of London had nothing to do with it. At these seminars in 1961 Burstall met Landin, and Landin taught him lambda calculus and functional programming in the Duke of Marlborough pub. +Connections in the functional underground did not come without consequences. Several years later, in 1964, Donald Michie called Burstall and offered him a job in the "Experimental Programming Group," or perhaps the experimental programming group at the University of Edinburgh. Burstall agreed. +It soon turned out that the candidature had been recommended by Strachey, with the idea that the new staff member would spend the first months working with him on CPL [Burs2000], especially since the experimental group did not yet have a computer, so experimental programming there was postponed. +Burstall's work on CPL, as was often the case with work on CPL, did not have particularly good consequences for CPL. But before going to work on CPL in October 1965, Burstall met his colleague Popplestone. -### Робин Поплстоун +### Robin Popplestone -Робин Поплстоун (Robin John Popplestone) - математик, увлекшийся программированием после того, как впервые попробовал программировать в университете Манчестера на одном из тех самых Атласов. Поплстоун хотел имплементировать логику, но обнаружил, что для удобной работы с деревьями мало аллокаций только на стеке. Чтоб разобраться, как программисты решают такие проблемы, он посетил ту самую летнюю школу по "нечисленным вычислениям", которая популяризировала псевдоCPL [Fox66] (Бурсталл также был знаком с этими лекциями, он писал рецензию на книгу [Burs67]). Наибольшее впечатление на него произвели доклад Стрейчи по CPL, LISP, расширения ALGOL 60 для работы со списками от радиолокационного института, доклад Ландина про то, как использовать стек и сборщик мусора для вычисления выражений. С этого времени Поплстоун стал последовательным пропонентом использования сборщика мусора. -Проблема была в том, что если когда-то у него был доступ к компьютеру, но не было нужных знаний и идей, то сейчас знаний и идей было достаточно, но уже не было доступа к компьютеру, на котором он мог бы их воплощать в жизнь. Теперь он преподавал математику в университете Лидса и программировал на существенно более ограниченной университетской машине. Настолько, что выражения на Лиспе не помещались в "быструю" часть памяти, и Поплстоун придумал и имплементировал стековый язык COWSEL. Разумеется, это будет бесполезно на следующих машинах, но недостатки будут жить и десятилетия спустя. -Кстати, о следующей машине: вот и она. Новая университетская машина, правда, работала только в пакетном режиме, а Поплстоун привык к интерактивной разработке и не обладал важной для программиста того времени способностью писать все правильно с первого раза. Или хотя-бы с сотого. Это означало, что то что Поплстоун делал на предыдущей машине за вечер, теперь могло требовать месяцы. Поплстоун так и не смог освоить старый подход к программированию и перенести на новую машину свой язык и пользоваться улучшенной производительностью. -Ситуация складывалась не радужная. Поплстоун преподавал в Лидсе математику, к которой испытывал все меньше интереса и выпрашивал машинное время на более совместимой с ним машине за пределами университета для своих экспериментов. К счастью, на дворе весна 65-ого года и некий Дональд Мики заглянул в Лидс в поисках каких-нибудь "нечисленных вычислений". Вычисления были обнаружены и Поплстоун приглашен сделать доклад в Эдинбурге. Там он познакомился с Бурсталлом, который уговорил его экспериментально программировать в экспериментальной группе, что удалось очень легко [Popplestone]. +Robin John Popplestone was a mathematician who became fascinated with programming after first trying to program at the University of Manchester on one of those Atlas machines. Popplestone wanted to implement logic, but discovered that for convenient work with trees, stack-only allocation was not enough. To understand how programmers solve such problems, he attended that summer school on "non-numerical computation" that popularized pseudoCPL [Fox66] (Burstall was also familiar with these lectures; he wrote a review of the book [Burs67]). The greatest impression on him was made by Strachey's talk on CPL, LISP, ALGOL 60 extensions for list processing from the radar institute, and Landin's talk about how to use a stack and garbage collector to evaluate expressions. From that time, Popplestone became a consistent proponent of garbage collection. +The problem was that if at one time he had access to a computer but not the necessary knowledge and ideas, now he had the knowledge and ideas but no access to a computer on which he could put them into practice. He now taught mathematics at the University of Leeds and programmed on a much more limited university machine. So limited that Lisp expressions did not fit in the "fast" part of memory, and Popplestone invented and implemented the stack language COWSEL. Of course, this would be useless on the next machines, but the shortcomings would live on for decades. +By the way, about the next machine: here it is. The new university machine, however, worked only in batch mode, and Popplestone was used to interactive development and did not have the programmerly skill of getting everything right the first time. Or even the hundredth. This meant that what Popplestone did in one evening on the previous machine could now take months. Popplestone never managed to master the old approach to programming and port his language to the new machine to enjoy improved performance. +The situation looked grim. Popplestone taught mathematics in Leeds, which he found increasingly uninteresting, and begged for machine time on a more compatible machine outside the university for his experiments. Luckily, it was the spring of 1965, and Donald Michie dropped by Leeds looking for some "non-numerical computations." The computations were found, and Popplestone was invited to give a talk in Edinburgh. There he met Burstall, who persuaded him to do experimental programming in the experimental group, which was easy to do [Popplestone]. -### Энергия маленькой целевой машины. +### The Energy of a Small Target Machine -> Для измученного имплементатора с маленькой целевой машиной мысль о том, что ему придется писать код для поиска свободных переменных в теле процедуры, и организовывать присвоение им значений перед выполнением этого тела, казалась слишком сложной. -> Поплстоун, Р. Дж. Ранняя разработка POP [Popplestone]. +> For a weary implementer with a small target machine, the thought that he would have to write code to find free variables in a procedure body and organize assigning values to them before executing that body seemed too complex. +> Popplestone, R. J. The Early Development of POP [Popplestone]. -Экспериментальная группа Дональда Мики была задуман им как воспроизведение другого, существенно более масштабного эксперимента "Project MAC". В разные времена аббревиатура MAC расшифровывалась по разному, а в обсуждаемое время в первую очередь как Multiple Access Computer. Одна из первых (если не первая) система разделения времени была крупным успехом МТИ, привлекшим финансирование, которое было использовано для следующих, не таких успешных проектов. Мики посетил МТИ, и успехи проекта MAC, а также ростки будущих неуспехов проекта MAC произвели на него сильное впечатление. Он решил организовать что-то похожее в Великобритании, но поменьше и подешевле. -Проект мини-МАК - и в узком и в расширенном смысле - должен был не просто воспроизвести большой МАК, а должен был сделать это другим путем. Это было хорошо для всего, что не любили в проекте МАК, например решатели теорем и логическое программирование [Emde06]. Хорошо и для того, к чему там были просто равнодушны, как к функциональному программированию. Проблема была в том, что некоторые вещи необходимые для ФП, такие как сборка мусора, в проекте МАК (пока что) любили. И, разумеется, в экспериментальной группе собирались все писать на ALGOL 60. -Это угрожало полной катастрофой всем замыслам Поплстоуна. Он доказывал, что использование языка без сборщика мусора - ошибка, а добавить сборщик в имплементацию Алгола сложно потому, что не известно где на стеке указатели. Особых результатов это не принесло, но тут - в начале 66-го - экспериментальная группа наконец-то получила компьютер Elliott 4120. Поплстоун сразу же начал тайную имплементацию своего языка на нем, и вскоре имплементировал достаточно, чтоб языком заинтересовался Бурсталл. -Бурсталл организовал для Мики демонстрацию, на которой тот попросил Поплстоуна написать небольшую функцию прямо у него на глазах, что тот легко сделал. Увидев интерактивное программирование, Мики стал энтузиастом языка. Не понравилось Мики только название COWSEL, так что как-то раз Поплстоун вернувшись из отпуска обнаружил, что язык уже называется POP (Package for Online Programming). -Вскоре после того, как первая версия была готова и описана в апреле 66-го, а сборка мусора и интерактивное программирование завоевали себе место в мини-версии проекта MAC, Поплстоун и Бурсталл приступили к созданию POP-2. -Примечательно то, что проработавший три года программистом в индустрии Бурсталл играл роль "теоретика", а программировавший в свободное от преподавания математики время Поплстоун - "практика". Видимо оба очень хотели передохнуть от того, чем занимались на работе. -Разработку сначала планировали начать с нуля, чего не произошло. POP-2 унаследовал, по видимому, самую необычную фичу POP-1 - "открытый" стек. Как авторы POP, так и авторы CPL утверждают, что POP-2 произошел от CPL [Camp85] [Rich2000]. И это утверждение не так легко объяснить, даже с учетом крайне разреженного дизайн-пространства языков в те годы. Даже если выбирать из двух языков со сборкой мусора, одного имплементированного частично и с ошибками и второго, по большому счету, вообще воображаемого, то LISP выглядит как более правдоподобный предок, чем CPL. Изначально, возможно, планировалось сделать язык похожий на CPL, но ряд ключевых решений оттуда были отвергнуты Поплстоуном по "практическим" соображениям и заменены решениями, которые сам же Поплстоун впоследствии называл "ошибками" и "хаками". -В сначала POP-2 планировали лексическую видимость как в ALGOL 60 (и CPL), но решили сделать динамическую. Это решение Поплстоун обосновывал проблемами имплементации: для того чтоб сделать и "открытый" стек и лексическую видимость на целевой машине было слишком мало регистров. Также Поплстоун считал, что язык с динамической видимостью проще отлаживать. -Поплстоун еще и планировал имплементировать функции высшего порядка с помощью метапрограммирования, как в LISP, для чего достаточно дать программисту доступ к компилятору как функции. Что было не как в LISP, так это то, что Бурсталл знал, что этого недостаточно. +Donald Michie's experimental group was conceived by him as a reproduction of another, much larger experiment, "Project MAC." At different times the acronym MAC was expanded differently, and in the period in question primarily as Multiple Access Computer. One of the first (if not the first) time-sharing systems was a major MIT success that attracted funding used for subsequent, less successful projects. Michie visited MIT, and the successes of Project MAC, as well as the seeds of its future failures, made a strong impression on him. He decided to organize something similar in the UK, but smaller and cheaper. +The mini-MAC project - both in the narrow and the broad sense - was not supposed to merely reproduce the big MAC, but to do it in a different way. This was good for everything that was disliked in Project MAC, such as theorem provers and logic programming [Emde06]. It was also good for what they were simply indifferent to there, such as functional programming. The problem was that some things necessary for FP, such as garbage collection, were (so far) loved in Project MAC. And of course, in the experimental group they were going to write everything in ALGOL 60. +This threatened a complete catastrophe for all of Popplestone's plans. He argued that using a language without garbage collection was a mistake, and that adding a collector to an ALGOL implementation was difficult because it was unknown where pointers were on the stack. This did not bring much result, but in early 1966 the experimental group finally received an Elliott 4120 computer. Popplestone immediately began a secret implementation of his language on it, and soon implemented enough that Burstall became interested in the language. +Burstall organized a demonstration for Michie, in which he asked Popplestone to write a small function right in front of him, which he easily did. Seeing interactive programming, Michie became an enthusiast of the language. Michie disliked only the name COWSEL, so one day Popplestone returned from vacation and discovered that the language was already called POP (Package for Online Programming). +Soon after the first version was ready and described in April 1966, and garbage collection and interactive programming had won their place in the mini version of Project MAC, Popplestone and Burstall began creating POP-2. +It is notable that Burstall, who had worked for three years as an industry programmer, played the role of "theorist," while Popplestone, who programmed in the time free from teaching mathematics, was the "practitioner." Apparently both wanted a break from what they were doing at work. +Development was initially planned from scratch, which did not happen. POP-2 inherited, apparently, the most unusual feature of POP-1: an "open" stack. Both the POP authors and the CPL authors claim that POP-2 descended from CPL [Camp85] [Rich2000]. This claim is not easy to explain, even given the extremely sparse design space of languages in those years. Even if one chooses between two garbage-collected languages, one implemented partially and with errors and the other largely imaginary, LISP looks like a more plausible ancestor than CPL. Initially, it may have been planned to make a language similar to CPL, but a number of key decisions from there were rejected by Popplestone on "practical" grounds and replaced by solutions that Popplestone himself later called "mistakes" and "hacks." +At first POP-2 planned lexical scope as in ALGOL 60 (and CPL), but decided to make it dynamic. Popplestone justified this decision by implementation problems: to have both an "open" stack and lexical scope on the target machine there were too few registers. Popplestone also believed that a language with dynamic scope is easier to debug. +Popplestone also planned to implement higher-order functions via metaprogramming, as in LISP, for which it is sufficient to give the programmer access to the compiler as a function. What was not like LISP was that Burstall knew this was not enough. ``` function funprod f g; @@ -825,7 +824,7 @@ function funprod f g; end; ``` [Burs68] -И наш традиционный пример будет выглядеть так: +And our traditional example will look like this: ``` function map f l; @@ -836,15 +835,15 @@ vars y; 1 -> y; map (lambda x y; x + y end(% y %)) [1 2 3] ``` -Поплстоун позднее вспоминал, что математика была их с Бурсталлом общим языком, который позволил им согласовать их цели в разработке POP-2 и достичь того, что язык, при всех его странностях можно было использовать в функциональном стиле. Но их сотрудничество ограничивало то, что Бурсталл интересовался применением математических идей более последовательно, а Поплстоун неохотно поддерживал его формальный подход [Popp2002]. -Можно ли его было использовать в функциональном стиле? К этому мы вернемся позже, когда будем обсуждать рождение функционального стиля. Но можно сказать определенно, что языки Эдинбургской программы оказались основаны не на нем, а на тех языках, использовать которые в функциональном стиле было удобнее. -Компромиссные фичи, срезания углов и остатки POP-1 сильно подорвали модернизационный потенциал POP-2. Например, в Эдинбурге добавили статическую типизацию к ISWIM, но добавить ее к POP-2 оказалось слишком сложно: попытавшийся это сделать позднее Поплстоун обнаружил, что из-за открытого стека сигнатуры функций будут тайплевел-парсерами и посчитал, что не стоит связываться с такими сложностями [Popp2002]. -Важны ли были эти компромиссы для практичности? Сомнительно. Сейчас нам известно более-менее достоверно, что тот, кто может позволить такие дорогие фичи как сборка мусора или динамическая "типизация" - может позволить и лексическую видимость и компилятор, который сам ищет свободные переменные в лямбдах. -Получился ли язык практичным? Он определенно применялся для написания программ больше, чем PAL и прочие ISWIMы того времени, но это не очень высокая планка. На нем была написана первая версия решателя теорем Бойера-Мура, одной из немногих программ, которая была переписана на LISP, а не с LISPа на какой-то другой язык. Первая имплементация POP-2 и более поздние для PDP-10 использовались для имплементации языков Бурсталла. Имплементация 80-х годов служила бэкендом для компилятора SML. Установленным в первой главе критериям практичности он соответствует. И для этого не понадобилось приносить все функциональные фичи в жертву как в BCPL. Это шаг вперед, по сравнению с обоекембриджской программой. Но не все в POP-2 было шагом назад по сравнению даже с планами для CPL. Но о прогрессивных фичах POP-2 и его влиянии на языки Эдинбургской программы мы подробнее расскажем ниже в соответствующих главах. -Конечно проблемы с практичностью у POP-2 были, сборка мусора мешала программировать роботов, а из-за динамической "типизации" компилятор генерировал медленный код, который проверял теги и вызывал функции для каждой арифметической операции, что мешало писать на нем код для распознавание образов. Но такие проблемы не решены полностью и у современных "динамических" языков со сборкой мусора. [Popp2002] -Почему на POP-2 вообще пытались писать код для управления роботами и распознавания изображений? Потому что он стал тем, чем хотели сделать CPL - единственным языком единственной машины, используемой в экспериментальной группе. И был он единственным не потому, что лучше всего подходил для всего "экспериментального программирования", которым в ней занимались. Операционная система Multipop68, ставшая ответом мини-MACа MACу большому, была системой основанной на языке, безопасность в которой обеспечивается инкапсуляцией и проверками языка. В данном случае проверками тегов в рантайме. -Первоначальный план с аппаратной изоляцией процессов не удалось осуществить потому, что университет не смог приобрести подходящие жесткие диски для свопа. Со временем в Эдинбурге сделали более традиционную систему разделения времени с аппаратной защитой и свопом Multipop 70, но пользователи не любили её за медлительность и предпочитали продолжать пользоваться Multipop68 до получения новой машины PDP-10 в середине 70-х. -Пока POP-2 был обязательным к нему привыкли, он был портирован на другие машины и внедрен в других университетах, стал популярным языком в британском ИИ, как Лисп в американском [Popplestone] [Popp2002]. Мики в 1969-ом даже основал компанию Conversational Software Ltd, которая пыталась POP-2 коммерциализировать [Howe07]. +Popplestone later recalled that mathematics was the common language between him and Burstall, which allowed them to align their goals in developing POP-2 and achieve that the language, with all its oddities, could be used in a functional style. But their collaboration was limited by the fact that Burstall was more consistently interested in applying mathematical ideas, while Popplestone was reluctant to support his formal approach [Popp2002]. +Could it be used in a functional style? We will return to this later, when we discuss the birth of the functional style. But we can say definitively that the languages of the Edinburgh program were based not on it, but on those languages in which it was more convenient to use a functional style. +Compromise features, corner-cutting, and remnants of POP-1 seriously undermined POP-2's modernization potential. For example, in Edinburgh they added static typing to ISWIM, but adding it to POP-2 proved too difficult: when Popplestone later tried, he found that due to the open stack, function signatures would be type-level parsers and decided it was not worth dealing with such complexities [Popp2002]. +Were these compromises important for practicality? Doubtful. We now know more or less reliably that anyone who can afford such expensive features as garbage collection or dynamic "typing" can also afford lexical scope and a compiler that itself finds free variables in lambdas. +Did the language turn out practical? It was definitely used to write programs more than PAL and other ISWIMs of the time, but that is not a very high bar. The first version of the Boyer-Moore theorem prover was written in it, one of the few programs that was rewritten into LISP rather than from LISP to some other language. The first POP-2 implementation and later ones for the PDP-10 were used to implement Burstall's languages. The 1980s implementation served as a backend for the SML compiler. It meets the practicality criteria established in the first chapter. And this did not require sacrificing all functional features as in BCPL. This is a step forward compared to the two-Cambridge program. But not everything in POP-2 was a step backward even compared to the plans for CPL. We will discuss the progressive features of POP-2 and its influence on the languages of the Edinburgh program below in the relevant chapters. +Of course POP-2 had problems with practicality: garbage collection interfered with programming robots, and because of dynamic "typing" the compiler generated slow code that checked tags and called functions for each arithmetic operation, which made it hard to write image recognition code. But such problems are not fully solved even in modern "dynamic" languages with garbage collection [Popp2002]. +Why did they even try to write robot control and image recognition code in POP-2? Because it became what they had wanted to make CPL: the single language of the single machine used in the experimental group. And it was the single language not because it was best suited for all "experimental programming" done there. The Multipop68 operating system, the mini-MAC's answer to the big MAC, was a language-based system whose safety was ensured by encapsulation and language checks. In this case, tag checks at runtime. +The original plan with hardware process isolation could not be carried out because the university could not purchase suitable hard disks for swapping. Over time, Edinburgh built a more traditional time-sharing system with hardware protection and swapping, Multipop 70, but users disliked its slowness and preferred to continue using Multipop68 until the arrival of a new PDP-10 machine in the mid-1970s. +While POP-2 was mandatory, people got used to it, it was ported to other machines and deployed at other universities, and became a popular language in British AI, like Lisp in American AI [Popplestone] [Popp2002]. In 1969 Michie even founded Conversational Software Ltd, which tried to commercialize POP-2 [Howe07]. ### Filtrage de motif diff --git a/ru/compilers.md b/ru/compilers.md new file mode 100644 index 0000000..e507faf --- /dev/null +++ b/ru/compilers.md @@ -0,0 +1,2218 @@ +История применения и оценки функционального программирования. +======= + +- [История применения и оценки функционального программирования.](#история-применения-и-оценки-функционального-программирования) +- [Часть 1: Каталог компиляторов.](#часть-1-каталог-компиляторов) + - [От Луки](#от-луки) + - [Лука Карделли](#лука-карделли) + - [Пари PASCAL](#пари-pascal) + - [FAM](#fam) + - [Диалоги](#диалоги) + - [Сумма против Милнера](#сумма-против-милнера) + - [Ссылки больше не против](#ссылки-больше-не-против) + - [Детали](#детали) + - [Без сборщика и со сборщиком.](#без-сборщика-и-со-сборщиком) + - [Разъединение](#разъединение) + - [Новые эксперименты с VAX](#новые-эксперименты-с-vax) + - [Редукция графов и как её избежать](#редукция-графов-и-как-её-избежать) + - [Леннарт Августссон](#леннарт-августссон) + - [SIMPLE Made Easy](#simple-made-easy) + - [SKI не едут](#ski-не-едут) + - [Ленивый ML](#ленивый-ml) + - [Неполная ленивость](#неполная-ленивость) + - [Еще ортогональнее](#еще-ортогональнее) + - [Джон Хьюз и великие комбинаторы](#джон-хьюз-и-великие-комбинаторы) + - [Полная ленивость](#полная-ленивость) + - [Чайники Рассела](#чайники-рассела) + - [Russell](#russell) + - [Poly](#poly) + - [Воспроизведение](#воспроизведение) + - [Живые ископаемые](#живые-ископаемые) + - [Насколько "мини" может быть мини-MAC?](#насколько-мини-может-быть-мини-mac) + - [Жерар Юэ](#жерар-юэ) + - [Лоуренс Полсон](#лоуренс-полсон) + - [PSL](#psl) + - [Le Lisp](#le-lisp) + - [Cambridge ML](#cambridge-ml) + - [Лисп, который покончит с Лиспами](#лисп-который-покончит-с-лиспами) + - [Какой вы маклиспер?](#какой-вы-маклиспер) + - [Достаточно смертельная ловушка](#достаточно-смертельная-ловушка) + - [Мэри Поппинс, до свидания](#мэри-поппинс-до-свидания) + - [True == False](#true--false) + - [Туда и обратно](#туда-и-обратно) + - [FEL](#fel) + - [ALFL](#alfl) + - [Компиляция ALFL](#компиляция-alfl) + - [Этого имени я не слышал уже давно](#этого-имени-я-не-слышал-уже-давно) + - [Ржавый пояс](#ржавый-пояс) + - [Машины освобождения](#машины-освобождения) + - [Норман и NORMA](#норман-и-norma) + - [Small](#small) + - [SKIM](#skim) + - [SKIM II](#skim-ii) + - [NORMA](#norma) + - [Скобочный потолок](#скобочный-потолок) + - [Персональный миникомпьютер](#персональный-миникомпьютер) + - [Персональный микрокомпьютер](#персональный-микрокомпьютер) + - [Сон Черного Короля](#сон-черного-короля) + - [Железное дерево](#железное-дерево) + - [The data must flow](#the-data-must-flow) + - [Группа функциональных языков](#группа-функциональных-языков) + - [Конец группы функциональных языков](#конец-группы-функциональных-языков) + - [Алиса в ржавом поясе](#алиса-в-ржавом-поясе) + - [Вообразите машины](#вообразите-машины) + - [Голатак](#голатак) +- [Литература](#литература) + + +Часть 1: Каталог компиляторов. +============================== + +Как мы выяснили в предыдущей - нулевой - части истории, к началу 80-х уже появился функциональный язык, похожий на современные - HOPE. Но имплементаторы ФЯ не все и не сразу захотели имплементировать языки, похожие на HOPE. Были желание и, главное, возможность имплементировать более необычные для нас функциональные языки. +Поэтому в первые годы десятилетия появилось несколько имплементаций ФЯ, которые только позднее стали имплементациями ФЯ, похожих на HOPE. Или были использованы как части таких имплементаций. Первая часть истории функционального программирования будет о них. +Наконец-то прошли времена, когда никакие идеи по имплементации функциональных языков не работали. С появлением относительно распространенных компьютеров с большой памятью заработали все. Конечно, не все одинаково быстро и одинаково хорошо. +На смену этим имплементациям придет новое поколение компиляторов, многие из которых будут написаны на языках, имплементированных с помощью первых компиляторов функциональных языков, использовать их в качестве бэкендов. Будут использовать первые имплементации для бутстрапа или, хотя-бы, прототипирования. Таким образом сформируются главные единицы нашей истории ФП - _семейства имплементаций_. В отличие от имплементаций ФЯ, большинство из которых давно заброшены, большинство семейств имплементаций живы и сегодня, хотя и могут влачить довольно жалкое существование. +Мы начинаем историю функционального программирования с компилятора, разработка которого началась в одном из важнейших мест, в которых разворачивалась предыстория. + +От Луки +------- + +> Прежде всего, следует понять, что, по крайней мере, потенциально, ML может быть скомпилирован очень эффективно. +> Л. Карделли, Компиляция функционального языка [Card84]. + +В то время, когда авторы HOPE позиционировали свой язык как едва исполняемую спецификацию, в том же Эдинбурге более известный другими своими работами Лука Карделли начал работу на более амбициозным проектом. + +### Лука Карделли + +Лука Карделли (Luca Andrea Cardelli) изучал компьютерные науки в Пизанском Университете (University of Pisa) в 73-78 годах. В том же городе, где шли работы над одним из первых языков уравнений TEL. Там Карделли получил доступ к IBM 370. Карделли хотел писать на Algol 68 [Card07], но на машине не было такого компилятора. Уже знакомый нам Algol68C, хоть и заработал на IBM 370 в конце 73-го [Birr77], но зарелизили его только в начале 80-го [ALGOL80]. Первый компилятор Algol 68 для IBM 370 со сборщиком мусора - FLACC - анонсировали в 78-ом году [Thom78]. Единственный компилятор, имплементировавший окончательный Algol 68 полностью, коммерциализированная версия компилятора FLASC, бывшего магистерской диссертацией [Thom76] как и RABBIT, генерирующий не такой быстрый код, как у компиляторов из Кембриджа и Молверна из-за большого числа проверок времени исполнения. +Все слишком поздно для Карделли. Как он говорит в 2007-ом году - "к счастью" [Card07]. Но в середине 70-х Карделли, как и Уоррену, Алгол 68 нравился. Большинство прочих доступных языков: Fortran, APL, Lisp 1.5, PL/1 Карделли не нравились вовсе. Оставался только один язык, которому, как вспоминает Карделли не было никакой альтернативы - SIMULA 67. На IBM 370 компилятор Симулы был доступен с мая 72-го [Holm98]. Как раз вовремя! Не смотря на то, какими работами Карделли наиболее известен, ООП ему не особенно понравилось. Понравилась ему сборка мусора, типизация, поддержка строк в языке. Классы он использовал для кодирования индуктивных типов и не считал особенно удобными для этого. +Карделли вспоминает, что в студенческие годы познакомился с функциональным программированием и Схемой, но не упоминает Схему как язык-кандидат, рассматриваемый им для какого-то практического использования. Как мы писали в главе про уравнения, в Пизе работали люди, которые интересовались результатами Эдинбургской исследовательской программы, но Карделли, по видимому, ознакомился с Эдинбургскими работами только в Эдинбурге. +Карделли прибыл в Эдинбург и стал работать над диссертацией в ноябре 78-го [Card21] [MacQ15] под руководством Плоткина. Это плохо согласуется с тем, что остатки бывшей Группы Экспериментального Программирования влились в Департамент Компьютерных Наук Эдинбургского Университета через год после этого, в конце 79. Может быть, Плоткин сделал это раньше Бурсталла, а может быть это событие датировано неправильно. Если это слияние двух ветвей Эдинбургской программы произошло раньше - некоторые странности и несоответствия истории HOPE исправляются, но слабое влияние HOPE на ранние работы Карделли становится еще более странным. Это слабое влияние, впрочем, делает сам вопрос не очень важным. +Также Карделли изучил Паскаль и посчитал его "приятным языком для имплементации". И это было не очень популярное мнение в Эдинбурге в частности и среди имплементаторов функциональных языков вообще. Судя по тому, что долгое время не было много желающих работать над программой, которую он писал на Паскале. Что он писал на Паскале? +Диссертация Карделли была вовсе не об имплементации компилятора языка общего назначения, как у Уоррена или Стила, а об описании и верификации железа [Card82d]. Какое он имеет отношение к нашей истории? Карделли писал программы для форматирования текста своей диссертации [MacQ14]. И для новой машины Эдинбургского Университета, VAX-11/780, нет компилятора Симулы, которой Карделли привык пользоваться, пока был студентом в Италии. Не беда. Карделли вспоминает [Card07], что в Эдинбурге "наконец-то" узнал о первом языке программирования, который он посчитал "приятным". Об ML. +К сожалению, ML - это не язык на котором можно писать какие-то программы для обработки текстов. И первоначальные авторы не собираются его таким делать [Miln93] [Miln2003]. Но Карделли решил: Почему нет? Почему бы мне не имплементировать первый компилятор для функционального языка, на котором я хочу писать код для форматирования текста моей диссертации? На Паскале - языке, приятном языке для имплементации ФЯ. И имплементировал. Потому что мог. + +### Пари PASCAL + +На самом-то деле - поясняет Карделли - ML можно компилировать в быстрый код [Card84]. У функционального языка Эдинбургской программы ряд важных свойств, облегчающих его эффективную имплементацию: + +* статические (лексические) области видимости - лексический контекст известен во время компиляции. +* статическая типизация - нет необходимости в информации о типах и ее проверке в рантайме. +* регулярность применения функций - в большинстве случаев можно использовать стек, как в Алголах. +* паттерн-матчинг позволяет оптимизировать проверки. +* описания типов данных достаточно высокоуровневые, чтобы можно было оптимизировать их расположение в памяти. +* абстрактные типы данных - позволяют использовать эффективные структуры данных вроде массивов и битмапов. + +Как и Уоррен, Карделли посчитал решениями многое из того, что его предшественники считали проблемами. +Но не все так радужно, для эффективной компиляции ФЯ есть и препятствия, основное из которых - полиморфизм. Полиморфизм требует универсального представления, боксинга (почти) всех данных, чтоб они были одинаково представлены - как указатели, что не полезно для производительности и потребления памяти. +Типы данных, которые можно описывать в LCF/ML тоже имеют плохое представление в памяти. Потому, что собираются из пар как придумал МакКарти и типизировал Моррис. +Та же проблема и у структур в памяти, которые создают имплементации ФЯ. Все эти стеки-списки, окружения-списки, замыкания-списки желательно заменить на плоские структуры. +Наконец, использовать абстрактные типы данных для представления изменяемых структур данных в LCF/ML нельзя из-за ограничений `letref`. +Нужно решить массу проблем. + +### FAM + +К октябрю 1980 Карделли описал виртуальную машину для имплементации ML и прочих ФЯ на машинах с большой памятью. Машину он называл сначала AM [Card80], а позднее FAM (Functional Abstract Machine). Это SECD, в которой используемые там для всего списки заменены на массивы и сделаны некоторые изменения для более быстрой имплементации функций. Эти изменения: + +* три отдельных стека для аргументов, для возвратов и для имплементации исключений. +* операция применения функций разделена на три: сохранение фрейма, восстановление фрейма и собственно само применение в новом окружении, создаваемом и "демонтируемом" предыдущими операциями. +* оптимизация хвостового вызова. +* "плоские" замыкания. + +Замыкание представлено в памяти массивом ссылок. То, что собирался делать Стил в RABBIT, да так и не собрался. Замыкания должны разделять между собой мутабельные ячейки и замыкания из списков (как в SECD и в RABBIT) позволяют осуществить такое. Но цена не устраивала Карделли. Поскольку он предполагал, что использоваться будут, в основном, неизменяемые переменные он выбрал имплементацию, которая делает дороже использование изменяемых переменных: значения изменяются не в массиве, представляющем замыкание, а в ячейке кучи, на которую есть указатель в этом массиве. Замыкания "плоские", но только для неизменяемых переменных. Имплементация Схемы или языков вроде обоекембриджских ISWIM-ов PAL и McG потребовала бы для этого нелокального анализа, но в LCF/ML мутабельность явная. Удобно! +Массивы доступны и для пользователя виртуальной машины. И массивы обычных объектов и специальные строки. +(F)AM использовала более компактное представление данных [Card80] [Card83], чем LCF/ML. +В слово на VAX-11 помещается только один указатель, так что, анбоксинг не требует ухищрений, которые были необходимы на PDP-10. Вместо указателя на объект в каждое слово можно поместить целое число довольно полезного размера, хотя полным 32-битным диапазоном придется пожертвовать. Разумеется, один бит нужно потратить на то, чтоб отличать указатель от числа или еще какого-нибудь значения, которое можно закодировать как целое число. +Один бит используется для этого сегодня в OCaml, но Карделли различает указатели и целые числа не так. Карделли умещает на место указателя существенно менее полезное число - только 16-бит. Все, что больше 65536 - указатель. +Дополнять эти некрупные целые должны были сначала 32-битные числа с плавающей точкой в куче [Card80]. Позднее Карделли передумал и решил имплементировать длинные целые [Card83]. Предполагалось, что значения одного и того же типа могут быть представлены как 32-бит слово с 16-бит. целым числом или как указатель на массив в куче, представляющий настолько длинное число, насколько длинным позволяет быть ограничение на размер массива. Массивы, разумеется, разрешены не очень большие - до 64Кб. +Но поддержка длинных целых не будет имплементирована до 84-го года [Card84b]. Все это время ML Карделли будет поддерживать только 16-бит целые числа и никакие другие. +Как небольшие целые числа кодируются и некоторые другие значения: пустой список, `true` и `false`, аналог хаскельного типа `()` [Card80] [Card83]. +Также машина поддерживает рекорды со многими полями, они не ограничены парами. Но представление для вариантов все еще как у МакКарти, требует лишний уровень косвенности, как и изменяемая ссылка. Это ссылка на пару из ссылки на объект кучи и тега. Почему-то именно в таком порядке [Card83]. +Описывая виртуальную машину, Карделли отмечает, что сборщик мусора надо использовать с поколениями, как у Хьюита и Либермана. И ссылается на более позднюю версию их отчета [Lieb81]. Но сначала сборщик мусора - это только планы, что уже не должно удивлять читателя. Когда же планы воплощены в реальность - сборщик все равно был не сборщиком Хьюита и Либермана. Карделли имплементировал копирующий сборщик [Card82a]. Рекурсивный, даже не алгоритм Чейни, так что сборщик падал, обходя списки в тысячи элементов [Card84b]. Сначала размер кучи не менялся. Рантайм Карделли занимал как минимум 1Мб памяти. 512Кб доступно для программы на ML, еще 512Кб резервировалось для копирования. Пользователь системы не мог установить этот размер ни при запуске, ни при компиляции программы на ML. Размер кучи - константа в рантайме на Паскале, который нужно пересобирать, чтоб изменить значение. Карделли пишет что минимальная куча, которая позволяет коду на ML работать ("но не долго") - 8Кб [Card82a]. Позднее размер кучи стал изменяться автоматически [Card83b], но рекурсивность алгоритма Карделли так и не исправил [Card84b]. +Фиксированная ML-куча была только минимальной оценкой требуемой памяти потому, что помимо ML-кучи, была еще и куча без сборки мусора, которая использовалась компилятором и постоянно росла. Почему компилятор занимает какую-то память во время исполнения? Это необычный компилятор. + +### Диалоги + +Но необычный не по тем причинам, по которым был необычным RABBIT. Компилятор Карделли совсем не похож на RABBIT, на который Карделли ссылается [Card84]. Вместо десятка проходов только четыре: парсинг, проверка типов, компиляция в стековую виртуальную машину и генерация VAX машкода. Вывод и проверка типов совмещались с вычислением свободных переменных и смещения стека. +В компиляторе Карделли вместо сложных нелокальных оптимизаций RABBIT, трансформирующих абстрактные синтаксические деревья, оптимизации напоминающие скорее компилятор Уоррена. На который Карделли не ссылается. Оптимизатор работает с последовательностью команд виртуальной машины и заглядывает в этой последовательности не очень далеко. Например, поддержка быстрого карринга реализуется как выбрасывание из последовательности операций виртуальной машины для применения функции `f x y` + +``` +сохранить фрейм, применить, восстановить фрейм, сохранить фрейм, применить, восстановить фрейм +``` + +сочетаний `восстановить фрейм, сохранить фрейм`. Оптимизация хвостового вызова делается не трансформацией всего кода как в RABBIT, а заменой набора операций + +``` +сохранить фрейм, применить, восстановить фрейм, возврат +``` + +на специализированную операцию `хвостовое применение`. Типичный подход в компиляторе Уоррена. +Как в компиляторах Пролога Уоррена, BCPL Ричардса, как в компиляторе Algol 68C, эти операции виртуальной машины разворачиваются в код по шаблону. Это довольно распространенный способ имплементации. В отличие, например, от того, который использовал Стил в RABBIT. Получающийся в результате код не особенно хороший. Например, нет хорошего распределения регистров. Карделли пишет, что в это время считается, что пользы от хорошего использования регистров для ФЯ нет вовсе. Сам Карделли несколько дистанцируется от этого мнения и не говорит, что так считает он сам. Но он считает, что пока сойдет и так. +Но то, что компилятор Карделли использует виртуальную машину, как многие другие, еще не означает, что это обычный компилятор. Есть то, что делает компилятор карделли непохожим на другие. +В отличие от компилятора Уоррена, Карделли не использует системный микро-ассемблер. Почему? Компилятор Карделли интерактивный. Для каждого вводимого пользователем или загружаемого из файла выражения производится компиляция. Компиляция целиком происходит в памяти и результат подлинковывается к коду, полученному на предыдущих шагах REPL. Каждое выражение компилируется в окружении, сформированном на предыдущих шагах - статическая видимость даже на топлевеле. +Карделли пишет, что компилятор выглядит необычно потому, что он не был знаком с тем как пишут обычные компиляторы. В качестве примера таких знаний, которые ему удавалось избегать до поры - книга зеленого дракона Ульмана и Ахо. +Но Карделли, в отличие от имплементаторов LCF/ML и HOPE, умел парсить менее экзотическим способом. Карделли написал парсер рекурсивным спуском вручную. +Зато Карделли, по его заверениям, знаком с тем как имплементировались Лиспы и Схемы. Отсюда, объясняет он [Card84] и все странности. Мы уже отмечали, что сходство с RABBIT мало. Но давайте посмотрим, что думают о компиляторе Карделли лисперы. +Имплементатор Схем Кент Дибвиг (Kent Dybvig) в 84-ом году познакомился [Dybv06] с Карделли и тот показал ему свой интерактивный компилятор ML. +Он понравился Дибвигу и тот имплементировал Chez Scheme как интерактивный компилятор. Хотя первоначально планировал другой подход, который считал обычным для Лиспов - как пару из интерпретатора и компилятора. Интерактивный компилятор он, наоборот, считает совсем необычным для Лиспа. +Также как и Карделли, Дибвиг выбрал для своей Схемы и "плоское" представление замыканий, но еще до того, как познакомился с Карделли. Этот способ организации замыканий он тоже не считает лисповым, считает схожим с алголовыми "дисплеями" и почерпнул его из книги Рассела об имплементации Алгола. +В докладе [Card07] о SIMULA карделли говорит, что ML интерактивный и потому более объектно-ориентированный, чем SIMULA. Что бы не значило это загадочное утверждение, но наверное не значит то, что Карделли позаимствовал идею об интерактивном компиляторе у симулистов. +Куча, которую использует интерактивный компилятор только растет [Card82a]. Раз компилятор интерактивный - компиляция раздельная с очень небольшой минимальной единицей компиляции, как и у Уоррена. Но функциональные программисты, как мы видели в коде на HOPE, не всегда охотно делят код на такие небольшие единицы. Карделли рекомендует писать "небольшие" функции в 10-100 строк [Card84b]. +Фичи виртуальной машины Карделли позволяют компилировать обычный код на LCF/ML, но, чтоб использовать их полностью, в ML нужно добавить некоторые фичи, доступные непосредственно программисту. Что Карделли и сделал. + +### Сумма против Милнера + +Виртуальная машина Карделли поддерживает рекорды, в которых больше двух полей. И варианты, объединяющие более двух видов объектов кучи. Для того, чтоб программист на ML мог объявлять эти новые, менее ветвистые структуры данных, Карделли добавил новые конструкторы типов и значений в ML [Card82b]. +Не смотря на то, что представление в памяти у них скорее как у типов данных, которые придумывал и пытался продвигать Хоар - наборы полей со ссылками на другие наборы полей со ссылками - как языковая фича они ближе к типам данных в Algol 68. +Как типы данных в Algol 68, но не как типы данных Хоара или алгебраические типы данных, рекорды и варианты Карделли не требуют деклараций, создающих новые типы. Они все уже существуют как наборы пар имен и типов, порядок которых не имеет значения: + +``` +- let r = (|a=3; c="1"; b=true|); +> r = (|a=3; b=true; c="1"|) : (|a:int; b:bool; c:tok list|) +``` + +Рекорды-произведения у Карделли дополняют варианты-суммы. Это существенное отличие от типов-объединений Algol 68. С этим взглядом на структуры данных как на сочетание дополняющих друг друга произведений и сумм Карделли познакомил его научрук Плоткин [Card07]. В языке Карделли можно объединять объекты одного типа и строить сложные структуры, которые не коллапсируют в алголовское одноуровневое объединение. Программисту нужно придумывать названия для каждого тега. Но для имплементатора должно быть удобно, что не нужно получать теги из типов. Тем более в языке с параметрическим полиморфизмом и стиранием типов. В первом же примере Карделли особо не раздумывает и делает названия тегов именами типов: `[|int: int; bool: bool|]`. +Попробуем сконструировать значение такого типа. + +``` +- [|int=3|]; +Unresolvable Variant Type: [|int:int|] +``` + +Да, вторая версия системы МакКарти еще хуже для вывода типов, чем первая. Естественно, такой конструктор - не конструктор значения единственного типа, а конструктор значения любого из бесчисленного числа уже имеющихся типов, даже если они вам все и не нужны. Какого именно - придется часто указывать: + +``` +- [|int=3|]:[|int: int; bool: bool|]; +``` + +Поскольку каждый раз записывать структурный тип может быть хлопотно, как и в Algol 68 можно объявить короткое имя для них: + +``` +let type R = (|a:int; b:bool; c:tok list|); +let type OneOfTwo = [|int: int; bool: bool|]; +``` + +И, скорее, даже нужно. +Как и синонимы типов в Algol 68, синонимы типов в LCF/ML и в ML Карделли не параметризованы. Как и в Algol 68, хотя и по другой причине, в ML Карделли нельзя просто так взять и написать рекурсивный тип. Рекурсию нужно разбивать "прокладкой". Если в Algol 68 рекурсия не работает из-за плоского представления, то у Карделли из-за вывода типов. К счастью, в ML, в отличие от Algol 68, есть способ объявлять новые типы и решить проблемы с рекурсией и параметризацией - абстрактные типы данных: + +``` +let type * list <=> [| cons: (| head: *; tail: * list |); + nil |] +``` + +Разумеется, представление в памяти у такого списка, даже во второй версии системы МакКарти, хуже, чем у "непосредственной имплементации" с суммами произведений. Значение `abslist [| cons = (| head = 1; tail = abslist [|nil|] |) |]` представляется в памяти так: + +``` + + ┌───┐ ┌───┬───┐ + │ ├──►│ │ 0 │ + └───┘ └─┬─┴───┘ + │ + ▼ + ┌───┬───┬───┐ ┌───┬───┐ + │ 2 │ 1 │ ├──►│ 0 │ 1 │ + └───┴───┴───┘ └───┴───┘ + +``` + +Тег варианта в отдельном объекте от объекта-рекорда. Ячейка с числом `2`, перед той, на которую указывает ссылка - это размер рекорда, информация для сборщика мусора. +Поэтому для конструкторов списков специальные объекты в FAM, и список - все еще встроенный тип. +Для конструкторов вариантов типа `.` (в Хаскеле это `()`) есть специальные сокращенные имена и конструкторы. Например, тут можно писать просто `nil` вместо `nil: .` и конструировать `[|nil|]` вместо `[|nil=()|]`. Что удобно для определения перечислений: + +``` +let type color = [|red; orange; yellow|]; +``` + +Не смотря на то, что перечисление можно представлять числом, в FAM значения такого типа - объекты в куче, и занимают там больше одного слова. Первое слово - `0` соответствующий конструктору `()`, а сразу за ним число - тег. +Обращение к полям рекордов неожиданно современное и обычное: + +``` +- r.a; + 3 : int +``` + +Если у сумм проблемы с выводом типов при конструировании, то у произведений, понятное дело, проблемы с выводом типов при разборе: + +``` +- \r. r.a; +Unresolvable Record Selection of type: (|a:*a|) +Field selector is: a +``` + +У таких функций нужно или аннотировать тип или употреблять их тут же, в однозначном контексте: + +``` +- (\r. r.a) r; + 3 : int +``` + +Карделли пытался решить проблему с выводом типов подтипированием рекордов и вариантов, но, пока что, безуспешно [Card07]. +Для разбора сумм-вариантов используется конструкция `case` почти как у Бурсталла: + +```haskell +let rec map f xs = case replist xs of + [| cons = (| head; tail |) . abslist [| + cons = (| head = f head; tail = map f tail |)|]; + nil . abslist [| nil |] + |] +``` + +Для ПМ рекордов тоже есть сокращенный синтаксис. Можно писать `(| head; tail |)` вместо `(| head=head; tail=tail |)`. +Интересно, что тут, в отличие от Algol 68 и идей Хоара, паттерн-матчинг хотя-бы вложенный. Эта новая для ML разновидность паттерн-матчинга не очень хорошо сочетается со старой. Суммы и произведения можно матчить в `let` паттерне и даже как-то сочетать с разновидностями паттернов из LCF/ML. Но не всегда. `case` на первом уровне работает только для сумм, на что недвусмысленно указывает и его синтаксис, с такими же квадратными скобками. +В месте разбора суммы тип выводится, но потому, что этот `case` обязан перечислить все теги. И только один раз, так что в этом он ближе к `case` в core, чем к `case` в Haskell. Так что, скорее всего, не получится использовать старую разновидность ПМ в `case` с помощью такого вот трюка: + +``` +case [| l = xs |]:[| l: * list |] of + [| l = h :: t . ...; + l = [] . ... + |] +``` + +Но, надо полагать, что если бы пользователи ML Карделли хотели что-то такое использовать, то язык бы выглядел несколько иначе. В примерах работа со списками происходит по старому: + +```ocaml +let rec map f l = + if null l then [] + else f(hd l)::map f (tl l); +``` + +дополним этот пример до нашего традиционного примера: + +``` +map (\x. x + y) [1; 2; 3] where y = 2; +``` + +Добавив в ML эти типы данных МакКарти второго поколения, Карделли оставил и систему МакКарти первого поколения из LCF/ML, в которой все собирается из пар. Со многими другими пережитками первого ML он поступил иначе. + +### Ссылки больше не против + +Как мы помним, `letref` не особенно часто использовался в коде на LCF/ML и мы рассмотрели одну из причин - большая часть кода и практически весь мутабельный код был написан на Лиспе и только вызывался из ML. Разумеется, в имплементированном не через компиляцию в Лисп языке не так хорошо обстоят дела с вызовом лисповых функций. +Отсутствие в LCF/ML первоклассных изменяемых ссылок - другая причина. Мутабельность в ML сделали ради изменяемых деревьев. Но делать их без первоклассных ссылок не особенно удобно. Поэтому Гордону хотелось [Gord80] добавить мутабельные структуры, такие как массивы или мутабельные списки в ML. При этом Гордон сохраняет скепсис в отношении изменяемых ссылок. Считает, что идея совсем не очевидно хорошая и может быть "тем же по отношению к `letref`, чем является `goto` по отношению к `while`". Критика, похожая на критику изменяемых ссылок Хоаром. Это довольно сложное отношение Гордона к первоклассным изменяемым ссылкам еще больше осложнялось тем, что они с Милнером просто не знали как их типизировать так, чтоб сохранить безопасность и не получить средства приведения любого типа к любому другому. +Так что Гордон поручил разобраться как типизировать конструктор типов `ref` Луишу Дамашу, пишущему в это время в Эдинбурге диссертацию под руководством Милнера. +Луиш Мануэл Мартинш Дамаш (Luís Manuel Martins Damas) придумал [Dama84] даже два способа: доработал первоначальную идею Гордона о "слабых" переменных и собственный. И оба позаимствовал Карделли для своей версии ML [MacQ15]. Не одновременно, а последовательно. +Читатель может заподозрить, что если придумано два способа сделать что-то и не так очевидно какой лучше - вопрос все еще не закрыт. Да, так и есть, эмелистам предстоит работать над этой проблемой еще много лет. Но первые практические результаты уже получены. Сам Дамаш имплементировал типо-безопасные первоклассные изменяемые ссылки для LCF/ML. +Итак, вместо `letref` как в первоначальном LCF/ML у Карделли первоклассные ссылки. Было +`letref a = 3 in a := a + 1`, а стало `let a = ref 3 in a := @a + 1`. Можно писать `let a = ref[]` на топлевеле без аннотации типа. Можно использовать для определения абстрактных типов `let type * array <=> * ref list` [Card82b]. +И хотя вполне можно было бы поддержать старую `letref` аннотацию, в ML Карделли она не попала. + +### Детали + +Некоторые отличия были продиктованы ограничениями виртуальной машины [Card82b] [MacQ15]. Например: размеры токенов, целых чисел. Отсутствие информации о типах во время выполнения изменило поведение операции структурного сравнения. +Новые фичи языка перехватили некоторые старые операторы. Конструктор списка теперь `_`. `.`, которая была в LCF/ML, в Лиспе и, первоначально, в Прологе теперь занята селекторами рекордов. Конкатенация двух списков, которая в LCF/ML была `@` у Карделли `::`. `@` - теперь операция получения значения по ссылке. +Виртуальная машина позволяет делать хвостовую оптимизацию, и циклов `if then loop` пока нет, но планируются. Полностью Карделли от них не отказался. Вот циклов, управляемых выбросами исключений, как в LCF/ML у Карделли нет и не планируется. +У абстрактных типов новый синтаксис: вместо `abstype TA = TC` у Карделли `let type TA <=> TC`. +Отличаются наборы символов из которых можно составлять имена. Вместо `-3` у Карделли `~3`. +Карделли заменил длинные ключевые слова LCF/ML вроде `letrec` на составные вроде `let rec`. +Карделли не только просто менял одни токены в декларациях на другие. Он ввел в ML конструкцию `decl1 ins decl2`. Раннюю версию современной конструкции `local decl1 in decl2`. +Одних только косметических изменений синтаксиса хватило бы для того, чтоб при переносе кода с LCF/ML на ML Карделли пришлось бы редактировать почти каждую строчку. В лучших традициях диалектов NPL и языков Тернера. Не то чтобы, правда, было много кода на LCF/ML, было что переносить и было желание переносить. Все-таки в 70-е функционального программирования не было. +Получившийся язык Карделли известен под множеством имен. В описании [Card82a] отличий от LCF/ML он называется Edinburgh ML. Карделли также называет его ML under VMS, ML under Unix, "моя имплементация для VAX". Вадсворт называет его Cardelli ML, Гордон - Luca ML. В редакционной колонке первого номера самиздат-журнала "Полиморфизм" [Card82c] Карделли и МакКвин называют язык VAX ML. Так же называет язык описывающий его историю [MacQ14] МакКвин и в наши дни. +Появление нового диалекта ML вызвало некоторое желание у эмелистов и не только стандартизировать ML. Это желание приведет к появлению языка, который мы считаем диалектом NPL. Но в названии этого диалекта NPL есть ML, так что мнение о том, что это еще один диалект ML не совсем лишены основания. Это, впрочем, уже другая история, к которой мы еще вернемся. + +### Без сборщика и со сборщиком. + +Милнер вспоминает [Miln93] [Miln2003], что Карделли имплементировал компилятор за несколько недель. Но между первым описанием виртуальной машины в октябре 80-го и первой версией компилятора прошли месяцы. +Первый релиз готов 12 июня 81-го года [Card82c]. В этой версии не полностью имплементированы абстрактные типы, но самое главное - нет сборщика мусора. +Все это было исправлено 13 октября 1981 во второй и последней версия компилятора Карделли для VMS. +Карделли не ограничивал использование в университетах и распространение в другие университеты, но использование в исследовательских лабораториях и в индустрии требовало его согласия в каждом отдельном случае [Card82a]. И некоторые университеты заинтересовались компилятором ML. Как и у Тернера, у Карделли был список [Card82c] известных пользователей VAX ML. Не очень впечатляющий список, умещающийся на одну страничку и включающий самого Карделли. Но само наличие списка уже означает выдающуюся популярность имплементации ФЯ в те времена. +Список пользователей говорит скорее о том, когда пользователь перестал получать новые версии, а не когда начал. И многие не обновляли компилятор никогда, первая версия без сборщика мусора была для них и последней. +С компилятором Карделли ознакомился Милнер, Гордон и Вадсворт. Ознакомились в важных центрах разработки ФЯ в Гётеборге, Оксфорде и в INRIA, рассказ о которых еще впереди. +Как мы выяснили в предыдущей части, VMS - стандартная ОС VAX-11 - не особенно хорошо подходила для использования требующих большой памяти программ вроде компиляторов ФЯ. По крайней мере, компилирующих что-то побольше однострочников. BSD подходила лучше и была популярна в академии. По каким-то причинам Карделли не начал имплементацию на этой системе, так что за исправление этой ошибки взялись другие. +Не позднее марта 1982 [Sait82] в Университете Карнеги — Меллона Сайто Нобуо (Saito Nobuo) [MacQ14] портировал 13-10-81 версию [Card82c] компилятора Карделли на Unix с помощью Berkeley Pascal. Того самого компилятора Паскаля, который писали в Беркли Кен Томпсон и другие [McKus]. Долгое время Berkeley Pascal был только интерпретатором байт-кода, но в 1980-ом появился компилятор [Joy86]. +Известным Карделли пользователем этой версии был Ханс Боэм (Hans Boehm), который еще поучаствует в нашей истории. +Иэн Коттам (Ian Cottam) планировал [Card82c] портировать эту версию на одну из первых рабочих станций с Motorola 68K. Рабочую станцию продавала компания Apollo, написавшая Multics/Unix-подобную ОС Aegis на Паскале. Можно предположить, что их компилятор был получше. Если б эти планы закончились успехом - название VAX ML стало бы не таким подходящим, но пока что смена названия откладывается. + +### Разъединение + +В апреле 1982 Лука Карделли покинул Эдинбург и отправился в Нью-Джерси. Но история VAX ML в Эдинбурге на этом не заканчивается. Кто-то (Милнер не помнит кто) предложил использовать компилятор Карделли для обучения второкурсников [Miln93] [Miln2003]. Вскоре для компилятора Карделли нашли в Эдинбурге и другое применение, но об этом позже. +В Лабораториях Белла в Мюррей Хилл работать Карделли уговорил МакКвин [Card12]. Там Карделли проработал с апреля 82-го по сентябрь 85-го. Научился использовать C, но язык ему совсем не понравился. Карделли не заинтересовался C++, хотя и слышал что Страуструп делает Симулу из C. Не заинтересовался потому, что из C. Хотя кабинет Страуструпа был дальше по коридору [Card07]. +В Bell Labs Карделли сам занялся портированием своего компилятора на Unix. Он использовал не только Berkeley Pascal, далеко не самую быструю имплементацию Паскаля, которая заметно хуже Паскаля для VMS [Nebe83], с которого он начинал. Если для имплементации компилятора Berkeley Pascal еще более-менее подходил, то рантайм Карделли переписал на C. Первый релиз был готов уже 13 августа 1982. +В этой версии в ML вернулись циклы. А именно конструкция `if then loop` из LCF/ML. Имплементированы массивы. +Как мы помним, в Эдинбурге Карделли не поддавался влиянию языков бывшей Группы Экспериментального Программирования. Но в Мюррей Хилл МакКвин на него, видимо, повлиял и VAX ML сделал первый небольшой шаг к "хопизации": `cons` оператор называемый в первых версиях `_` получил современное имя `::` как в HOPE и до того в POP-2. Этот шаг был не последним, но это уже другая история. В предыдущих версиях VAX ML `::` называлась конкатенация списков, которая теперь получила современное имя `@`. И, соответственно, операцию над ссылками Дамаша `@` переименовали в современную `!`. +Вскоре, 24 августа 1982 была готова следующая версия. В ней Карделли добавил функции для ввода-вывода и VAX ML, видимо, впервые стал языком общего назначения. Немного поздно для того, чтоб писать программы для форматирования диссертации Карделли. +В том же году была сделана еще одна версия, от 5 ноября 1982 с новым алгоритмом проверки типов для изменяемых ссылок. Версия вышла незадолго до судьбоносного совещания в лаборатории Резерфорда - Эплтона, которое завершило предысторию ML и начало его историю, но об этом мы расскажем в следующей части. + +### Новые эксперименты с VAX + +Насколько быстрый код генерировал компилятор Карделли? Сам Карделли в своих статьях не приводит таких измерений, только пишет, что производительность можно считать удовлетворительной для типичных применений. Что может означать довольно плохую производительность, когда такое пишет довольный пользователь Симулы. К счастью, компилятор Карделли поучаствовал в том сравнении ранних имплементаций ФЯ [Augu84] [Augu89], которым мы уже пользовались, чтоб продемонстрировать невеселые итоги 70-х: + +| | fib 20 | primes 300 | 7queens | insort 100 | tak | +| :------------ | :------: | :--------: | :------: | :--------: | :------: | +| VAX ML | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | +| LCF/ML (LISP) | 92.0 | 24.2 | 18.9 | 15.0 | 19.3 | +| SASL (LSECD) | 62.0 | 16.7 | 18.9 | 12.0 | | +| Miranda (SKI) | 144 | 10.3 | | | | +| LISP int. | 42.0 | 6.50 | 5.33 | 6.40 | 4.75 | +| LISP comp. | 2.20 | 0.92 | 0.58 | **0.80** | 0.19 | +| C | **0.92** | **0.17** | **0.04** | | **0.11** | +| Pascal | 1.84 | | 0.11 | | 0.16 | + +Как видите, компилятор Карделли легко обошел все предыдущие попытки имплементировать ФЯ, предпринятые участниками Эдинбургской исследовательской программы: + +``` +VAX ML +▒▓▓ +LCF/ML (LISP) +░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +SASL (LSECD) +░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +Miranda (SKI) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +LISP int. +░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓ +LISP comp. +░▒▓ +C +▒ +``` + +И был примерно равен Franz LISP. Не самой передовой имплементации Лиспа, но достаточно хорошо работающей для компиляции "главной" программы на Лиспе - Macsyma. + +``` +VAX ML +░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +LISP comp. +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +C +░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▓▓ +``` + +Итак, компилятор ML Карделли генерировал код, сопоставимый по производительности с кодом, который генерировал компилятор Лиспа. Через несколько лет после того, как соответствующего успеха добился Уоррен при имплементации компилятора Пролога. Соответствие этих успехов, правда, неполное. Компилятор Уоррена хорошо себя показал не только на микробенчмарках, но и при компилировании более серьезной программы - самого себя. Но, в отличие от Уоррена, Карделли не писал свой компилятор на языке, который тот компилировал. Может быть из-за того, что интерактивный компилятор слишком тесно интегрирован с рантаймом, а ML не самый подходящий язык для имплементации рантайма. Может быть потому, что не считал, что системные требования такого компилятора позволили бы им пользоваться. Попытаются использовать компилятор Карделли для имплементации ML не сразу и другие люди. И это уже другая история. + +Редукция графов и как её избежать +------------------ + +> Если б не Девид Тернер, я не уверен, что занимался бы функциональным программированием. +> Л. Августссон [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: + +```haskell +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: + +```haskell +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] : + +```haskell +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 } +``` + +```haskell +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, не в самых лучших традициях поздней системы МакКарти и ранних языков описания спецификаций: + +```haskell +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` принимающий и возвращающий любой тип с интерфейсом списка: + +```haskell +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]. Получился язык в котором функции выглядят так: + +```js +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]. + +```js +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)) +``` + +и сделал синтаксис еще полегче: + +```js +foo|a:b = +{ x = baz:a + y = baz:b + RESULT bar|x:y + baz:x = x } +``` + +Наш обычный пример выглядит на FEL так: + +```JS +{ 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`-конструкция никуда не делась: + +```js +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 паттерны, паттерн-выражения не вводят имена а используют их. Это скорее более компактные, но менее выразительные гарды: + +```haskell +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]. +Языки программирования большие, сложные и негибкие, утверждает Бэкус. Их ограниченная выразительность не может оправдать их размер. В отличие от некоторых других авторов Алгола, Бэкус не ограничивается такой расплывчатой критикой разбухающих без особого толка фортранов и алголов и переходит к конкретике. +Фон-Неймановские компьютеры построены вокруг бутылочного горлышка: соединения между памятью и центральным процессором, пересылающего одно слово за раз. И обычные языки программирования построены вокруг этого пропихивания слова за словом через бутылочное горлышко, а не вокруг оперирования более крупными "концептуальными единицами". Обычные ЯП - тонкие обертки абстракции вокруг Фон-Неймановской машины, `:=` - языковое бутылочное горлышко Фон-Неймана. +Бэкус предлагает рассмотреть такой вот код на Алголе: + +```pascal +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 +``` + +что примерно соответствует такому вот коду на Хаскеле + +```haskell +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]. + +```haskell +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-ные массивы. Они не изменяемые, но с отложенной инициализацией: + +```haskell +let + a = array(1..3); + a[1] = 25; + a[2] = 6; + a[3] = 4 +in a +``` + +Попытка прочесть неинициализированное значение из I-структуры откладывает это вычисление до тех пор, пока инициализация не будет сделана. Попытка инициализировать I-структуру второй раз прекращает исполнение программы. +Конструкций для управления распараллеливанием нет. Предполагается, что оно полностью автоматическое. Программист не должен ничего аннотировать. +Все прочее выглядит знакомо [Nikh86]: + +```haskell +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: + +```haskell +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-исходников Марсельского интерпретатора Пролога. Но это уже другая история. + +Литература +========== + +[ALGOL80]: AB45.4.2 ALGOL 68 Implementations - ALGOL 68C - Release 1, Algol Bulletin No. 45, January 1980 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A45/P42.HTM +[Apol86]: http://bitsavers.informatik.uni-stuttgart.de/pdf/datapro/datapro_reports_70s-90s/Apollo/M11-075-11_8605_Apollo_Domain.pdf +[Arvind]: Arvind https://csg.csail.mit.edu/Users/arvind/ +[Arvi78]: Arvind, Kim P. Gostelow and Wil Plouffe. “An asynchronous programming language and computing machine.” (1978). https://escholarship.org/uc/item/2bm0t173 +[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 \ No newline at end of file diff --git a/ru/hopes.md b/ru/hopes.md new file mode 100644 index 0000000..993be27 --- /dev/null +++ b/ru/hopes.md @@ -0,0 +1,1677 @@ +История применения и оценки функционального программирования. +======= + +- [История применения и оценки функционального программирования.](#история-применения-и-оценки-функционального-программирования) +- [Часть 2: Великое уравнивание](#часть-2-великое-уравнивание) + - [Синтаксическая элегантность уравнений](#синтаксическая-элегантность-уравнений) + - [Бегство из MACSYMA](#бегство-из-macsyma) + - [В Ньюкасл со своими уравнениями](#в-ньюкасл-со-своими-уравнениями) + - [Небыстрая сортировка](#небыстрая-сортировка) + - [Случай на мосту через поток данных](#случай-на-мосту-через-поток-данных) + - [Лука Карделли и поздняя эволюция NPL](#лука-карделли-и-поздняя-эволюция-npl) + - [Коллекционирование ML-ей](#коллекционирование-ml-ей) + - [Standard ML 83.4](#standard-ml-834) + - [Standard ML 83.6](#standard-ml-836) + - [Карделли против Карделли](#карделли-против-карделли) + - [Standard ML 83.11](#standard-ml-8311) + - [Любой принц в Янтаре](#любой-принц-в-янтаре) + - [Столетняя война (40%)](#столетняя-война-40) + - [Standard ML 84.7](#standard-ml-847) + - [Façadisme](#façadisme) + - [Fasadrenovering](#fasadrenovering) + - [Standard ML 85.9](#standard-ml-859) + - [Le ML](#le-ml) + - [Неравенство уравнений](#неравенство-уравнений) + - [Edinburgh ML, SML/NJ, ML Kit](#edinburgh-ml-smlnj-ml-kit) + - [ANU ML](#anu-ml) + - [Poly/ML](#polyml) + - [POPLOG ML](#poplog-ml) + - [Rutherford ML](#rutherford-ml) + - [Да, fib](#да-fib) + - [Пираты Кремниевой Долины](#пираты-кремниевой-долины) + - [Дети Дюны](#дети-дюны) + - [Отец с атомным сердцем](#отец-с-атомным-сердцем) + - [Big in Japan](#big-in-japan) + - [Успели забыть о Прологе больше, чем японцы успели узнать](#успели-забыть-о-прологе-больше-чем-японцы-успели-узнать) + - [Новая надежда](#новая-надежда) + - [Граф 0](#граф-0) + - [Барендрегт против лямбды](#барендрегт-против-лямбды) + - [Самый известный птеродактиль на Панталасском театре](#самый-известный-птеродактиль-на-панталасском-театре) + - [Негативное пространство](#негативное-пространство) + - [День зависимости](#день-зависимости) +- [Литература](#литература) + + +Часть 2: Великое уравнивание +============================ + +> Существует ряд довольно похожих языков, из которых, пожалуй, наиболее известен HOPE. И то, что представлено здесь, можно считать иллюстрацией современного стиля функционального программирования и противопоставить его более традиционному стилю функционального программирования, основанному на LISP. +> Дэвид Тернер, Функциональные программы как исполняемые спецификации [Turn84]. + +В нулевой части истории мы выяснили, что к началу 80-х годов первый функциональный язык современного вида, с выводом полиморфных типов, АлгТД и паттерн-матчингом уже существовал [Burs80]. Но вышло так, что ни один из первого поколения компиляторов ФЯ, обзор которых мы сделали в первой части, не начал с имплементации такого языка. +В результате в начале 80-х появилось заметное число самобытных функциональных языков. Но им не долго оставалось быть существенно отличающимися друг от друга, да и вообще быть. +Вскоре начался процесс их унификации. Он включал в себя и скоординированные усилия по созданию одного универсального языка, и стремления отдельных разработчиков добавить в свой язык фичи как у всех. В результате немногие оставшиеся функциональные языки Эдинбургской исследовательской программы приобрели свой современный, узнаваемый вид. Об этом процессе мы и расскажем во второй части нашей истории. + +## Синтаксическая элегантность уравнений + +Не смотря на все описанные в прошлой части успехи по компиляции ФЯ, HOPE - первый ФЯ современного вида - все еще оставался имплементированным с помощью медленного интерпретатора. Чего недоставало компилируемым функциональным языкам для того, чтоб догнать передний край худо-бедно интерпретируемых? +Все они уже функциональные, идеи Берджа восторжествовали после пары не самых простых для них десятилетий. Даже некоторые лисперы уже узнали про возвращение функций из функций и о том, что это неплохо бы поддержать в имплементации языка. Конечно, то, что несколько имплементаторов ФЯ теперь понимают возвращение функций из функций - это еще не значит, что это что-то широко известное. И донесение этого до более широкой общественности - основная тема и предназначение статей и докладов из которых можно было впервые узнать об остальных фичах. +Вывод типов Хиндли-Милнера достаточно хорошо известен и обычно либо уже имплементирован (в VAX-ML, LML и Cambridge ML) или не имплементирован потому, что автору языка хотелось бы чего-то большего и это что-то большее уже имплементировано вместо него (Ponder, Poly). +Хуже всего дела с АлгТД и уравнениями с паттерн-матчингом. Глава Хоара про композитные типы данных [Hoar72], написанная до изобретения BNF-образной формы АлгТД намного известнее, чем статья в которой такая форма описана [Hoar75]. Паттерн матчинг в VAX-ML самобытный, работающий не с привычными АлгТД. В ALFL есть уравнения с ПМ как в SASL, работающие только со встроенными списками. +Работавшие над языками с уравнениями с ПМ Бурсталл, Дарлингтон и Тернер не достаточно писали о своей работе над уравнениями с ПМ. В 70-е все эти уравнения оставались в обычно диссертациях, университетских отчетах и руководствах пользователя. Тот кто читал про работу Дарлингтона с Бурсталлом, как правило читал о ней в статье [Darl76] в которой уравнений еще нет. И если это легко объяснить тем, что когда статья была написана - уравнений с ПМ еще не было и статья просто долго ждала публикации, то похожую проблему со статьей Тернера [Turn79] объяснить сложнее. В самой популярной и цитируемой статье Тернера об SKI-интерпретаторе, вдохновившей так много героев нашей истории, Тернер по какой-то причине не посчитал нужным продемонстрировать уравнения с ПМ в новом SASL 76. Код похож на написанный на старом SASL и, следовательно, на ISWIM. +Карделли, скорее всего узнавший об уравнениях с ПМ таким же образом как и Тернер, приехав в Эдинбург, выбрал имплементировать ПМ и композитные типы данных не так как в HOPE. И Дарлингтон с Тернером выбрали (пока что) не имплементировать компиляторы своих языков с уравнениями для обычных машин. +В результате, книга [Hend80] об ФП одного из изобретателей ленивости Хендерсона, вышедшая спустя пять лет после книги Берджа все так же использует ISWIM, а не язык уравнений с паттерн-матчингом. Хендерсон описывает разбор структур в неформальной нотации, напоминающей Ландинскую, после чего имплементирует со всеми предикатами `if`-ми и селекторами. Одноуровневый ПМ и `case`-выражение упоминаются только как интересная концепция с одним примером. При том, что Хендерсон ссылается на работы Бурсталла и Тернера. Но, конечно же те, в которых они не посчитали нужным продемонстрировать уравнения с ПМ. + +### Бегство из MACSYMA + +По большему счету все эти уравнения с ПМ в функциональных языках были представлены миру только на первых ФП конференциях в 80-е, когда работа над первыми компиляторами ФЯ уже шла вовсю. +В том, что первые ФП-конференции состоялись только в 80-е нет ничего удивительного. Как мы уже выяснили в нулевой части, до начала 80-х функционального программирования не было. Но чего наши читатели, скорее всего, не ожидают, так это того, что первая ФП-конференция была и первой регулярной Лисп-конференцией [Stee82b]. До нее была только конференция 64-го года в Мехико. +Где же лисперы докладывали о своей бурной деятельности, например о разработке MacLisp [Whit77] или NIL [Whit79]? Разумеется, на конференциях пользователей MACSYMA. Ведь, как мы уже выяснили в нулевой части, Лисп - это в первую очередь язык для имплементации MACSYMA, а все остальное - как повезет. +Но в 80-е Лисп довольно успешно освобождается от главного бремени (и смысла) своего существования, так что есть все основания начать проводить конференцию посвященную именно Лиспу. +Конференцию организовали и профинансировали Джон Аллен (John Allen) со своей женой Рут Дэвис (Ruth E. Davis) [McJo22]. Аллен работал в Стенфорде над тем самым мини-Маклиспом для не таких богатых лисперов из Стенфордского мини-Мака, который позднее использовал уже в Эдинбургском мини-Маке Милнер для написания LCF/ML. В конце 70-х Аллен основал The Lisp Company, которая разрабатывала Лисп для x86-машин. +Джон Аллен - не обычный лиспер, но лиспер, написавший книгу о Лиспе [Alle78], в которой ссылается не только на большинство имеющих какое-то отношение к ФП лисперов, но и на большинство героев обоекембриджской и ранней эдинбургской частей нашей истории, а также оказавших на них влияние. На Бойера, Берджа, Бурсталла, Черча, Карри, Дарлингтона, Гордона, Ландина, Милнера, Джея Мура, Джеймса Морриса, Локвуда Морриса, Ньюи, Плоткина, Поплстоуна, Рейнольдса, Скотта, Стрейчи (в том числе на тот самый доклад [Stra67]), Вадсворта (в том числе и на его диссертацию про редукцию графов), Вюийемена и, чтоб охватить совсем уж все имеющее отношение к ФП, Вейнгаардена, Algol 68 и прочих алголистов. +Что только частично можно объяснить стенфордскими связями авторов LCF и вообще отличием стенфордских лисперов от гораздо более стойко сопротивляющихся всему эдинбургскому лисперов МТИ. +Так что нет ничего удивительного в том, что он не хотел ограничиваться только Лиспом [Alle2005] и организовал первую конференцию по Лиспу, ставшую первой в серии проходящих раз в два года конференций по Лиспу и функциональному программированию (Lisp and Functional Programming Conference). +Конференция прошла в конце августа 1980-го года в Стенфорде, вскоре после первой конференции AAAI. Да первые ИИ-конференции, AAAI и ICML тоже начали организовывать только в 1980-ом году. Для тех, кто работает над каким-нибудь ИИ, который не является системой компьютерной алгебры MACSYMA. +Именно на этой конференции можно было услышать доклад о HOPE [Burs80] (и SKIM, что не так важно для этой части нашей истории). +Доклад о HOPE содержит редкий пример объявления АлгТД современного вида + +``` +data otree == empty ++ tip(num) + ++ node(otree#num#otree) +``` + +и разбирающих его функций, написанных как уравнения с ПМ + +``` +dec insert : num#otree -> otree + +--- insert(n,empty) <= tip(n) +--- insert(n,tip(m)) + <= n0} +``` + +с помощью которого демонстрируют и преимущества Хаскеля на его официальном сайте и сорок лет спустя, в момент написания этих слов. +Завершается лекция Тернера полным текстом стандартной библиотеки KRC в которой демонстраций уравнений с ПМ более чем достаточно. Демонстраций многих современных возможностей, но не обязательно современного вида: + +```haskell +filter f [] = [] +filter f (a:x) = a:filter f x, f a + = filter f x +``` + +Тернер заявляет, что его опыт аппликативного программирования раз за разом подтверждает, что код на SASL-образных языках на порядок короче, чем эквивалентный код на традиционных языках программирования. +Тернер не приводит никаких сравнений и примеров таких успехов и даже не говорит какой именно порядок имеется в виду. Даже выбранный им пример кода плохого Лиспа не в десять раз длиннее, чем пример кода хорошего KRC. + +### Небыстрая сортировка + +Но, конечно, среди функциональных программистов позднее нашлись желающие подтвердить, что код на ФЯ в 2-10 раз короче. В зависимости от смелости трактовки слова "порядок". Подробность описаний этих подтверждений серьезно варьируется. +Так Вадлер в стенограмме обсуждения одного из более поздних докладов Тернера [Turn84] вспоминает, что написал одну и ту же программу на Лиспе и KRC. Кода на Лиспе получилось 40 страниц, т.е., надо полагать, 1.5-3 тыс. строк. Кода на KRC, вышло 4 страницы. +Наш старый знакомый по рассказу о SKI-машине NORMA Ричардс пишет [Rich85], что лексический анализатор (видимо, часть имплементации ARC SASL?) получается размером в 800 строк на Паскале-образном системном языке мэйнфрейма B1900, 400 строк на Лиспе и только 40 строк на ARC SASL. +Подробнее всего описан один из самых скромных успехов. В 83-ем году Саймон Пейтон-Джонс написал [SPJ85] на SASL генератор парсеров. Получилось 835 строк. Но Пейтон-Джонсу не удалось написать в десять раз больше строк кода на BCPL. Получилась только 1501 строка. Нужно заметить, справедливости ради, что BCPL не особенно многословный язык из-за отсутствия сигнатур типов и довольно легковесного ФЯ-образного синтаксиса. В статье достаточно примеров кода Пейтон-Джонса на SASL (но не полный текст программы), но не на BCPL. Как он писал на BCPL можно посмотреть в статье про сравнение SKI-интерпретатора с SECD [SPJ82]. +Но даже такие совсем небольшие примеры - слишком большие для демонстрации в статье о преимуществах ФП. И пример подходящего размера был найден. Во времена первых ФП-конференций он еще не был готов, но вскоре после них начал использоваться для демонстрации краткости функциональных языков. Да, это был так называемый "квиксорт". +Мы уже встречались с его ранними версиями в нулевой части нашей истории. Первоначально, "квиксорт" Берджа-Левенворта не был таким уж немногословным и был вполне сравним с квиксортом на Паскале по числу строк кода. У Ван Эмдена и Тернера он стал несколько компактнее благодаря паттерн-матчингу, но все еще не демонстрацией краткости, а в первую очередь демонстрацией того, что на логическом или функциональном языке вообще можно написать не совсем тривиальную программу. +Свою финальную форму этот "квиксорт" принял только с появлением ЦФ-нотации в языках Тернера. И придал ему эту финальную форму работавший Университете Кента над диссертацией Силвио Мейра (Sílvio Romero de Lemos Meira). +Рассказывая о "квиксорте" Мейры, Тернер ссылается [Turn84] на неотсканированный отчет [Meir83] 83-го года об имплементации сортировки на KRC. Отчет до нас не дошел, но дошло сообщение [Meir83b] Мейры в usenet группе net.applic, от 16 августа 83 в котором есть такой KRC код: + +``` +quick [] = [] ; +quick (a:x) = quick {y | y <- x; y <= a} ++ + a:quick {z | z <- x; z > a} ; +``` + +В стандартной библиотеке [Turn83] новой версии SASL c ЦФ-нотацией от ноября 83-го года "квиксорт" занимает всего-то две строки: + +``` +sort () = () +sort (a:x) = sort {b <- x; b<=a} ++ a : sort {b <- x; b>a} +``` + +Квиксорт на Паскале - пара десятков строк. Сокращение кода на десятичный порядок продемонстрировано. +Ричардс [Rich85] сравнивает 18 строк квиксорта на Паскале с "квиксортом" на ARC SASL, который он растянул на четыре строки. + +``` +sort [ ] = [ ] +sort (a:x) = sort [b; b <- x; b < a] + ++ [a] ++ + sort [b; b <- x; b >= a] +``` + +Может быть для того, чтоб продемонстрировать то, что преимущества в краткости растет с ростом серьезности программ. Может какие-нибудь игрушечные примеры и короче всего в несколько раз, но посмотрите на лексер. Реальная программа в 20 раз короче! Код реальных программ, правда, не показан. +Разработчик Lazy ML и его компилятора Томас Йонссон в своей диссертации [John87] тоже сравнивает квиксорт с "квиксортом". В Lazy ML не было ЦФ-нотации, так что он просто использовал `filter` из стандартной библиотеки для получения двухстрочника. Не включил имплементацию фильтра в пример, в отличие от Берджа и Ван Эмдена. Но у Йонссона и квиксорт на Паскале покороче, чем у Ричардса - 15 строк. Только на одну больше, чем "квиксорт" Берджа. + +### Случай на мосту через поток данных + +В том же 81-ом году, когда Тернер и Дарлингтон прочитали свои лекции в Ньюкасле, состоялась вторая ФП-конференция, на которой с их идеями и языками уравнений познакомилась более широкая аудитория. +В октябре 81-го, уже знакомые читателям по предыдущей части, группа функциональных языков и архитектуры Арвинда совместно с группой вычислительных структур Денниса организовали в Портсмуте, Нью-Гэмпшир конференцию по функциональным языкам программирования и компьютерной архитектуре (FPCA). Как и LFP, эта конференция с тех пор проходила раз в два года, только по нечетным годам, пока в 96-ом они не объединились в одну ежегодную и, скорее всего, знакомую читателю ICFP. +Конференция была организована создателями (эмуляторов) специальных машин и они были центральной темой, но нашлось место и для мечтателей о том, у чего не будет такого мрачного будущего. +На первой FPCA 81-го года, скорее всего, присутствовали авторы LML и разработчики его компилятора Йонссон и Августссон. Там они не только увидели доклад Тернера, но и обсуждали с ним имплементацию ФЯ. Этот разговор подтолкнул Йонссона к изобретению G-машины, но, к сожалению, не подтолкнул Тернера к работе над более-менее эффективными имплементациями ФЯ на обычном железе. Это, разумеется, оказало серьезное влияние на то, как развивалась история ФП. +На этой конференции Тернер выступил с докладом "Семантическая элегантность аппликативных языков" [Turn81], в котором продемонстрировал написание и оптимизацию более серьезной программы, чем однострочники, которые демонстрировались до того. Серьезная программа растянулась на пару десятков строк. Сохранилась она не только в статье. Несколько вариантов этого кода поставлялись как примеры вместе с первой имплементацией KRC и её исходными кодами. +Программа строит гомологический ряд парафинов со всеми возможными изомерами. Да, в наборе бенчмарков GHC nofib есть такая программа. Но нет, это не порт KRC-кода на Haskell. В nofib переписанное на Хаскель другое решение той же задачи, которое демонстрировало уравнения с ПМ добавленные в другой язык, о котором мы еще напишем. +Когда программа на языке уравнений с ПМ разрастается аж до десятков строк, в ней начинают встречаться и бросаться в глаза более сложные примеры сопоставлений с образцом, использующие и вложение. + +```haskell +invert [[a,b,c],d,e,f] = [a,b,c,[d,e,f]] +invert x = x +``` + +Что может быть важно. Ведь, как мы выяснили в нулевой части, есть серьезные основания подозревать, что некоторые из тех немногих, кому посчастливилось увидеть примеры ПМ в 70-х, даже не поняли, что такое вложение возможно. +На этой же конференции состоялся доклад [Darl81] про планирующуюся параллельную ФП-машину ALICE с примерами на псевдо-HOPE. Да, и тут Дарлингтон использовал примеры без `---` разделителей, вместо которых только отступы + +``` +MinOfList : List Integer -> Integer + MinOfList(x::nil) <= x + MinOfList(x::y::L) + <= Min(x, MinOfList(y::L)) +``` + +Псевдокод похож на одну из версий NPL, от которых осталось меньше всего следов, но часто не имеет и обязательных сигнатур с типами. Так что слушатель докладов и читатель статей и в этот раз спасен Дарлингтоном от суровых реалий синтаксиса HOPE, близкое знакомство с которыми навряд ли способствовало бы популярности языков уравнений с ПМ. +В первые годы 80-х уравнения с ПМ используют как язык программирования или псевдокод в статьях только имеющие какое-то отношение к разработке NPL/HOPE и SASL. +Конференция 81-го года дает возможность посмотреть как даже Вадлер, впоследствии знаменитый своими статьями, использующими уравнения с ПМ, еще использует примеры на ISWIM с другой ассоциативностью применения функций (как обычно, в каждой статье свой ISWIM). В статье уже есть ссылки на работы Бурсталла, так что использование Вадлером ISWIM, скорее всего, последнее. +Родственное уравнениям с ПМ направление - алгебраическая спецификация - все еще представлено уже знакомыми нам по нулевой части бывшими разработчиками AFFIRM Гуттагом [Gutt81] и Мюссером [Muss81] и все еще разработчиками SCRATCHPAD [Jenk80]. Но ближайшие исполняемые языки с паттерн-матчингом и "уравнениями" были представлены только Пролог-образными языками [Clar81] и примерами кода вроде таких + +```prolog +mode merge(?,?,^) +merge(u.x,u.y,u.z) <- merge(x,y,z) +merge(u.x,v.y,u.z) <- u < v |merge(x,v.y,z) +merge(u.x,v.y,v.z) <- v < u |merge(u.x,y,z) +``` + +где на Прологах часто пишут обычные функции, которые проаннотированы как обычные функции. И может возникнуть вопрос: зачем вообще в таком случае писать на Прологе и смотреть на все эти неиспользуемые отличия от уравнений с ПМ? +После вышеописанных конференций и курсов про уравнения с ПМ знают уже все, кто должен о них знать для того, чтоб в 82-ом году на судьбоносной встрече в лаборатории Резерфорда — Эплтона затянувшаяся предыстория функционального программирования наконец закончилась и началась история. + +## Лука Карделли и поздняя эволюция NPL + +В октябре 1977, в том же месяце, когда Бэкус получил премию Тьюринга, британское агенство SRC (с 81-го года SERC: Science (and Engineering) Research Council) сформировало панель Робертса. S(E)RC распределяло государственное финансирование между британскими проектами о которых обычно рассказывает наша история. И панель Робертса должна была решить, кто получит деньги в тяжелые времена "кризиса программного обеспечения". Который, напомним, заключался в том, что железо дешевеет, а ПО дорожает. Отчет панели вышел в марте 79-го. Ответом на кризис должна была стать Software Technology Initiative (STI). +Наверное, решили в S(E)RC, у академиков или уже есть решения, или, по крайней мере, скоро будут. Но они не доходят до индустрии. Что делать? Нужно проводить больше курсов, на которых академики будут знакомить со своими исследовательскими языками и программами. Нужно создать "общую базу" ПО и железа, чтоб что-то разрабатываемое одними академиками могло быть скомпилировано и запущено другими академиками и не только ими. Общие языки Pascal и FORTRAN 77, общая ОС UNIX, общие рабочие станции PERQ [RAL83] [RAL84]. +Pascal, FORTRAN 77 и UNIX выбраны потому, что уже популярны в академической среде. PERQ выбрана под влиянием Хоара и не появлявшегося в нашей истории с начала нулевой части разработчика Лондонского компилятора CPL Кулуриса [PERQ1]. +Выбор Pascal и FORTRAN 77 не означал, что SERC боролся с исследовательским ПО на прочих языках и с новыми исследовательскими языками. В SERC планировали организовать разработку совместимых компиляторов для основных машин, причем планировали сделать так, чтоб из Pascal можно было вызвать функцию на FORTRAN 77 из которой, в свою очередь, можно бы было вызвать функцию на Pascal. +Организовывать совещания и курсы должна была Лаборатория Резерфорда - Эплтона (Rutherford Appleton Laboratory). И 17 ноября 1982-го года дошла очередь до встречи разработчиков ML, LCF и HOPE. + +### Коллекционирование ML-ей + +На встрече присутствовали как давние герои нашей истории: Милнер, Бурсталл, Дарлингтон, Хьюз и Майкрофт (один из изобретателей анализа строгости), так и будущие герои, такие как Митчелл. Гордон и Полсон не смогли принять участие, но прислали документ со своими новостями и пожеланиями. Хоар тоже должен был участвовать, но не поучаствовал. Впрочем, он уже сыграл существенную роль в начинающейся на этом собрании истории продвигая PERQ и приняв на работу в Оксфорде Бернарда Суфрина (Bernard Sufrin), научного руководителя Хьюза, который участие в совещании принял. +Заметки [Wads83] о совещании оставил Вадсворт, который после окончания работы над LCF/ML в 78-ом году ушел из Эдинбурга в Лабораторию Резерфорда - Эплтона. +Предполагалось, что участники встречи поделятся своими целями и общими нуждами и расскажут как STI лучше использовать их исследовательские достижения. SERC хотел, чтоб ML, LCF и HOPE заработали на рабочих станциях PERQ и чтоб были организованы курсы по их использованию. Разработчикам ML, LCF и HOPE нужно было придумать, как это может быть осуществлено. +На совещании зачитали послание Гордона и Полсона, подготовленное за пару недель до него [Gord82]. +В заметках описаны работы Полсона и Жерара Юэ в Кембридже и INRIA над компилятором LCF/ML о котором мы уже писали подробнее в предыдущей части. +Полсон с Гордоном не горят особым энтузиазмом связанным с продолжением этих работ. Пишут, что они хотят использовать LCF, а не работать над его имплементацией и имплементацией компилятора ML для него. +Во время встречи в лаборатории Резерфорда-Эплтона Полсон планировал находится в Гетеборге, и обсуждать объединение усилий с теми, кто развивает собственную ветку LCF там. +Но если Гордон с Полсоном не очень-то и хотят работать над собственным компилятором, то почему не использовать компилятор Карделли? +Они пишут, что композитные типы данных и мутабельные ссылки в VAX ML лучше, чем в LCF/ML. Так же им нравится производительность кода, который генерирует компилятор Карделли. Но использовать этот компилятор они не хотят. +Компилятор и рантайм еще не доделаны и не готовы для использования. Например, не имплементирован ввод-вывод. Также, Полсон с Гордоном не хотят переписывать весь код на LCF/ML с которым у VAX ML множество мелких но хорошо распределенных по коду отличий. И в это время на LCF/ML написано уже больше LCF кода, чем в 78-ом году, к чему мы еще вернемся. Тем более они не хотят переписывать Лисп-код, который нужно переписывать потому, что у VAX ML нет интеропа с Лиспом, в отличие от их Кембриджского компилятора. Для имплементации LCF на PERQ они рекомендуют SPICE LISP, который имеет будущее потому, что имплементирует Common Lisp. И Common Lisp похож на MacLisp, так что потребуются только минимальные изменения LCF и Кембриджского компилятора ML. +Наконец, Полсон и Гордон недолюбливают VAX ML как язык, называя его "барочным". А что им нравится? +Мэттьюз разрабатывает в Кембридже язык Poly (про который мы уже писали в предыдущей части), производительность имплементации Гордон с Полсоном считают хорошей, со временем можно будет использовать для написания будущих имплементаций LCF. Правда, будущее Poly не ясно, возможно скорое окончание финансирования от SERC. Гордон с Полсоном надеются на то, что Poly и ML могут быть, со временем, унифицированы. Poly может послужить источником идей для расширения системы типов ML. +Но больше всего им нравится HOPE. Ни Полсон, ни Гордон не писали ничего на HOPE, но дизайн языка произвел на них хорошее впечатление. Они настоятельно рекомендуют продолжать его развитие. HOPE - это ML, который "проще и чище". +В каком направлении развивать HOPE? Нужны эксперименты с обработкой исключений, мутабельными ссылками и ленивостью. Также нужно больше экспериментов с отладчиками для таких языков как HOPE и ML. +Что еще нравится Гордону с Полсоном? Идеи Дэвида Тернера. Какие именно они не пишут, пишут только, что "не обязательно только нормальный порядок вычисления". Больше неназванных идей Тернера должны быть использованы для развития языка HOPE, а идеи Карделли нужно использовать в имплементации. Со временем, HOPE с производительной имплементацией как у VAX ML может стать подходящим языком для имплементации будущих систем, таких как LCF. +Доклады прочих участников, по видимому, были похожи на письмо Полсона и Гордона, но пересказаны Вадсвортом сжато. +То, что в этой работе называется "Эдинбургская исследовательская программа" Вадсворт называет "типизированное функциональное программирование происходящее от ISWIM". ML он называет "функциональным языком высшего порядка в стиле ISWIM". Сегодня мало кто считает языки первого порядка функциональными. Но в 82-ом году были еще не так избалованы большим количеством ФЯ, так что записывали в ФЯ все что можно. +ML выделяют из прочих ISWIM-ов полиморфная система типов и абстрактные типы. HOPE - это ML без мутабельности и обрабатываемых исключений, но с паттерн-матчингом и функциями, определяемыми уравнениями, перегрузкой и ленивыми списками. +Тут нужно напомнить, что с отсутствием мутабельности не все так просто. Как мы выяснили в нулевой части, Эдинбургский HOPE позволял писать некоторые функции на POP-2, который поддерживал мутабельность еще как. И в Эдинбурге писали такие функции. Примерно как LCF/ML позволял писать некоторые функции на Лиспе. И как мы выяснили в прошлой, первой части, Лондонский HOPE имплементировали так, что полноценной поддержки (неоднократной) мутабельности нельзя было сделать в принципе. +На совещании вспомнили имплементации LCF/ML на разных Лиспах. В это время имплементация для PDP-10 сначала на Stanford Lisp, а затем на Rutgers Lisp еще работала в Эдинбурге. Но этому PDP-10 оставалось работать только пару лет, перспективы у имплементации неважные. +Другое дело версии для VAX Unix и Multics, которые разрабатывают совместно Кембридж и INRIA. Эти должны пригодится для портирования LCF на PERQ. Участники совещания не обратили внимания на жалобы в письме Гордона и Полсона о том, что они не особенно хотят работать над этим компилятором. +Обсуждают и независимый от предыдущего порт на VAX Unix в Гетеборге - видимо тот самый, который вдохновил чудом вывода типов Августссона делать не очередной SASL, а типизированный ленивый ФЯ. +Cardelli ML (так Вадсворт называет VAX ML) выглядит как идеальный ответ на требования SERC: имплементирован на Паскале и уже портирован на VAX Unix. +Обсудили и имплементации HOPE. О двух из них мы уже рассказывали. Первоначальная эдинбургская на POP-2 для PDP-10 тоже доживает последние годы. Интересно, что никто не рассматривает возможность портировать её на VAX с помощью POP-11. Может быть даже еще и не знают об этой имплементации или не особенно верят в её перспективность. +Бурсталл рассказал о планах по развитию HOPE в Эдинбурге: параметризованные модули и обработка исключений. +Дарлингтон рассказал о работах в Имперском колледже Лондона. Компилятор HOPE на HOPE, который там пишут не подходит для компиляции в FAM. И им нужен, по крайней мере, быстрый интерпретатор HOPE. Для целевого языка есть только эмулятор параллельного компьютера. Так что код на HOPE Дарлингтон с коллегами пишут дистанционно на Эдинбургской машине, которой недолго осталось работать. +Дарлингтон заверяет, что они поддерживают тесную связь с Эдинбургом для того, чтоб не допустить раскола HOPE на несовместимые диалекты. Тут нужно заметить, что в Эдинбурге и в Лондоне из опыта использования HOPE сделали разные выводы и сформулировали разные, часто противоположные пожелания по развитию языка, так что раскол на два диалекта вполне возможен. +Идет работа и над новой имплементацией HOPE на новом месте, правда с участием знакомых уже героев нашей истории. МакКвин в Bell Labs пишет имплементацию HOPE на Franz Lisp. Она транслирует HOPE в коды ВМ FAM Карделли. Но эти коды потом интерпретируются интерпретатором FAM на Franz Lisp, так что, видимо, не очень быстро. Не понятно, почему не использует компилятор своего коллеги по Bell Labs Карделли как бэкенд. Может быть просто руки пока не дошли? +Бурсталл недоволен скоростью исполнения HOPE кода и видит решение в микрокодовом интерпретаторе для ВМ Карделли FAM, в код которой будет транслировать компилятор, который пишет МакКвин в Bell Labs. Но в Эдинбурге рассматривают и другой план - использовать Spice Lisp, разработчики которого собираются использовать микропрограммируемый PERQ как Лисп-Машину. Но у собравшихся нет иллюзий о том, что это решение заработает раньше, чем через несколько лет. +В Эдинбурге Роберт Рэй, один из разработчиков WPOP, имплементации POP-2, которую использовали для разработки первого интерпретатора HOPE, как раз занимается портированием Franz Lisp на PERQ Unix. Перспективность Franz Lisp пока никого не беспокоит. Хотя уже идет работа над Common Lisp и наступление МТИ с Лисп-машинистами на группу Фейтмана. Может считают, что Franz Lisp не сильно от подмножества Common Lisp отличается, а группа Фейтмана еще не полностью разгромлена и Franz Inc. не основана. +Поэтому на совещании заключают, что самый простой путь портирования LCF и HOPE на PERQ - использовать их имплементации, написанные на Franz Lisp для VAX Unix. "Не должно потребовать больше нескольких дней", когда Franz Lisp наконец заработает на PERQ Unix. +Но некоторым исследователям нужен ML без LCF, и чем выше производительность кода, который генерирует такая имплементация - тем лучше. Это исследователи, работающие над тремя проектами. В заметках Вадсворта не говорится, что это за проекты, но из последующих событий мы можем заключить, что Ханна хочет писать на ML доказатель, а Хеннесси с Митчеллом хотят писать имплементацию ML на ML. В следующей части мы расскажем об этих проектах подробнее. Для чего хотел использовать быструю имплементацию ML Суфрин - нам не известно. +Милнер советует (и с ним, в основном, соглашаются), что начать важнее с имплементации ML, которую легко использовать. Чтоб ML быстрее нашел больше пользователей. +К счастью, компилятор ML Карделли и удобнее в использовании, чем LCF/ML и генерирует более быстрый код, чем Кембриджской компилятор. И потому следует именно его использовать как основу. Но тут парой дней не обойтись. +Нужно писать новый генератор кода, сборщик мусора и, возможно, сделать какие-то изменения в коде компилятора на Паскале из-за отличия в диалектах для VAX и PERQ. Эту работу оценили в два человеко-месяца. +Суфрин предложил в среднесрочной перспективе, когда для PERQ будет доступно расширение памяти для микрокода, написать микрокодовый интерпретатор для абстрактной машины Карделли. За одно, можно будет использовать его и для новой имплементации HOPE, которую пишет МакКвин. И которая тоже компилирует в FAM-код. Хеннесси с Митчеллом и Суфрин даже готовы поработать над такой имплементацией. +Собравшиеся согласны, что переписывание LCF на ML Карделли того не стоит, LCF так и останется на Franz Lisp. +Курсы по HOPE, ML и LCF было решено провести летом 83-го в Эдинбурге и Лондоне. К этому времени, решили участники совещания, будет достаточно для них рабочих станций PERQ и будет завершено портирование имплементаций на эти рабочие станции. +На встрече разгорелась дискуссия о том, есть ли вообще смысл в поддержке и ML и HOPE. Не лучше ли объединить их в один язык, сочетающий их сильные стороны? Кто начал эту дискуссию и каковы были мнения всех её участников Вадсворт не пишет. Но пишет, что Милнер сформулировал её итог так: HOPE и ML представляют собой "разные точки спектра возможных типизированных ФЯ". Разные цели привели к тому, что были выбраны отличающиеся компромиссы в дизайне этих двух языков. И оба языка нужно продолжать развивать. Пока еще не ясно, как должен выглядеть окончательный функциональный язык. На данном этапе нужно экспериментирование, а не стандартизация. +Через полгода после подведения этого итога, в апреле 83-го, у Милнера был готов первый черновик "стандарта" [Miln83], объединяющего HOPE и два диалекта ML. + +### Standard ML 83.4 + +Робин Милнер не только изменил свое отношение к объединению ML и HOPE. С конца 70-х годов он не уделял особого внимания ML и LCF, занимаясь вместе с Хеннесси семантикой конкурентности. Но теперь решил вернуться к работе над ML. Что же случилось? +Организованное SERC совещание о будущем ML напоминает прошедшее в предыдущем году организованное ARPA совещание о будущем Лиспа. И не так уж удивительно, что оно привело к похожим результатам - новому языку, претендующему на объединение существующих до него диалектов. В случае Common Lisp один из главных инициаторов работы по объединению писал и историю событий. Но не в случае с ML. Позднее Милнер напишет, что разрабатывать язык, объединяющий LCF/ML, VAX ML и даже HOPE, его убедил Бернард Суфрин. Как ему это удалось, правда, не известно. Авторы истории SML попытались разузнать какие-то подробности у самого Суфрина много лет спустя [MacQ20], но уточнили только время, когда переубеждение Милнера состоялось. Это произошло не на заседании в ЛРА, но на встрече Милнера и Суфрина вскоре после него, еще до окончания осени. Вероятно в Йорке. +Попытка объединения ML-ей, вероятно, могла бы состояться и без Милнера, но его участие несомненно оказало на процесс объединения и его результат огромное влияние. Мы можем оценить разницу сравнивая со следующей попыткой объединить ФЯ. Автора ФЯ, вокруг которого планировалось это следующее объединение, не удалось убедить поучаствовать. +Первый стандартный ML, который мы будем называть SML 83.4, но который так не называл его автор, спроектирован в основном Робином Милнером, который советовался с Аланом Майкрофтом. Черновик рукописного описания был готов в апреле 83-го. +Милнер пишет, что Standard ML - временное название. Как и Common Lisp. Но время перейти от временного к постоянному так и не пришло. +Standard ML не предполагается каким-то инновационным. И SML 83.4 таким не является. Цели: +1) исправить неверные решения и убрать излишества оригинального дизайна LCF/ML +2) добавить то, чего не хватает, а именно ПМ и АлгТД как в HOPE +3) определить, какой минимум пользователь может ожидать от имплементации ML +Типы данных HOPE, по мнению Милнера, нужны для того, чтоб доделать, завершить разработку ML наиболее естественным образом. +Милнер перечисляет с чем он связываться не собирается. В SML не будет ленивости, которая есть в HOPE. Но мы выяснили в нулевой части, что Эдинбургские разработчики HOPE, с которым общается Милнер, решили что ленивость не нужна. В отличие от Лондонских. Не будет и системы исключений, позволяющих бросать какие-то значения кроме строк. Только такие, как в LCF/ML. Но нельзя сказать, что уже есть готовое решение для более выразительных, хотя и есть идеи о том, как типизировать. Родственные идеям Дамаша о типизации изменяемых ссылок. +Интересно, что не задолго до этого Милнер писал о дизайне LCF/ML. Эта статья [Miln82] вышла в январе 83-го в том же номере самиздат-журнала Полиморфизм, что и отчет о встрече в ЛРА. Там он пишет, что АлгТД и уравнения с паттерн-матчингом может и слишком новые для языка, который принимает только проверенные фичи. И полиморфных изменяемых ссылок и более развитых исключений как раз не хватает ML для завершенности. +И тут мы переходим к более неожиданной части списка того, что в SML не попадает. SML должен стать объединением LCF/ML и HOPE с VAX ML Карделли, но фичам из VAX ML почему-то не находится места в SML. Полиморфные мутабельные ссылки, рекорды и варианты Карделли в SML не попадают. +Милнер пишет, что не считает что-то из этого неправильным. Но считает, что сбалансированный язык получается и без этого. Все это может быть добавлено потом, как расширение, но в базе этого не будет. +Основное влияние на дизайн SML, пишет Милнер, - это работы Карделли над VAX ML и Бурсталла и др. над HOPE. Но получается так, что Бурсталл придумал то, что в SML добавлено, а Карделли то, что в SML добавлять не нужно. Милнер утверждает, что "многое позаимствовано у Карделли". Но многое ли? На наш взгляд позаимствовано не очень много. Видимо, не только Гордону и/или Полсону не особенно понравился VAX ML. +Милнер для начала определяет минимальное подмножество SML под названием Bare ML. Минимальный синтаксис без альтернативных конструкций. Без мутабельных ссылок и встроенных типов. +При применении функций `f x` в LCF/ML сначала вычислялась `f`, а затем `x`, но в VAX ML сначала вычисляется `x`, что позволило Карделли имплементировать более быстрое применение. В новом стандартном ML порядок вычисления не определен. Что можно посчитать редкой уступкой Карделли со стороны Милнера. +Одна из основных конструкций Bare ML - это некаррированная лямбда с несколькими ветвями ПМ как в HOPE (и как, позднее, `function` в OCaml и `\case` в Haskell) + +```sml +fun v1. exp1 | ... | vn. expn +``` + +в HOPE она выглядела так [Burs80]: + +``` +lambda v1 => exp1 | ... | v2 => expn +``` + +Матчинг происходит, правда, не как в HOPE, а как в языке Тернера, Прологе и современных ФЯ: результат зависит от порядка паттернов. Одно из отличий ПМ в Bare ML в том, что в случае невозможности сопоставить ни один паттерн, выбрасывается обрабатываемое исключение. +Милнер почему-то приписывает изобретение HOPE-лямбды Алану Майкрофту. Милнер отмечает, что Майкрофт может не согласится с этим. И да, это вполне вероятно. Это лямбда из HOPE, едва ли Майкрофт претендовал бы на её изобретение. Может быть имеется в виду именно сочетание. Лямбды с многоветочным ПМ как в HOPE, но сам ПМ при этом как в SASL? +Вместо `\`, как в ML-ях до того, ключевое слово `fun`. И синтаксис изменился не только тут. Синтаксис в новом ML новый, не делается никаких попыток сохранить хоть какую-то совместимость с LCF/ML или VAX ML. И это, на самом деле, новое явление в создании диалектов ML. Те, что появлялись до сих пор, LCF/ML, VAX ML и Lazy ML могли отличаться мелкими деталями в "ядре" языка и не такими мелкими расширениями или исключениями существующих в LCF/ML фич, все равно оставались узнаваемыми ML-ями. Новый Standard ML 83.4 Милнера похож на LCF/ML не больше и не меньше, чем остальные ФЯ о которых мы пишем, не называющиеся ML + +| DEC10 ML | VAX ML | STANDARD ML | +| :--------------- | :------------ | :------------------ | +| `let z = x+y in` | `let z = x+y` | `let var z <- x+y;` | +| `let w = z*z` | `enc w = z*z` | ` var w <- z*z` | +| `in` | `in` | `in` | +| ... | ... | ... | + +От звездочек для обозначения параметров типов переходим к буквам с `'` префиксом. Но конструкторы типов остаются постфиксными `'a ref`, как в LCF/ML. +Все конструкторы с одним параметром, но не нужно писать `c ()` в случае если тип этого единственного параметра `unit`. Для того, чтоб получить больше используются туплы и можно применить конструктор к туплу. +`cons of 'a #'a list` вместо `cons ('a #'a list)` как в HOPE для того, чтоб больше походило на речь на английском. `|` вместо `++` как в HOPE потому, что так в BNF и так разделяются ветки ПМ в лямбдах. +Суммы типов как у МакКарти из LCF/ML в новый ML не попали, только произведения. +Абстрактные типы данных теперь имеют конструкторы, которые можно разбирать паттерн-матчингом. Не автоматически объявляемые селекторы как в LCF/ML и VAX ML. Правда `<=>` вместо `=` для объявления абстрактных типов как в VAX ML. Действительно, нельзя сказать, что заимствований оттуда совсем нет. +Но это потому, что "пришло время избавиться от `=` в объявлениях". В остальных объявлениях вместо `=` будет `<-`, но рассматривается и вариант `be` как в ранних SASL. Не `<=` как в HOPE потому, что Милнер решает, чтоб это означало только меньше или равно, "чтоб облегчить переход для программистов на Паскале". Не думаем, что это самое крупное препятствие для перехода с Паскаля на HOPE. +BareML расширяется: декларациями инфиксной нотации, ассоциативности и приоритета. +Расширяется мутабельными ссылками. Да, Дамаш уже придумал как делать полиморфное присваивание, но лучше остановимся на мономорфном. В принципе, разрешает Милнер, желающие имплементаторы могут расширить расширение и добавить полиморфную мутабельность. И даже полиморфную рекурсию, если уж на то пошло. Но сам Милнер не собирается этого делать. +Обходить ограничения мономорфных мутабельных ссылок Милнер предлагает так же, как обходят мономорфизм сравнения: передавать мономорфные операции над ссылками в полиморфные ФВП. +И в SML есть перегружаемая проверка на равенство. Но не соответствующие ему констрейнты для параметров типов. Мономорфные операции проверки (не)равенства, которые определены для АлгТД (сравнение структурное) и мутабельных ссылок (равенство указателей). +BareML расширяется конструкцией для описания цикла. Но только одной и не такой выразительной, как в LCF/ML. Вместо всех разновидностей циклов только `while`. +Милнер не позаимствовал для Standard ML модули. Ни простые, которые уже есть в HOPE, ни параметризованные, которые для HOPE только разрабатывали на основе CLEAR. Можно импортировать файлы с кодом по имени файла. +BareML расширяется функциями ввода-вывода и сериализующими АлгТД в строки и разбирающие строки в АлгТД. +Минимальный набор конструкций Bare ML расширяется в Standard ML 83.4 несколькими конструкциями для удобства программиста, которые определяются однострочным описанием с помощью конструкций Bare ML или уже определенных расширений. Так Standard ML 83.4 есть `where`-выражение, определенное через `let in`. `case of`, определенное применением HOPE-лямбды с ПМ. `if then else`, определенный через `case of`, никакого тернарного оператора, как в обоекембриджских языках и LCF/ML. +Литералы для списков как в LCF/ML с `;` разделителями. +Каррированные лямбды и декларации именованных функций определенные через базовые некаррированные лямбды. Определения минимальные, но похоже, что нельзя совмещать каррированные формы с многоветочным ПМ. И определения именованных функций несколькими уравнениями с ПМ не разрешены. +Немногочисленные примеры кода написаны в стиле, похожем на стиль в котором написан код на HOPE из [Ryde82]. В случае HOPE это объяснимо тем, что для объявления локальных функций нельзя использовать ничего кроме лямбды с ПМ, но из черновика следует, что у Standard ML 83.4 не должно быть такого ограничения. Некоторые расширения используются, но не синтаксический сахар для каррирования и именованных функций: + +```sml +infix :: 30 right; +rec data 'a list <- nil | :: of 'a #'a list; + +rec var map <- fun f, nil. nil + | f, x::l. f x :: map(f,l) + +map (fun x. x + y, [1; 2; 3]) where y <- 2 +``` + +причем выражение where то ли многократно используется с ошибкой, то ли короткое описание через `let` не отражает отсутствие необходимости писать `var`, обязательного в случае базового `let`. +Булевы операции - обычные функции как в VAX ML, вычисляют оба аргумента в любом случае. +Сильная сторона АлгТД в HOPE в том, пишет Милнер, что они в принципе позволяют определить все встроенные типы. В Bare ML нет предопределенных типов, но в Standard ML 83.4 они есть: `unit`, `bool`, `Token`, `int` и `list`. Милнер описал определения для них как АлгТД, которые варьируются от более-менее обычных сегодня для `unit`, `bool` и `list`, до непрактичных чисел Пеано для `int` и вовсе невозможного определения для `Token` с бесконечным количеством конструкторов для каждого из возможных сочетаний букв. Предполагается, что имплементатор предоставит эти типы данных с более практичными представлениями. Имплементаторов у Standard ML 83.4, правда, не было. Вскоре на смену ему пришла другая версия. + +### Standard ML 83.6 + +Первый черновик описания стандартного ML был, в основном, работой Милнера. Пусть и объединяющего идеи других людей, но самостоятельно решающего какие идеи объединить. Той же весной 83-го работа над новым языком стала более коллективной. Милнер пишет, что обсуждает язык со многими. Теперь даже большая часть идей о том, какие идеи объединять уже не его. А чьи? +Милнер вспоминает [Miln83b], что написать пропозал его уговорил Бернард Суфрин. Но не упоминает никаких подробностей и не включает (пока что) Суфрина в список авторов SML. Этот список разделен на две части. В второй его части те, кто не участвовал в обсуждении первого черновика, но принимали участие в создании первоначального ML: Локвуд Моррис, Малкольм Ньюи и Крис Вадсворт. Но нет тех, кто работал над NPL, например, Дарлингтон. +Первая часть списка авторов состоит из тех, кто обсуждал SML с Милнером лично или передал свои идеи. Мы не знаем точно всех обсуждавших SML с Милнером. Уже через пару месяцев, в самой первой записи об этих событиях Милнер пишет, что не помнит кто это был. Но, по крайней мере большинство известно. +Участники совещания в ЛРЭ может и стали делать то же, что и участники совещания лисперов, организованного DARPA незадолго до того, но SERC не стал делать то, что делало DARPA, не предоставил всем работающим над доступ к электронной почте. Так что авторам SML приходилось больше собираться вместе и писать друг другу с помощью почты обычной. Поэтому ранняя работа над SML имела выраженный географический центр. +Большинство авторов из первого списка Милнера работало в Эдинбурге вместе с ним. Это Кевин Митчелл (Kevin Mitchell), Джон Скотт (John Scott) и Алан Майкрофт (Alan Mycroft), работающие над имплементацией VAX ML, оставленной Карделли в Эдинбурге [MacQ14]. Этот форк VAX ML называется так же, как назывался VAX ML в его документации [Card82a], пока Карделли работал над ним в Эдинбурге - Edinburgh ML. Майкрофт уже поучаствовал в нашей истории как один из изобретателей анализа строгости. +Это пользователи и разработчики LCF [Miln82] [Gord2000] Дэвид Шмидт (David Schmidt) [Schmidt], Брайан Монахан (Brian Monahan), Стефан Соколовский (Stefan Sokolowski) [Sokolowski]. Шмидт один из тех, про которого вспоминают, что он не обсуждал SML с Милнером лично [MacQ15]. Другой такой участник - Дэвид Моссес (Peter David Mosses) [Mosses]. Соколовский, Моссес и Шмидт - доктора, которые поработали в Эдинбурге год или два в 82 и 83-ем, Монахан работал там над диссертацией до 85-го года. +Это авторы, имплементаторы и пользователи HOPE, уже знакомые нам по предыдущим частям: Род Бурсталл, Дональд Саннелла и Дэвид Райдхерд. Но вторая, Лондонская группа имплементаторов HOPE в обсуждениях участия не принимала. +Так вышло, что в это время работающие в Bell Labs в Нью-Джерси имплементатор ML Лука Карделли и имплементатор HOPE Дэвид МакКвин тоже были в Эдинбурге, судя по воспоминаниям МакКвина [MacQ14] [MacQ20], они не приезжали туда специально для того, чтоб обсудить SML. +Из Кембриджа приезжали другие наши старые знакомые имплементаторы LCF и ML: Майкл Гордон и Ларри Полсон. Из Франции приезжал новый важный герой нашей истории и новый имплементатор ML Ги Кузино (Guy Cousineau), передававший пожелания от еще одного заочного участника и уже знакомого нам имплементатора ML Жерара Юэ. +Принимавшие участие в судьбоносном совещании в ЛРЭ от Оксфорда и контактировавшие с Кембриджем и подписанные на почтовые рассылки эмелистов разработчики форка LCF и компилятора Lazy ML из Гетеборга не имели представителей. +В Эдинбурге, дома у Милнера [MacQ14], состоялись два больших обсуждения по 8-10 человек. Милнеру не особенно понравились дискуссии с большим количеством участников и он пишет, не хочет их больше проводить, считает, что собирать мнения в небольших локальных обсуждениях лучше. +И между апрелем и июнем 1983 состоялось несколько небольших обсуждений в INRIA, Кембридже, Эдинбурге. Но связующим звеном между ними был не Милнер, а МакКвин, который развернул активную деятельность и старался донести мнения от тех или тем, кто не мог поучаствовать в одном из основных обсуждений и обеспечить, как пишет Милнер, "когерентность". Её сохранение очень беспокоит Милнера. Он торопится принять решения. Потому, что иначе "импульс будет потерян" и потеряны шансы получить "когерентный" дизайн. +Милнер пишет, что взгляды всех этих новых авторов SML нередко сильно отличались. Но и соглашались они друг с другом по многим вопросам. Милнер "надеется", что те, чьи ожидания SML не оправдал смогут "принять" то, что у них получилось. Так что новый, второй черновик описания "пытается представлять консенсус". Но часть этого консенсуса в том, утверждает Милнер, что хороший язык комитет не разработает. К счастью, SML разрабатывает не комитет. Что может прозвучать неожиданно, если речь идет об SML. Что Милнер имеет в виду? +Это, по видимому, означает, что может труд и коллективный, но с явным лидером. Скорее, как работа над CPL, но не как работа над Common Lisp, которую направлял "Квинкивират", он же "Банда пяти". +Это, конечно, не какая-то особенность разработки языка, которая мешает считать, что язык разработан комитетом. Или мешает языку являться языком, разработанным комитетом. Не стоит и преувеличивать желание Милнера направлять разработку. +Существует более сильный и необычный довод в пользу того, чтоб не считать SML языком, разработанным комитетом. Но к тому, какое желание Милнера, наоборот, не стоит приуменьшать и чем на самом деле является SML мы вернемся позднее. Когда будем писать о временах, в которых все это оказывало более существенное влияние на развитие SML. +С помощью "не-комитета" Милнер, в июне 83-го подготовил второй черновик описания SML [Miln83b]. Пока писал его, Милнер "убедился, что проблемы ML можно исправить сохранив характер ML". И что из себя представляет этот самый "характер ML"? Опять таки, не совсем ожидаемо, но это HOPE. + +```sml +type rec 'a list == nil | op :: of 'a # 'a list; + +val rec map ($, nil) == nil + | map (f, x::l) == f x :: map(f,l); + +map (fun x. x + y, [1; 2; 3]) where val y == 2 end +``` + +Делает ли добавление АлгТД и ПМ в любой ФЯ HOPE из этого языка? Нет. Мы еще увидим такую модификацию ML, которая не мешает языку быть узнаваемым ML. И увидим не одну. Даже предыдущий черновик описания SML представляет некоторую альтернативу настолько тотальной хопификации. +SML 83.4 выглядел более своеобразным языком, в котором в ISWIM-образный язык выражений добавлены АлгТД и паттерн матчинг. Но SML 83.6 довольно обычный в описываемое время язык уравнений. +К этому добавляется и множество совсем необязательных деталей, характерных для HOPE вроде более тяжелого синтаксиса со множеством ключевых слов и некоторого предпочтения туплов в ущерб каррированию. Объявление каррированных функций с помощью нескольких уравнений "не-комитет" обсуждал, но решил что это не понятно и ненужно. Исключили и каррированные лямбды, которые в 83.4 сосуществовали с лямбдами с многоветочным ПМ, как позднее будут сосуществовать в OCaml. Теперь только одна разновидность лямбд с многоветочным ПМ, как в HOPE. Упрощенная декларация каррированных именованных функций с помощью одного уравнения, правда, осталась. +Множество авторов и пользователей ML в "не-комитете" не особенно помогло стандартному ML больше походить на ML. Похоже, что многие участники не любят не только VAX ML, но и LCF/ML, включая и многих его авторов. Разумеется, нелюбовь авторов Standard ML к ML не единодушна и те, кому нравится LCF/ML еще заявят о себе позднее. +Большинство участников обсуждения предпочитают туплы из нескольких элементов как в HOPE `(x,y,z)` неоднородным спискам из пар как до того было принято в ML. Перечислениям через запятую там соответствуют такие конструкции: `(x,(y,z))`. Но не в SML. Там теперь туплы будут как в HOPE, плоские структуры с `n` безымянных полей. +В LCF/ML и первом черновике абстрактные типы данных были одной из основных конструкций, но Бурсталл и другие придумали энкодинг через АлгТД, которые Милнер называет более мощными. Так что в SML 83.6 абстрактные типы больше не в ядре языка. Это расширение, определенное через обычные АлгТД с одним конструктором, видимость которого ограничена телом АТД. +Но SML 83.6, конечно, не HOPE - это HOPE 2. Многие отличия от HOPE - это реализация планов Эдинбургских авторов и имплементаторов HOPE, с которыми мы уже знакомы. Вроде императивных фич, таких как мутабельность и исключения. Отказ от поддержки ленивых списков, которые в Эдинбурге посчитали бесполезными. Радикальное сокращение перегрузки, но все еще наличие перегрузки для некоторых операций вроде сравнения. Важное отличие от ML в котором перегрузки не было вообще. +Другие отличия от HOPE до этого момента в статьях о HOPE не обсуждались, но все равно продвигаются его авторами и имплементаторами. Так, МакКвин продвигает смену ПМ со своеобразного (и, по видимому, своеобразного и из-за особенностей имплементации) на привычный, зависящий от порядка паттернов как в SASL, Прологе и современных ФЯ. Но все равно линейный как в HOPE, переменные не могут повторяться для обозначения равенства как они могут в SASL и Prolog. +На заседаниях "не-комитета" много обсуждали ограничения ПМ и последовательность сопоставления с образцами. Неназываемые обсуждающие считают, что сопоставление от более конкретного к менее конкретному можно имплементировать эффективнее, хотя порядок слева направо и проще для программиста. Но МакКвин как раз разбирался с имплементацией ПМ для своего компилятора HOPE и убедил Милнера, что эффективную имплементацию можно получить и для порядка слева направо. +Акцентируем внимание на то, что воплощались планы на будущее HOPE именно от его Эдинбургских авторов и имплементаторов, не Лондонских, у которых совсем другие, часто противоположные планы. Но Лондон не имеет представителей в "не-комитете". Правда, неназываемые участники обсуждения хотели запретить в SML изменяемые ссылки и исключения, но они оказались в меньшинстве. +Делает ли добавление в HOPE мутабельных ссылок (не таких как в LCF/ML) и абстрактных типов данных (не таких как в LCF/ML) из HOPE ML с элементами HOPE? С точки зрения Милнера - да. Но с нашей точки зрения HOPE, уже в момент своего создания получивший главную особенность и основное нововведение ML - вывод типов Хиндли-Милнера, больше похож на объединение двух Эдинбургских школ ФП-строения, чем Standard ML. +И NPL/HOPE линейка всегда отличалась от ML переработками синтаксиса, которые потребовали бы редактировать практически каждую строку кода. SML прошел через несколько таких переделок. +Уже во втором черновике многие детали синтаксиса снова изменились. Милнер не хочет использовать `_` в качестве паттерна, который матчит все. Потому, что можно использовать `_` и в именах. Поэтому в предыдуще черновике вместо `_` использовалось `any`, а в этом используется `$`. Некоторые "не-комитетчики" хотят использовать `,` больше, например не только в туплах, но для списков как в HOPE. Но все равно пока для разделения элементов списка используется `;` как в LCF/ML. +Выражения `let` и `where` теперь с `end`. "Не-комитет" достаточно много спорит о виде `let` и дополнительных ключевых словах для декларации типов и значений. Но большинство определенно считает, что `val` лучше, чем `var` из прошлого черновика. +`<-` вместо `=` из прошлого черновика тоже никому не нравится, так что теперь будет `==`. Почему не хотят специальный оператор для сравнения, а не для байндингов? Сколько они собираются сравнений писать? Не понятно. Карделли не против того, чтоб использовать `=` и как сравнения и в синтаксисе объявлений. Пока что эта идея не победила. Может быть потому, что Карделли не против неё. +Карделли заявлял, что скоро структурные редакторы освободят программистов от заботы о конкретном синтаксисе, так что нужно описывать абстрактный синтаксис, а конкретный только рекомендовать. Но всех остальных конкретный синтаксис волнует гораздо больше и они спорят о нем до озверения, утверждает Милнер. +Решено, что язык будет полностью специфицирован. Очень важное для дальнейшей истории SML решение. Точнее важно то, что этому решению будут следовать. Как мы увидим в дальнейшем, не все создатели единых ФЯ способны на такое. +Цели обновленного SML декларируются теми же, что и в предыдущем черновике. Но, на практике, уже сделаны некоторые отступления от его анти-экспериментальной сущности. Исключение сделали для исключений. Теперь в SML исключения, позволяющие бросать любые значения, которые обобщают исключения-строки. В прошлом черновике это делать не собирались. +Новая система исключений придумана Майкрофтом, с использованием идей Монахана. Да, новая система исключений нигде не испытана, пишет Милнер, мы собирались избегать таких фич, но, вроде бы фича простая, работающая и безопасная. +Так что некоторые эксперименты возможны. Даже для экспериментов Дамаша с полиморфными изменяемыми ссылками появляется какая-то надежда. В SML 83.6 они не попали, но это уже не исключено в будущих редакциях. МакКвин пообещал разобраться с этим вопросом, пишет Милнер. +Эксперименты, которые все еще недопустимы - это, разумеется, эксперименты Карделли с рекордами. Этому ход в SML закрыт. АлгТД и ПМ как в HOPE поддерживает большинство участников обсуждений. Милнер пишет, что их можно было бы объединить с системой Карделли, добавив к конструкторам именованные поля. Но объединять не стали. Милнер опасается, что от такого богатства фич у пользователей языка глаза разбегутся. +Личное участие Карделли в заседаниях "не-комитета" не помогло сделать язык SML больше похожим на VAX ML. Но это не касается его стандартной библиотеки. Библиотеку потоков для ввода-вывода для SML теперь разрабатывает Карделли. Наработки Милнера по вводу-выводу выброшены, и Милнер передумал добавлять в ML перегруженные для любого АлгТД функции `read` и `write` потому, что их имплементация потребует "сложного и деликатного" взаимодействия с компилятором. +Но, конечно, когда сегодня один из тех (немногих), кто знает Standard ML читает, что ядро этого языка не задумывалось для экспериментирования с фичами, то экспериментальная фича, о которой он немедленно думает - это, скорее всего, не исключения и не рекорды. Это параметризованные модули. +И, разумеется, модули, не только простые модули, но и параметризованные и первоклассные уже обсуждаются комитетом. Простите, "не-комитетом", конечно же. Модулей нет в LCF/ML, но простые модули есть в HOPE и уже позаимствованы Карделли для VAX ML. Мы также помним, что одна из самых амбициозных идей для следующей версии NPL/HOPE (Эдинбургской, не Лондонской, конечно) - это параметризованные модули на основе CLEAR. +Так что многие участники "не-комитета" хотят модули в SML, особенно имплементаторы ФЯ МакКвин, Карделли, Митчелл и Полсон, которым они нужны для раздельной компиляции, необходимой для практически полезной имплементации языка общего назначения. +Но Милнер не хочет модулей. Отказываясь от них он вспоминает о "консолидации" и отказа от экспериментирования. Как будто не делает тут же рядом исключений для экспериментирования. И как будто модули, по крайней мере простые, не проверены уже в HOPE не меньше, чем ПМ и АлгТД. +Но, конечно, сторонники разных модулей мешают друг другу добавить их в SML. Простые модули как в Hope и MODULA 2, за которые выступает Карделли, не добавляют, чтоб оставить место для параметризованных модулей МакКвина. Не понятно, правда, почему простые модули исключают добавление параметризованных, которые могут стать просто расширением простых. Но у модулей вообще, всех их видов, есть и другие, неназванные Милнером противники. +Правда, как и в случае с типизацией ссылок по Дамашу, Милнер против модулей только временно. Он не только допускает, но и ожидает, что модули будут добавлены, но не сейчас и не Милнером. +Милнер считает, что не разбирается в модулях. Писать стандарт с модулями надо тому, кто разбирается в них, имеет опыт использования. И такой стандарт будет через год или два. Чем скорее закончим с описанием SML, тем скорее начнем описывать его дальнейшее развитие - MML (Modular ML). Который, вероятно, будет совместим со всем SML кроме абстрактных типов данных, которые в языке с модулями не нужны - можно регулировать видимость конструктора АТД посредством модулей. +Но способ раздельной компиляции, который не обязательно является модулями, нужен уже сейчас, для SML. +Для этого используются система директив `spec` для декларации без имплементации. Эти декларации функций и типов в тех файлах, где их используют позволяют проверять типы и компилировать файлы по отдельности. Имплементации этих сигнатур должны быть в окружении в момент загрузки скомпилированного файла. +Типы в сигнатурах могут быть более общими. Если для АлгТД не используется ПМ и автоматически генерируемое равенство, то декларировать можно только часть конструкторов и "конструкторы" могут быть имплементированы как функции. +Для объявления синонимов для типов, `deftype` в LCF/ML (или `type` в Haskell) тоже используются директивы. Почему директивы? В SML 83.4 синонимов не было вовсе. Но все "не-комитетчики", которые писали код какого-то заметного размера на ФЯ говорили Милнеру, что они очень нужны. Но даже они по какой-то причине считают их сомнительным хаком, так что они добавлены в язык не как обычная конструкция для объявления типа, а как директива вроде тех, что для раздельной компиляции. +Итак, процесс стандартизации ML продолжает отторгать идеи Карделли. Но Карделли был "достаточно добр, чтоб принять это" - продолжает хвалить его Милнер. Карделли написал детальный черновик мануала по VAX ML. И "щедро предложил" приостановить работу над мануалом для VAX ML пока описание SML не будет закончено. Но почему Карделли приостанавливает описание собственного языка? Что он "принимает", в чем его жертва, за которую его продолжает благодарить Милнер? + +### Карделли против Карделли + +До сих пор типичный ФЯ Эдинбургской программы был языком, определенным имплементацией. И если такой язык и изобретался на бумаге, как ISWIM до того, он либо так и не был имплементирован, либо то, что было позднее имплементировано довольно заметно отличалось, носило другое название и было типичным определенным имплементацией языком. +Не так с SML. Как Algol 68, SML сначала определен достаточно строго и имплементирован позднее достаточно точно для того, чтоб можно было говорить только о некоторых отличиях между языком и его имплементацией. +Как и авторы Algol 68, Милнер надеялся [Miln83b], что имплементаторы найдут проблемы в описании и самом языке. В отличие от авторов Algol 68 Милнер надеялся, что это произойдет в ближайшие месяцы и серьезных изменений не потребуется. +И у Милнера были основания надеяться. Как мы выяснили в предыдущей части, ко времени появления первых черновиков описания SML уже существовало несколько компиляторов ФЯ, и даже нестандартного еще ML, создатели которых проделали уже значительную часть работы нужной для того, чтоб имплементировать ML стандартный. +МакКвин пишет [MacQ14], что Митчелл, Скотт и Майкрофт, работавшие над форком компилятора VAX ML Карделли в Эдинбурге, начали использовать его для прототипирования дизайна SML после апрельских совещаний. +Тем временем в Кембридже Гордон посоветовал Мэттьюзу написать компилятор SML на Poly. Чем Мэттьюз и занялся [Matt89]. +Как мы помним, сами Гордон с Полсоном не горели особым желанием имплементировать ML, так что вместо того, чтоб переделывать Cambridge ML в компилятор SML Полсон стал первым пользователем компилятора ML Мэттьюза. Но Poly не так похож на SML как ML, так что ничего удивительного, что его автору хоть и удалось имплементировать SML в ближайшие месяцы, но не в самые близкие. +Работающие над форком компилятора Карделли в Эдинбурге были лучше позиционированы для того, чтоб быстро сделать компилятор SML, но в описываемое время были заняты его полным переписыванием. +Поэтому, по всей видимости, первую имплементацию SML и единственную более-менее полную имплементацию SML 83.6 написал Карделли. И даже успел выпустить несколько версий. +Карделли выпустил последнюю версию VAX ML с достаточно подробным описанием [Card83] 14-го августа 83-го года. Она уже содержала некоторые элементы HOPE-фикации, не только названия конструкторов списков как в HOPE, но и экспериментальные простые модули как в HOPE. Это, конечно, не шло ни в какое сравнение с тем, что произошло дальше. +Уже в следующем месяце девятого сентября [Card83b] Карделли начал серию компиляторов SML и переработанных руководств пользователя. Эти версии Карделли называл "позициями". Но, в отличие от танцевальных позиций, позиции у Карделли нумеруются с нуля (Pose O). +Трудно сказать, когда была выпущена первая позиция, в руководстве [Card83c] указана явно неправильная дата - 1-е августа - т.е. раньше, чем нулевая позиция и даже последняя версия VAX ML. Вторая позиция [Card83d] и последняя, (частично) имплементирующая SML 83.6, вышла десятого октября 83-го. +Разница между этими тремя версиями компилятора и их описаниями незначительная. +Разница между ними и последним компилятором VAX ML очень значительная. Карделли не добавил SML как дополнительный фронтенд, как делал в это время автор Poly Мэттьюз. Карделли заменил один фронтенд на другой. Просто удалил имплементацию своего собственного языка из своего собственного компилятора. Даже разработчики форка его компилятора в Эдинбурге этого не сделали, так что это еще не конец VAX ML. Но, определенно, конец его развития. Карделли пожертвовал своим основным экспериментом - рекордами и вариантами [MacQ14], которые заменены на алгебраические типы данных как в HOPE. Та самая жертва, за которую его благодарит Милнер в своем черновике. +Язык, который имплементируют Pose 0/1/2 отличается от SML 83.6, но предполагается, что отличия временные. Карделли пишет [Card83e], что не собирается больше поддерживать имплементацию собственного диалекта ML. Со временем, или его компилятор измениться чтоб соответствовать стандартному ML или стандартный ML изменится, чтоб соответствовать компилятору. Трудно сказать, почему Карделли все еще надеется на второй исход. +Отклонения имплементации Карделли от SML 83.6 можно разделить на две разновидности [Card83e]. Во-первых, не все эксперименты Карделли с ML закончены. Ввод-вывод частично имплементирует предложенный Карделли новый ввод-вывод для SML. Вместо системы деклараций сигнатур для раздельной компиляции из SML 83.6 в имплементации Карделли простые, непараметризованные модули. Они имплементируют идеи Карделли не полностью и работают не совсем хорошо. +Проверка типов для изменяемых ссылок осталась такой же как в VAX ML, более обобщенной, чем в черновике описания SML. В компиляторе Карделли осталась и поддержка массивов, которых нет в SML 83.6. Надежды на то, что это все может попасть в SML не считаются безнадежными. +Некоторые новые эксперименты из SML, зато, в компиляторах Карделли пока что не начались. Компилятор Карделли еще не поддерживает новые исключения, пока что можно бросать только строки как в LCF/ML. Карделли не имплементировал `local` конструкцию из SML. Не имплементированы полностью проверки для нового, перегруженного сравнения. +Работая над вторым черновиком, Милнер передумал делать порядок вычисления аргументов функции неопределенным и определил его не так как в VAX ML. В новой имплементации SML от Карделли порядок пока тот же, что в VAX ML, т.е. неправильный. +И, после перечисления таких недоработок в имплементации не самых амбициозных инноваций SML, может показаться неожиданным, что самое крупное изменение SML по сравнению с VAX ML - уравнения с паттерн-матчингом - имплементированы. Интересно, что Карделли, когда пишет эти уравнения с ПМ в своем руководстве пользователя компилятора, ставит разделители между уравнениями не в начале строк, как пишущие код на NPL/HOPE и Милнер в своем черновике, а в конце строк: + +```sml +val rec map ($, nil) == nil | + map (f, x::l) == f x :: map(f,l) +``` + +Такой стиль оформления кода не получил особого распространения в среде программистов на SML, но, как мы увидим в дальнейшем, не будет считаться чем-то немыслимым программистами на родственных языках. +Итак, уравнения с ПМ наконец имплементированы. В значительной степени. Карделли не имплементировал сопоставления с числовыми и строковыми константами в паттернах. Хотя `case of` в SML 83.6 определяется через применение лямбды с ПМ, предполагается, что это специальный случай для проверки типов. Но, по видимому, это не(до) имплементировано в компиляторе Карделли. Компилятор Карделли не проверяет линейность паттернов, т.е. то, что новые связываемые имена не повторяются. Поведение в случае если они повторяются не определено. Проверка полноты ПМ тоже не готова. +Но главная часть - компиляция ПМ более-менее современного вида сделана. Теперь и в ML паттерн-матчинг - это не проблема для компиляции, как у имплементаторов языков алгебраической спецификации, а как и в Уорреновском Прологе - решение для получения кода, более быстрого чем Лиспо-образный код с предикатами и селекторами. Решение даже более эффективное из-за линейности ПМ, меньшей мощности паттерн-матчинга, чем в Прологе и в SASL. +В 83-ем году (в худшем случае в 84-ом, когда Карделли описал свой компилятор) уже не просто рассуждают о том, как это можно было бы сделать, как рассуждали в 70-е, а действительно делают [Card84]. Сопоставление образцов оптимизируется для исключения лишних проверок, и представление АлгТД в памяти становится достаточно компактным, чтоб определенный пользователем список был не хуже специального Лиспового. Дополнительная косвенность из-за МакКартиевского разделения на суммы и произведения больше не требуется: + +``` + ┌───┐ ┌───┬───┐ ┌───┬───┐ + │ ├──►│ 1 │ ├──►│ 2 │ 0 │ + └───┘ └───┴───┘ └───┴───┘ + +``` + +специальные объекты без служебных полей в FAM-машине, правда, только для пар и троек, более крупные рекорды хранят еще и свой размер в начале. +Разумеется, это важное достижение стало возможным благодаря существенным наработкам сделанным до того. Карделли не единственный, кто пожертвовал своими собственными экспериментами ради SML. Эта жертва, правда, намного понятнее, чем жертвы Карделли. Потому, что эксперименты просто переехали в SML, экспериментатор получил, по видимому, все что хотел. Этот экспериментатор - коллега Карделли по Bell Labs МакКвин прекратил работу над своим незаконченным компилятором HOPE. И эта работа не пропала даром. +Идеи о компиляции ПМ появились у МакКвина еще в конце семидесятых, во время работы с Жилем Каном [Card84], с которым МакКвин работал в Эдинбурге в 75-76гг над корутинами для POP-2. С Каном мы уже знакомы по истории dataflow-машин. +Идеи получили дальнейшее развитие во время работы над первой имплементацией HOPE на POP-2 в 1979-80гг, вспоминает [MacQ22] МакКвин в 22-ом году. Но и в тот раз идея так и осталась идеей, в той имплементации компиляция ПМ так и не была сделана. Компиляция ПМ этим методом появилась впервые в имплементации HOPE на Franz LISP, которую МакКвин писал в начале 80-х в Bell Labs. И эти наработки были использованы Карделли для оптимизации ПМ в его компиляторе SML [Card84]. +Идеи не были опубликованы, так что другому герою нашей истории, не так тесно связанному с разработкой SML пришлось изобретать компиляцию ПМ независимо и он свои работы опубликовал. +После всех этих лет "дозревания", первое поколение паттерн-матчера МакКвина устарело на следующий же год после первого использования в реальном компиляторе. У МакКвина появились идеи получше. +Но это уже другие истории других компиляторов. + +### Standard ML 83.11 + +В ноябре 83-го был готов третий черновик описания SML [Miln83c]. Последний в этом году и первый дошедший до нас не в рукописной форме. +В предисловии более явно обозначается роль наработок HOPE в исправлении недоработок ML. Оттуда исчезли рассуждения про то, что комитет хороший язык не спроектирует и что SML проектирует совершенно точно не комитет. +В новом черновике Милнер больше не делит авторов SML на действующих и исторических. Теперь они разделены на два сорта. Основные контрибьютеры: Бурсталл, Карделли, Гордон, МакКвин, Милнер, Моррис, Ньюи и Вадсворт. И "также поучаствовавшие" Кузино, Юэ, Митчелл, Монахан, Моссес, Майкрофт, Полсон, Райдхерд, Саннелла, Шмидт, Скотт и Соколовский. На этот раз Суфрин записан в эту вторую категорию авторов. Еще новые люди в ней: Роберт Милн (Robert Milne) и Филип Вадлер. +Революционное предложение использовать ПМ-лямбду из HOPE и новая система исключений, как видим, не помогли Майкрофту попасть в основные авторы. +Главными результатом обсуждений второго черновика, пишет (печатает?) Милнер, было решение о разделении описания SML. +Двумя самыми обсуждаемыми частями языка о которых меньше всего согласия стали ввод-вывод и раздельная компиляция. Но все разногласия о том, каким должно быть ядро языка, касаются мелких деталей. Так что Милнер концентрирует усилия на описании ядра языка без ввода-вывода и модулей. Тем более, что ни выбор того, как именно будет устроен ввод-вывод, ни какие именно модули будут в ML, практически не повлияет на вид ядра языка, утверждает Милнер. +Тут можно было бы предположить, что разделение современного SML на язык модулей и язык функций - это результат действия закона Конуэя и дизайн SML воспроизводит структуру "не-комитета", в котором собираются разрабатывать модули отдельно, а ядро языка - отдельно. Но мы уже выяснили в предыдущих частях, что еще в CLEAR, от которого модули МакКвина происходят, уже было явное разделение на язык модулей и язык функций. И оба языка составляющих CLEAR разрабатывали одни и те же люди в одно и то же время. +Описания ввода-вывода от Карделли и модулей от МакКвина должны быть готовы одновременно с третьим черновиком описания ядра SML и все вместе составят полное его описание. +Имплементатор может имплементировать все три описания или только часть из них, пишет Милнер, но "надеется", что все три компонента будут приняты имплементаторами. +Полиморфные изменяемые ссылки все еще считаются недостаточно проверенными временем и Милнер все еще ждет хорошего описания от Дамаша или МакКвина. +Решение полностью исключить типы как у Карделли в VAX ML, а не совмещать их с АлгТД HOPE Милнер называет трудным. +В третьем черновике произведение типов обозначается не `#` (как в HOPE), а `*` (не как в HOPE). Сопротивление Милнера преодолено и теперь паттерн, который сопоставляется со всем - это привычное `_` (как в HOPE), а сравнение и связывание имен привычное `=` (не как в HOPE). SML приобретает еще одну фичу HOPE - `as`-паттерн и теряет одну из самых распространенных фич эдинбургских и обоекембриджских языков - выражение `where`. +Еще одно отступление от / усовершенствование HOPE: теперь можно объявлять каррированные функции несколькими уравнениями + +```sml +val rec map _ nil = nil + | map f (x::xs) = f x :: map f xs +``` + +авторы SML придумали как рассахаривать такой код в лямбды и `case` выражение: + +```sml +val rec map = fun f . fun l . + case (f, l) of + ( _, nil . nil + | f, x::xs . f x :: map f xs ) +``` + +Рекордам Карделли ход в SML закрыт, но какие-то рекорды нужны и в SML 83.11 добавили "рекорды", которые, скорее всего, уже знакомы читателю по другому функциональному языку. Такое вот объявление списка + +```sml +type rec 'a list = nil | op :: of (hd:'a, tl:'a list) +``` + +рассахаривается в такой вот код: + +```sml +type rec 'a list = nil | op :: of 'a * 'a list +exception hd : unit val hd(x::l) = x | hd(_) = escape hd +exception tl : unit val tl(x::l) = l | tl(_) = escape tl +``` + +И наш традиционный пример теперь выглядит вот так: + +```sml +infixr 30 :: +type rec 'a list = nil | op :: of (hd:'a, tl:'a list) + +val rec map _ nil = nil + | map f (x::l) = f x :: map f l + +let val y = 2 in map (fun x. x + y) [1; 2; 3] end +``` + +Третий черновик стандарта вырывается за пределы тесного кружка авторов SML и вращается в несколько более широких кругах подписчиков самиздат-журнала "Полиморфизм". Он опубликован в декабрьском выпуске. Как и обещал Милнер, вместе со статьями описывающими стримы Карделли для ввода-вывода и модули МакКвина. В том же выпуске и руководство пользователя очередной версии компилятора Карделли от 15 ноября 83-го [Card83e]. Теперь предполагается, что период накопления опыта имплементации и использования продлится "год или вроде того". И Карделли не особенно торопится. Конечно, двойные равенства он заменил на одинарные, а `$` на `_`, но менее тривиальные нововведения, вроде декларации каррированных функций несколькими уравнениями Карделли не имплементировал ни в этой "третьей позиции", ни в следующей "четвертой позиции" [Card84b], вышедшей аж пятого апреля 84-го года. +Не известно, насколько полно имплементировал SML экспериментальный компилятор Мэттьюза на основе компилятора Poly, первая версия которого была готова и передана на испытания Полсону в конце 83-го года [Matt89]. + +### Любой принц в Янтаре + +Итак, после того, как Карделли выпустил четыре ежемесячных версии компилятора SML, пятую версию он выпустил уже почти через полгода. И шестую не выпустил никогда. Как вспоминает МакКвин [MacQ14], в 84-ом году усилия Карделли по работе над SML были "разбавлены" работой над "другими проектами" чтоб в 85-ом году "полностью иссякнуть" из-за "других интересов". Что это были за проекты и интересы? +Карделли работал над надежным алгоритмом проверки типов для языка Альбано (Antonio Albano) и Орсини (Renzo Orsini) Galileo [Card85], который планировали имплементировать на основе компилятора VAX ML. Galileo - язык с рекордами и множественным наследованием. И пока Карделли работал над ним, у него появились идеи о том, как его структурные рекорды и варианты из VAX ML можно улучшить [Card84c]. +Как мы помним, от структурности типов данных в VAX ML были одни неудобства. Даже если функция обращается только к полям `a` и `b` ее тип не может отражать только этот факт. Функция типа `(|a:int; b:bool|) -> int` не сможет принимать рекорд с полями, которые эта функция не использует. И нет смысла аннотировать значение, созданное конструктором варианта `[|a=3|]` как `[|a:int|]` - его не разобрать в `case`-выражении, которое разбирает и другие конструкторы. Придется объявлять синонимы для структурных типов и использовать их дальше как алгебраические типы из HOPE, только без удобств вроде вывода типов. +И первый шаг к исправлению всех этих проблем Карделли сделал решив, что функция, конечно, должна принимать рекорды с полями, которые она не использует, тип рекорда `(a:a, b:b, c:c)` должен быть подтипом `(a:a, b:b)`, тип любого рекорда - подтип `type any = ()`. И тип варианта `[a:a]` должен быть подтипом варианта `[a:a, b:b]`. Вариант `type nothing = []` - подтип любого типа варианта. +Можно, например, описать тип только для структуры списка + +``` +type anyList = rec list. [nil: unit, cons: (rest: list)] +``` + +и функцию, которая обходит его + +``` +val rec length(l: anyList): int = + if l is nil then 0 else 1 + length(rest l) +``` + +а потом применять её к списку с "полезной нагрузкой" вроде целых чисел с типом + +``` +type intList = rec list. [nil: unit, cons: (first: int, rest: list)] +``` + +Аннотировать типы все равно пока что придется вручную, но может быть имплементатор Edinburgh ML Митчелл что-нибудь придумает [Mitc84]. +Обратите внимание на то, что для описания своих идей [Card84c] сначала в отчетах Bell Labs 84-го года, а позднее и в статьях, Карделли использует псевдокод, больше похожий на VAX ML и позаимствовавший у SML некоторые детали, но не уравнения с ПМ как в HOPE или SASL. И естественным образом появляется подозрение, что Карделли переделал свой компилятор ML в компилятор языка уравнений не потому, что его переубедили и научили любить уравнения. +Эти подозрения находят новые подтверждения в новом языке Карделли Amber [Card86], который он разработал и имплементировал в том же 84-ом году для экспериментов со своими рекордами после того, как для них не нашлось места в SML. Amber и его имплементация [Card86b] описаны в отчетах Bell Labs в сентябре 84-го и июне 85-го, которые позднее, в 86-ом году опубликованы как статьи. + +``` +type IntList = + rec(List) [nil : Unit, cons : {first : Int, rest : List}]; + +value map = + rec(map: (Int -> Int) -> IntList -> IntList) + fun (f: Int -> Int, list: IntList) + case list of + [cons = c] [cons = {first = f(c.first), + rest = map(f,c.rest)}] + otherwise [nil = unity]; + +value y = 2; + +map(fun(x : Int) x + y, + [cons = {first = 1, rest = + [cons = {first = 2, rest = + [cons = {first = 3, rest = + [nil = unity]}]}]}]) +``` + +Во времена его появления, Amber считался ML-образным, но мы его таковым не считаем. Мы считаем его редким примером языка, который произошел от прото-языка Эдинбургской программы, но утратил один из ключевых признаков - параметрический полиморфизм. +Карделли не против параметрического полиморфизма в принципе, но пока не знает как совмещать его с полиморфизмом через подтипирование. Карделли не считает, что они несовместимы, предполагает, что наработки автора языка Ponder Фейрберна могут помочь решить эту проблему. +К имплементации Amber решение не поспело, но решение Карделли нашел. Тут нам уже надо переходить от рассказа о работах более известного другими работами Карделли как раз к тем другим работам которыми он и известен. И это уже другая история. +Amber - функциональный язык, Карделли пишет об отношениях подтипирования между функциональными типами и об уникальности семейства Galileo/Amber как функциональных языков с полиморфизмом через подтипирование. +Карделли написал компилятор Amber в байткод интерпретатора на основе FAM на Amber [Card86b]. Интерпретатор работал на Macintosh, персональном компьютере с процессором как на рабочих станциях, но с существенно меньшей памятью. Из-за этого ограничения имплементация Amber и была интерпретатором, а не компилятором в нативный код, а сборщик мусора был компактифицирующим, а не копирующим как в VAX ML. +Разработчики Standard ML смогли убедить Карделли начать переделывать свой компилятор в компилятор SML, но продолжал работать над имплементацией языка, отчищенного от интересных для Карделли фич он не долго. + +Столетняя война (40%) +--------------------- + +> Человеческие существа деградируют до состояния животных, когда спорят о конкретном синтаксисе. +> Робин Милнер, Проект стандартного ML (второй черновик) [Miln83b] + +> Мы знаем, чем это закончилось. +> Лоуренс Полсон, Воспоминания: от Edinburgh ML до Standard ML [Paul22b] + +После убедительной победы стандартных эмелистов над одним из важнейших и, по-видимому, самым успешным на тот момент имплементатором ML их ждали новые победы. С, как выяснилось позднее, еще более важными последствиями. + +### Standard ML 84.7 + +Следующая встреча разрабатывающего SML не-комитета состоялась в Эдинбурге 6-8 июня 84-го года. В ней приняли участие эдинбуржцы Бурсталл, Милнер, Митчелл, Саннелла, Скотт. Бывший эдинбуржец, теперь представляющий Bell Labs МакКвин. Полсон представлял Кембридж, INRIA - Кузино, а Корнелл - Джеймс Хук (James Hook) - новый автор SML второй категории. Не-комитет также рассмотрел комментарии от Майкрофта и Кента Карлссона (Kent Karlsson), который суммировал взгляды неупомянутых эмелистов Гетеборга. +"К сожалению", пишут [MacQ85] МакКвин и Милнер, в очередной встрече комитета не смогли принять участие Карделли, Юэ и Гордон. Но они были представлены своими коллегами. +На встрече обсуждаются планы о написании учебника, возможно на основе руководства к компилятору Карделли, но ориентирующийся как на образец на вышедший в том же году учебник по Scheme - Структура и интерпретация компьютерных программ. +Планируют разобраться с описанием формальной семантики. Может Моссес использует свои наработки. +Согласились, что SML - общественное достояние и кто угодно имеет право его имплементировать. +Решают, что нужна быстрая имплементация, чтоб "убедить мир в жизнеспособности SML". Очень важно, чтоб SML не приобрел репутацию непрактичного, игрушечного языка, пишут МакКвин и Милнер. Конечно, очень некстати, что автор самой быстрой имплементации SML и, скорее всего, самой быстрой имплементации ФЯ на тот момент отвлекся на какие-то свои проекты и утратил интерес к имплементации SML. +Нужно разрабатывать и портируемую систему, что-то вроде Smalltalk 80. Системный образ, который смогут загружать несколько имплементаций одной виртуальных машины на разных платформах. Этим как раз занимаются Митчелл и другие, которые в 83-км году в Эдинбурге переписали форк нативного компилятора Карделли в интерпретатор байт-кода FAM. +Не-комитетчики считают, что первое время хорошая производительность будет несовместима с портируемостью, так что для осуществления этих планов потребуются по крайней мере две имплементации. +Значительная часть обсуждения посвящена модулям, вернее объяснению МакКвином модулей другим эмелистам. Мы рассмотрим модули позднее, пока отметим только, что запланированная независимость дизайна ядра языка от дизайна модулей уже не состоялась. +Для синтаксиса модулей нужны точки, так что лучше сократить их использование в синтаксисе ядра языка. +Милнер предложил заменить точки их отсутствием и требованием к паттернам быть "атомарными". Не думаем, что тут требуется объяснение, потому что это предложение отвергли. Между `..` и `=>` выбрали `=>`, как в HOPE. +Синтаксис списков тоже сделали как в HOPE, с разделителем `,`, а не `;` как в LCF/ML. +Эти изменения - очередной шаг к современному виду SML, но не все шаги были к современному виду, были и шаги в другом направлении. +Поскольку синонимы типов снова стали одной из обычных конструкций для объявления типа (как были в LCF/ML, а точнее как в HOPE потому, что параметризованные), было решено использовать ключевое слово `data` для того, чтоб отличать от них декларации алгебраических типов. Но, скорее всего, не так как вы подумали, а так: + +```haskell +type bool = data true | false +``` + +Соберем все эти изменения в наш традиционный пример: + +```sml +infixr 30 :: +type rec 'a list = data nil | op :: of 'a * 'a list + +val rec map _ nil = nil + | map f (x::l) = f x :: map f l + +let val y = 2 in map (fun x => x + y) [1, 2, 3] end +``` + +Да, Хаскеле-образных "рекордов" Милнера в SML больше нет. Но фича нужна имплементаторам, которые хотят писать компилятор SML на SML. Поскольку Карделли уже потерял интерес к SML, в ML можно вернуть рекорды без кавычек, типы-произведения с метками в "гармонии" с произведениями без меток. Займется их разработкой МакКвин. Решили, что сначала пара видов рекордов будут расширениями, не частью ядра языка, но интеграция в ядро лучшей из экспериментальных систем рекордов весьма вероятна. +Не смотря на то, что ядро языка должно разрабатываться отдельно от модулей, МакКвин продолжает генерировать идеи о том, что можно заменить при наличии модулей. Может и генеративные АлгТД как в HOPE не нужны и можно обойтись структурными суммами как у Карделли, а новые, отличающиеся типы будут производиться из них модульными механизмами. Было бы забавно, если бы сразу после того, как Карделли потерял интерес к SML, его бы превратили в VAX ML с параметризованными модулями, но идею посчитали слишком экспериментальной для SML. +То, что абстрактные типы данных не нужны в языке с модулями решили уже давно, но ядро языка все еще собираются стандартизировать отдельно от модулей и что делать с ними после добавления модулей пока не ясно. Может быть АТД будет просто альтернативным синтаксисом для модуля, а не отдельной конструкцией в SML с модулями. +В SML добавлены числа с плавающей точкой, а с ними добавляются и новые перегруженные операции. Арифметика теперь тоже перегружена. +Но это не все, возвращаются перегруженные операции для печати значений, от которых уже отказывались. Добавляются две специальные функции, называемые теперь "type-driven": `makestring` и `print`. `print` - тоже специальная функция потому, что написать полиморфную "type-driven" функцию нельзя, не будет контекста с информацией о типе для `makestring` и требование такого контекста нельзя поместить в сигнатуру. Примерно в это же время Карделли работает над решением этой проблемы, но не для SML, а для своих полиморфных рекордов. +Милнер подготовил новую редакцию описания ядра языка [Miln84] в следующем месяце, в июле 84-го. В октябре - еще одну редакцию [Miln84c], отличающуюся уточнениями и исправлениями в примерах кода. Например уточняется, что синонимы типов не могут быть рекурсивными, а в примеры с туплами добавлены скобки, которые теперь нужны для того, чтоб отличать их от списков. +Но SML 84.7 (или 84.10) - это не тот SML, который стал в 84-ом году известен в более широких кругах, чем подписчики самиздат-журнала "Полиморфизм". +В августе 1984 состоялась очередная конференция по Лиспу и функциональному программированию LFP. Мы не знаем, о каком SML был доклад, но в сборнике докладов конференции опубликовали [Miln84b] ноябрьский черновик прошлогоднего SML 83.11 c Хаскеле-образными "рекордами" и без нового Алгол-68-образного способа объявления АлгТД, что, скорее всего сказалось на его распространенности в дальнейшем. Понятно, что с момента отправки статьи до ее публикации проходит заметное время. +Но как описание языка помещается в лимит страниц для доклада? Просто: на каждой странице сборника по паре страниц описания, развернутых на 90 градусов. Выглядит не очень хорошо. +Впрочем, для нашей истории важнее не те изменения, которые были сделаны в SML 84.7, а те, которые делать оказались. +Конечно, не у всех несостоявшихся изменений были серьезные последствия. Например, некоторые не-комитетчики хотели оба вида комментариев: и скобки и однострочные, но это предложение отклонили. Полсон предлагал определения для операторов вида `infix precedence op = id`, где `id` уже определенная функция. Примерно как намного позднее сделали в PureScript. Этому предложению тоже не хватило поддержки. Обсудили или-паттерны, выяснили, что они никому особо не интересны, но возможен небольшой эксперимент в будущем. Кстати, на этом совещании договорились использовать термин "pattern" вместо "varstruct". +О предложениях из Гетеборга мы знаем только, что они не любили `as`-паттерны по какой-то причине, но `as`-паттерны из SML не убрали. +Несостоявшимися изменениями с серьезными последствиями были предложения Ги Кузино, представителя INRIA. +Кузино и примкнувший к нему представитель Корнелла и Роберта Констебля (Robert L. Constable) против исключения из языка `where`-выражения и хотели его вернуть в SML. И вообще в INRIA и в Корнелле хотели бы SML синтаксис больше похожий на ISWIM и LCF/ML, например `let f x =` вместо `let val f x =`. Некоторые участники обсуждения очень сильно ненавидели `val`, вспоминает Полсон, и до сих пор настаивают на том, что синтаксис SML уродливый. +Выражение-`where` из SML убрали потому, что если объявить ассоциативность, приоритет и т.д. оператора в нем, то это объявление будет после использования, что плохо для парсера. Также, конструкцию посчитали сложной для программиста. Трудно понять область видимости. И МакКвин с Милнером пишут, что `where` "с сожалением" решили не возвращать в язык. +`val` добавили для того, чтоб было легче отличать объявление функции от матчинга конструктора и убирать не собирались. +Но Кузино предлагает не только возвращение в прошлое, он желает и новых для ML фич, выступает за нелинейные паттерны как в SASL и Прологе, хочет писать + +```sml +val MP(x ==> y, x) = y +``` + +В не-комитете SML посчитали, что это плохо для оптимизации ПМ. Можно, конечно, и поэкспериментировать, тем более, что Кузино имплементировал такой ПМ, правда, как и ожидалось, не оптимально. +Помимо официального отчета МакКвина и Милнера о встрече [MacQ85] есть еще и воспоминания Полсона [Paul22b]. Полсон пишет, что не помнит точно, какую из встреч не-комитета он вспоминает, ту, что была в апреле 83-го или ту, что была в июне 84, но по описанию больше похоже на ту, что была в 84-ом. Вспоминает он это почти сорок лет спустя. Но смутность воспоминаний Полсона компенсируется его желанием обсуждать то, чего отчет МакКвина избегает. +Из документов, которые нашли и оцифровали историки SML видно, что кто-то побеждает. И понятно кого. Но кто этот победоносный автор, сделавший стандартный ML стандартным NPL? Полсон в своих воспоминаниях называет его имя. Это Дэвид МакКвин. +И пришло время МакКвину побеждать INRIA. Кузино представил предложения Юэ и все они были отклонены комитетом. У Юэ и МакКвина явно были разные виденья ML, заключает Полсон. +Полсон неоднократно приводит цитаты неких неназванных знакомых. И одна из них рассказывает о том, что на заседаниях не-комитета "было сложно услышать что-то за грохотом сталкивающихся Эго". И Ги Кузино, представлявший INRIA-эмелистов, вспоминает Полсон, просто не имел подходящего Эго для таких столкновений, был "тихим" и "мягким". +Жерар Юэ же, по мнению работавшего с ним над компилятором ML Полсона, подходил для таких столкновений гораздо лучше. Юэ бы "отчаянно настаивал на своих требованиях", утверждает Полсон, но Юэ не было на заседании не-комитета и МакКвин добился своего. И Полсон согласен с МакКвином по всем пунктам. Полсону не нравились ни нелинейные паттерны, ни выражение `where`, ни синтаксис LCF/ML. Этот синтаксис Полсон считает смесью идей из ISWIM и странностей, произошедших от неумения имплементаторов ML 70-х парсить, о котором ему рассказал Гордон. +Но Полсон не считает конкретный синтаксис таким уж важным. Трагедия раскола в том, пишет Полсон, что разногласий по важным принципиальным вопросам не было. И ради предотвращения раскола можно было бы и уступить по ряду не особенно важных вопросов. +"Французы не получили ничего из того, чего хотели" - цитирует Полсон еще одного своего неназванного знакомого. Мы знаем, чем все это закончилось, подытоживает Полсон. И мы действительно знаем, торжество французского ML над британским ML сегодня не вызывает никакого сомнения. В 22-ом году Полсон считает, что МакКвин выиграл битву, но проиграл войну. Но в 84-ом году такой исход должен был выглядеть не очень вероятным. Полсон называет это совещание "судьбоносным", именно на нем произошел "злополучный раскол", который "создал то, что позднее было названо Caml". Но для того, чтоб Standard ML стал, по словам Полсона, "трагически упущенной возможностью" не достаточно создать Caml. Caml создала небольшая оппозиция к более многочисленному движению, развивающему несколько имплементаций. Чтоб Caml стал для него угрозой, что-то в этом движении должно было пойти не так. И у разгромившего французов на судьбоносном заседании МакКвина есть идеи на этот счет. Но это уже другая история. + +### Façadisme + +Констебль и прочие разработчики Nuprl в Корнелле, неудовлетворенные направлением развития SML, просто продолжили использовать и поддерживать форк Cambridge ML 4.3 [Nuprl94], имплементирующий LCF/ML в практически первозданном виде. Так этот язык пережил многие более новые функциональные языки и дожил до наших дней. +Но в INRIA участники проекта Formel не хотели использовать LCF/ML, они хотели использовать такие фичи HOPE как паттерн-матчинг и алгебраические типы данных. Но чтоб у всего этого была LCF/ML эстетика. +Когда Полсон пишет, что в результате "раскола" получился Caml, читатель скорее всего подумает про OCaml и будет обескуражен тем, что OCaml не так и похож на LCF/ML, не имеет выражения `where` и нелинейного паттерн-матчинга. Но OCaml - не первый и даже не второй язык, который получился в результате этого самого "раскола". +Первый получился после того, как летом 84-го Ги Кузино добавил ПМ и АлгТД в Cambridge ML [HOL88]. +Эти изменения не привели к изменению названия, язык продолжили называть ML, указывая версии Cambridge ML с новыми фичами 5 (скорее всего) и 6 (это уже точно). +Не убирая из языка обычную, каррированную ML-лямбду `\p1 p2. exp`, Кузино добавил некаррированную, поддерживающую несколько ветвей HOPE-лямбду `fun C1 p1. exp1 | C2 p2. exp2`, оставив при этом точку разделителем между паттерном и телом функции. Добавил и `case`-выражение + +``` +case x of + (C1 p1) . exp1 +    | (C2 p2) . exp2 +``` + +Уравнения с несколькими ветвями Кузино добавлять не стал, так что получившийся ML остался чистым языком выражений и выглядел скорее как придуманный Бурсталлом в конце 60-х ISWIM с паттерн-матчингом, а не как разработанные им в последующие пятнадцать лет языки уравнений. +Объявления алгебраических типов данных продолжили использовать те же ключевые слова, что и объявления синонимов типов, `|` достаточно, чтоб их отличить: + +``` +lettype T = C1 of T1 | C2 of T2 +``` + +Весь остальной синтаксис остался неизменным, ничего похожего на множество мелких отличий как у VAX ML Карделли и уж тем более переделывания синтаксиса почти с нуля как в SML. Язык получился примерным надмножеством LCF/ML, все инновации его только расширяли: + +```sml +rectype * list = nil | $. of * # * list;; + +letrec map f = fun nil . nil + | (x.xs) . f x . map f xs;; + +map (\x. x + y) [1; 2; 3] where y = 2;; +``` + +Использование этой модификации Cambridge ML не было ограничено INRIA или прочим анти-SML-сопротивлением, на этом языке стали писать и другие пользователи Cambridge ML, например Гордон, который, судя по посланию, зачитанному на встрече в ЛРА, положительно относился к HOPE. +И использование Cambridge ML продолжилось, тем более, что замена его на компилятор Карделли не состоялась, а все прочее требовало еще больше работы для доведения до состояния, пригодного для использования. +Но в INRIA замену для него стали готовить уже в том же 84-ом году. У французских эмелистов было много идей и сами они объясняют раскол не привязанностью к синтаксису LCF/ML, а сомнениями в том, что им удастся добавить в SML нужные им новые фичи. +Привязанность к синтаксису LCF/ML явно сыграла свою роль, вид обновленного Cambridge ML это убедительно демонстрирует. Но современный вид OCaml, в свою очередь, демонстрирует уже то, что привязанность к синтаксису LCF/ML со временем ослабела, а новых фич, за добавление которых в SML пришлось бы бороться с сомнительными перспективами - было довольно много. + +### Fasadrenovering + +Cambridge ML не был единственным LCF/ML, который подвергся HOPE-фикации. Скорее всего, не был и первым. Потеряв надежду на то, что смогут повлиять на решения SML-не-комитета, участники проекта Formel в INRIA оказались в том же положении, что и другая команда разработчиков компилятора ФЯ, которая не имела надежды на такое влияние с самого начала. +Речь идет о разработчиках Lazy ML и его компилятора - Леннарте Августссоне и Томасе Йонссоне. На очередной ФП-конференции LFP, состоявшейся в Августе 84-го в Остине, Техас, Августссон представил новую версию Lazy ML. +Августссон с Йонссоном, по видимому, не горели особым желанием изобретать собственный синтаксис, потому и взяли подмножество LCF/ML. Но теперь оказались в ситуации, в которой были вынуждены его изобретать. +Конечно, авторы и имплементаторы Lazy ML все равно могли основывать свою работу только на подмножестве SML, как до того - на подмножестве LCF/ML. Они не знали как совместить исключения и изменяемые ссылки с ленивым языком. Но подмножество какого именно SML им делать? Каждые несколько месяцев появляется новый SML. +Августссон с Йонссоном добавили фичи HOPE в свой ML довольно рано, в конце 83-го или начале 84-го года. И, как и их французские коллеги, рано начали использовать свой язык для написания кода. Так что можно предположить, что ни переписывание компилятора и компилируемого им кода раз в полгода из-за очередного полного переделывания синтаксиса SML, ни ожидание годами того момента, когда переделывание синтаксиса SML наконец закончится, не должно было выглядеть для них очень заманчиво. +Все что им оставалось - добавить в свое подмножество LCF/ML алгебраические типы и паттерн-матчинг своим способом. +В отличие от французов, они добавили в LCF/ML не многоветочную HOPE-лямбду, а функции, определяемые группами уравнений как в SML [Augu84]. Матчинг зависит от последовательности: слева - направо, сверху вниз как в языке Тернера и в Прологе. Неполное покрытие паттернов обнаруживается, но разрешено. В LML нет обработки исключений, если значение будет таким, которое неполный набор паттернов не сможет обработать - исполнение программы прекращается. +В отличие от авторов HOPE и подобно INRIA и авторам SML они добавили еще и Бурсталл-`case`-выражение из 60-х. И сделали именно `case`-выражение основной конструкцией через которую выражаются все производные. Что, полагаем, более привычный подход сегодня, чем определение всех ПМ-конструкций через лямбду с ПМ как в прото-SML. + +```sml +case x in + C1 p1 : exp1 +    || C2 p2 : exp2 +end +``` + +Еще один вариант `case`-выражения, не в точности как у Бурсталла, в SML или французском Cambridge ML, с новым видом разделителя между уравнений - `||`. +Лямбды с такими разделителями в LML нет, но та лямбда, что в LML есть, хотя и похожа на LCF/ML лямбду на первый взгляд, не поддерживает компактное объявление каррированных функций. Так что тут нет выбора между поддержкой каррирования или многоветочного паттерн-матчинга, лямбда в LML просто хуже, чем любая из двух обычных их разновидностей. Упрощенная декларация каррированных функций с помощью уравнений с ПМ, правда, поддерживается. +Августссон пишет [Augu85], что ПМ в LML развивался под влиянием HOPE и языков Тернера независимо от SML и "пока еще" отличается. Возможно, что это "пока еще" указывает на какие-то планы сделать LML более похожим на SML, которые так никогда и не материализовались. Но некоторые детали синтаксиса ПМ уже выглядят так, как будто позаимствованы у ранних версий SML, например `$`-уайлд-кард и `as`-паттерн. Со временем, правда, уайлд-кард в LML, как и в SML, сделают более привычного вида. +Есть и ПМ-фичи отсутствующие в SML, а именно гарды. Но гарды не такие как у Тернера в KRC + +``` +f x = r1 , p + = r2 +``` + +и некоторых других языках уравнений, а более привычного вида, располагающиеся ближе к паттернам: + +``` +f x & p = r1 +f $ = r2 +``` + +но не совсем привычные, без возможности пропустить имя функции и паттерны-параметры, которая обычно есть в ФЯ сегодня: + +```haskell +f x | p = r1 + | otherwise = r2 +``` + +Августссон пишет, что гарды могут быть средством имплементации нелинейных паттернов, но нелинейные паттерны в LML, по видимому, не добавил. +Уайлд-карды, `as`-паттерны и гарды, правда, появляются уже в более поздних статьях 85-го года, например в докладе [Augu85] на конференции FPCA 85, в котором Августссон более подробно рассказывает о компиляции паттерн-матчинга. Как компилировать паттерн-матчинг Августссон придумал может и не раньше МакКвина, но раньше опубликовал. Потому, что МакКвин не особенно торопился с публикацией. Изобретать самостоятельно компиляцию ПМ авторам LML пришлось не только потому, что наработки МакКвина были мало известны даже подписчикам самиздат-журнала "Полиморфизм", но и потому, что компилировать ПМ для ленивого языка с гардами нужно было другим способом. +Наш традиционный пример на обновленном LML. + +```haskell +nonfix "_[]"; +infixr "_."; +type rec List *a = [] + *a . (List *a); + +rec map $ [] = [] +|| map f (x.l) = f x. map f l; + +map (\x. x + y) [1; 2; 3] where y = 2 +``` + +Реальный библиотечный `map` отличается тем, что не использует `$`. +Как видите, синтаксис для декларации АлгТД в LML тоже оригинальный, не похожий в точности на тот, что в HOPE или ML-ях. Но больше похожий на тот, что в HOPE. +Но главное отличие АлгТД в LML от тех, что в SML, не в синтаксисе. В SML конструкторы могут либо не иметь параметра вовсе, либо иметь один параметр. Для конструирования объекта кучи со многими полями нужно использовать туплы. В LML конструкторы могут иметь более одного параметра-поля потому, что LML - ленивый язык и конструктор со многими полями вычисляется не так как конструктор с одним полем-туплом. Конструктор с несколькими полями, правда, не обязательно будет конструировать один объект в куче. +Представление списка чисел в памяти [John86] выглядит примерно настолько хуже VAX-ML-представления, насколько можно ожидать от ленивого языка. + +``` + + ┌───┐ ┌───┬───┬───┐ ┌───┬───┬───┐ ┌───┐ + │ ├──►│C12│ │ ├──►│C12│ │ ├──►│C00│ + └───┘ └───┴─┬─┴───┘ └───┴─┬─┴───┘ └───┘ + │ │ + │ ┌───┬───┐ │ ┌───┬───┐ + └─►│C01│ 1 │ └─►│C01│ 2 │ + └───┴───┘ └───┴───┘ + +``` + +Лениво вычисляющиеся числа требуют своих объектов в куче и каждый объект требует указатель на функции, который работает и как тег. Но более сложные АлгТД могут иметь представление, которое намного хуже. +Дело в том, что первоначально Lazy ML был ленивым LCF/ML, в котором нет АлгТД и все типы данных нужно собирать из пар (троек в куче). Отсутствие необходимости в прочих конструкторах привело к выбору имплементирующего ленивость представления в памяти, которое отличается от, например, представления в GHC-Haskell, в котором АлгТД были с самого начала. В LML все ленивые конструкторы должны были помещаться на месте конструктора применения - тройки указателей - соответствующего конструктору АлгТД с двумя параметрами. Так что конструктор с тремя параметрами становился в куче тройкой со ссылкой на тройку, а конструкторы с большим числом - тройкой со ссылкой на массив ссылок, хранящий свою длину в одном из полей. Даже такое представление появилось не сразу, первоначально единственным решением был, по видимому, список троек. +Другое ограничение не было настолько же вынужденным и сложившимся исторически, но тоже происходящим от особенностей имплементации. Тегом конструктора является не число, которое можно сравнить с числом, а ссылка на набор функций. Авторы LML посчитали, что в АлгТД обычно не так много конструкторов и не нужно создавать слишком большой набор готовых конструкторов с разными тегами. Достаточно конструкторов для пяти значений тегов. Если у типа больше конструкторов - представление в памяти будет примерно как в раннем VAX ML: отдельный объект в куче хранит значение тега и ссылку на следующий объект кучи с полями конструктора. +Одна из общих фич LML и первых версий SML, из тех, что заметно повлияли на то, как ФП выглядит сегодня, не имела отношения к паттерн-матчингу и АлгТД. Как и в первых SML, раздельная компиляция в LML требовала от программиста декларировать используемые функции, конструкторы и типы. Например, для использования `map` нужно написать + +``` +import map: ((*a->*b)->((List *a)->(List *b))); +``` + +Разумеется, такой подход придумали не авторы SML, но, наверное, они сыграли какую-то роль в нормализации такого в ФЯ середины 80-х. Конечно, писать такое вручную всякий раз программистам не очень хочется. Как мы уже выяснили в прошлой части, компилятор LML писали как инструмент UNIX-экосистемы и к решению проблемы подошли как там было принято. Компилятор генерировал для экспортов в `.m` файлах c LML-кодом заголовочные `.t` файлы с соответствующими импортами, после чего использующий их программист мог вставить их в свой файл с кодом с помощью C-препроцессора. И такое широкое использование C-препроцессора запустило последовательность событий, к результатам которой мы еще вернемся позднее. +После докладов на ФП-конференциях 84-го и 85-го годов о LML [Augu84] [Augu85] [John85] и VAX ML [Card84] то, как можно имплементировать практичные ФЯ или, по крайней мере, где искать информацию об этом, стало более-менее широко известно в узком кругу ФЯ-имплементаторов. + +### Standard ML 85.9 + +Очередной большой съезд не-комитета SML состоялся в Эдинбурге в конце мая 1985 [Harp85]. Эдинбург на нем представляли Милнер, разработчики HOPE Бурсталл и Саннелла, работающие над Edinburgh ML (форком компилятора Карделли) Митчелл и Скотт, а также три новых героя нашей истории: имплементатор SML Дэвид Берри (David Berry) и два более важных деятеля, Роберт Харпер (Robert Harper) и Мадс Тофте (Mads Tofte), появление которых знаменует начало новой главы в истории ML. +Еще трое поработавших когда-то над ML и/или HOPE в Эдинбурге представляли уже другие группы разработчиков. Один из основных авторов HOPE и теперь SML МакКвин, работающий в Bell Labs вместе с Карделли, который кстати вернулся к обсуждению SML после перерыва чтоб потерять последние остатки интереса к SML. Майкрофт теперь представлял эмелистов Кембриджа вместе с Полсоном и автором Poly Мэттьюзом. К работе над ML после долгого перерыва вернулся и Вадсворт, работающий в лаборатории Резерфорда - Эплтона. +Во встрече не-комитета поучаствовал некий Ник Шарман (Nick Sharman) от Software Sciences. Не уверен, что мы еще о нем услышим после этого. +Не-комитет ознакомился еще и с письмом с комментариями от Дэвида Парка, того самого, который уже поучаствовал в нашей истории как не самый успешный имплементатор LISP и CPL. Харпер составил и описание этого заседания не-комитета. +Харпер, Мэттьюз и Парк поучаствовали в разработке языка достаточно, чтоб попасть в список авторов SML второго класса [Miln85]. +Как обычно, значительная часть обсуждений была посвящена параметризованным модулям МакКвина, которым мы до сих пор не уделяли особого внимания потому, что пишем историю имплементаций и имплементаторы SML и родственных языков в это время активно работают над имплементацией ПМ и АлгТД, но история имплементации МакКвиновских модулей еще как следует не начиналась. Но это все скоро изменится. В 85-ом году Харпер начал работать над имплементацией модулей в Edinburgh ML и мы рассмотрим историю модулей подробно в следующей части. +Другая тема с большим будущим - перегрузка. На этой встрече Майкрофт выступает за более универсальный и доступный программисту механизм перегрузки, чем просто перегрузка арифметики для ограниченного числа типов и операций сравнения для менее ограниченного числа типов. У Майкрофта есть несколько сторонников, но голосование он проиграл. +В очередной раз обсудили полиморфные изменяемые ссылки и в очередной раз решили, что они не готовы. МакКвину не нравится подход Дамаша, считает его ограниченным. +Карделли предложил отказаться от локального объявлений типов. Прочие не-комитетчики признали, что никогда не использовали локальные типы сами, но решили оставить их в SML. +На этой же встрече МакКвин пересказал свое предложение по дизайну рекордов и Милнер предложил решить ряд вопросов: + +1. Использовать ли именованные конструкторы рекордов как в Хаскеле и в рекордах Милнера. Они позволили бы меньше аннотировать типы, но достаточно единодушно решили, что их в SML не будет. +1. Функции-селекторы, как в Хаскеле и в рекордах Милнера. Вадсворт против селекторов и было решено, что селекторов не будет. Программист, которому понадобятся селекторы может их и сам написать. +1. Именованные поля в конструирующих выражениях и в паттернах. Рекорды будут скорее не как в предложении Милнера, а как у Карделли в VAX ML. +1. Перегрузка имен полей в рекордах. Милнер за перегрузку полей. И перегрузка полей будет. +1. Значимость порядка и приведение между рекордами с именованными полями и с безымянными (туплами). Порядок в выражениях имеет значение - определяет последовательность вычислений - но не в паттернах. +1. Ключевые слова для вызовов. Нет селекторов - нет проблем. +1. Специальные скобки и символы. Скобки решили делать `(| |)` как были сделаны у Карделли в VAX ML, а `#` в начале имен полей `#foo` не нужно. +1. Имена полей в типах. Будут. +1. Что будет основной формой, а что - производной. Туплы - производная форма. Это рекорды с полями, названными по их порядковому номеру. +1. Полиморфизм и подтипирование рекордов. Милнер против, считает что эти проблемы еще не решены. И потому такого в SML не будет. А значит, что перегрузка полей будет такой же ограниченной как и вся прочая перегрузка в SML в это время. +1. Можно ли писать `(| x |)` вместо `(| x=x |)` в паттернах? Карделли заявил, что это он изобрел такой синтаксис и он ему больше не нравится. Решили, что так делать нельзя. + +Получилось так, что Карделлиевские рекорды из VAX ML попали таки в SML. Но, справедливости ради нужно отметить, что принятие не-полиморфных рекордов в SML с самого начала, скорее всего, не решило бы проблему потери Карделли интереса к имплементации SML. Принятие на этом этапе определенно не решило. С не-полиморфными рекордами он уже поэкспериментировал до того. +Кто-то предложил добавить и Карделлиевские не-полиморфные варианты, но не-комитет против, включая и самого Карделли. +У многих авторов и имплементаторов ФЯ, в числе прочих и SML, позднее будут другие мнения по всем этим рекордо-вопросам. Но пока что, судя по описанию заседания, все эти важные решения дались не-комитету относительно легко и похоже, что большинство не-комитетчиков не имело какого-то сформировавшегося и важного для них самих мнения по всем этим вопросам. +Что им дались значительно тяжелее? Разумеется очередное переделывание синтаксиса. По этим вопросам хорошо сформировавшееся мнение было у многих. +Карделли предложил сделать все, что называется буквами префиксным, а символами - инфиксным. Не-комитетчики оценили простоту предложения, но все равно единодушно отклонили. +Милнер хочет заменить `type rec` на `data`, а `val rec` на `fun` и, соответственно `fun` для обозначения лямбды на `fn`. Дальнейшее осуждение Харпер описывает фразами вроде "общее недовольство и несогласие", "в этот момент дискуссия стала непродуктивной". +Не-комитет дал решительный отпор Милнеру и постановил, что разобраться с тем, как будет выглядеть синтаксис должен под-не-комитет по синтаксису. +На встрече сформировали три под-не-комитета. По вводу-выводу, которым занимался когда-то Карделли, пока не перестал, в составе: Харпер, МакКвин, Мэттьюз, Митчелл. По раздельной компиляции: Харпер, МакКвин, Мэттьюз, Митчелл. По синтаксису: Харпер, МакКвин, Скотт. +Позднее в том же году под-не-комитет по синтаксису принял предложение Милнера. А Карделлиевские составные скобки рекордов `(| |)` заменили на скобки комментариев `{ }`, а скобки комментариев наоборот стали составными `(* *)`. Так что "ядро" SML в сентябрьской редакции описания [Miln85] наконец приняло вид, похожий на современный: + +```sml +infixr 5 :: +datatype 'a list = nil | op :: of 'a * 'a list + +fun map _ nil = nil + | map f (x::l) = f x :: map f l + +let val y = 2 in map (fn x => x + y) [1, 2, 3] end +``` + +На этом история адаптации изобретений 70-х закончилась и началась история формального описания Standard ML и решения проблем, которые были в 70-х в основном только сформулированы: разработка и имплементация параметризованных модулей, перегрузки и интеграции изменяемых ссылок в ФЯ. Но это уже другая история. + +### Le ML + +В том же 84-ом году, когда Кузино добавил в Cambridge ML паттерн-матчинг и АлгТД, в INRIA начали работу над более амбициозной имплементацией ML [Maun86]. На этот раз ML не стали транслировать в Лисп. Le Lisp считается хорошей имплементацией Лиспа, но его компилятору нужно пытаться компенсировать проблемы Лиспа с производительностью, вроде особенностей (отсутствия) типизации, которых в ML просто нет. Так что решено использовать виртуальную машину LLM3, которую Le Lisp использует для облегчения портирования. Она достаточно низкоуровневая для этого. +Из-за использования рантайма и бэкенда Le Lisp новый компилятор сначала называли Le ML. Позднее его стали называть CAML по имени другой виртуальной машины уровнем выше [Cous90]. +Уровнем выше над LLM3 в новой имплементации ML - виртуальная машина CAM (Categorical Abstract Machine) [Cous87]. Почему "categorical"? Набор её инструкций основывался на исчислении категорных комбинаторов Курьена (Pierre-Louis Curien). Кузино посчитал, что набор инструкций на основе формализма поможет доказывать корректность оптимизаций. +Как основные операции тернеровского SK-интерпретатора соответствуют инстансу `Applicative` для функций, основные операции CAM соответствуют операциям из Хаскельного модуля `Control.Arrow` над функциями и парами вроде `(<<<)` и `(&&&)`. Но главное отличие CAM от комбинаторного интерпретатора в том, что она-то не комбинаторная. +CAM собирает окружения из пар указателей. Как мы выяснили в предыдущих частях, имплементаторы ФЯ или хотя-бы мечтали, как Стил, или даже успешно смогли, как Карделли, отказаться от такого подхода и сделать окружение массивом, чтоб оптимизировать время доступа к нему. Но Кузино и др. считают, что лучше оптимизировать конструирование замыкания. И новые развесистые окружения-деревья из пар быстрее склеивать из уже имеющихся конструированием пар. По крайней мере асимптотически. +Быстрое конструирование замыканий, например, частичных применений, посчитали авторы новой виртуальной машины, лучше подходит для функционального программирования вообще и для имплементации ленивости в частности. Помимо компилятора строгого по умолчанию языка Le ML/CAML, над которым работал Аскандер Суарес (Ascander Suarez), Мишель Мони (Michel Mauny) писал и компилятор ленивого ФЯ Le LML/Lazy CAML. И даже в строгом по умолчанию языке были конструкции для опциональной ленивости. +В INRIA уже с лета 86-го стали использовать для написания доказателя. Версию CAML 2.5, используемую не позднее ноября 87-го, в INRIA считали более-менее стабильной [Huet15] и не позднее весны 88-го начали писать о ней в рассылках [Maun88]. Версию 2.6 в 90-ом предлагали [Neum90] выслать желающим за $300 ($740 в 2025). +Этот CAML все еще не тот язык, который сегодня ассоциируется с этим названием. Но современный OCaml уже вполне узнаваем в нем, как и современный SML узнаваем в SML 85.9. +Что в CAML узнать гораздо сложнее - это LCF/ML. Синтаксис комментариев и строк в CAML теперь как в SML. Больше нет соединений ключевых слов вроде `letrec` и `whererec`. Типы-произведения становятся `*` как в SML. Конструктор списка теперь `::` как в SML. Точки в лямбдах и `case`-выражениях заменили на стрелки, но одинарные `->`, а не двойные `=>` как в SML [Cous90]. +Да, незадолго до того в INRIA бережно добавили фичи HOPE в LCF/ML так, чтоб не потребовалось править каждую строку их кода на LCF/ML. И теперь там же спроектировали язык, который потребовал править практически каждую строку их кода на LCF/ML++, которого стало только больше. Мы не знаем, почему так произошло. +CAML стал больше походить на SML в деталях, но остались отличия важные для представителей INRIA на заседаниях не-комитета. В CAML нет групп уравнений, обычных для HOPE-фицированных языков того времени, CAML - язык выражений, а не уравнений, в большей степени ISWIM, чем NPL. `let` остался как в LCF/ML, не похожим на тот, что в SML, и пока что никуда не делось выражение `where`. +Есть различия, которые не обозначали как важные. Элементы в списках перечисляются через `;`, а туплы не плоские как в HOPE и SML, а состоят из пар. +Основная причина несовместимости CAML с процессом стандартизации SML, по видимому, не желание сохранить побольше LCF/ML, как в случае с модифицированным Кузино Cambridge ML. CAML несовместим с SML-процессом из-за обилия новых фич. Они не ограничиваются опциональной ленивостью. +В CAML множество экспериментов с паттерн-матчингом. Два вида лямбд. Одна лямбда - `function`, позволяющая многоветочный паттерн-матчинг, но не позволяющая упрощенной декларации каррированных функций. Как лямбда в HOPE, SML, одна из двух лямбд в OCaml и `\case` в Haskell. +Но второй вид лямбд - `fun` - соответствовал не современным `fun`-лямбдам OCaml или `\`-лямбдам в Haskell, поддерживающим простую декларацию каррированной функции, но не многоветочный ПМ. `fun` в CAML поддерживает и то и другое, приблизительно как `\cases` в GHC 9.4. + +```ocaml +let chop_list = (fun n l -> chop_aux n ([],l)) + where rec chop_aux = + fun 0 (l1,l2) -> rev l1,l2 + | n -> + fun (_,[]) -> failwith "chop_list" + | (l1,h::t) -> chop_aux (pred n) (h::l1,t);; +``` + +Но код на CAML развивается в сторону соглашения, соответствующего ролям `fun` и `function`, которые в OCaml устанавливаются самим языком. +CAML большой и избыточный язык, в нем сосуществовали два синтаксиса для ПМ-выражений. И `case` `of` и более привычный программисту на OCaml `match` `with`. Но вторая конструкция использовалась чаще и к версии 3 осталось только выражение `match` `with`. +Там появляются как более-менее знакомые нам сегодня или-паттерны, менее обычные версии обычных вещей, такие как `as`-паттерны в которых сложные паттерны могут быть с обеих сторон `as` одновременно, так и более необычные вещи, вроде парсеров, объявляемых как ПМ. Как этот ПМ-парсер, разбирающий код с ПМ [Caml261]: + +```ocaml +and Fnexpr = + parse Literal "fun"; Match m -> MLmatch m + | Literal "function"; Umatch um -> MLmatch um + | Literal "match"; Expr e; Literal "with"; Umatch um + -> MLapply (MLmatch um, e, []) + | Literal "case"; Expr e; Literal "of"; Umatch um + -> MLapply (MLmatch um, e,[]) ... +``` + +Важная причина того, что разработчики доказателей не спешили использовать VAX ML и SML, а продолжали выбирать Cambridge ML или собственную имплементацию ML - это средства метапрограммирования. В мета-языке для доказателя хотелось бы иметь средства для манипуляции кодом на языке для доказателя. И в CAML они есть. + +``` +let rec MAP f = + function (<<[]>> as S) -> S + | <<#x::#L>> -> <<(#f #x)::#(MAP f L)>> +``` + +Можно и конструировать AST с помощью цитат, и разбирать в ПМ. +Но довольно привычные и ожидаемые теми, кто знаком с OCaml, гарды появились там не сразу, только в третьей версии в начале 90-х. Позже, чем в LML, но намного раньше, чем в SML. +Наш обычный пример выглядит так: + +```ocaml +type 't list = [] | prefix :: of 't * 't list;; + +let rec map f = fun [] -> [] + | (h::l) -> f h::map f l;; + +map (fun x -> x + y) [1; 2; 3] where y = 2 +``` + +Его можно сделать еще более похожим на код на OCaml, но мы старались подчеркнуть различия. + +### Неравенство уравнений + +Так в INRIA создали свою, узнаваемую разновидность HOPE-фицированных ML-ей. С паттерн-матчингом, но без групп уравнений. Это довольно привычно сегодня, но не в середине 80-х. Для большинства HOPE-фикаторов, добавление уравнений было чем-то самим собой разумеющимся. На каждый компилятор французского ML-я приходилось несколько имплементаций ФЯ с уравнениями. Несколько одних только имплементаций SML, которые в эти времена делали один за другим. И до того, как завершилось формальное описание его семантики, так что нельзя приписать успех решению эту семантику формально описывать. + +#### Edinburgh ML, SML/NJ, ML Kit + +После того, как Карделли забросил свой компилятор, следующей главной экспериментальной имплементацией для SML не-комитетчиков стал его ранний форк Edinburgh ML. Разделение произошло, по видимому, задолго до начала переделывания компилятора Карделли из компилятора VAX ML в компилятор SML, так что наработки Карделли использованы не были. Тем более, что Митчелл с коллегами в Эдинбурге переписали Edinburgh ML с Паскаля на другой язык. В Edinburgh ML не заменили поддержку VAX ML на SML как сделал Карделли, а продолжили поддерживать VAX ML вместе с SML. Правда, VAX ML потерял статус ФЯ с нативным компилятором потому, что Edinburgh ML переделали из нативного компилятора в интерпретатор байт-кода. Справедливости ради нужно отметить, что переделали в интерпретатор нового поколения с заметно более высокой производительностью, чем у первой волны интерпретаторов ФЯ вроде LCF/ML, обеих основных SASL и Edinburgh HOPE. Почему из компилятора сделали интерпретатор? Об этом в следующей части. +Эстафету главной песочницы SML-не-комитетчиков Edinburgh ML передал новому компилятору, который был компилятором SML с самого начала, написанному вернувшимся к имплементации ФЯ МакКвином и новым героем нашей истории Аппелем. Работа над этим новым компилятором Standard ML of New Jersey (SML/NJ) началась в Принстоне, в марте 86-го [MacQ14]. Осенью 87-го вышел первый доступный публично релиз. +В 89-ом году в Эдинбурге Мадс Тофте и др. начали писать еще один экспериментальный компилятор ML Kit [MLKit2002]. +Все эти компиляторы сами являются важными экспериментами по написанию реальных программ на ML и будут подробно рассмотрены в следующей части. +Но было еще несколько компиляторов SML, которые или стали экспериментами по написанию кода на ML не сразу. Или не стали уже никогда. +Начнем с компилятора SML, написанного на самом нефункциональном языке - Паскале. Да, после стольких лет, нашлись еще имплементаторы ML кроме Карделли, готовые писать имплементацию ML на Паскале. + +#### ANU ML + +Написанный на Pascal компилятор Карделли не пропал, после того, как Карделли забросил работу над ним. Один из первых авторов и имплементаторов LCF/ML Малкольм Ньюи, который после работы в Эдинбурге вернулся в Австралию работать в Австралийском национальном университете (Australian National University), создал там еще один форк компилятора Карделли, предпоследней его версии Pose 3. Этот компилятор, получивший название ANU ML [Berr90], был портирован на рабочие станции и даже на Макинтош. ANU ML имплементировал более новую версию SML и более полно, чем Pose 4. Но имплементация все же была не полной, не были имплементированы параметризованные модули и она отставала от самых последних представлений о том, как SML должен работать. Что не является нетипичным для имплементаций SML того времени. +Интересно, что Ньюи, еще один "ядерный" разработчик LCF/ML, в отличие от своих французских и шведских коллег, похоже предпочел не LCF/ML, а что-то более HOPE-образное. +SML-не-комитет узнал об ANU ML не позднее мая 88-го [Miln88], но он не перечисляется как доступная имплементация SML до 90-го года [Moss89] [Berr90]. К концу 80-х производительность генерируемого кода все еще оценивают как хорошую, а требования к памяти как более скромные, чем у новых компиляторов. + +#### Poly/ML + +На заре HOPE-фикации Гордон и Полсон [Gord82] рассматривали Poly как перспективный источник идей для развития ML и даже как потенциальный язык для объединения в один с ML. Мэттьюз в дальнейшем писал, что неплохо было бы добавить в Poly паттерн-матчинг и конструкторы исключений с параметрами [Matt88], но ничего из этого не было сделано. +Poly не был хопифицирован добавлением ПМ и АлгТД как VAX ML, Lazy ML, Cambridge ML. Компилятор Poly был использован как основа для компилятора SML. Но, поскольку Poly использовался для разработки самого компилятора (и, по видимому, ни для чего другого), возможность компилировать код на Poly осталась. Так же как поддержка VAX ML осталась в Edinburgh ML, имплементирующем SML. +Первая экспериментальная версия Poly/ML была сделана до конца 83-го года [Matt89] и была пригодна для написания реальных проектов не позднее 86-го года. По мнению Полсона, Poly/ML был, на протяжении ряда лет единственной эффективной имплементацией SML. Что не удивительно, Полсон с самого начала не считал компилирующиеся через Лисп ML-и серьезными имплементациями по сравнению с Poly и VAX ML [Paul22b]. И в это время одна ветка VAX ML была заброшена, другая переделана в интерпретатор и третья пропадала на другом конце Земли и, по видимому, не была известна европейским и американским эмелистам до конца 80-х [Berr90]. +Дэвид Мэттьюз работал в Кембридже над Poly/ML один, по видимому, до конца 89-го года [Moss89]. Компилятор был портирован на популярные рабочие станции SUN 3, где требовал 3-6Мб памяти, но VAX-версия была заброшена [Berr90]. Распространяла компилятор компания Cambridge University Technical Services, занимающаяся коммерциализацией того, что делали академики в Кембридже. А именно, другие академики получали компилятор за 100 фунтов ($333 в 2025) [Wolf89], а коммерческий пользователь мог договориться во сколько ему обойдется лицензия. Но не позднее весны 1990-го CUTS передала права на разработку и распространение Poly/ML компании Abstract Hardware Limited, в которой Мэттьюз продолжил работу, но уже не один [Matt2000] [Berr90]. Abstract Hardware Ltd. продавала компилятор академикам за 500 фунтов ($1664 в 25-ом), а коммерческим пользователям за две тысячи фунтов ($6655 в 2025). + +#### POPLOG ML + +POPLOG - система, разработчики которой старались сделать ее удобной основой для имплементации ФЯ. Разумеется ее использовали и для имплементации SML. +С появлением популярных рабочих станций, POPLOG [Slom89] сделали более удобным для портирования на новые машины, для этого в нем теперь два промежуточных языка, с которыми работают имплементаторы расширений: PVM (Poplog Virtual Machine) - в это компилируют новые языки писатели фронтендов, PIM (Poplog Implementation Machine) - это имплементируют на новой машине писатели новых бэкендов. Весь конвейер между PVM и PIM машино- и языконезависим. +С середины 84-го POPLOG работает на 4BSD, сначала на VAX, а позднее и на машинах с MC68K вроде рабочих станций SUN и Apollo, c 386 и менее важных. +Университет Сассекса продавал POPLOG с 82-го года [POP19], с существенными скидками для прочих академиков, с 83-го года продажами занялась компания Systems Designers Ltd. (позднее SD). В ней имплементацию MACLISP-диалекта Каннингема на основе POPLOG посчитали не соответствующей ожиданиям от коммерческого продукта и профинансировали новую, более серьезную имплементацию Common LISP. Деньги на это выделил и S(E)RC. Над имплементацией работал в основном Джон Уильямс (John Williams) под руководством Каннингема. Для этой имплементации как и для имплементации Пролога, в POPLOG добавили новые фичи. Гибсон внес изменения в виртуальную машину для лучшей поддержки статической видимости и исключений. +В 86-ом году, в POPLOG версии 12, Common Lisp был уже имплементирован процентов на 90% [POPLOG], а в феврале 87-го состоялся релиз версии 1.0 Common Lisp фронтенда. К осени 88-го заработала даже оптимизация хвостовой рекурсии. +Добавленная в POPLOG статическая видимость стала использоваться и в POP-11, сначала как опциональная, летом 86-го с версии 10.3 [POPLOG], а позднее и по-умолчанию. В последних версиях POPLOG пример Поплстоуна, демонстрирующий проблемы с возвращением функций больше проблемы не демонстрирует. Но превращение POP-11 в полноценный ФЯ Эдинбургской программы не состоялось, как и превращение Poly. Вместо этого Роберт Дункан (Robert Duncan) и Саймон Николс (Simon Nichols) написали на основе POPLOG имплементацию Standard ML, для которой улучшенная поддержка статической видимости как раз пригодилась. +Версия 1.0 PML, как назывался Standard ML на основе POPLOG, появилась в октябре 88-го года, но не поставлялась в комплекте с POPLOG (в это время 13.6). Первоначальная имплементация SML доросла до версии 1.3 и в декабре 90-го сменилась версией 2.0, вошедшей в основной комплект поставки POPLOG 14 в 91-ом году. +Считалось, что POPLOG SML генерировал код, который исполнялся немного помедленнее Poly/ML, но компилировал побыстрее [Appe91]. +В 90-ом году счастливым обладателем POPLOG можно было стать заплатив 7.5тыс. фунтов (25тыс. долларов в 2025-ом) [Berr90]. Для академиков скидка 85%! + +#### Rutherford ML + +Standard ML получил поддержку от программы-наследника STI и Лаборатория Резерфорда - Эплтона подключилась к его разработке и имплементации. В 85-ом Вадсворт принял участие во встречах комитета, а в самой лаборатории занялись доведением Cambridge ML до полной и окончательной HOPE-фикации. +Эта имплементация называлась Rutherford ML или Rutherford SML. +Филип Микаэль Хедлунд (Philip Mikael Hedlund) начал работу в лаборатории в январе 85-го [Witt85]. К февралю 86-го Хедлунд под руководством Вадсворта [RAL87] написал новые парсер и проверку типов, которые сделали Rutherford ML компилятором подмножества Standard ML без модулей. Сделав это он приступил к улучшению генератора кода и рантайма [Duce86]. Заявляется, что оптимизации в новом генераторе кода на Лиспе улучшили время выполнения некоторых программ в 10 раз [RAL87], но нам не известно каких программ. +Имплементация Rutherford SML была закончена в июне 1986. Тестировал новый компилятор SML Полсон в Кембридже [RAL87]. После этого Хедлунд начал работу над генератором парсеров для ML, но летом 87-го его работа в ЛРА закончилась и он вернулся в Швецию [Danc87]. +Cambridge LCF и, естественно, Cambridge ML и Rutherford SML в ЛРА заработали на IBM-совместимом мэйнфрейме с UNIX - Atlas 10 [Faul84] [Duce86]. Так в середине 80-х мечты Стрейчи и Уилкса осуществились уже полностью, Реально-Существующий-CPL (ML) заработал на пригодном для его использования Atlas (но 10, а не 1 и не 2 и разработанный Fujitsu). +Машина с одним ЦПУ в 15 MIPS и, что более важно, с 16Мб памяти, так что нужно было меньше ждать когда страницы поднимут в память с диска. Так что Кембриджский Университет проверял на этой машине за минуты доказательства, которые требовали ночь работы VAX [GEC63] с процессором всего-то раз в 15 медленнее. +И когда Кембридж использует очередной реальный CPL на очередном Atlas, то понятно, что другая организация мечтателей о CPL не остается в стороне. Королевский Радиолокационный Институт тоже захотел компилятор SML, но на этот раз не разрабатывал компилятор самостоятельно, а заказал [Berr90] его компании Harlequin Ltd. (Кембридж, СК), разработчику коммерческой имплементации Common Lisp. +Но Cambridge ML с нестандартно HOPE-фицированным французским ML, даже заброшенный французами и с новоявленным стандартным форком-конкурентом не погиб и оставался, по видимому, более популярным, чем Rutherford ML, количество пользователей которого стало заметно падать уже через пару-тройку лет после первого релиза [RAL89]. +Жизнь Cambridge ML продлило и то, что в отличие от Rutherford ML, так и оставшегося компилятором ML через Franz LISP и прочие нестандартные и уже умирающие в эти годы Лиспы, Hewlett-Packard профинансировал [Gord2000] создание Кэрроллом (John Carroll) версии нестандартного Cambridge ML, компилирующего через стандартный Common Lisp. + +#### Да, fib + +Давайте посмотрим, насколько быстро работает код, который генерируют компиляторы HOPE-фицированных ML-ей. Таблица показывает насколько больше вызовов функций делается за единицу времени по сравнению с интерпретатором LCF/ML. + +| | nfib | +| :------------------------------------ | :------: | +| Le ML (CAML) [Maun86] | 197 | +| SML/NJ 0.18 [Augu89] | 115 | +| _C_ [Augu84] | 100 | +| ANU ML (VAX ML) [Augu84] | 92.0 | +| Lazy ML [Augu89] | 59.0 | +| Cambridge ML V6.2 Franz Lisp [Maun86] | 31.5 | +| Poly/ML [Maun86] | 25.8 | +| Poplog ML [Berr90] | 25 ? | +| Le LML [Maun86] | 16.0 | +| Edinburgh ML [Augu89] | 13.5 | +| _LCF/ML_ [Augu84] | 1.00 | + +``` +Le ML (CAML) [Maun86] +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +SML/NJ 0.18 [Augu89] +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +C [Augu89] +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +ANU ML (VAX ML) [Augu84] +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +Lazy ML [Augu89] +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +Cambridge ML V6.2 Franz Lisp [Maun86] +░░░░░░░░░░░░░░░░ +Poly/ML [Maun86] +░░░░░░░░░░░░░ +Poplog ML ? [Berr90] +░░░░░░░░░░░░░ +Le LML [Maun86] +░░░░░░░░ +Edinburgh ML [Augu89] +░░░░░░░ +LCF/ML [Augu84] +░ +``` + +Разумеется, основной микробенчмарк для оценки производительности ФЯ 80-х не особенно хорошо подходит для такой оценки. С появлением более-менее серьезного кода на этих языках стало возможно более качественное сравнение, которое поменяло взаимное расположение имплементаций в этом рейтинге. Но это уже тема следующей части. + +### Пираты Кремниевой Долины + +Академия стала переписывать свой Лисп-код с MacLisp-диалектов на Common Lisp. Видимо предполагая, что прочие Лиспы теперь не имеют будущего. В некоторых случаях так и вышло. +InterLisp для имплементации ФЯ и до того не использовался, но Standard Lisp использовался, имел компилятор PSL, производящий быстрый код и получил поддержку HP. Для Standard Lisp разрабатывался новый экспериментальный компилятор EPIC [Kess86], компилирующий в семь стадий, больше похожий на оптимизирующие компиляторы S-1 Lisp и BLISS-11. Но Standard Lisp не поддерживал лексическую видимость и потому был не особенно удобен и эффективен для использования в качестве бэкенда компилятора ФЯ. +Ожидалось, что этот недостаток будет преодолен, но преодолен одновременно с превращением в компилятор Common Lisp, что было еще одним поводом использовать CL, а не Standard Lisp. +Сконвертировать наработки по имплементации хорошего компилятора Standard Lisp в хороший компилятор Common Lisp не удалось. Сделать хороший компилятор Common Lisp было вообще не легко, как это и было задумано, по крайней мере некоторыми, авторами его стандарта. Так что HP прекратил поддержку разработки PSL и Standard Lisp, заказав компилятор Common Lisp у коммерческой компании, основанной авторами стандарта [Stee96]. +Но не все нестандартные Лиспы прекратили существование, работы над Le Lisp продолжались. +Торжество Common Lisp, тем временем, оборачивалось не самым удачным развитием событий для академии. Создание стандартного (со строчной буквы) Лиспа было использовано для того, чтоб перезапустить экосистему и превратить её из экосистемы насыщенной доступным для академии кодом, на основе которого можно строить что-то свое, в экосистему, в которой практически все работающее коммерциализировано и закрыто и академику не за что ухватиться. +Эта новая коммерциализированная экосистема состояла из Лисп-машинистов, уступающих под натиском обычных рабочих станций, и использующих их остальных лисперов. Проблемы Лисп-машинистов мы изложили подробнее в прошлой части. +Лисп-машинисты со временем придумали, как использовать победоносное шествие обычных рабочих станций. Не нужно пытаться продавать свои персональные мини-компьютеры величиной с холодильник, нужно продавать плату расширения - Лисп-акселератор, который лиспер будет вставлять в свои новые, все более мощные рабочие станции и даже простые ПК из верхнего ценового сегмента вроде Макинтоша. Эта идея несколько продлила жизнь Лисп-машинизма, но, по видимому, не стала достаточно релевантной для того, чтоб сыграть важную роль в нашей истории функционального программирования. Рабочие станции развивались быстро и акселератор для Лиспа был не так уж и нужен. +Основными коммерческими имплементаторами Common LISP для рабочих станций были Lucid Inc. и Franz Inc. Некоторые производители обычного железа начали с разработки собственных имплементаций, но, со временем, отказывались от них в пользу продуктов Lucid, которая выступала OEM имплементатором для них. Franz больше работала с небольшими компаниями и индивидуальными программистами. +Franz - это уже знакомые нам разработчики Franz LISP, самой популярной имплементации MACLISP-образного Лиспа, являющейся общественным достоянием. Для разработчиков ФЯ в академии это подходило, но группа Фейтмана в Беркли положила этому конец, став коммерческой компанией, которая сначала продавала новые версии Franz LISP, а с конца 86-го стала продавать ExCL (Extended Common Lisp), переименованный в 88-ом году в Allegro CL [Franz]. +Lucid основали важные разработчики Common LISP, которые написали компилятор с нуля в 85-ом. Компания привлекла 16 (49.5 в 2025) миллионов долларов венчурного финансирования [Gabr96]. Их соперники из Franz считают, что вместе с господдержкой набиралось 25 (77 в 2025) миллионов [Franz]. +Franz существовала на совсем другом уровне. Джон Фодераро тоже написал имплементацию Common LISP с нуля, но один, сидя дома за рабочей станцией, полученной от SUN или какого-то другого клиента из первых, как плата за портирование Franz LISP. Хотя Фодераро и писал код какое-то время один у себя дома, у него были коллеги. Офис располагался дома у другого со-основателя Фрица Кунце (Fritz Kunze), Фодераро просто не хотел "отвлекаться" на какое-то взаимодействие с ними. +То, что Franz мог конкурировать с Lucid, по видимому, объясняется не какой-то необычной эффективностью Franz, а скорее необычной неэффективностью Lucid. И мы знаем, что они могли конкурировать потому, что даже SUN, отношения с которой считались ключевыми [Stee96] для выживания Lucid с 86 по 87-ой годы переключалась на сотрудничество с Franz. +Сам CTO Lucid вспоминает [Gabr96], что программисты часто предпочитали Franz, но их руководство предпочитало Lucid. Например, основатели SUN из-за знакомства с основателями Lucid по университету. +Он же считает, что Franz мог конкурировать с ними потому, что SUN предоставила тем альфы и беты ПО Lucid. Но это может быть проекцией. Потому, что он же вспоминает, как они сами пытались получить такой доступ к ПО Franz, но те пригрозили им судом. +Их соперники из Franz называют причиной своей конкурентоспособности то, что из-за нищеты они разрабатывали свой софт на дешевых рабочих станциях, похожих на те, что использовали их клиенты [Stee96]. В Lucid писали на Лиспе сначала на Лисп-машинах Symbolics [Gabr96], а затем на топовых рабочих станциях SUN. +Имплементаторы ФЯ в академии, которые стали переключаться с MACLISP-вариантов на Common LISP обычно делали свои проекты собираемыми как Lucid CL так и Allegro CL. Но этого было недостаточно. По понятным причинам важно, чтоб проект мог использовать и имплементацию с открытым кодом, может быть не такую качественную как коммерческая, но как-то работающую. И с этим дела обстояли не особенно хорошо. + +### Дети Дюны + +Как мы выяснили в предыдущей главе, Spice Lisp стал эталонной имплементацией Common Lisp, код которой был общественным достоянием. +Несколько имплементаторов коммерческих Лиспов воспользовались этим. В DEC использовали код Spice Lisp для имплементации Лиспа на своих машинах вроде VAX. В TI - для MC68K [Stee96]. Самые важные платформы для имплементации ФЯ в это время. Но ни эти разработчики, ни прочие, из тех что использовали Spice Lisp не отдали никакого кода в Spice Lisp. Ни VAX, ни MC68K бэкенда он не получил. +Сегодня коммерческие разработчики развивают проекты вроде LLVM даже если их не обязывает лицензия. Но у Столлмана были все основания ожидать, что без такого принуждения не обойтись. Коммерциализаторы Лиспа в 80-е это убедительно продемонстрировали. +Тем временем, в 1985-ом году финансирование проекта SPICE прекратилось [MacL15], проект закрылся. +Но разработка бывшего SPICE LISP продолжилась, только теперь он назывался CMU Common Lisp (CMUCL) и был небольшой автономной [CMUCL] частью нового проекта университета Карнеги-Меллона. Этот проект был представителем успешного направления по созданию параллельных машин с разделяемой памятью, которое привело к созданию обычных современных компьютеров. А именно проект по написанию ядра ОС для таких машин, совместимого с 4BSD - Mach (так Джуизе записал произнесенный Рашидом придуманный Тевоняном акроним MUCK, Multiprocessor Universal Communication Kernel). +Нужно отметить, что роль имплементации Лиспа на Mach была существенно менее важной, чем имплементация Лиспа на 3BSD в Беркли несколькими годами ранее, о которой мы писали в нулевой части нашей истории. 3BSD для VAX создавался практически для того, чтоб использовать Franz LISP и написанные на нем программы. Позднее, 4BSD для MC68K уже не был так связан с Лиспом, и получил Franz LISP с некоторым опозданием. Но это не идет ни в какое сравнение с тем, насколько неважным был CMUCL для проекта Mach. Разработчики Mach заявляли поддержку Common Lisp [Acce86], но ни на популярных и важных для истории ФП VAX и MC68K ни на остальных, поддерживаемых Mach платформах, CMUCL в 80-е так и не появился [Rash89] [McDo89]. Кроме одной. +Нашлась одна коммерческая компания, которая не просто взяла общественное достояние - код SPICE LISP/CMUCL - и использовала для своих нужд, ничего не вернув в CMUCL, но решила поучаствовать именно в развитии его кода и сделать свою платформу первым обычным компьютером на котором он будет работать. И так получилось, что единственной на все оставшиеся 80-е. Этой компанией была IBM. +Платформа была не той, о которой вы, скорее всего, подумали. В IBM решили выйти на рынок рабочих станций со своей новой платформой RT, поконкурировать с SUN и Apollo. +Но лисперы нашли ответ на эту новую угрозу появления открытой имплементации Лиспа. Руководитель группы разработчиков SPICE LISP/CMUCL Фалман был одновременно и основателем Lucid. И он воспользовался своим положением руководителя группы для того, чтоб убедить IBM заказать разработку имплементации Лиспа для их новой рабочей станции у Lucid. И ему это удалось. Но дальше события развивались не очень благоприятно для Lucid, и хитрый план Фалмана был источником многих проблем, большинство из которых не выглядит такими уж неожиданными. +Для начала, этичность и даже законность действий Фалмана показалась IBM сомнительной. Возникли опасения, что кто-то из переигранных Фалманом может обратиться в суд. В IBM решили обстоятельно изучить такие риски и потратили на это от полугода до года. Только после этого IBM заключил договор с Lucid. +Это не только задержало деньги от IBM. Фалману удалось уговорить IBM давать эти деньги ему, а не университету Карнеги-Меллона в числе прочего и потому, что дедлайн не изменился. И до него теперь оставался только один год. +Тут дала о себе знать следующая проблема. У Lucid уже был собственный компилятор, но не было инструментов вроде редактора, отладчика и библиотек, например для графического интерфейса пользователя. К счастью, все это было написано в УКМ в рамках проекта SPICE и Lucid просто взяла написанный в университете код в надежде доработать его до индустриального уровня. Но написанный в университете код оказался не таким уж хорошим, а требования IBM к индустриальному уровню повыше, чем ожидали лисперы. +Наконец, IBM почему-то не понравилось, что Lucid хочет продать им как документацию написанные на деньги DARPA руководство пользователя и репорт по Common LISP. Представьте себе, пришлось в Lucid еще и документацию писать. Которая была, в основном, копией репорта. Гэбриел называет её "редакцией Пьера Менара" и тратит на объяснение шутки страницу своей книги, сопровождая пояснение обширной цитатой из Борхеса. +Короче говоря, как и в случае с лисп-машинами, с приватизацией прибылей поторопились, еще не все убытки были национализированы. +Проблема, которая оказалась неожиданной (для 80-х годов) заключалась в том, что новая рабочая станция IBM провалилась. +Вся эта история нанесла Lucid значительный ущерб, пишет Гэбриел, "возможно, что невосполнимый". +Как видно, у лисперов не было недостатка решимости ударить других лисперов ножом в спину. Но, обычно, недоставало способности сконвертировать этот удар в свою победу или выгоду для себя. +Забавно, что эта история описана не какими-то злопыхателями из Franz, а одним из основателей Lucid Гэбриелем [Gabr96], который неиронично считает себя, Фалмана и других своих коллег по Lucid жертвами IBM. +Что успели сделать для IBM RT в университете Карнеги-Меллона [MacL15] - это рантайм в шесть тысяч строк ассемблера и раскрытие команд SPICE-Lisp байт-кода в стиле компиляторов Уоррена и Карделли. Что наверное было не очень хорошо для байт-кода, который планировалось интерпретировать микрокодовым интерпретатором на PERQ. Но насколько это было плохо было бы важно в том случае, если б IBM RT была хоть сколько-нибудь популярна, а она не была. +В 85-ом году один из разработчиков CMU CL (из тех, у кого не было собственной Лисп-компании) Маклахлан (Rob MacLachlan) начал работу над более серьезным компилятором для этой имплементации Лиспа под названием Python [MacL15]. Компилятор для Лисп-системы часто носит отдельное название. Так компилятор Franz Lisp назывался liszt, а компилятор Le Lisp назывался Complice. +Но до конца 80-х компилятор CMUCL Python не генерировал код ни для каких релевантных для нашей истории платформ. В 90-е генерировал, но это уже другая история. + +### Отец с атомным сердцем + +Как видно, в это время в США было не много лисперов, желающих разрабатывать имплементации Common Lisp с открытыми исходниками. Возможно, что это объясняет, почему последнюю версию Franz Lisp, являющуюся общественным достоянием не попытались превратить в компилятор Common LISP, например, использовав CMU CL как фронтэнд. +Помощь пришла из Исследовательского Института Математических Наук Киотского Университета (Research Institute for Mathematical Sciences, Kyoto University). +Юаса Тайити (Yuasa Taiichi) и Хагия Масами (Hagiya Masami) написали имплементацию [Yuas85] Common Lisp под названием Kyoto Common Lisp без особого взаимодействия со своими американскими коллегами, просто по описанию Стила [Stee84]. На 60% на C и на 40% на Лиспе. Это было не ново, Franz Lisp тоже имел портируемый рантайм на C. Новым было то, что компилятор KCL транслировал Common Lisp в C-код для последующей компиляции компилятором C. Европейские и американские имплементаторы Лиспов и ФЯ считали такой подход бесперспективным [Stee96]. И появление этого компилятора изменило их мнение, в конце 80-х и начале 90-х многие попробовали этот подход. Но это уже другая история. +Компилирование через C означало медленную компиляцию и легкость портирования. Лисперы вспоминают, что и конкурирующие с KCL компиляторы тоже не отличались высокой скоростью компиляции. А вот легкость портирования была важной. +Первой машиной, для которой написали KCL был миникомпьютер Data General Eclipse MV, но не позднее ноября 85-го появились версии для более актуальных миникомпьютеров VAX-11 и рабочих станций SUN с BSD 4.2. +Портирование KCL на первую Unix-машину заняло три дня до первой работающей версии и еще несколько дней на устранение ошибок. Портирование на следую Unix-машину было сделано уже "за три вечера". +Как компилирование через C сказалось на производительности? К этому мы еще вернемся. +Конечно же, последняя версия KCL вышла уже в июне 87-го. И эту имплементацию коммерциализировали как Ibuki Common Lisp. Но, как и в случае с компиляцией через C, произошло некоторое отклонение от американской лисперской нормы. Киотский университет продолжал раздавать последнюю версию бесплатно всем академикам, которые напишут им обычное, не электронное письмо [Boye92]. Так что академик мог продолжать выпускать патчи для этого последнего KCL но не мог потом основать собственную фирму, которая будет продавать этот код, как уже поступили многие коллеги с кодом-общественным достоянием. +Теоретически, развивать KCL таким способом мог любой желающий академик, но на практике нашелся только один, по крайней мере в 80-е. +В прошлой части мы писали, как МТИ сделал из MACSYMA-общественного достояния MACSYMA-коммерческий продукт, который должен обеспечить преимущества для Лисп-машинистов из Symbolics. И что департаменту (министерству) энергетики США не особенно понравилось, что их многолетние вложения стали чьим-то там коммерческим продуктом. И МТИ передал им код MACSYMA на мертворожденном Лиспе NIL с некоторыми ограничениями для распространения. Для начала, эту DoE MACSYMA нужно было переписать на язык поживее и энергетики нашли для этого подходящего человека [Fate2003]. +Уильям Шелтер (William Frederick Schelter) с 79-го года работал в Университете Остина вместе с нашим старым знакомым по нулевой части Бойером. Шелтер поддерживал код DoE MACSYMA с 82-го года и со временем переписал его на Common Lisp. Ожидалось что этот Лисп будет поживее NIL, в академии стали писать на нем, вот и очередная версия доказателя Бойера-Мура на нем, и очередная производная LCF с Cambridge ML, будут на нем писать и другие программы о которых мы еще планируем рассказать подробнее в другой раз. +И когда KCL перестал обновляться, пришлось Шелтеру разрабатывать набор патчей для него - Austin Kyoto Common Lisp. Пока CMUCL не начал поддерживать хоть какие-то актуальные для академии платформы, вся экосистема открытого кода на Common Lisp держалась на одном человеке. +Так что технически компиляция ФЯ через Common Lisp была возможна, но возможность эта была пока что не особенно привлекательна. Так что имплементаторы ФЯ желающие использовать Лисп рассматривали и другие варианты. Но это уже другая история. + +Big in Japan +------------ + +> Срочный, захватывающий и грамотный репортаж о головокружительном будущем компьютерной революции. +> San Francisco Chronicle + +> Это не просто вторая компьютерная революция, это важнейшая из двух. +> Эдвард Фейгенбаум [Feig83] + +> УЛЕТ, ОТПАД! +> The New York Times + +> Настоятельно рекомендуем... должно быть прочитано каждым, кого интересует экономическое и технологическое будущее. +> Library Journal + +В прошлой части мы оставили создателей (симуляторов) специализированного железа в не самое лучшее для них время. Но перед тем, как дела у них пошли совсем уж плохо, ситуация ненадолго улучшились. Помощь пришла из Японии. +Еще в 79-ом году в Министерстве международной торговли и промышленности Японии (Ministry of International Trade and Industry) появилось желание начать амбициозный проект по созданию компьютера нового поколения [Shap83]. Следующее поколение выходило пятым, так этот проект впоследствии и стали называть. По видимому, это была реакция на обсуждения "кризиса программного обеспечения" и стартовавшие в 77-79-ом проекты, о которых мы уже рассказывали. +Министерство выяснило, что считает компьютером будущего индустрия. Оказалось, что в индустрии считают, что это графическая рабочая станция с большой памятью для микрокода вроде тех, что сделали в лабораториях Xerox. И сеть будущего - это Ethernet. +Тут они были правы, в 80-е многие делали рабочие станции и даже персональные компьютеры верхнего ценового диапазона вроде Макинтоша, на которые явно повлияли эти машины. Микрокодовый аспект, правда, не был важным. Но этот ответ не понравился министерству. +И дело не в том, что для успеха нужно пробовать что-то новое, а не пытаться сделать лучше то, что уже кто-то делает. Предыдущие программы министерства как раз направляли такое догоняющее развитие и были успешны. Может быть, слишком успешны. Потому, что сформировали стереотип, который министерство хотело сломать новой программой. +Для того чтоб продолжать заниматься тем, чем все компьютерные компании занимались и так, помощь министерства промышленности уже не требовалась. Весь смысл был в том, чтоб попробовать по-развивать что-то, что компьютерным компаниям обычно не так интересно. Но что именно? Как мы выяснили в предыдущих главах, придумано уже довольно много вещей, которые компьютерным компаниям не так уж интересны. +И работник NTT Мураками Кунио (Kunio Murakami) нашел для министерства промышленности обладателей виденья достаточно амбициозного будущего: это были знакомые его начальника Мото-Ока Тору (Moto-Oka Tohru) и Фути Кадзухиро (Fuchi Kazuhiro). Познакомились они за десятилетия до того в университете в США. И достаточно интересным виденьем будущего, которое они нашли для министерства промышленности, были параллельные компьютеры программируемые на Прологе. У японской промышленность не было никакого энтузиазма, там точно не стали бы заниматься этим, если б не министерство промышленности. У неяпонской промышленности энтузиазма тоже не было. То что надо! +Почему параллельные машины программируются именно на Прологе? Фути интересовался МТИ-антипрологом Planner и идеями Ковальского но не считал их особенно практичными. Но в 76-ом году Фурукава Коити (Furukawa Koichi) привез Фути из SRI ксерокопию ксерокопии ксерокопии распечатки FORTRAN-исходников Марсельского интерпретатора Пролога. Восстановленный Фути по распечаткам Марсельский интерпретатор работал быстрее, чем он ожидал и его мнение о практичности идей Ковальского изменилось в лучшую сторону. Мото-Ока решил, что Пролог - язык будущего не только потому, что был знаком с Фути, но и потому, что Пролог полюбили его студенты. +Помогло победить Прологу еще и то, что он соревновался не с такими же и даже более экзотическими языками, о которых мы писали в предыдущих главах, а с Лиспом. И Лисп в это время выглядел еще слишком нормальным для министерства промышленности, еще не откололся от унылого мейнстримного будущего рабочих станций. +Планы был обнародованы в октябре 81, а проект стартовал в апреле 82-го года. Для осуществления плана был сформирован исследовательский институт ICOT, Фути стал его директором, Мураками - руководителем лаборатории, занимающейся железом, а Фурукава - руководителем лаборатории, занимающейся ПО. +Бюджет на 83-ий финансовый год был 11 миллионов долларов ($36.8M в 2025) - полтора проекта MAC 63-его года. За десятилетие получилось 320 миллионов долларов (миллиард долларов 2025-го). +Насколько это много? Сотрудник министерства международной торговли и промышленности Накамура посчитал, что не особенно много [Odag97], он утверждает, что каждый год, пока проект продолжался, он никогда не превышал 1% от расходов на R&D японской коммерческой электроники и телекома. И в один только первый год японского проекта по созданию компьютера пятого поколения в IBM потратили на R&D в пять раз больше, чем японцы на весь проект компьютера пятого поколения. +Можно согласиться, что это мало для создания компьютера пятого поколения. Но похоже на то, что это существенная поддержка для Пролога. Разумеется, только часть этих средств были потрачены на важные для развития Пролога работы. Но это означает, что в течении десятилетия только в ICOT в лаборатории Фурукавы над программным инструментарием для Пролога постоянно работали 8-10 человек. Что по-видимому сравнимо с вкладом Mozilla в развитие Rust. Конечно, начало такого десятилетия для Пролога было во времена, когда программисты писали по тысяче строк в год, так что сравнимые ресурсы вряд ли давали сравнимый результат. +Но даже скромные результаты были нужны для Пролога, который к началу японской инициативы переживал не самые лучшие времена. В это время компилятор Уоррена гибнет вместе с PDP-10. И чтоб начать что-то писать на Прологе в ICOT приходится использовать доживающие последние годы машины DEC, на которых работает единственная серьезная имплементация Пролога. +Но мы не пишем историю Пролога. И для нашей истории важнее не японский проект по созданию "компьютера пятого поколения", а реакция на него. А реакция была существенной. +Дело в том, что в 80-е, после успехов предыдущих и до потерянных следующих десятилетий, к тому, что считают перспективным в Японии относились серьезно. Первые сообщения о японском проекте не были сфокусированы на Прологе и новости о том, что в Японии считают ИИ перспективным были использованы для того, чтоб вдохнуть новую жизнь во многие проекты. Или правильнее сказать - гальванизировать многие проекты. +Дело не ограничивалось Прологом или параллелизмом. Например, Эдвард "Отец экспертных систем" Фейгенбаум (Edward Feigenbaum) и Памела МакКордак (Pamela McCorduck) написали книгу под названием "Пятое поколение: искусственный интеллект и японский компьютерный вызов миру" [Feig83]. Книга вышла в 83-ем, а в 84-ом её переиздали в мягкой обложке, на которой изображена Статуя Свободы в гриме осирои, держащая факел в механической руке. Фейгенбаум "раздувал" японскую угрозу, считает пишущая историю ИИ-стартапов 80-х Филлипс, чтобы напугать своего читателя и подтолкнуть его к действию. Успешно. Что же читатель должен был делать? Разумеется, платить отцу экспертных систем деньги. +Он поучаствовал в основании двух компаний [Phil99]. Первая - называвшаяся сначала IntelliGenetics, а позднее IntelliCorp, пыталась коммерциализировать экспертную систему MYCIN, над которой Фейгенбаум работал в 70-е годы в Стенфорде. Не данные, но саму программу на Лиспе, которую предполагалось наполнять данными пользователя. Этой компании противостояла вторая компания, основанная Фейгенбаумом - Teknowledge. Вторая компания должна была победить первую, продавая более дешевый инструментарий для создания экспертных систем, работающий на более дешевых компьютерах. Как этого можно было добиться? Использовав вместо Лиспа и Пролога новый, перспективный язык для разработки искусственного интеллекта - C. Teknowledge была открывателем и законодателем моды на этот язык среди разработчиков экспертных систем 80-х. Да, успехи по избавлению от ассоциации с ИИ у разных языков и "парадигм" программирования были очень разными. +К этой очередной победе лисперов над Лиспом и ее последствиям мы вернемся позднее, а сейчас настала очередь важнейшего для нашей истории ответа на японский вызов. И важнейшим для нашей истории был ответ британцев, которые уже + +### Успели забыть о Прологе больше, чем японцы успели узнать + +Великобритания ответила на японскую программу пятого поколения в июне 83-го программой Алви (Alvey). Джон Алви (John Alvey) - это председатель комитета, который рекомендовал ответить. Ответ был размером в 350 миллионов фунтов (1 миллиард 830 миллионов долларов в 2025). Из них 200 были от государства и 150 от коммерческих компаний [Blac85] [Oakl90]. +Да, это почти в два раза больше средств, потраченных за примерно вдвое меньшее время. Потраченных экономикой меньшего размера, чем японская. И не смотря на все это, история с пятым поколением ассоциируется в основном с Японией. +Ответ не предполагал создания специального института, хотя наш знакомый по нулевой части Мики и пытался это организовать. +Как обычно, новости из Японии были представлены всеми как убедительное подтверждение того, что им нужно и дальше продолжать делать то, что они и так делали. Правда, новостей о том, что искусственный интеллект объявили чем-то перспективным в Японии, по-видимому, было еще недостаточно для реабилитации его в Британии после отчета Лайтхилла, так что ИИ обычно фигурирует под псевдонимом "интеллектуальные базы знаний", IKBS (Intelligent Knowledge-Based Systems). +Кому из героев нашей истории лучше всего удалось убедить, что им нужно продолжать давать деньги на то же самое? Это были создатели (симулятора) HOPE-машины ALICE из Имперского Колледжа Лондона, группа Дарлингтона. +В рамках программы Алви разрабатывались четыре проекта, "большие демонстраторы". И компании - основные исполнители двух из них посчитали (или, может быть отдельные элементы внутри этих компаний посчитали), что им понадобится параллельный компьютер вроде ALICE. +ICL занималась системой для министерства здравоохранения, а Plessey - самым амбициозным проектом программы Алви: распознаванием речи. +ICL (International Computers Limited) - компания, разрабатывавшая компьютеры на которых использовали POP-2 в Эдинбурге и KRC в Кенте. ICL в 60-е купила компьютерный отдел Ferranti, которая в свое время построила Atlas и Atlas 2, для которых пытались имплементировать CPL. И вот эта компания ICL решила построить для Дарлингтона с коллегами ALICE, которую те только симулировали. И похожий на ALICE, но новый параллельный компьютер, который разрабатывали уже для программы Алви. +И благодаря предполагаемой нужности для двух демонстраторов из четырех, проект создания этого компьютера стал самым большим проектом программы Алви. 3 декабря 85-го британский министр промышленности и информационных технологий обнародовал детали [Dett86]. И раз уж проект самый большой - его называли Flagship. Насколько большой? 15.5 миллионов фунтов (80 миллионов долларов в 2025). Новый проект объединял проект ALICE Имперского колледжа Лондона с проектом Университета Манчестера, построившего dataflow-машину MDF. +ICL построила три прототипа ALICE. Первый прототип ALICE заработал в феврале 86-го [SPJ87] [Fiel88]. Так ALICE стала реальностью, но реальность эта оказалась не слишком радужной. Реальная машина работала "разочаровывающе медленно" [Clac24]. +Насколько медленно? nfib-число этой машины было 16000 [Harr87], что немного побыстрее, чем SKIM II, но в два раза медленнее, чем NORMA. И это производительность всей параллельной машины. Один переписыватель графов в ней, более точный аналог SKIM II и NORMA имел nfib-число в 1000. +У разочаровывающе медленной работы было несколько причин. Реальная ALICE, которую построили в ICL, существенно отличалась от ALICE, которую задумывали в Имперском Колледже. Предполагалось [Dett86], что сеть, соединяющая вычислительные элементы с памятью будет работать со скоростью 300Мбит/сек., реальный кастомный чип, который переключал соединения между вычислительными элементами мог переключать достаточно быстро, чтоб обеспечить скорость в 150Мбит/сек., а реальные вычислительные элементы, построенные на Transputer, британской компании INMOS могли установить соединение только в 10Мбит/сек. Но такой медленной сети хватало, чтоб продемонстрировать хорошую распараллеливаемость вычисления чисел Фибоначчи потому, что переписыватели графов были очень медленными - интерпретаторами промежуточного языка ALICE ISL (Implementation Specific Language), написанными на OCCAM [Harr87]. +Эти проблемы можно было исправить, в 86-ом году уже умеют делать переписыватели графов намного быстрее. Но главная проблема ALICE не имела отношения к особенностям имплементации в ICL, она была архитектурной. Даже задуманная сеть в 300 Мбит/c не лучшее средство для соединения вычислительных элементов имеющих только 64Кб локальной памяти с блоками памяти в 2Мб, как это предполагали делать разработчики ALICE. +У новой машины Flagship, которую разрабатывали в университете Манчестера и собирались построить в ICL к 88-му году, другая архитектура. В Flagship нет разделения на блоки памяти и вычислительные. У каждого вычислительного блока мегабайты памяти, к которой он имеет более быстрый доступ, чем через сеть к памяти остальных вычислительных блоков. И эксперименты с симулятором показали, что можно добиться 90% обращений к памяти в том же вычислительном блоке вообще без создания нагрузки на сеть для соединения блоков. И вычислительные блоки делались на процессорах MC68020. Пусть не британских, зато быстро работающих. +Но для нашей истории, конечно, важнее не эти машины, а что машинистами было сделано полезного для имплементаций на обычном железе. + +### Новая надежда + +Flagship был частью британского ответа на планы японцев по построению Пролог-машин. Но главным языком программирования этого проекта стал не Пролог, а HOPE. +Не единственным. Работали над имплементациями на Flagship-машине и других языков, в том числе и Пролога. Но главным - на котором должно был быть написано системное ПО - выбрали HOPE. +Причиной того, что выбрали HOPE, а не Пролог называют [Kean94] то, что в Имперском Колледже Лондона группа Дарлингтона уже занималась имплементацией HOPE и просто продолжила делать то, что уже делала. Что было вполне обычной реакцией на японский проект. Но Имперский Колледж был и важным центром по работе над Прологом. Ковальский из Эдинбурга отправился работать как раз туда. +Трудно сказать насколько на выбор языка повлияли технические аргументы, а не какие-то административные маневры, но аргументы были. +Во-первых, Пролог из за красных катов и императивных "предикатов" - менее "декларативный" язык, чем чисто функциональный ФЯ. Программисту на Прологе поэтому нужно больше думать о процедурной семантике. +Проблема этого аргумента в том, что те же претензии можно предъявить и к реально существующим в это время функциональным языкам. Как мы помним, эдинбургский HOPE только называется чисто функциональным, в реальном коде используют функции с побочными эффектами, написанными на POP-2. Как решать практические проблемы вроде ввода-вывода не нарушая чистоту языка пока не придумали. +К истории того как эти проблемы решали в ФЯ и некоторых логических языках мы вернемся в следующей части. +Следующим преимуществом ФЯ называют то, что ФЯ с хорошей производительностью имплементировать легче, чем Пролог. Что звучит правдоподобно, но как мы выяснили в нулевой части нашей истории, имплементацию Пролога с хорошей производительностью удалось сделать раньше, чем такую имплементацию языка, похожего на HOPE. +С остальными аргументами поспорить значительно труднее: в HOPE есть типы и функции высшего порядка, а в Прологе их нет. +Уоррен, правда, убеждал, что конструкции высшего порядка в Прологе и не нужны. Но убеждал не очень успешно в описываемое время. Сегодня мы знаем, что он победил и Пролог остался Прологом. Но в 80-е появились планы по усовершенствованию Пролога примерно таким же образом, каким в это время усовершенствовали ФЯ. +Когда-то типов и ФВП не было и в NPL и Пролого-образные языки позднее расширят и типами и ФВП. Но, по видимому, слишком поздно. О HOPE-фикации Пролога мы расскажем подробнее в другой главе. +Роль основного языка принесла и новые вызовы. Эдинбургский HOPE, E-HOPE, как его называли в Лондоне - ФЯ не самый практичный. И его эдинбургский интерпретатор - тем более. +И решение этой проблемы нельзя откладывать до появления HOPE-машины. Нужна быстрая имплементация для обычных машин. Потому, что нужно писать код еще до того, как первый прототип специальной машины будет готов. +На судьбоносном совещании в лаборатории Резерфорда - Эплтона Лондонская группа сообщала, что собирается написать более быстрый интерпретатор промежуточного языка, чем симулятор параллельного компьютера. +И можно бы было ожидать, что более практическая имплементация HOPE для обычных машин будет просто еще одним бэкендом для компилятора в инструкции параллельной машины. Который транслирует из того же промежуточного языка. Со временем такой компилятор действительно появился. Но только со временем. +Сначала в Лондоне написали один за другим два компилятора HOPE для разработки на обычных машинах, которые имели мало общего с компиляторами для параллельных машин. В самом большом проекте британской программы по созданию компьютера пятого поколения такое могли себе позволить. Развитие событий практически неизбежное потому, что этот самый промежуточный язык для новой специальной машины разрабатывался не в Лондоне и даже не в рамках того же проекта. Даже сегодня разработчики могли бы выбрать что-то внутреннее, не отягощенное координацией с другим проектом. Что уж говорить о временах бумажной корреспонденции и посылок с магнитной лентой. +Нежелание использовать результаты этого параллельного проекта оправдалось, промежуточный язык не был готов к тому времени, когда Лондонской группе Дарлингтона уже нужно было начинать писать ПО на HOPE. +Первый компилятор - IC HOPE для VAX-11 написал Энтони Филд (Antony Field) [Fiel88] [Perr91], имплементировав виртуальную машину под названием FPM (Functional Programming Machine). Есть ссылки на недошедший до нас университетский отчет 85-го года о нем. +Имплементаторы HOPE противопоставляют FPM остальным функциональным виртуальным машинам и утверждают [Perr91], что с имплементациями обычных языков программирования вроде Algol 60 у FPM больше общего. Спорное утверждение. Может и справедливо в случае G-машины или CAM, но FPM довольно похожа на FAM Карделли или имплементацию Poly (ML). +FPM - это очередная практичная версия SECD с поддержкой замыканий и оптимизация хвостовых вызовов. С копирующим сборщиком с фиксированным размером кучи. Там типы данных - обычно объекты кучи с тегами, но есть пары без поля для тега. И целые числа не являются объектами в куче вовсе, отличаются от указателей с помощью двухбитных тегов. Все это гораздо ближе к FAM, чем даже к самым смелым мечтам о функциональном Алголе 68, что уж говорить об Алголе 60. +Существенное отличие от FAM только в том, что в FPM отсутствуют оптимизации для каррирования. Это объяснимо, объявления функций в HOPE (пока что) не декларируют каррированные по умолчанию функции. +И IC-HOPE похож на компилятор Карделли. Операции ВМ разворачиваются макросами. И производительность генерируемого кода вычисления чисел Фибоначчи получилась побыстрее LML, но помедленнее, чем SML/NJ. Как и у компилятора Карделли. +Хорошо измерить производительность, правда, не успели. Первый компилятор быстро устарел, потеряв актуальность вместе с VAX-11. На смену этому компилятору для VAX вскоре пришел следующий компилятор, для MC68K. +Этот компилятор написали [Perr91] Найджел Перри (Nigel Perry) и Кейт Сефтон (Keith Sephton). Рантайм был доработан и куча теперь могла расти во время исполнения. Генерируемый код стал заметно быстрее. +Имперский Колледж Лондона распространял этот компилятор, анонс появился в рассылках в декабре 88-го года. +Переход на более перспективную платформу - не единственная причина для развития компилятора. Проблему практичности Эдинбургского HOPE не решить одной только быстрой имплементацией. Нужны изменения в самом языке. +Интересно, что с развитием HOPE сложилась ситуация, симметричная ситуации с развитием ML. Ядро разработчиков HOPE в Эдинбурге не особенно заботилось о похожести в деталях и забросило HOPE, начав проектирование Standard NPL. Извините, Standard ML. Периферийная группа разработчиков в Лондоне заботилась о сохранении детального сходства с HOPE гораздо сильнее, как и в случае с ML и INRIA. +Но в случае HOPE это более объяснимо, чем в случае ML. Эдинбургские разработчики NPL, HOPE и SML никогда особенно не заботились о деталях. И каждая версия, которых было множество, требовала бы править практически каждую строку кода. Если бы он был. Это кардинально отличается от LCF/ML, который не менялся долгие годы. +И стремление группы Дарлингтона изменять HOPE меньше не было полностью добровольным. У того, что HOPE стал основным языком флагманского проекта директората Алви, были не только полезные для его практичности последствия. Оказалось, что раз уж на заседаниях комитетов и комиссий договорились имплементировать HOPE, то нужно имплементировать HOPE а не какой-нибудь SML. Не совсем понятно какие отклонения от HOPE группа Дарлингтона могла себе позволить, но какие-то могла. Если и не HOPE 2, то по крайней мере HOPE+. + И второй компилятор имплементировал этот HOPE+. +Некоторые изменения в HOPE+ по сравнению с HOPE были сделаны для решения тех же проблем и решения были теми же, что в SML. В HOPE+, как и в SML, добавили [Darl89] [Robe89] целые числа со знаком и числа с плавающей точкой, массивы и стандартные 8-битные символы. +Судя по "категорному" коду, про который мы рассказывали в нулевой части, немногочисленные программисты на HOPE любили писать много локальных функций не требующих сигнатур с указанием типов. В отличие от тех, что на топлевеле. Так что в HOPE+ написание таких функций сделали удобнее, разрешив в `let` и `where` рекурсию. +Некоторые решения, хоть и были похожими - все же отличались. В HOPE планировали добавить исключения и их добавили и в SML и в HOPE+, но это были разные разновидности исключений. Чтоб не нарушить чистоту языка в HOPE+ сделали значения-ошибки, населяющие каждый ссылочный тип. +В новом HOPE (пока) не сделали функции каррированными по умолчанию, но в функциональном языке удобное частичное применение желательно, так что оно все равно было добавлено. Добавлено, как и в SML, но не такое как в SML. В новом HOPE можно было писать `f(?,x,?)` что соответствовало такой вот лямбде `lambda (a, b) => f(a, x, b)`. +Но некоторые выводы, которые сделали из опыта применения HOPE в Эдинбурге существенно отличались от выводов сделанных в Лондоне. +Опыт имплементации перегрузки в HOPE не считался особенно удачным, в основном из-за сложного и медленного алгоритма. В Эдинбурге не отказались от перегрузки полностью, как в свое время в LCF/ML. Но в SML она осталась для ограниченного набора операций. В новом HOPE перегрузка для пользовательских функций осталась, но имплементированная с помощью более быстрого алгоритма и несколько иначе. Старое поведение перегрузки не было определено ничем кроме эдинбургской POP-2 имплементации и не было воспроизведено полностью. +В SML отказались от паттерн-матчинга независящего от последовательности уравнений, который считали важным разработчики языков алгебраической спецификации. В Лондоне не порвали с этим алгебраическим прошлым и последовательность уравнений не стала важной как стала важной в SML, Прологе и языках Тернера. Но программисты хотели писать ПМ с пересекающимися паттернами, так что для HOPE+ нашли более-менее понятную систему определения того, какое сопоставление с образцом более точное [Perr91]. +Сопоставление паттернов-констант - более точное, чем сопоставление с переменной. Так что при применении функции + +``` +--- f(1, y) <= y; (a) +--- f(x, 2) <= x; (b) +--- f(a, b) <= a * b; (c) +``` + +так `f(1,3)` будет выбрано уравнение (a), в случае `f(5,2)` уравнение (b), в случае `f(3,4)` уравнение (c), а применение `f(1,2)` вызовет ошибку во время исполнения потому, что ни один из вариантов (a) и (b) не подходит лучше другого. Компилятор предупреждал о возможности такого исхода во время компиляции. +Эдинбургской группе разработчиков HOPE не пригодилась ленивость и в SML не попали ленивые списки из HOPE. Лондонская группа посчитала ленивость полезной, так что в HOPE+ все конструкторы стали ленивыми. Правда, функции все равно строгие, каждый параметр вычисляется до первого конструктора. +Это сочетание сегодня выглядит довольно необычно. И противоположно популярным рекомендациям писать на Haskell ленивые функции и типы данных со строгими полями. +В другом HOPE+ можно было добиться более привычного в наше время поведения. В другом HOPE+? +Между компиляторами HOPE для обычных машин и машин параллельных было мало общего. Разумеется, это привело к тому, что они имплементировали разные языки. +В параллельном HOPE+ были аннотации ленивости [Glyn90] и можно было форсировать "позвоночник" структуры данных без форсирования элементов: + +``` +dec length : !# ST SN #! list alpha -> num; +``` + +Или вычислить всю структуру: + +``` +dec sum : !# ST IN #! list num -> num; +``` + +А можно было и сделать функции ленивыми: + +``` +dec append : !# ST(WH,UN) #! (list alpha # list alpha) -> list alpha; +``` + +Здесь `WH` - аннотация поведения по умолчанию, вычисления до первого конструктора. А `UN` - означает ленивый аргумент. Так что параллельный HOPE+, пусть и с помощью аннотаций, но мог быть более обычным в наше время ленивым языком. +Практически до самого конца проекта в параллельном HOPE+ был [Robe89] и более привычный для нас сегодня паттерн-матчинг. Паттерны могли пересекаться и результат сопоставления зависел от последовательности уравнений. Прочие отличия были менее интересными недоработками. Взаимная рекурсия в `let` и `where` и числа с плавающей точкой не были имплементированы. +Отличий вариантов HOPE+ от HOPE не достаточно, чтоб наш обычный пример выглядел существенно иначе: + +``` +infixr :: : 5; +data list alpha == nil ++ alpha :: list alpha; + +dec map : (alpha -> beta) # list alpha -> list beta; +--- map(f, nil) <= nil; +--- map(f, x::xs) <= f x::map(f, xs); + +map(lambda x => x+y,[1,2,3]) where y == 1 end; +``` + +Он мог бы быть полностью идентичным, но мы не ставили перед собой такой цели. +Для тех, кто не был убежден в ненужности Пролога Лондонские разработчики HOPE планировали логические расширения. И даже имплементировали, хоть и не в основном, рабочем компиляторе. +Возможно, делать "функции" со многими результатами по умолчанию - не самая хорошая идея. Но почему бы не добавить в язык специальную нотацию для этого? Такая нотация - ЦФ-выражения уже была в языках Тернера и в NPL. И планировалась для HOPE, но долгое время продолжала только планироваться. + +``` +filter(p,xs) <= {x | x in xs : p x} +``` + +Правда, ЦФ-выражения, list comprehensions - не встроенный Пролог, а скорее какой-то из анти-Прологов Сассмана и Хьюита. Но что если встроить именно Пролог? + +``` +{(l1, l2) with l1, l2 | l1 <> l2 = [1, 2]} +{([], [1, 2]), ([1], [2]), ([1, 2], [])} : set(list num # list num) +``` + +Теперь функции можно применять слева от `=` как в более выразительных языках алгебраической спецификации. Но пока что только внутри новых "абсолютных абстракций множеств" [Darl87] [Darl89]. +Существует ли список из двух элементов? + +``` +{l with l | length l = 2} +``` + +Да! + +``` +{u :: (v :: nil)} : set(list alpha) +``` + +В функциональных языках Эдинбургской исследовательской программы такие расширения не прижились. Они сыграли более заметную роль в HOPE-фикации Пролога. Но это уже другая история. + +### Граф 0 + +В апреле 84-го при поддержке директората Alvey состоялась встреча представителей академии и индустрии на которой обсуждали общую вычислительную модель для параллельных вычислений. Участники встречи разделились на два лагеря. Одни считали, что общий промежуточный язык будет иметь достаточно плюсов чтоб скомпенсировать возможные проблемы с производительностью. Другие считали, что производительность слишком важна для таких компромиссов. Сторонники единого промежуточного языка победили. Победили в первой битве, но смогли ли они победить в войне? Давайте выясним. +Единый промежуточный язык назвали DACTL (Declarative Alvey Compiler Target Language). DACTL должен [Glau87] был подходить для трансляции в него таких языков как HOPE, Prolog и даже Lisp. Промежуточный язык также должен был подходить для исполнения на параллельных машинах программы Алви, и это не только Flagship-машина. О другой спецмашине программы мы еще расскажем позднее. +Язык должен был быть достаточно низкоуровневым чтоб описывать детали порядка вычисления и где происходит копирование, а где - переписывание для разделения результатов. Но не слишком низкоуровневым. Быть ФЯ, о свойствах кода на котором легко рассуждать и свойства которого легко доказывать. +Директорат Алви организовал команду по разработке DACTL в июле 84-го. Процесс создания DACTL пошел в Университете Восточной Англии (University of East Anglia) под руководством Ронана Слипа (Ronan Sleep) и Джона Глоерта (John Glauert). +Слип был соавтором героя прошлой части Келлера. Второй руководитель был связан с событиями предыдущих частей сильнее. +Глоерт [Glau1] [Glau2] получил степень бакалавра в Кембридже в 76-ом. Степень магистра он получал в Университете Манчестера, где в это время шли работы над Dataflow-машиной, которую мы упоминали в прошлой части. Магистерская диссертация была об имплементации ФЯ с рекурсией на Dataflow-машине. +Вернувшись после этого в Кембридж Глоерт защитил там докторскую диссертацию. Затем он вернулся в Манчестер и снова поработал там в Dataflow-проекте, над дизайном и имплементацией SISAL. После всего этого, в 84-ом году Глоерт начал работу в Университете Восточной Англии. +DACTL планировали создать за три итерации [Glau87]. Описание первой итерации Dactl0 было готово в мае 85-го. В сентябре 85-го состоялся первый релиз образцового интерпретатора [Glau91]. +Финансирование от Alvey стало поступать в университет с мая 85-го. DACTL-проект был третьим по размеру спецмашинным проектом Алви. Выделенные на него средства [Blac85] - 360тыс. фунтов или 1.9 миллионов долларов в 2025 - были несопоставимы с первым по размеру проектом Flagship, но вполне сопоставимы со вторым. +DACTL - язык уравнений с паттерн-матчингом [Glau87] + +``` +PROGRAM test; +IMPORTS lists; +ATOM append; +RULE + => !$x; + $x> := >; + := !; +REWRITE ! >; +ENDPROGRAM test; +``` + +паттерны в Dactl лево-линейные (имена переменных не могут повторяться в паттерне для обозначения равенства). +Все вычисления явно обозначаются `!` и если восклицательного знака нет - это не вызов функции, а просто данные, конструкторы. Переписывание на месте `:=` и возвращение копии ссылки `=>` тоже явно указывается. +Вся эта получившаяся явность авторам быстро разонравилась и они решили сделать следующую версию менее явной или, по крайней мере сделать указания менее многословными и более высокоуровневыми. +И если не хватало проблем со взаимодействием между проектами от того, что промежуточный язык разрабатывается в отдельном проекте от имплементирующих его машин и компилирующих в него компиляторов, то теперь проект разработки промежуточного языка стал еще и международным. + +### Барендрегт против лямбды + +Университет Восточной Англии [Glau87] [Brus87] объединил усилия с голландским проектом по созданию параллельной машины редукции (Dutch Parallel Reduction Machine Project) под руководством более известного другими своими работами Хенка Барендрегта (Hendrik Pieter Barendregt). +Совместно они разработали язык LEAN (the Language of East Anglia and Nijmegen), который стал основой для Dactl1. Да, это означает, что у разработки Dactl было более трех итераций, но это, наверное, самое безобидное отклонение от первоначальных планов. Этот LEAN не имеет никакого отношения к современному языку Lean. +Как было принято в 80-х, Нидерланды тоже решили ответить на японский вызов [Bare87]. Так что в ноябре 1984 три исследовательских группы университетов Амстердама, Неймегена и Утрехта +начали совместный проект, спонсируемый министерством науки и образования. +Первая фаза, продолжающаяся до конца 87-го должна была ответить на вопрос: возможно ли и реалистично ли построить эффективную машину параллельной редукции? Эта цель может показаться не очень амбициозной но голландский ответ на японский вызов был довольно серьезным. Разрабатывались как минимум две специальные машины. Philips Laboratories работали над Distributed Object Oriented Machine, а университеты над Experimental Parallel Reduction Machine. +Что более важно для нашей истории - разрабатывали семейство быстрых имплементаций функциональных языков для обычных машин, которое если и не живет в полном смысле этого слова, то по крайней мере влачит жалкое существование до сих пор. +Первая имплементация семейства имплементировала не язык LEAN, а его чисто-функциональное подмножество без операции переписывания `:=` - язык CLEAN. +Некоторые читатели, возможно, знают современный язык с таким названием - чисто-функциональный ленивый язык с классами типов, который известен приличной производительностью, которая достигается не без помощи "плоских" объектов и уникальных типов, использующихся для изменения массивов на месте. Это не тот язык о котором мы рассказываем сейчас. В нем нет массивов, классов типов, уникальных типов да и типов вообще. +Нельзя сказать, что CLEAN 87-го года не имеет никакого отношения к современному, как было в случае LEAN. У первого и современного CLEAN общие авторы Марко ван Ээкелен (Marko C.J.D. van Eekelen) и его научрук Маринус Плазмейер (Marinus J. Plasmeijer). Оба языка являются продуктами одной исследовательской программы. Но один не превратился в другой путем добавления тех фич, которые сегодня выделяют Clean из множества прочих ФЯ. Синтаксически они настолько различны, насколько возможно в обсуждаемую эпоху HOPE-фикации. Первый CLEAN как LML, HOPE и SML происходят от поздних версий NPL с характерными разделителями между уравнениями одной группы, а современный Clean как Haskell и языки Тернера от раннего NPL с более легковесным синтаксисом. +Мелкие отличия от прочих поздних NPL-ей CLEAN 87 по видимому унаследовал от предыдущего ФЯ Барендрегта под названием TALE (Typed Applicative Language Experiment) [Bare86] на момент описания в 86-ом году Он не был имплементирован и не было конкретных планов имплементации. + +``` +list $ t = rectype ls: (* |(t, ls)). + +Def: +map f <> -> <>; +map f (a:b) -> (f a) : (map f b). + +map (`x -> x + y) <1, 2, 3> where y = 1 end +``` + +Так выглядит TALE, а (C)LEAN - намного проще [Brus87]. + +``` +Map f Nil -> Nil | +Map f (Cons a b) -> Cons (Ap f a) (Map f b); +``` + +Планы имплементации CLEAN были. И та их часть, которая касалась обычных компьютеров была осуществлена. +Разработчики CLEAN считают, что хорошую имплементацию ФЯ на обычном железе может сделать и не так просто, но сделать можно. В середине 80-х эта проблема решена. Имплементаторы ФЯ теперь знают как эффективно реализовать ленивость, возвращение функций из функций и рекурсию. Если оптимизировать хвостовые вызовы, использовать редукцию графов и избегать ее где это возможно, заменяя обычными вычислениями на стеке, то можно добиться производительности сравнимой с имплементациями обычных императивных языков. +Разработчики CLEAN считают, что не все еще хорошо со сборкой мусора и анализом строгости - было бы неплохо поработать на этих направлениях. И тут сложно с ними не согласиться. +Существенно позднее авторы языка напишут, что он разрабатывался с самого начала проекта в 84-ом году, проектирование первой версии закончилось в 85-ом, первый компилятор был написан в 86-ом [Plas95]. В 87-ом году вышла первая дошедшая до на статья, в которой описывается версия 4.0 компилятора и языка, который определен только своей имплементацией. Версия современного Clean меньше, но это потому, что следующей версией после 4.0 была 0.5. Этот компилятор был первым публичным релизом в том же 87-ом году [Plas01]. +Как и их лондонские коллеги, авторы CLEAN начали с компилятора для VAX-11, но более интересные результаты получили продолжив работу компиляторами для MC68K. +(C)LEAN был задуман как промежуточный язык, так что сначала в нем не было `let`, `where`, локальных функций и даже лямбд. Что может выглядеть неожиданно, принимая во внимание то, какими работами Барендрегт более известен. +Как ни странно, Барендрегт и остальные авторы (C)LEAN считают, что промежуточным языком для имплементаций ФЯ должна быть не лямбда с расширениями, а язык уравнений. +Лямбда-исчисление часто представляется как сама собой разумеющаяся вычислительная модель для ФЯ, пишут [Brus87] они, вот только большинство имплементаций основаны не на лямбда-исчислении, а на комбинаторной логике (SKI-интерпретатор Тернера, G-машина, CAM), а для представления функциональных программ используются графы, лишние вычисления в которых предотвращаются разделением подграфов. ПМ очень важен в ФЯ, так что имеет смысл включить его в вычислительную модель. +Получается, что если мы хотим вычислительную модель для ФЯ близкую к имплементации, то лямбда-исчисление уже совсем не выглядит очевидным выбором. +Это авторы (C)LEAN дискутируют с теми, кто имплементировал HOPE на обычном железе и с другим машинистом, будущим важным героем нашей истории. К этому мы еще вернемся. +Первоначально планировалось, что компилятор CLEAN станет компилятором LEAN поддержав мутабельность. И может быть даже логические переменные, приблизившись к DACTL. +Питер Копман (Pieter Koopman) и др. показали [Brus87], что в Clean можно компилировать SASL, TALE и OBJ2 - новую версию языка алгебраической спецификации OBJ0, о которой мы рассказывали в нулевой части. Главным же достижением должен был стать компилятор нового языка Тернера. Этим планам не суждено было сбыться и к причинам мы еще вернемся в следующей главе с новыми приключениями Тернера. Но и остальные перечисленные компиляторы не стали чем-то большим, чем эксперименты. CLEAN как промежуточный язык не состоялся. +Проблема в том, что CLEAN пока что не очень хороший промежуточный язык. Он не поддерживает локальные функции оставляя лямбда-лифтинг разработчикам транслятора в CLEAN. Не поддерживает CAF что важно для ленивых языков. Но хуже всего то, что CLEAN компилируется в медленный код. Код сгенерированный компилятором Lazy ML работает в 7-9 раз быстрее, в зависимости от микробенчмарка. +Но авторы компилятора CLEAN не унывают. Они считают, что смогут существенно улучшить производительность кода и собираются сконцентрировать свои усилия на этом. Больше использовать стек, лучше компилировать ПМ, умнее управлять памятью. +Важную роль в отставании от LML сыграло то, что первые версии CLEAN хоть и были языком уравнений с самого начала, еще не были полностью HOPE-фицированными. +Но CLEAN стал полностью HOPE-фицированным языком до конца 80-х годов. В это время еще не угасла надежда на то, что CLEAN будет промежуточным языком для какого-то компилятора кроме CLEAN, но появилось движение в сторону более-менее полноценного языка, хоть и все еще простого [Gron90]. И CLEAN оброс соответствующими фичами [Nock91]. + +``` +MODULE Test; +TYPE + +:: List x -> Cons x (List x) | + List x -> Nil ; + +RULE + +:: Map (=> x y) [x] -> [y] ; + Map f [] -> [] | + Map f [a | b] —> [f a | Map f b] ; +``` + +знакомые с современным Clean могут заметить тут пару знакомых синтаксических деталей, но это добавление деталей никогда не сложится в CLEAN похожий на современный. Современный будет сделан более-менее с нуля спустя годы, но это уже другая история. + +### Самый известный птеродактиль на Панталасском театре + +После совместной с голландцами "фундаментальной переработки вычислительной модели" в Университете Восточной Англии разработали новую версию DACTL основанную на LEAN. +Спецификация ядра Dactl была готова в марте 87, принята Flagship вскоре после того. Черновик описания новой версии Dactl появился в июне 87-го и готовое описание в декабре 87-го. +Никаких фундаментальных переработок больше не было, так что последняя версия Dactl не особенно отличалась от второй и была готова в начале января 89-го. Образцовый интерпретатор - вскоре после этого [Glau91]. +Теперь DACTL выглядел так: + +``` +MODULE Test; +IMPORTS Lists; +SYMBOL REWRITABLE Append; +RULE + Append[Cons[h t] y] => #Cons[h ^*Append[t y]] | + Append[Nil y] => *ForceList[y] ; + Append[x y] => #Append[^*x y] ; +ENDMODULE Test; +``` + +`|` - разделитель между уравнениями, порядок которых не важен. И `;` - разделитель между уравнениями, порядок которых важен. +Аннотации порядка вычисления теперь показывают зависимость одних промежуточных результатов от других, так что ленивый код требовал меньше аннотаций и даже меньше уравнений: + +``` +Append[Cons[h t] y] => *Cons[h Append[t y]] | +Append[Nil y] => *y ; +``` + +Как видите, язык хоть и напоминает (C)LEAN, но не является просто им с дополнительными аннотациями. +Dactl разрабатывался итерационно для того, чтоб учитывать опыт его использования и для приобретения этого опыта использования Dactl0 и Dactl1 в Университете Восточной Англии и не только написали несколько трансляторов. +Промежуточный язык должен был подходить и для трансляции логических языков, так что Джордж Пападопулос (George Papadopoulos) написал трансляторы Parlog [Papa89] и GHC [Glau88] в DACTL. GHC не имеет ничего общего с более известным и релевантным для нашей истории Glasgow Haskell Compiler и в данном случае расшифровывается Guarded Horn Clauses. +Компания Harlequin, которая уже упоминалась в нашей истории, ей Радиолокационная Лаборатория заказала компилятор SML, для проекта Flagship писала в 87-ом году Harlequin Flagship Common Lisp Compiler. +Более соответствующими теме нашей истории компиляторами были три транслирующих HOPE-фицированные ФЯ в DACTL. +Один из авторов DACTL в университете Восточной Англии Ричард Кеннвей (Richard Kennaway) написал в 88-ом году транслятор CLEAN. Этот язык в описываемое время был довольно минималистичным и близким к DACTL, так что компилятор вышел не самым сложным. Второй восточно-английский компилятор был существенно сложнее. +Новый важный герой нашей истории Кевин Хаммонд (Kevin Hammond) написал транслятор языка PSML. Что означает Parallel Standard ML. Как обычно бывает в этой главе, стандартный ML был не совсем стандартным и относился к SML версии 2 (т.е. образца 88-го года) на основе которого был сделан, примерно так же как первоначальный Lazy ML относился к LCF/ML. PSML - декларативное подмножество Standard ML. Из этого языка убраны все императивные конструкции: циклы, изменяемые ссылки и большинство функций ввода-вывода. Исключения не убраны полностью, а заменены на ошибки-значения, похожие на те что в Hope+ [Hamm89b]. +PSML компилировался сначала в DACTL0 [Glau87] +, а потом в в DACTL1, Хаммонд экспериментировал и со строгой и с ленивой версией языка. Хаммонд писал компилятор в рамках работы над диссертацией и защитил её [Hamm91] в ноябре 88-го. +Третьим транслятором был уже упомянутый нами лондонский компилятор параллельного HOPE+. Правда, компилировал он не в DACTL, а в его подмножество, поддерживаемое Flagship-машиной. Подмножество называлось MONSTR. +Как и трансляторы в CLEAN, эти экспериментальные компиляторы не были важными для нашей истории. DACTL тоже был не особенно удачным промежуточным языком. Идея единого промежуточного языка восторжествовала на этапе планирования, но не выдержала испытаний на этапе имплементации. Вместо единого языка получилось множество. Больше, чем получилось бы, если бы каждый проект спецмашины имел собственный язык с самого начала. +Существовали только две полных имплементации DACTL: университетский образцовый интерпретатор и транслятор в C под названием ICL IIS, который написали в ICL - компании, строившей прототипы Alice и Flagship. +Транслятор в C может для нашей истории быть важнее всех этих спецмашин вместе взятых. Но не этот. На новых рабочих станциях SUN, которые были в 2-3 раза быстрее VAX-11/780 он выдавал только 1000 nfib в секунду. Чуть более чем в два раза быстрее, чем образцовый интерпретатор. И на десятичные порядки медленнее, чем последнее слово техники функционального компиляторостроения. +Оба спецмашинных проекта директората Алви нашли способ не использовать DACTL. И если Flagship имплементировал подмножество, только то, что хотел, то другая Алви-машина отказалась от промежуточного языка уравнений с ПМ полностью, выбрав расширенную лямбду. Но это уже другая история. + +### Негативное пространство. + +Саймон Пейтон-Джонс (Simon Loftus Peyton Jones) [SPJ18], как и его шведский коллега Августссон, относится к тому поколению имплементаторов ФЯ, которое познакомилась с компьютером еще в школе. В этой же школе учился функциональный машинист из прошлой части Томас Кларк. В результате этого опыта у Кларка и Пейтон-Джонса появилось желание разрабатывать компьютеры. И разработка компьютеров - это не те работы, которыми Пейтон-Джонс известен. Но всему свое время. +После школы, в Кембриджском университете Пейтон-Джонс повстречал ещё больше героев нашей истории: Хьюза и Фейрберна. +В Кембридже, как и в университете Чалмерса, пока что не было компьютерных наук как специальности. Так что Пейтон-Джонс сначала изучал математику. Но математика показалась ему слишком сложной, поэтому он стал изучать электротехнику. Компьютерные науки, впрочем, успели появится достаточно рано чтоб Пейтон-Джонс получил и такую специальность за один 79-80 год. +На то, что Пейтон-Джонс заинтересовался не просто разработкой компьютеров, а именно специальных ФП-машин, повлияли уже упоминавшиеся нами преподаватель Артур Норман от которого Пейтон-Джонс впервые узнал о функциональном программировании, Тьюринговская лекция Бэкуса и, разумеется, статья Тернера. +Как и на его шведских коллег, на Саймона Пейтон-Джонса произвела впечатление статья Тернера про комбинаторный интерпретатор. Дипломная работа Пейтон-Джонса была сравнением комбинаторного интерпретатора с SECD. Сам СПЖ считает ее не особенно полезной потому, что из сравнения наивных интерпретаторов не стоит делать далеко идущих выводов. На основе этого диплома была написана уже упомянутая нами в прошлых частях статья. +Пейтон-Джонс не хотел работать над докторской диссертацией, он хотел работать в индустрии. Об этом он вскоре пожалел - работа в индустрии ему не особенно понравилась, и через два года он вернулся в академию. +В 82-ом Пейтон-Джонс стал читать лекции в Университетском колледже Лондона (University College London). Но для того чтоб преподавать надо же защитить диссертацию? Не в те времена. Внезапно оказалось, что нужно учить компьютерным наукам много-много студентов, а преподавателей не хватает. Так что Пейтон-Джонс мог работать, а диссертацию защитить когда-нибудь потом. +Для того чтоб освоить исследовательскую работу, Пейтон-Джонс написал уже упомянутую нами статью про написание Yacc на SASL. Пока работал над ней, он общался с Тернером и даже называет Тернера своим неформальным научным руководителем. +В апреле 83-го Саймон Пейтон-Джонс организовал [SERC83] коллоквиум по декларативному программированию в Университетском колледже Лондона. Там Йонссон впервые выступил с докладом о G-машине, о том как быстро можно редуцировать графы, а избегать их редукции - еще быстрее. +И именно этим Пейтон Джон со своими коллегами Клеком (Christopher D. Clack), Салкилдом (Jon Salkild) и Харди (Mark S. Hardie) и стали заниматься, когда Университетский колледж Лондона начал второй по величине проект директората Алви по созданию ФП-машины. Проект назвали GRIP (Graph Reduction In Parallel). Индустриальными партнёрами были все та же ICL и High Level Hardware Limited. +На второй по величине проект выделили в десятки раз меньше денег, чем на первый - всего 480 тыс. фунтов (два с половиной миллиона долларов в 2025) [Blac85], так что подходы исполнителей проектов просто обязаны были существенно отличаться. +Но GRIP-машина отвечала на проблемы ALICE с производительностью схожим с Flagship образом. Правда, хватало и различий [SPJ88a]. +В GRIP осталось разделение на вычислительные элементы и элементы разделяемой памяти. Вычислительные элементы PE (Processing Element) - настолько обычные компьютеры, насколько это возможно. С новыми процессорами Motorola 68020 и большой локальной памятью, недоступной остальным процессорам компьютера. Сначала 128Кб но планировалось увеличение до 512Кб [SPJ87a] а затем и 1Мб [SPJ88a]. Обычные компьютеры умеют делать очень хорошо и дешево, и с каждым годом все лучше и дешевле, очень важно воспользоваться этими возможностями. +И что более важно для нашей истории, схожесть вычислительного элемента с обычной рабочей станцией означает, что участники проекта GRIP разрабатывали способы имплементации ФЯ применимые на обычной рабочей станции. +Но где же тогда специальное в этой спец-машине? Специальным микропрограммируемым железом являются только блоки разделяемой между процессорами памяти IMU (Intelligent Memory Unit). Эти микропрограммируемые компьютеры, оптимизированные для перемещения данных и операций с указателями, собирали мусор и предоставляли доступ к разделяемой памяти не как к массиву слов, а к операциям над графами и их элементами. +Авторы считают основным недостатком то, что работа с памятью и особенно кэшами в обычных компьютерах лучше и быстро совершенствуется. Специальный компьютер блока памяти не может полноценно использовать эти успехи. Авторам не очевидно, что специальность компьютера сможет компенсировать это отставание. +Эта организация памяти еще больше мотивирует минимизацию изменений графа. Переписыватель графа, чтоб быть эфективным переписывателем, переписывает только в том случае, если без переписывания будет потеряно разделение. Это мотивировало работу разработчиков GRIP над усовершенствованием способов компиляции ФЯ. +В блоке разделяемой памяти до 4 миллионов 40-битных слов. +Для соединения блоков используется уже существующая обычная шина, в отличии от прочих разработчиков спецмашин, разработчики GRIP не собирались связываться и с этим. +Но к существующим шинам не подключить много блоков, так что структура становится двухуровневой. 4 вычислительных блока и один блок разделяемой памяти объединяются на одной плате, которых может быть до 20. К блоку разделяемой памяти такой платы есть доступ и с других плат, но более медленный. +Так что на втором уровне блоки все-таки объединяются как во Flagship, но в каждом больше процессоров у каждого из которых больше собственной памяти. +Первый прототип с одной группой блоков планировали построить в середине 87-го, а с несколькими группами - в конце лета того же года [SPJ87a]. Не позднее 88-го первый прототип, смонтированный накруткой, был готов. Новый прототип с печатными платами ожидался в середине 88-го. +ФЯ имплементировались на этих вычислительных блоках с помощью суперкомбинаторов. Первоначальная имплементация была наивной, но не позднее 87-го года для этого использовали наработки Йонссона - G-машину [SPJ87a]. И это не последняя наработка Йонссона, которую использовали в GRIP. +До 87-го года разработчики GRIP собирались имплементировать DACTL. И использовать компиляторы в DACTL, написанные остальными Алви-проектами [SPJ87a]. +Но оказалось, что DACTL не так легко имплементировать. Никто не сделал полноценной имплементации. Даже Flagship, и этот проект намного богаче GRIP. Нужно было что-то менее амбициозное. Этим неамбициозным чем-то стал FLIC (Functional Language Intermediate Code) [SPJ88b]. +Над этим промежуточным языком работали еще и в Университете Уорика (University of Warwick), и FLIC - один из ранних примеров языка, который разрабатывали обсуждая в электронной почтовой рассылке. Но в университете Уорика наработали не много полезного. +Авторы пишут, что общий промежуточный язык может быть решением проблемы большого числа функциональных языков. Таким решением, которое не заставляет их унифицировать и позволяет им сохранить их сильные стороны. +Ирония в том, что средство спасения от необходимости заменять многообразие функциональных языков на один универсальный язык придумывал разработчик языков, более известный своей работой над универсальным языком для замены многообразия ФЯ. +Похоже, что авторы хотели подтолкнуть образование экосистемы вокруг FLIC. Вроде той, что тогда же пытались построить вокруг DACTL. По крайней мере для функциональных языков. В Лондоне не собирались поддерживать на GRIP ничего кроме функциональных языков, но другая группа исследователей в университете Эссекса работала над или-параллельным Прологом для этой машины. +Для них легкой жизни не обещали, но авторы пишут, что ФЯ транслировать во FLIC просто! Но никто не успел написать практичный транслятор ФЯ во FLIC за них. +И это было проблемой. Flagship мог себе позволить разработку двух компиляторов, но GRIP не может себе позволить даже одного. По крайней мере, пока не может. +Но если написание собственного компилятора требует слишком много усилий, то изменение существующего компилятора может быть вполне по силам. +Пейтон-Джонс и др. использовали язык и компилятор Lazy ML Августссона и Йонссона. Так что этот проект тоже получился международным. Но если в случае CLEAN/Dactl все закончилось международными согласованиями, после которых все делали что-то свое, то GRIP просто использовал то, что уже было сделано в Швеции. Согласования были позже, но это уже другая история. +Авторы пишут что использовали компилятор LML потому, что имели доступ к его коду. И выбор в это время был по видимому сильно ограничен. Особенно для тех кому нужен чисто функциональный язык и для ценителей работ Тернера. +Для компилятора LML написали бэкенд, транслирующий во FLIC. И легкость трансляции из промежуточного кода компилятора LML должна была сыграть важную роль в определении того, как FLIC выглядел. Функциональные языки - обычно лямбда с расширениями, пишут его авторы, так что все их можно компилировать в лямбду с несколько меньшим числом расширений. +Ну, допустим, что в это время ФЯ - уже обычно уравнения с паттерн матчингом, тут правы скорее авторы CLEAN. Но вот промежуточные языки у реально существующих и более-менее зрелых имплементаций все ещё лямбда с не особенно большим числом расширений. И FLIC - язык идейно родственный промежуточным языкам IF1 Манчестерской dataflow-машины и FP/M Лондонского компилятора HOPE+ для обычных машин. Но немного более универсальный и высокоуровневый. +Но главное то, что промежуточный язык компилятора Lazy ML - это сам Lazy ML. Но не Lazy ML второй половины 80-х, большой HOPE-фицированный язык уравнений с ПМ, а его минимальное подмножество - лямбда с расширениями. +Имплементации ПМ в компиляторах уже есть, разработчику спец-машины можно не имплементировать все возможные его версии, которых в то время было больше, чем сегодня. +Ирония в том, что это восстание против языков уравнений произошло потому, что разработчик компиляторов, который более известен имплементацией большого и сложного языка уравнений, не хотел имплементировать не такой большой и менее сложный язык уравнений. +Хотел или не хотел, правда, не так важно как то, что не мог. Альтернативное настоящее в котором роль Core в сегодняшнем GHC играет совсем не сегодняшний CLEAN было возможно, но такой исход не был особенно вероятным. +Важно, правда, отменить, что FLIC - это не Core. Да, FLIC - лямбда-исчисление с расширениями. Язык ленивый по умолчанию, но с помощью аннотаций можно писать энергичный код. Функции каррированы по умолчанию, способа объявлять некаррированные функции многих параметров нет. Но FLIC должен быть машиночитаемым. Хотя код на FLIC должен быть корректно типизирован, аннотация типов не требуется, а их проверка не производится. Компилирующий во FLIC компилятор может сохранить информацию о проверенных им типах только в аннотациях как метаданные. +Во FLIC нет АлгТД и даже одноуровневого ПМ. Есть только структуры с полями и тегом и функции для их разбора. Такой код на языке уравнений + +``` +f (LEAF x) = g x +f (NODE t1 t2) = h t1 t2 +``` + +нужно транслировать в такой FLIC + +``` += (f) (\x CASE 2 + (UNPACK 1 g x) + (UNPACK 2 h x) x) +``` + +Во FLIC есть развитая система аннотаций. Авторы GRIP - энтузиасты аннотаций, которые не изменяют результата исполнения кода, но помогают сделать его быстрее. Пионером такого подхода был Уоррен, но в ФЯ он не стал распространенным до спецмашинных Алви-проектов. +Существенное преимущество ФЯ в том, пишут авторы GRIP, что параллелизм в них неявный и аннотации вообще говоря не требуются. Разработчики GRIP написали для компилятора обнаружение таких возможностей для параллелизма с помощью анализа строгости. Но они все равно имплементировали такие аннотации и пользовались ими. Аннотации заметно влияли на производительность. +Но аннотации для LML, похоже, серьезно отставали от аннотаций для FLIC. Так один из пользователей модифицированного компилятора пишет [Trin89] как он транслировал LML во FLIC и расставлял аннотации строгости и параллелизма уже только в сгенерированном FLIC-коде. +На этом закончена история о том, как Саймон Пейтон-Джонс успешно избегал работы над тем, чем он как раз известен. Пока, наконец, не перестал избегать. Но это уже другая история. + +### День зависимости + +Ответом правительства США на японский вызов была DARPA Strategic Computing Initiative [Stef85]. Десятилетий план, анонсированный осенью 83-го. 300 миллионов долларов ($976 миллионов 2025) на первые три года. +Это был обычный ответ. Но был ещё и ответ необычный. Необычным американским ответом на японский вызов было создание MCC (Microelectronics and Computer Technology Corporation) [Peck86]. Этот, в отличие от японского проекта машины пятого поколения и Алви - полностью частный. Да, 21 коммерческая компании вроде Motorola и AMD и, почему-то, Локхид и Боинг, готовы были не получать деньги на ответы японцам, а даже тратить свои. От государства требовалось пока что только разрешить создавать такие консорциумы, запрещённые в то время антимонопольным законодательством. Что было сделано в 84-ом. +Японская инициатива была, правда, только одной из угроз. Другой была IBM. 21 компания хотели совместно создать что-то вроде Белл Лабс AT&T или Йорктаун Хайтс IBM, про которую мы не раз писали в нулевой части. На свой ответ они планировали тратить сопоставимые средства - 80 миллионов долларов (237 миллионов в 2025) в год к концу десятилетия. +Лаборатории MCC расположились в Остине, где как раз в это время закрыли ARC, про который мы писали в прошлой части. Готовность отвечать Японии ARC не помогла, но отдельные исследователи перебежали оттуда в MCC. +Масштабы ответа впечатляют, но они не конвертируются в серьёзный вклад в нашу историю. Ответ японцам, как обычно, означал - делать больше того, что и так уже делали. И мало кто делал что-то связанное с Эдинбургской программой. А те, кто делали - делали не особенно самобытные вещи. Которые ещё и потеряли самобытность в ходе HOPE-фикации. +Посмотрим, например, на организованную MCC конференцию по редукции графов, прошедшую осенью 86-го [Fase87]. +Организаторами были наш старый знакомый по предыдущей части Келлер и новый герой нашей истории Джозеф Фазель (Joseph H. Fasel) из Лос-Аламосской национальной лаборатории (Los Alamos National Laboratory). +Доклады работавшего над Ponder Фейрберна, Йонссона про Lazy ML и Худака с коллегами - про имплементации ФЯ для обычных машин и не относятся к теме этой главы. +Доклад героя нулевой главы Гогена про очередную версию его языка алгебраической спецификации, глава про который и прочие родственные Эдинбургским ФЯ направления ещё впереди. +Среди докладчиков преобладают европейцы: разработчики ALICE и Манчестерской Dataflow машины. Французской специальной машины MaRS и представители Голландского проекта. +Американские докладчики релевантные для этой главы и нашей истории в целом это уже знакомые нам разработчики FEL Келлер с коллегами, разработчики Id Арвинд с коллегами и новый герой Кибурц (Richard B. Kieburtz). +Кибурц работал в Орегоне над моделированием аппаратной G-машины [Kieb86] [Kieb87]. Кибурц, как и Пейтон-Джонс, использовал модифицированный компилятор Lazy ML Йонссона и Августссона. +Как и другие разработчики (стимуляторов) специального железа, Кибурц и др. использовали два симулятора - более детальный и медленный Microsim, пригодный только для исполнения программ в десятки команд и относительно быстрый инструментированный интерпретатор Macrosim. Macrosim написан Бориславом Агапиевым (Borislav Agapiev) на Modula-II и работал "примерно так же быстро, как интерпретатор Лиспа". Раман Теннети (Raman Tenneti) написал компилятор кодов виртуальной машины для VAX-11. Не позднее 86-го. +Как и разработчики транслятора PSML в DACTL, Кибурц экспериментировал с ленивостью и тестировал и строгие и ленивые версии. Мы уже видели ленивый "Standard" ML и вот, смотрите: энергичный "Lazy" ML. Обе имплементации, конечно, были не особенно серьёзными, даже по меркам этой истории. +Другие разработчики симуляторов машин Келлер с коллегами работали в 84-ом году над типизированной версией их языка FEL под названием TFEL [Mish85] [Mish84]. Конечная версия HOPE-фикации должна была стать, по видимому, самой буквальной HOPE-фикацией из всех, псевдокод в статье [Mish84] "похожий на TFEL" выглядел как HOPE без `<=` и с применениями функций как в FEL и FP: + +``` +data integer = zero + + succ[integer] + +dec even: integer -> bool +--- even:zero = true +--- even:succ[zero] = false +--- even:succ[succ[x]] = even:x +``` + +К сожалению, для реконструкции нашего обычного примера недостаточно информации. +HOPE-фикация FEL, правда, не состоялась. В 85-ом году вместо этого заменили FEL на его версию с Лисп-синтаксисом и безо всяких групп уравнений, ПМ и АлгТД под названием RediLisp [Kelle85] [Kelle87]. +Но не все разработчики специальных ФП-машин остались разработчиками симуляторов. Группа Арвинда в МТИ занялась созданием реального компьютера Monsoon, первый прототип которого должен был быть готов в конце лета 88. Но немного задержался. +Прототип, смонтированный накруткой, заработал в сентябре 88-го. В декабре он уже исполнял скомпилированные программы на Id. В том же 88-ом Motorola стала индустриальным партнёром МТИ по разработке Monsoon [LCS89]. +Тем временем Нихил HOPE-фицировал Id. Эта HOPE-фикация - одна из самых поздних. Описание [Nikh88] нового Id 88.0 было готово в марте 88-го. +Теперь функциональное подмножество Id, пишет Нихил, сравнимо с современными ФЯ. В Id теперь есть параметрический полиморфизм, определяемые пользователем алгебраические типы данных и паттерн-матчинг для их конструкторов, абстрактные типы данных и лист-компрехеншоны. +В LCF/ML было три основных способа и стиля именования параметров типов. Их можно было именовать например `*` и `**`, `*a` и `*b`, `*1` и `*2`. В последующих языках с параметрическим полиморфизмом обычно ограничивались каким-то одним. В Lazy ML, SML и CAML оставили второй способ (заменив позднее `*` на `'` в некоторых из них). В Id выбрали третий способ [Nikh88]. + +``` +type list *0 = nil | (:) *0 (list *0) + +typedef map = (*0 -> *1) -> (list *0) -> (list *1) + +def map f nil = nil +| map f (x:xs) = f x : map f xs + +map = { fun f nil = nil + | f (x:xs) = f x : map f xs } + +def map f l = { case l of + nil = nil + | (x:xs) = f x : map f xs } + +def map f l = {: f x || x <- l } + +{ y = 1 + In + map { fun x = x + y } (1: (2: (3: nil))) } +``` + +Как видите, авторы Id не стали себя ограничивать и позаимствовали большинство способов разбора АлгТД. Есть и группы уравнений и `case`-выражения и лямбды, которые одновременно поддерживают и несколько веток и карринг, как в CAML и новом GHC Haskell. `:` после открывающей скобки компрехеншона тут означает, что создается список, эта нотация работает и для массивов. +Поддержку Id 88 добавил в компилятор весной 88-го Джеймс Хикс (James Hicks) [Nikh88b]. ПМ компилировался методом, описанным Вадлером с некоторыми изменениями. Первоначально язык был имплементирован не полностью, не поддерживались проверка типов для всех нововведений и объявления констант на топлевеле, можно было писать только функции с параметрами. Полная имплементация ожидалась к концу лета 88-го. +И по крайней мере проверка типов была имплементирована полностью к осени Шаилом Адитьей (Shail Aditya). +В 88-ом компилятор модифицировали чтоб генерировать код для Monsoon и решили, что неплохо было бы генерировать его и для обычных машин. Трауб работал над техниками имплементации нестрогих но не ленивых языков на обычных машинах. И в 89-ом году Лина Мурьянто (Lina Muryanto) и Питер Тан (Peter Tan) написали кодогенератор для MC68020, но пока только для подмножества языка [LCS89]. +На этом очередная глава про специальные ФП-компьютеры подходит к концу. Возможно, вклад героев этой главы в историю функционального программирования пока что не выглядит впечатляющим. Но основной их вклад в дело HOPE-фикации еще впереди. Это, впрочем, уже другая история. + +ПРОДОЛЖЕНИЕ СЛЕДУЕТ + +Литература +========== + +[Nikh88b]: R.S. Nikhil. Computation Structures Group Progress Report 1987-88. Memo-288, 1988, June +[Nikh88]: Nikhil, R.S. Id (Version 88.0) Reference Manual. Computation Structures Group Memo 284. MIT Laboratory for Computer Science, Cambridge, MA March 1988 +[LCS89]: MIT Laboratory for Computer Science Progress Report 26, Jul 1988 - Jun 1989 https://apps.dtic.mil/sti/tr/pdf/ADA228606.pdf +[Kelle85]: Keller, Robert M. "Rediflow architecture prospectus." In TR UUCS-85-105. Dept of Computer Science, University of Utah USA, 1985 Aug. +[Kelle87]: Keller, R.M., Slater, J.W., Likes, K.T. (1987). Overview of Rediflow II development. In: Fasel, J.H., Keller, R.M. (eds) Graph Reduction. GR 1986. Lecture Notes in Computer Science, vol 279. Springer, Berlin, Heidelberg. doi:10.1007/3-540-18420-1_56 +[Mish84]: Prateek Mishra and Robert M. Keller. 1984. Static inference of properties of applicative programs. In Proceedings of the 11th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '84). Association for Computing Machinery, New York, NY, USA, 235–244. doi:10.1145/800017.800535 +[Mish85]: Prateek Mishra and Uday S. Reddy. 1985. Declaration-free type checking. In Proceedings of the 12th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '85). Association for Computing Machinery, New York, NY, USA, 7–21. doi:10.1145/318593.318603 +[Kieb86]: Richard B. Kieburtz. 1986. Performance measurement of a G-machine implementation. In Proceedings of the Workshop on Graph Reduction. Springer-Verlag, Berlin, Heidelberg, 275–296. +[Kieb87]: Rchard B. Kieburtz. 1987. A RISC architecture for symbolic computation. In Proceedings of the second international conference on Architectual support for programming languages and operating systems (ASPLOS II). Association for Computing Machinery, New York, NY, USA, 146–155. doi:10.1145/36206.36197 +[Fase87]: Fasel, Joseph H. Graph Reduction: Proceedings of a Workshop Santa Fe, New Mexico, USA, September 29-October 1, 1986. Vol. 279. Springer Science & Business Media, 1987. +[Peck86]: Merton J. Peck, Joint R&D: The case of microelectronics and computer technology corporation, Research Policy, Volume 15, Issue 5, 1986, Pages 219-231, ISSN 0048-7333, doi:10.1016/0048-7333(86)90023-5. +[Stef85]: Mark Stefik. 1985. Strategic computing at DARPA: overview and assessment. Commun. ACM 28, 7 (July 1985), 690–704. doi:10.1145/3894.3896 +[SPJ88a]: Peyton Jones, Clack, Salkild and Hardie, Functional Programming on the GRIP multiprocessor. in Proceedings IEE International Specialist Seminar on Design and Application of Parallel Digital Processors, pp 116-127. 1988 +[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 +[SPJ87a]: Peyton Jones, Clack, Salkild and Hardie, GRIP - a high performance architecture for parallel graph reduction, LNCS 274:98-112, ISSN 0302-9743, Springer 1987. +[SPJ88b]: S. L. Peyton Jones. 1988. FLIC—a functional language intermediate code. SIGPLAN Not. 23, 8 (August 1988), 30–48. doi:10.1145/47907.47910 +[Trin89]: Phil Trinder, A FUNCTIONAL DATABASE, PRG82, 1989. +[Bare87]: Barendregt, H.P., M.C.J.D. van Eekelen, P.H. Hartel, L.O. Hertzberger, M.J. Plasmeijer and W.G. Vree. The Dutch Parallel Reduction Machine Project, In Future Generations Computer Systems 3, pp. 261-270. Proceedings of the International Conference on Frontiers of Computing (Amsterdam, December 1987). +[Brus87]: Brus, T., M.C.J.D. van Eekelen, M. van Leer, M.J. Plasmeijer and H.P. Barendregt. CLEAN - A Language for Functional Graph Rewriting, In Proc. of Conference on Functional Programming Languages and Computer Architecture (FPCA '87), Portland, Oregon, USA, Kahn Ed., Springer-Verlag, LNCS 274, pp. 364-384. +[Nock91]: Nöcker, E.G.J.M.H., J.E.W. Smetsers, M.C.J.D. van Eekelen and M.J. Plasmeijer. Concurrent CLEAN, In Proc. of Parallel Architectures and Languages Europe (PARLE '91), Eindhoven, the Netherlands, Aarts, Leeuwen and Rem Eds., Springer-Verlag, LNCS 505, pp. 202-219. +[Plas01]: Rinus Plasmeijer, Marko van Eekelen, CLEAN LANGUAGE REPORT version 1.3.1 September 2001 +[Plas96]: Rinus Plasmeijer, Marko van Eekelen, Clean Language Report - Version 1.1 - March 1996 +[Gron90]: Groningen J.H.G. van. (1990), ‘Implementing the ABC-machine on M680x0 based architectures'. Master Thesis, University of Nijmegen, November 1990. +[Hamm91]: Hammond, Kevin. "Parallel SML: a Functional Language and its Implementation in DACTL." (1991). +[Glau2]: John Glauert: Research Background https://web.archive.org/web/20041207045351/http://www2.cmp.uea.ac.uk/~jrwg/MultiPar/resbg.html +[Glau1]: John Glauert, Personal profile. https://research-portal.uea.ac.uk/en/persons/john-glauert +[Hamm89b]: Hammond, Kevin. "Exception handling in a parallel functional language: PSML." In Fourth IEEE Region 10 International Conference TENCON, pp. 169-173. IEEE, 1989. +[Glau87]: Glauert, J. R. W., J. R. Kennaway, and M. R. Sleep. "DACTL: A computational model and compiler target language based on graph reduction." ICL Technical Journal 5, no. 3 (1987): 509-537. +[Papa89]: Papadopoulos, G.A. (1989). A fine grain parallel implementation of PARLOG. In: Díaz, J., Orejas, F. (eds) TAPSOFT '89. TAPSOFT 1989. Lecture Notes in Computer Science, vol 352. Springer, Berlin, Heidelberg. doi:10.1007/3-540-50940-2_44 +[Glau88]: John R. W. Glauert, George A. Papadopoulos: A Parallel Implementation of GHC. FGCS 1988: 1051-1058 +[Glau91]: Glauert, J.R.W., Kennaway, J.R., Sleep, M.R. (1991). Dactl: An experimental graph rewriting language. In: Ehrig, H., Kreowski, HJ., Rozenberg, G. (eds) Graph Grammars and Their Application to Computer Science. Graph Grammars 1990. Lecture Notes in Computer Science, vol 532. Springer, Berlin, Heidelberg. doi:10.1007/BFb0017401 +[Bare86]: H P Barendregt and M van Leeuwen. 1986. Functional programming and the language TALE. Current trends in concurrency. Overviews and tutorials. Springer-Verlag, Berlin, Heidelberg, 122–207. +[Darl87]: Darlington, J. (1987) Software development using functional programming languages. ICL Technical J., 5 (3): 492-508 +[Glyn90]: Kewley, John M., and Kevin Glynn. "Evaluation annotations for Hope+." In Functional Programming: Proceedings of the 1989 Glasgow Workshop 21–23 August 1989, Fraserburgh, Scotland, pp. 329-337. London: Springer London, 1990. doi:10.1007/978-1-4471-3166-3_22 +[Robe89]: Robertson, I.B., Way, Wenlock. "Hope+ on Flagship." In Functional Programming: Proceedings of the 1989 Glasgow Workshop, 21-23 August 1989, Fraserburgh, Scotland, p. 296. Springer, 1990. doi:10.1007/978-1-4471-3166-3_20 +[Darl89]: Darlington, J. et al. (1989). A Functional Programming environment supporting execution, partial execution and transformation. In: Odijk, E., Rem, M., Syre, JC. (eds) PARLE '89 Parallel Architectures and Languages Europe. PARLE 1989. Lecture Notes in Computer Science, vol 365. Springer, Berlin, Heidelberg. doi:10.1007/3540512845_46 +[Perr91]: Perry, Nigel. "The implementation of practical functional programming languages." PhD diss., Department of Computing, Imperial College, 1991. +[Fiel88]: Field, Antony and Peter G. Harrison. “Functional Programming.” (1988). +[Kean94]: Keane, John A. "An overview of the Flagship system." Journal of Functional Programming 4, no. 1 (1994): 19-45. +[Acce86]: Accetta, Mike, Robert Baron, William Bolosky, David Golub, Richard Rashid, Avadis Tevanian, and Michael Young. "Mach: A new kernel foundation for UNIX development." (1986): 93-112. +[Alle78]: John Allen. 1978. Anatomy of LISP. McGraw-Hill, Inc., USA. +[Alle2005]: John Allen. History, Mystery, and Ballast https://international-lisp-conference.org/2005/media/allen-slides.pdf https://international-lisp-conference.org/2005/media/allen-audio.mp3 +[Appe91]: Appel, Andrew W. "Compiling with Continuations." (1991). DOI:10.1017/CBO9780511609619 +[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 +[Augu85]: Augustsson, L. (1985). Compiling pattern matching. 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_48 +[Berr90]: Dave Berry, COMP.LANG.ML Frequently Asked Questions and Answers 19 Apr 1990 +[Blac85]: Blackburn,J. F. The Alvey Conference in Edinburgh: A Review of the United Kingdom's Research Program in Computer Science. 1985 Aug 22 https://apps.dtic.mil/sti/tr/pdf/ADA158963.pdf +[Boye92]: Robert S. Boyer. Frequently Asked Questions about KCL and AKCL. https://web.cecs.pdx.edu/~mperkows/=LISP/kcl +[Burs80]: R. M. Burstall, D. B. MacQueen, and D. T. Sannella. 1980. HOPE: An experimental applicative language. In Proceedings of the 1980 ACM conference on LISP and functional programming (LFP '80). Association for Computing Machinery, New York, NY, USA, 136–143. DOI:10.1145/800087.802799 +[Caml261]: CAML V2-6.1 +[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 +[Card83]: Luca Cardelli, Pre-Standard ML under Unix, August 14 1983. http://lucacardelli.name/Papers/MLUnix.pdf +[Card83b]: Luca Cardelli, ML under Unix Pose 0. 9/9/83 http://lucacardelli.name/Papers/MLUnix%20Pose%200.pdf +[Card83c]: Luca Cardelli, ML under Unix Pose 1, http://lucacardelli.name/Papers/MLUnix%20Pose%201.pdf +[Card83d]: Luca Cardelli, ML under Unix Pose 2, 10/10/83 http://lucacardelli.name/Papers/MLUnix%20Pose%202.pdf +[Card83e]: Luca Cardelli, ML under Unix Pose 3, 11/15/83 http://lucacardelli.name/Papers/MLUnix%20Pose%203.pdf +[Card84]: Luca Cardelli. 1984. Compiling a functional language. In Proceedings of the 1984 ACM Symposium on LISP and functional programming (LFP '84). Association for Computing Machinery, New York, NY, USA, 208–217. doi:10.1145/800055.802037 +[Card84b]: Luca Cardelli, ML under Unix Pose 4, 4/5/84 http://lucacardelli.name/Papers/MLUnix%20Pose%204.pdf +[Card84c]: Luca Cardelli, A semantics of multiple inheritance. in Semantics of Data Types, International Symposium, Sophia-Antipolis, France, June 1984, Proceedings. Lecture Notes in Computer Science, Vol. 173, Springer-Verlag, 1984, ISBN 3-540-13346-1. pp 51-67. +[Card85]: Antonio Albano, Luca Cardelli, and Renzo Orsini. Galileo: a strongly typed, interactive, conceptual language. ACM Transactions on Database Systems (TODS), 10(2):230-260, 1985. +[Card86]: Luca Cardelli. Amber. In Guy Cousineau, Pierre-Louis Curien, and Bernard Robinet, editors, Combinators and Functional Programming Languages, Lecture Notes in Computer Science, Vol. 242, pp 21-70. Springer-Verlag, 1986. +[Card86b]: Luca Cardelli. The amber machine. In Guy Cousineau, Pierre-Louis Curien, and Bernard Robinet, editors, Combinators and Functional Programming Languages, Lecture Notes in Computer Science, Vol. 242, pp 21-70. Springer-Verlag, 1986. +[Clac24]: Christopher D. Clack. Research https://christopherclack.com/research/research-topics +[Clar81]: Keith L. Clark and Steve Gregory. 1981. A relational language for parallel programming. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA '81). Association for Computing Machinery, New York, NY, USA, 171–178. doi:10.1145/800223.806776 +[CMUCL]: CMUCL: Project history & who's who (2020) https://cmucl.org/credits.html +[Cous87]: Cousineau, Guy, P-L. Curien, and Michel Mauny. "The categorical abstract machine." Science of computer programming 8, no. 2 (1987): 173-202. +[Cous90]: Cousineau, Guy, and Gérard Huet. "The CAML primer." Version 2.6.1, INRIA, 1990. +[Danc87]: Gill Dancey, INFORMATICS DIVISION NEWSLETTER No. 10 May 1987 https://www.chilton-computing.org.uk/inf/pdfs/inf_newsletters.pdf +[Darl76]: Darlington, J., & Burstall, R. M. (1976). A system which automatically improves programs. Acta Informatica, 6(1). doi:10.1007/bf00263742   +[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 +[Darl82]: Darlington J, Henderson P, Turner DA, editors. Functional programming and its applications: an advanced course. CUP Archive; 1982 Feb 18. +[Dett86]: Dettmer, Roger. “Flagship. A fifth-generation machine.” Electronics and Power 32 (1986): 203-208. +[Dijk81] Dijkstra, E. (1981). Trip report E.W. Dijkstra, Newcastle, 19-25 +July 1981. Dijkstra working note EWD798. https://www.cs.utexas.edu/~EWD/transcriptions/EWD07xx/EWD798.html +[Duce86]: D. A. Duce, Software Engineering Research at RAL, 13 February 1986 https://www.chilton-computing.org.uk/inf/pdfs/seg/seg101.pdf +[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 +[Faul84]: Faulkner, T. L., & Pavelin, C. J. (1984). Atlas 10 computer. ICL technical journal, 4, 13-32. +[Feig83]: Feigenbaum, Edward A.; McCorduck, Pamela (1983). The fifth generation: artificial intelligence and Japan's computer challenge to the world. Reading, Mass: Addison-Wesley. ISBN 978-0-201-11519-2. +[Fiel88]: Anthony J. Field, Peter Harrison - Functional Programming, 1988 +[Franz]: History of Franz Inc. https://franz.com/about/company.history.lhtml +[GEC63]: GEC Series 63 https://www.chilton-computing.org.uk/inf/alvey/p003.htm +[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). +[Gutt81]: John Guttag, James Horning, and John Williams. 1981. FP with data abstraction and strong typing. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA '81). Association for Computing Machinery, New York, NY, USA, 11–24. doi:10.1145/800223.806758 +[Harp85]: Robert Harper, Report on the Standard ML Meeting, Edinburgh, May 23-25, 1985. https://smlfamily.github.io/history/Harper-SML-meeting-1985_05.pdf +[Harr87]: Harrison, P.G., Reeve, M.J. (1987). The parallel graph reduction machine, ALICE. In: Fasel, J.H., Keller, R.M. (eds) Graph Reduction. GR 1986. Lecture Notes in Computer Science, vol 279. Springer, Berlin, Heidelberg. doi:10.1007/3-540-18420-1_55 +[Hend80]: Henderson, Peter B.. “Functional programming - application and implementation.” Prentice Hall International Series in Computer Science (1980). +[Hoar72]: Hoare, Charles Antony Richard. "Chapter II: Notes on data structuring." In Structured programming, pp. 83-174. 1972. +[Hoar75]: Hoare, C.A.R. Recursive data structures. International Journal of Computer and Information Sciences 4, 105–132 (1975). doi:10.1007/BF00976239 +[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. doi:10.1145/3544585 +[HOL88]: HOL88 https://github.com/theoremprover-museum/HOL88 +[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 +[Huet15]: Gérard Huet, Thierry Coquand and Christine Paulin. Early history of Coq, September 2015 https://coq.inria.fr/refman/history.html +[Jenk80]: James H. Davenport and Richard D. Jenks. 1980. MODLISP. In Proceedings of the 1980 ACM conference on LISP and functional programming (LFP '80). Association for Computing Machinery, New York, NY, USA, 65–74. doi:10.1145/800087.802791 +[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 +[John86]: Johnsson, T. (1987). Target code generation from G-machine code. In: Fasel, J.H., Keller, R.M. (eds) Graph Reduction. GR 1986. Lecture Notes in Computer Science, vol 279. Springer, Berlin, Heidelberg. doi:10.1007/3-540-18420-1_53 +[John87]: Johnsson, Thomas. "Compiling Lazy Functional Language." PhD Thesis, Chalmers University of Technology (1987). +[Kean94]: Keane J. A. An overview of the Flagship system. Journal of Functional Programming. 1994;4(1):19-45. doi:10.1017/S0956796800000927 +[Kess86]: R. R. Kessler, J. C. Peterson, H. Carr, G. P. Duggan, and J. Knell. 1986. EPIC - a retargetable, highly optimizing Lisp compiler. In Proceedings of the 1986 SIGPLAN symposium on Compiler construction (SIGPLAN '86). Association for Computing Machinery, New York, NY, USA, 118–130. doi:10.1145/12276.13323 +[MacL15]: Rob MacLachlan, History of the CMUCL Project (2015) https://www.cons.org/cmucl/doc/cmucl-history.html +[MacQ85]: David MacQueen and Robin Milner. 1985. Record of the Standard ML Meeting, Edinburgh, 6–8 June 1984. Polymorphism: The ML/LCF/Hope Newsletter II, 1 (Jan.), 16. http://lucacardelli.name/Papers/Polymorphism%20Vol%20II,%20No%201.pdf +[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 +[MacQ20]: MacQueen, David B., Robert Harper and John H. Reppy. “The history of Standard ML.” Proceedings of the ACM on Programming Languages 4 (2020): 1 - 100.DOI:10.1145/3386336 +[MacQ22]: D. MacQueen, A New Match Compiler for Standard ML of New Jersey https://icfp22.sigplan.org/details/mlfamilyworkshop-2022-papers/3/A-New-Match-Compiler-for-Standard-ML-of-New-Jersey +[Matt88]: Matthews D.C.J. (1988) An Overview of the Poly Programming Language. In: Atkinson M.P., Buneman P., Morrison R. (eds) Data Types and Persistence. Topics in Information Systems. Springer, Berlin, Heidelberg. doi:10.1007/978-3-642-61556-6_4 +[Matt89]: Matthews, David CJ. Papers on Poly/ML. No. UCAM-CL-TR-161. University of Cambridge, Computer Laboratory, 1989. +[Matt2000]: David Matthews, History and Acknowledgements https://polyml.org/FAQ.html#history +[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 +[Maun88]: Michel Mauny, A new functional language: CAML Release 2.5 in comp.lang.misc, 05/09/88 +[McDo89]: D. McDonald, editor. CMU Common Lisp User's Manual: Mach/IBM RT PC edition. Carnegie Mellon University. CMU-CS-89-132, April 1989. https://doi.org/10.1184/R1/6604121.v1 +[McJo24]: Paul McJones, John Allen (1937-2022) and Anatomy of LISP https://mcjones.org/dustydecks/archives/2024/04/11/1249/ +[Meir83]: Meira, S. R. L. 1983 Sorting algorithms in KRC: implementation, proof and performance. Computing Laboratory rep. no. 14. University of Kent at Canterbury. +[Meir83b]: recursion -- again from net.lang srlm@ukc.UUCP (S.R.L.Meira) (08/16/83) https://usenet.trashworldnews.com/?thread=132780 +[Miln82]: Milner, Robin. “How ML evolved.” (1982). +[Miln83]: Robin Milner. 1983. A Proposal for Standard ML (TENTATIVE). April 1983. 25 pages. https://smlfamily.github.io/history/SML-proposal-4-83.pdf +[Miln83b]: Robin Milner. 1983. A Proposal for Standard ML (second draft). June 1983. 50 pages. http://sml-family.org/history/SML-proposal-6-83.pdf +[Miln83c]: A Proposal for Standard ML, by Robin Milner, November 1983. The third draft of Milner's proposal for core Standard ML. https://smlfamily.github.io/history/SML-proposal-11-83.pdf +[Miln84]: Robin Milner, The Standard ML Core Language, July 1984. https://smlfamily.github.io/history/SML-proposal-7-84.pdf +[Miln84b]: Robin Milner. 1984. A proposal for standard ML. In Proceedings of the 1984 ACM Symposium on LISP and functional programming (LFP '84). Association for Computing Machinery, New York, NY, USA, 184–197. doi:10.1145/800055.802035 +[Miln84c]: Robin Milner, The Standard ML Core Language, October, 1984. The second draft of the "Core Language" design. https://smlfamily.github.io/history/SML-proposal-10-84.pdf +[Miln85]: Robin Milner, The Standard ML Core Language (Revised), September, 1985. The third draft of the "Core Language" design. https://smlfamily.github.io/history/SML-proposal-9-85.pdf +[Miln88]: Harper, R.M., Milner, R., Tofte, M., The Definition of Standard ML, Version 2 Report ECS-LFCS-88-62, Laboratory for Foundations of Computer Science, Computer Science Department, Edinburgh University, 1988. +[Mitc84]: John C. Mitchell. 1984. Coercion and type inference. In Proceedings of the 11th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '84). Association for Computing Machinery, New York, NY, USA, 175–185. https://doi.org/10.1145/800017.800529 +[MLKit2002]: About the ML Kit https://web.archive.org/web/20020827144153/http://www.itu.dk/research/mlkit/about.html +[Moss89]: A. E. Mossberg, Edinburgh ML (summary) comp.lang.misc, Dec 18, 1989 +[Mosses]: Peter Mosses, Affiliations https://pdmosses.github.io/affiliations/ +[Muss81]: D. Kapur, D. R. Musser, and A. A. Stepanov. 1981. Operators and algebraic structures. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA '81). Association for Computing Machinery, New York, NY, USA, 59–64. doi:10.1145/800223.806763 +[Neum90]: Pierre-Louis Neumann, New Caml version in comp.lang.misc, 03/29/90 +[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 +[Odag97]: Odagiri, H., Nakamura, Y., & Shibuya, M. (1997). Research consortia as a vehicle for basic research: The case of a fifth generation computer project in Japan. Research Policy, 26(2), 191–207. doi:10.1016/s0048-7333(97)00008-5  +[Oakl90]: Brian Oakley and Kenneth Owen. 1990. Alvey: Britain's strategic computing initiative. MIT Press, Cambridge, MA, USA. +[Paul96]: Paulson, Lawrence Charles. “ML for the working programmer (2. ed.).” (1996). +[Paul22b]: Lawrence Paulson. Memories: Edinburgh ML to Standard ML https://lawrencecpaulson.github.io/2022/10/05/Standard_ML.html +[PERQ1]: PERQ History, 1.3. EARLY DAYS http://www.chilton-computing.org.uk/acd/sus/perq_history/part_1/c3.htm +[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. +[POP19]: INFORMATION ABOUT POPLOG AND POP-11 2019 https://poplogarchive.getpoplog.org/poplog.info.html +[POPLOG]: Poplog https://github.com/poplog/poplog +[RAL83]: DISTRIBUTED INTERACTIVE COMPUTING NOTE 893, RUTHERFORD APPLETON LABORATORY, 3 October 1983 http://www.dataweb.clrc.ac.uk/acd/pdfs/dic/dic841.pdf +[RAL84]: The Software Technology Initiative Final Report 1981-1984 +October 1984 http://www.dataweb.stfc.ac.uk/inf/literature/reports/sti_report/p001.htm +[RAL87]: Informatics Annual Report 1986-87 http://www.chilton-computing.org.uk/inf/literature/inf_annual_reports/p003.htm +[RAL89]: Informatics Annual Report 1988-9 http://www.chilton-computing.org.uk/inf/literature/inf_reports/p005.htm +[Rash89]: Rashid, Richard, Robert Baron, Alessandro Forin, David Golub, Michael Jones, Douglas Orr, and Richard Sanzi. "Mach: a foundation for open systems (operating systems)." In Proceedings of the Second Workshop on Workstation Operating Systems, pp. 109-113. IEEE, 1989. +[Rich85]: Richards, H. (1985). Applicative programming. Systems Research, 2(4), 299–306. doi:10.1002/sres.3850020409  +[Ryde82]: Rydeheard, David Eric. "Applications of category theory to programming and program specification." (1982). +[Schmidt]: CV https://people.cs.ksu.edu/~schmidt/vita.html +[Shap83]: Ehud Y. Shapiro. 1983. The fifth generation project — a trip report. Commun. ACM 26, 9 (Sept. 1983), 637–641. doi:10.1145/358172.358179 +[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. +[Sokolowski]: CV https://prabook.com/web/stefan_andrzej.sokolowski/90807 +[SPJ82]: Simon L Peyton Jones. 1982. An investigation of the relative efficiencies of combinators and lambda expressions. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 150–158. doi:10.1145/800068.802145 +[SPJ85]: Jones, S. L. P. (1985). Yacc in sasl — an exercise in functional programming. Software: Practice and Experience, 15(8), 807–820. doi:10.1002/spe.4380150807 +[SPJ87]: Peyton Jones, Simon L. The implementation of functional programming languages (prentice-hall international series in computer science). Prentice-Hall, Inc., 1987. +[Stee82b]: Guy L. Steele. 1982. Report on the 1980 LiSP Conference Stanford University. August 25-27, 1980. SIGPLAN Not. 17, 3 (March 1982), 22–36. doi:10.1145/947912.1361218 +[Stee84]: Steele, Guy L. "Common LISP. The language." Bedford: Digital Press (1984). +[Stee96]: Guy L. Steele and Richard P. Gabriel. 1996. The evolution of Lisp. Uncut draft. +[Stra67]: Strachey, Christopher S.. “Fundamental Concepts in Programming Languages.” Higher-Order and Symbolic Computation 13 (2000): 11-49. +DOI:10.1023/A:1010000313106 +[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  +[Turn81]: D. A. Turner. 1981. The semantic elegance of 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, 85–92. doi:10.1145/800223.806766 +[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.” +[Turn83]: Turner, D. A. "SASL language manual (revised version)." University of Kent (1983). +[Turn84]: Turner, D. A. (1984). Functional Programs as Executable Specifications [and Discussion]. Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences, 312(1522), 363–388. doi:10.1098/rsta.1984.0065  +[Wads83]: Christopher Wadsworth. 1983. ML, LCF, and HOPE. Polymorphism: The ML/LCF/Hope Newsletter I, 1 (Jan.), 5. http://lucacardelli.name/Papers/Polymorphism%20Vol%20I,%20No%201.pdf +[Wadl81]: Philip Wadler. 1981. Applicative style programming, program transformation, and list operators. In Proceedings of the 1981 conference on Functional programming languages and computer architecture (FPCA '81). Association for Computing Machinery, New York, NY, USA, 25–32. doi:10.1145/800223.806759 +[Warr77]: David H D Warren, Luis M. Pereira, and Fernando Pereira. 1977. Prolog - the language and its implementation compared with Lisp. In Proceedings of the 1977 symposium on Artificial intelligence and programming languages. Association for Computing Machinery, New York, NY, USA, 109–115. doi:10.1145/800228.806939 +[Warr77b]: David H D Warren. 1977. Prolog - the language and its implementation compared with Lisp. Slides https://www.softwarepreservation.org/projects/prolog/edinburgh/doc/slides-ACM1977.pdf +[Whit77]: White, Jon L. "Lisp: Program is Data: A historical perspective on MACLISP." In Proceedings of the 1977 MACSYMA Users' Conference, MIT Laboratory for Computer Science, Cambridge, Mass, pp. 181-189. 1977. +[Whit79]: Jon L. White. NIL: A perspective. Proceedings of 1979 MACSYMA Users' Conference, Washington, D.C., June 1979. https://www.softwarepreservation.org/projects/LISP/MIT/White-NIL_A_Perspective-1979.pdf +[Witt85]: R. W. Witty, SEG ANNUAL REPORT September 84-September 85, 30 September 1985 https://www.chilton-computing.org.uk/inf/pdfs/seg/seg74.pdf +[Wolf89]: David Wolfram, ACE Version 1.3 Distribution comp.lang.misc, Jun 20, 1989 +[Yuas85]: Yuasa, Taiichi, and Masami Hagiya. "Kyoto Common Lisp Report." Research Institute for Mathematical Sciences, Kyoto University (1985.11.13). diff --git a/ru/index.md b/ru/index.md new file mode 100644 index 0000000..bcacd33 --- /dev/null +++ b/ru/index.md @@ -0,0 +1,99 @@ +История применения и оценки функционального программирования +======= + +[Часть 0: Что было, когда функционального программирования не было.](prehist.md) +[Часть 1: Каталог компиляторов.](compilers.md) +[Часть 2: Великое уравнивание.](hopes.md) + +--------------------------- + +[текст на github](https://github.com/klapaucius/fphistoryru) +[обсудить на github](https://github.com/klapaucius/fphistoryru/discussions) +[обсудить в telegram](https://t.me/hateHaskellers) +[поддержать проект](https://boosty.to/fphistory/donate) + +--------------------------- + +[Обновление 2026-01-01](hopes.md#день-зависимости) + +[Обновление 2025-12-13](hopes.md#негативное-пространство) + +[Обновление 2025-11-16](hopes.md#граф-0) + +[Обновление 2025-10-30](hopes.md#новая-надежда) + +[Обновление 2025-09-06](hopes.md#big-in-japan) + +[Обновление 2025-08-09](hopes.md#le-ml) + +[Обновление 2025-06-30](hopes.md#fasadrenovering) + +[Обновление 2025-05-31](hopes.md#любой-принц-в-янтаре) + +[Обновление 2025-04-30](hopes.md#карделли-против-карделли) + +[Обновление 2025-03-31](hopes.md#standard-ml-836) + +[Обновление 2025-02-28](hopes.md#лука-карделли-и-поздняя-эволюция-npl) + +[Обновление 2025-01-31](hopes.md#часть-2-великое-уравнивание) + +[Обновление 2025-01-08](compilers.md#сон-черного-короля) + +[Обновление 2024-12-02](compilers.md#скобочный-потолок) + +[Обновление 2024-11-01](compilers.md#норман-и-norma) + +[Обновление 2024-09-30](compilers.md#ржавый-пояс) + +[Обновление 2024-08-31](compilers.md#true--false) + +[Обновление 2024-08-08](compilers.md#лисп-который-покончит-с-лиспами) + +[Обновление 2024-06-30](compilers.md#живые-ископаемые) + +[Обновление 2024-05-29](compilers.md#чайники-рассела) + +[Обновление 2024-04-29](compilers.md#еще-ортогональнее) + +[Обновление 2024-03-31](compilers.md#редукция-графов-и-как-её-избежать) + +[Обновление 2024-02-29](compilers.md#часть-1-каталог-компиляторов) + +[Обновление 2024-01-11](prehist.md#сколько-тысяч-слов--все-впустую) + +[Обновление 2024-01-03](prehist.md#декабрьский-апдейт) + +[Обновление 2023-11-30](prehist.md#у-меня-нет-памяти-а-я-должен-аллоцировать) + +[Обновление 2023-10-31](prehist.md#lambda-the-ultimate-misunderstanding) + +[Дополнения 2023-09-12](https://gist.github.com/klapaucius/ab5a543401248b6f517b4755fd4bb017) + +[Обновление 2023-08-26](prehist.md#надежда-умирает-последней) + +[Обновление 2023-07-19](prehist.md#ни-тот-ни-этот-имена-запретны) + +[Обновление 2023-06-30](prehist.md#две-диссертации) + +[Обновление 2023-05-31](prehist.md#спецификация---это-имплементация) + +[Обновление 2023-04-26](prehist.md#s-0) + +[Обновление 2023-03-31](prehist.md#уравнения-и-неравенства) + +[Обновление 2023-02-28](prehist.md#возвращение-резолюционизма) + +[Обновление 2023-01-31](prehist.md#типо-теоретическая-альтернатива) + +[Обновление 2022-12-24](prehist.md#следующие-700-изобретений-продолжений) + +[Обновление 2022-12-12](prehist.md#%D0%B7%D0%B0-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%B5%D1%89%D0%B5-%D0%BD%D0%B8%D0%BA%D0%BE%D0%B3%D0%BE-%D0%BD%D0%B5-%D1%83%D0%B2%D0%BE%D0%BB%D1%8C%D0%BD%D1%8F%D0%BB%D0%B8) + +[Обновление 2022-11-14](prehist.md#%D1%8D%D0%B4%D0%B8%D0%BD%D0%B1%D1%83%D1%80%D0%B3%D1%81%D0%BA%D0%B0%D1%8F-%D0%B8%D1%81%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%B0%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0) + +[Обновление 2022-10-10](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5-%D1%81%D0%B0%D0%BC%D1%8B%D1%85-%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D1%85-%D0%B8%D0%BC%D0%BF%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8%D0%B9) + +[Обновление 2022-09-29](prehist.md#%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B5-700-%D0%BD%D0%B5%D0%BF%D0%BE%D0%BF%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D1%85-%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2) + +[Обновление 2022-08-26](#%D0%BF%D1%81%D0%B5%D0%B2%D0%B4%D0%BEcpl) diff --git a/ru/prehist.md b/ru/prehist.md new file mode 100644 index 0000000..2c03066 --- /dev/null +++ b/ru/prehist.md @@ -0,0 +1,6194 @@ +История применения и оценки функционального программирования +======= + +- [История применения и оценки функционального программирования](#история-применения-и-оценки-функционального-программирования) +- [Часть 0: что было, когда функционального программирования не было.](#часть-0-что-было-когда-функционального-программирования-не-было) + - [Когда _чего_ не было?](#когда-чего-не-было) + - [Для чего нужна эта работа и в чем ее отличия от других](#для-чего-нужна-эта-работа-и-в-чем-ее-отличия-от-других) + - [Лямбда-криптозой](#лямбда-криптозой) + - [Лямбда-фанерозой](#лямбда-фанерозой) +- [Ветвь обоих Кембриджей](#ветвь-обоих-кембриджей) + - [В Кембридже](#в-кембридже) + - [CPL](#cpl) + - [псевдоCPL](#псевдоcpl) + - [Следующие 700 непопулярных языков](#следующие-700-непопулярных-языков) + - [Следующие 700 исписанных листов](#следующие-700-исписанных-листов) + - [Следующие 700 вариаций семи фич](#следующие-700-вариаций-семи-фич) + - [больше не фичи](#больше-не-фичи) + - [отсутствие лямбд](#отсутствие-лямбд) + - [выражение `where`](#выражение-where) + - [тернарный оператор ветвления](#тернарный-оператор-ветвления) + - [пока еще фичи](#пока-еще-фичи) + - [конструкция `let`](#конструкция-let) + - [аннотация рекурсии](#аннотация-рекурсии) + - [отсутствие аннотаций рекурсии](#отсутствие-аннотаций-рекурсии) + - [отсутствие аннотаций типов](#отсутствие-аннотаций-типов) + - [обозначение блоков отступами](#обозначение-блоков-отступами) + - ["одновременные"/"параллельные" декларации](#одновременныепараллельные-декларации) + - [обсуждавшиеся фичи](#обсуждавшиеся-фичи) + - [параметрический полиморфизм](#параметрический-полиморфизм) + - [Изобрел ли Ландин АлгТД?](#изобрел-ли-ландин-алгтд) + - [Следующие 700 не самых быстрых имплементаций](#следующие-700-не-самых-быстрых-имплементаций) + - [В другом Кембридже](#в-другом-кембридже) + - [Мартин Ричардс](#мартин-ричардс) + - [BCPL](#bcpl) + - [PAL](#pal) + - [McG](#mcg) +- [Эдинбургская исследовательская программа](#эдинбургская-исследовательская-программа) + - [От исследовательского программирования к экспериментальному.](#от-исследовательского-программирования-к-экспериментальному) + - [Род Бурсталл](#род-бурсталл) + - [Робин Поплстоун](#робин-поплстоун) + - [Энергия маленькой целевой машины.](#энергия-маленькой-целевой-машины) + - [Filtrage de motif](#filtrage-de-motif) + - [За возвращение функций еще никого не увольняли](#за-возвращение-функций-еще-никого-не-увольняли) + - [Уильям Бердж](#уильям-бердж) + - [Наука переполнять стек](#наука-переполнять-стек) + - [Комбинаторные основания функционального программирования](#комбинаторные-основания-функционального-программирования) + - [Разделение](#разделение) + - [Следующие 700 изобретений продолжений](#следующие-700-изобретений-продолжений) + - [Рейнольдс и GEDANKEN.](#рейнольдс-и-gedanken) + - [Изобретено независимо.](#изобретено-независимо) + - [Типо-теоретическая альтернатива](#типо-теоретическая-альтернатива) + - [Робин Милнер](#робин-милнер) + - [Для вычислимых функций](#для-вычислимых-функций) + - [Три мили разделения](#три-мили-разделения) + - [Метаязык](#метаязык) + - [Предыдущие 700 ISWIMов в одном](#предыдущие-700-iswimов-в-одном) + - [Невыносимая тяжесть аннотации](#невыносимая-тяжесть-аннотации) + - [Смотрите, не перепутайте Моррисов](#смотрите-не-перепутайте-моррисов) + - [Возвращение резолюционизма](#возвращение-резолюционизма) + - [Mutatis mutandis](#mutatis-mutandis) + - [Исключения подтверждают правило](#исключения-подтверждают-правило) + - [Не могу сконструировать бесконечный тип](#не-могу-сконструировать-бесконечный-тип) + - [Против методологии](#против-методологии) + - [Когда функционального программирования не было](#когда-функционального-программирования-не-было) + - [Уравнения и неравенства](#уравнения-и-неравенства) + - [Аксиоматизация](#аксиоматизация) + - [Индуктивное сопротивление](#индуктивное-сопротивление) + - [Квадратный Лисп](#квадратный-лисп) + - [Существует ли список из двух элементов?](#существует-ли-список-из-двух-элементов) + - [Индукционный переход](#индукционный-переход) + - [S-0](#s-0) + - [Ничего постояннее временного](#ничего-постояннее-временного) + - [Непосредственная имплементация](#непосредственная-имплементация) + - [Сложный путь](#сложный-путь) + - [OBJ](#obj) + - [TEL](#tel) + - [Язык О'Доннела](#язык-одоннела) + - [Новый язык программирования](#новый-язык-программирования) + - [Все ясно](#все-ясно) + - [Спецификация - это имплементация](#спецификация---это-имплементация) + - [Абстрактный синтаксис](#абстрактный-синтаксис) + - [Конец экспериментального программирования](#конец-экспериментального-программирования) + - [Две диссертации](#две-диссертации) + - [От экспериментального программирования к исследовательскому](#от-экспериментального-программирования-к-исследовательскому) + - [Переписывание графов](#переписывание-графов) + - [PAL или пропал](#pal-или-пропал) + - [Статический динамический язык](#статический-динамический-язык) + - [SASL 74](#sasl-74) + - [SASL 75](#sasl-75) + - [Функционального программирования нет в меньшей степени, чем обычно](#функционального-программирования-нет-в-меньшей-степени-чем-обычно) + - [Воплощение ISWIM в реальность завершено](#воплощение-iswim-в-реальность-завершено) + - [Имплементации настоящие и будущие](#имплементации-настоящие-и-будущие) + - [Проблема одинаковых кромок](#проблема-одинаковых-кромок) + - [`same-fringe ?` `yes)))))))))))]`](#same-fringe--yes) + - [ГИПЕР-ЧИСТЫЙ ЛИСП](#гипер-чистый-лисп) + - [Прокрастинирующая машина](#прокрастинирующая-машина) + - [SASL 76](#sasl-76) + - [Lazy NPL](#lazy-npl) + - [Ни тот, ни этот: имена запретны.](#ни-тот-ни-этот-имена-запретны) + - [S K](#s-k) + - [S B](#s-b) + - [S' S](#s-s) + - [B(n,m)](#bnm) + - [SASL 79](#sasl-79) + - [Рекурсивный Калькулятор и Декембриджизация.](#рекурсивный-калькулятор-и-декембриджизация) + - [Прибавляем](#прибавляем) + - [Отнимаем](#отнимаем) + - [Что если даже SK-машина - это слишком сложно?](#что-если-даже-sk-машина---это-слишком-сложно) + - [Все ещё нет?](#все-ещё-нет) + - [Надежда умирает последней](#надежда-умирает-последней) + - [Дэвид МакКвин](#дэвид-макквин) + - [Какие проблемы решали авторы HOPE?](#какие-проблемы-решали-авторы-hope) + - [ФВП](#фвп) + - [Вывод типов](#вывод-типов) + - [Типы данных](#типы-данных) + - [Абстракция данных](#абстракция-данных) + - [Что делать в 80-е годы](#что-делать-в-80-е-годы) + - [Что было и что не было сделано](#что-было-и-что-не-было-сделано) + - [Можно сказать, что компилируется. Можно сказать, что работает.](#можно-сказать-что-компилируется-можно-сказать-что-работает) + - [Дональд Саннелла](#дональд-саннелла) + - [Часть компилятора для части языка](#часть-компилятора-для-части-языка) + - [Реабилитация прокрастинирующей машины](#реабилитация-прокрастинирующей-машины) + - [Пролог](#пролог) + - [Эпилог](#эпилог) + - [LCF/ML (LISP)](#lcfml-lisp) + - [SASL (LSECD)](#sasl-lsecd) + - [Miranda (SKI)](#miranda-ski) + - [LISP](#lisp) + - [Потерянное десятилетие](#потерянное-десятилетие) +- [Lambda the Ultimate Misunderstanding](#lambda-the-ultimate-misunderstanding) + - [LAMBDA не конструирует функцию.](#lambda-не-конструирует-функцию) + - [И теперь у вас две FUNARG-проблемы](#и-теперь-у-вас-две-funarg-проблемы) + - [MacLisp](#maclisp) + - [Interlisp](#interlisp) + - [LISP/370](#lisp370) + - [Лишняя деталь](#лишняя-деталь) + - [Сказки Венского леса](#сказки-венского-леса) + - [Пожалуйста, возвращайтесь](#пожалуйста-возвращайтесь) + - [Удивительные приключения кроликов](#удивительные-приключения-кроликов) +- [У меня нет памяти, а я должен аллоцировать](#у-меня-нет-памяти-а-я-должен-аллоцировать) + - [Титаномахия](#титаномахия) + - [75K слов достаточно каждому](#75k-слов-достаточно-каждому) + - [Полдень, 1972 год.](#полдень-1972-год) + - [MACSYMA — суровая хозяйка](#macsyma--суровая-хозяйка) + - [Ветра зимы](#ветра-зимы) + - [Core War](#core-war) + - [Eight Megabytes And Constantly Swapping](#eight-megabytes-and-constantly-swapping) + - [Лисп-машина останавливается](#лисп-машина-останавливается) + - [Не в звездах, нет, а в нас самих](#не-в-звездах-нет-а-в-нас-самих) + - [STAR против сил Лиспа](#star-против-сил-лиспа) + - [Aut Caesar aut NIL](#aut-caesar-aut-nil) + - [Per aspera ad astra](#per-aspera-ad-astra) + - [декабрьский апдейт](#декабрьский-апдейт) + - [Стандартизация — это путь обмана](#стандартизация--это-путь-обмана) + - [Анизотропия фунарга](#анизотропия-фунарга) + - [План Йенсена](#план-йенсена) + - [Конвергенция?](#конвергенция) + - [Амстердамский заговор](#амстердамский-заговор) + - [Алгол с человеческим лицом](#алгол-с-человеческим-лицом) + - [Ускорение](#ускорение) + - [Перестройка](#перестройка) + - [Сюрприз](#сюрприз) + - [Посчитать компиляции в Молверне](#посчитать-компиляции-в-молверне) + - [ALGOL 68-R](#algol-68-r) + - [Algol68C](#algol68c) + - [Эдикт Фонтенбло](#эдикт-фонтенбло) + - [ALGOL 68 Revised](#algol-68-revised) + - [Расширения](#расширения) + - [ALGOL 77](#algol-77) + - [Ограниченное могущество кучи](#ограниченное-могущество-кучи) + - [Алголы 70-х](#алголы-70-х) + - [Антиалголы 80-х](#антиалголы-80-х) + - [Сколько тысяч слов – все впустую](#сколько-тысяч-слов--все-впустую) + - [THE GARBAGE COLLECTOR HAS BEEN CALLED](#the-garbage-collector-has-been-called) + - [_Миллиарды_](#миллиарды) + - [Лисп-машина атакует!](#лисп-машина-атакует) + - [GC considered harmful](#gc-considered-harmful) + - [Похищение огня](#похищение-огня) +- [Литература](#литература) + + +Часть 0: что было, когда функционального программирования не было. +=================================== + +Когда _чего_ не было? +------------------- + +> Функциональное программирование - это инженерная ветвь конструктивной математики. + Олег Нижников. + + +Мы не ставим перед собой цели дать определение функциональному программированию. Мы, однако же, вынуждены определить какие-то практические рамки для нашего исследования. Выбрать историю _чего именно_ мы пишем, и выбрать что-то обозримое. +И многие определения ФП просто непрактичны с этой точки зрения. История языков с первоклассными функциями в наши дни фактически равна истории языков программирования, ведь даже редкие языки в которых их до сих пор нет - вроде C++ или Rust - обычно связаны разными историческими отношениями с языками где первоклассные функции есть. +Не годится также и не такая необъятная, но все еще неподъемная группа языков "по каким-то историческим причинам считающиеся функциональными", о которых обычно и пишут те, кто пишут историю ФП. Главная причина того, почему это определения ФЯ для нас не годится - в этой группе языков есть Лисп, и мы не хотим писать историю Лиспа. В основном потому, что история Лиспа слишком громадная тема, чтоб рядом с ней было вообще можно заметить историю прочих языков этой "традиционной" группы. Мало того, сейчас есть проблема, которой во времена выбора "исторически сложившегося перечня ФЯ" просто не было: сегодня Лисп - типичный представитель огромной категории языков. Так же как и в какую-нибудь Java, первоклассные функции были добавлены в Лисп после того, как он уже долгое время существовал и это не такая уж значительная и важная деталь истории языка. И написание истории функционализации Лиспа потребовало бы сравнения с другими языками, прошедшими через тот же процесс. А именно почти со всеми существующими сейчас языками программирования. +Мы бы хотели поставить перед собой реальную цель и ограничиться функциональными языками в более узком смысле, историю которых мы рассмотрим немного более глубоко, чем можно позволить себе рассмотреть историю всех языков. +Чтоб сузить группу языков мы добавим к первой фиче + +* Первоклассные функции + +еще какие-нибудь. + +* Параметрический полиморфизм и вывод/реконструкция типов + +позволяют существенно сократить предмет исследования, но все еще недостаточно. Так что мы добавим + +* Алгебраические типы и паттерн-матчинг + +Вот эта последняя фича, наконец-то, дает нам семейство языков подходящего размера. +К сожалению, объединение фич выглядит довольно произвольным. Кроме того, если когда-нибудь АлгТД и ПМ станут так же распространены как первоклассные функции (мы бы не стали на это особо рассчитывать), проблема истории всех языков вернется, чтоб преследовать наших последователей, если они у нас, конечно, будут. + +Языки с этим набором фич, бывает, называются "Эмелеподобными языками". +Только вот люди не достаточно часто соглашаются какие языки ML-подобны. Не все ML-и так уж подобны другим ML-ям, а что уж говорить про не ML-и. + +Попробуем найти первый язык с такими фичами и определить семейство через него. +Хорошие новости: существует один такой язык - Hope, никто, насколько нам известно, не изобретал еще раз такое же сочетание независимо. Он получился из "слияния" языков, в каждом из которых был неполный набор интересных нам фич. + +Плохие новости: не все языки, историю которых мы хотели бы писать, происходят от этого языка, так что наши надежды на "Hope-образные языки" не оправдались так же, и по тем же причинам, что и на "ML-образные". "Слияние", вероятно, не самая подходящая перспектива в этом случае. Отношения между языками не хотят принимать вид удобных деревьев или графов. + +Правильнее будет говорить про набор языков, каждый из которых, развиваясь, позаимствовал недостающие до нашего определяющего наборы фичи из остальных, а Hope - просто первый результат этого процесса, который мы будем далее называть "хопизацией". ML как единый язык этот процесс не проходил, его прошли по отдельности по крайней мере три языка с "ML" в названии. + +В те времена такое взаимодействие языков, проектов и их авторов было бы затруднено, если бы участники находились далеко друг от друга. Они и не находились далеко. Исследовательская программа, историю которой мы будем писать, начиналась в Эдинбурге и соседнем с ним городе Сент-Эндрюсе, так что мы, наконец-то, нашли рамки которые нам подходят, пусть и географические, что, конечно, не лучший вариант, но что есть - то есть. + +Почти все языки, происходящие от первоначальной группы языков Эдинбургской программы, сохранили обсуждаемые свойства, а если мы сделаем еще шаг назад и рассмотрим то, от чего они произошли - назовем эту, предыдущую программу "ветвью обоих Кембриджей", то такой однородности всех происходящих от нее языков не будет. + +Мы пишем историю "Эдинбургской исследовательской программы". Но это звучит длинновато, так что далее мы обычно будем называть группу языков, которую мы выбрали для написания истории функционального программирования, просто "функциональные языки", как делали и наши великие предшественники. В этом смысле наша работа ничем не отличается от других работ по истории функционального программирования. +А в чем отличается? + +Для чего нужна эта работа и в чем ее отличия от других +----------- + +> История развития теории типов, которая в последствии привела к системе типов Standard ML, насчитывает более ста лет. +> Д. МакКвин, Р. Харпер, Дж. Реппи, История Standard ML [MacQ20] + + +Не то чтобы литературы по истории ФП было мало. Уже существуют как обзорные материалы по ФП вообще [Huda89] [Turn12], так и истории отдельных языков или их семейств [Hud07] [MacQ20], биографии исследователей [Camp85] [MacQ14]. Зачем нужна еще одна? + +### Лямбда-криптозой + +Когда, в очередной раз, не хватает десятков гигабайт памяти для компиляции кода на Хаскеле, естественным образом возникает вопрос: в каком же _смысле_ функциональное программирование существовало в каком-нибудь 1973-ем году? К сожалению, материалы по истории ФП обычно не уделяют этому особого внимания. Для историй функционального программирования в них часто слишком мало истории программирования. +Мы не ставим перед собой цели дать определение "программированию", но в этой работе предполагаем, что это процесс написания программ. И наши великие предшественники, в своих работах по истории функционального программирования, не особенно любят писать какого размера программы получались в результате этого процесса. +Более того, часто историк программирования уходит в такие глубины прошлого, про существование программирования в которых можно говорить только с большой натяжкой. Например, предыстория Standard ML начинается аж с 1874 года [MacQ20]. +Понятно, что в 1874 функционального программирования не было, но было ли оно, например, в 1974-ом? Какие программы к этому году были написаны на функциональных языках? Какие имплементации были доступны и для кого? До какого года ФЯ могло существовать только так же, как могло и в 1874-ом году: как нотация в книгах, тетрадях, на досках и так далее? +Например, часто утверждается, что ML появился в 1973 году, но что именно произошло в этом году? Упрощенно говоря, в этом году у Робина Милнера появилось желание писать интерактивный решатель теорем не на Лиспе. И в нашей работе мы покажем в каком году Милнер написал часть интерактивного решателя теорем на ML. Какая это была часть, сколько в ней было строк кода. Кто и через сколько лет написал интерактивный решатель теорем полностью на ML. И кто и когда написал интерактивный решатель теорем полностью на ML, имплементация которого, в свою очередь, и сама написана на ML. И сколько строк кода было в этой имплементации. +Между мечтой и возможностью большая разница и существенный временной интервал и мы считаем, что такая перспектива может быть полезной, если кто-то не согласен, что история чего-то начинается с мечты, и на ней же и заканчивается, ведь имплементация идеи тривиальна и не интересна. +Нельзя сказать, что вопрос применимости имплементаций ФЯ для программирования вовсе не поднимается, но он не особенно интересен нашим великим предшественникам. В некоторых обзорах перечисляют имплементации, которые авторы (обзора, но нередко автор обзора является и автором одной из этих имплементаций) считают "неигрушечными", "эффективными" [Huda89], "быстрыми" [SPJ87], но критерии не сформулированы четко, и вполне возможно, что у вас бы сложилось другое впечатление, если б вы узнали об этих имплементациях больше. У нас определенно сложилось другое впечатление. В таких списках через запятую перечислены компиляторы, которые компилировали себя и другие проекты в десятки тысяч строк вместе с компиляторами, которые этого не делали. +Поэтому мы постараемся установить, какого размера программы писали с помощью имплементаций ФЯ, какая была производительность у этих программ. + +Если что-то может помешать историку ФП все больше и больше углубляться в прошлое - то это название. Некоторые авторы так любят какое-то название, что продолжают использовать его для все новых и все менее схожих вещей. Синтаксисы многих функциональных языков в 80-х более похожи друг на друга, чем на свои версии с теми же названиями из 70-х. К счастью, первый раз назвали что-то ML очень давно, и нет препятствий для того чтоб начать историю ML с начала, или даже задолго до его начала. +К сожалению, многие любят называть одно и то же по-разному. Да, первый компилятор Хаскеля был получен всего за пару месяцев из компилятора Нехаскеля, что явно указывает на то, что уже была проделана большая работа, которую вполне обоснованно можно считать работой по созданию Хаскеля. Три из пяти первых компиляторов Хаскеля разрабатывались долгие годы до того, как появилась сама идея спроектировать этот язык. Увы, Нехаскель не назывался "Хаскель", так что ничего не поделаешь - в истории Хаскеля [Hud07] уделить этому больше нескольких строк нельзя. Да, авторы Хаскеля выбрали другой Нехаскель как основу для первого Хаскель-репорта. И этот Нехаскель похож на Хаскель 1.0 больше чем ML в 82-ом на ML в 84-ом. Извините, название было не "Хаскель" - наши великие предшественники не могут писать историю этого. + +Все это, в основном, последствия того, что наши великие предшественники писали в первую очередь истории идей. И они делали это не потому, что это легко. Одна из причин того, что мы не собираемся писать историю идей - проследить историю идей очень сложно. Так что мы пишем историю имплементаций в первую очередь, и только потом историю идей. +Для каждой идеи можно найти ту, от которой она произошла, и каждая тащит все дальше в глубины веков, там идеи до самого низа. Так историк функционального программирования и оказывается в 1874-ом году. История имплементаций легко решает эту проблему (решает даже слишком хорошо, но об этом позднее). +Идеи оставляют меньше следов, и следов менее удобных для историка. Имплементация оставляет после себя разные версии кода, отметки в нем, репорты, анонсы и описания релизов, из которых понятно что заработало и когда. +Идея оставляет после себя статью, которая может быть опубликована через годы и "приведена в порядок" каким-то стирающим историю способом. Что это значит? Обзор родственных работ в статьях документирует отношение идей, но не их историю. Влияние одних идей на другие может декларироваться, но на самом деле "повлиявшая" идея может быть обнаружена только после того, как идея на которую она "повлияла" была переоткрыта самостоятельно. +Бывает, что автор прямо говорит об этом. Например Ксавье Леруа пишет что переизобрел некоторые идеи из машины Кривина не зная о ней и добавил ссылки только после того, как другие люди обратили его внимание на сходство. Часть идей по имплементации Chez Scheme ее автор открыл самостоятельно, а потом только обнаружил их в VAX ML, а часть действительно позаимствовал. Но не все считают нужным сообщить об этом. Хуже того, такие свидетельства могут противоречить другим. Например, мнение авторов языка о том, на какие языки они повлияли может не совпадать с мнением авторов этих языков о том, какие языки повлияли на них. +Тут нет ничего удивительного, идеи о том, как делать языки программирования, нередко возникают независимо. Даже алгоритм Хиндли-Милнера - нетривиальная и точно специфицируемая идея - был независимо переизобретен, но никто не написал независимо два одинаковых компилятора. +Дополнительная сложность тут в том, что добавлять ссылки на статьи сильно легче, чем использовать код компиляторов, так что в графе получается слишком много ребер, если мы пишем историю идей. +Для объединения имплементаций в семейства мы будем использовать более редкие и надежно устанавливаемые отношения: общий код, общие авторы, использование одной для бутстрапа другой. + +### Лямбда-фанерозой + +Что представляет из себя наша история имплементаций? В первую очередь - это история компиляторов в код обычных машин, в меньшей степени - история интерпретаторов и специальных аппаратных имплементаций. Мы выбираем компиляторы для обычных машин как наиболее успешное направление имплементации ФЯ. Раз уж они дожили до наших дней (в отличие от специального железа) и больше применялись для написания нетривиальных программ (в отличие от специального железа и интерпретаторов) про них просто больше известно, есть о чем писать. С другой стороны, компилятор достаточно масштабный проект, чтоб их было не слишком много (в отличие от интерпретаторов), так что у нас получается вполне обозримый набор достаточно подробных историй, а не бесконечные списки с немногословными описаниями. Мы, конечно же, опишем и часть истории интерпретаторов и специальных машин в той степени, в какой это необходимо для понимания истории компиляторов, но не ставим перед собой цели сколько-нибудь полно осветить этот вопрос. + +Но и у истории имплементаций есть проблемы. История программирования не очень богата событиями, когда функциональные программисты не так уж много пишут нетривиальные программы. Что такое нетривиальные программы? Например, код компилятора этого или другого функционального языка, код интерактивного решателя теорем. +Планка не выглядит высокой, но не для ФЯ, конечно. Причем, не то что-бы был какой-то выбор того где должна находится эта планка, потому что кроме того как быстро работают микробенчмарки и как быстро компилирует компилятор написанный на каком-нибудь ФЯ, мало что можно узнать. +Можно прочесть про одни имплементации, что числа Фибоначчи вычисляются не сильно медленнее чем на C, и что на языке написан компилятор, компилирующий что-то за приемлемое время, а про другие прочесть то, что компилятор на них не написан, а производительность не так-то и важна. +Поскольку история программирования и имплементаций сильно сдвинута к концу истории идей, она плохо ложиться на типичную для истории идей структуру "прогресса" и "формаций", когда все начинается с Лиспа, продолжается строгими ФЯ и завершается ленивыми. Вместо этого у нас после периода, когда ничего не работает, наступает период, когда заработало все и везде. Так что нам нужно как-то дополнительно структурировать "еще не работает" и "уже начинает работать". +Нашу историю структурирует жизненный цикл имплементаций: +Если имплементация достаточно старая, она могла долгие годы существовать как имплементация языка, который не похож на то, что мы определили как ФЯ в предыдущей главе. Например, Chez Scheme была использована как бэкенд для имплементации языка, который мы считаем ФЯ - Idris 2 - примерно после 35-илетней истории (если не считать один эксперимент). +С течением времени, необходимые фичи в этот язык добавлялись, или для имплементации вовсе писался фронтенд для другого языка. Более новые имплементации уже могли с самого начала быть имплементациями ФЯ Эдинбургской программы. +Если имплементация существует достаточно долго, то она используется для бутстрапа другой имплементации, написанной с самого начала уже на этом ФЯ. Если имплементация недостаточно успешна, то этого может и не произойти. +У этого простого цикла "предыстория-хопизация-бутстрап" могут быть и другие осложнения, разветвления и т.д. но общая структура обычно видна. + +И последнее, но не наименее важное, история имплементаций ставит перед нами вопрос, ответ на который требует описывать и историю обычных машин и операционных систем, которую мы, разумеется, тоже будем описывать без каких-то претензий на полноту: +Если идея функционального программирования, как это выяснили наши великие предшественники, существовала столько же, сколько и программирование или даже дольше, то почему функционального программирования не было? Когда оно могло бы уже появиться? И что было тогда, когда функционального программирования не было? + +И, наконец-то, мы покончили с сильно затянувшимся предисловием к предисловию и у нас впереди сильно затянувшееся предисловие про сильно затянувшуюся эпоху, когда идея функционального программирования была, а функционального программирования не было, ведь первый компилятор функционального языка Эдинбургской исследовательской программы появился только в 1981-ом году. + +Ветвь обоих Кембриджей +====================== + +> Было сказано, в частности Морисом Уилксом, что этот проект был полным провалом и его не следовало и начинать. + Мартин Ричардс, Кристофер Стрейчи и Кембриджский компилятор CPL [Rich2000] + +> "Различные формы факториалов", рукопись 6 страниц, без даты + Каталог статей и писем Кристофера Стрейчи [Strachey] + + +История функциональных языков отделилась от истории всех прочих языков, когда в одном Кембридже задумали сделать практичный функциональный язык общего назначения, а затем в другом Кембридже удалось имплементировать два языка: один из них был практичным языком общего назначения, а другой - функциональным. + +Нужно заметить, что история языков с первоклассными функциями отделялась от истории всех прочих еще два или три раза, но Эдинбургская Исследовательская Программа произошла именно от этого, двойного кембриджского ответвления, которое называется так потому, что семейство языков разрабатывалось в основном исследователями, которые сначала поработали в одном Кембридже, а потом в другом Кембридже. + +В Кембридже +----------- + +До начала истории имплементаций ФЯ была история ошибочных представлений о том, что такая имплементация уже может быть написана. И одним из первых их носителей был Кристофер Стрейчи (Christopher Strachey). В начале 60-х он работал консультантом и в этом качестве участвовал в проектировании компьютеров и разработке ПО. По-видимому, с переменным успехом, но мы не изучали этот вопрос глубоко. Его единственным сотрудником и вторым программистом компании с января 1960 был Питер Ландин (Peter Landin), который часть рабочего времени (согласованно со Стрейчи) тратил на исследования, которые имеют непосредственное отношение к истории ФП [Camp85], а именно: формальную семантику ЯП, трансляцию ЯП в ЛИ и виртуальную машину для интерпретации лямбда-выражений [Land64]. +Знакомый Стрейчи Морис Уилкс (Maurice Vincent Wilkes) руководил кембриджской математической (позднее - компьютерной) лабораторией (University Mathematical (Computer) Laboratory, Cambridge). Вместе со Стрейчи Уилкс критиковал ALGOL 60 [Stra61] за отсутствие синтаксических различий между чистыми и прочими, рекурсивными и нерекурсивными функциями. В этой их работе появились первые наброски CPL [Rich13] + +### CPL + +Летом 62-го Уилкс пригласил Стрейчи работать в свою лабораторию для участия в разработке языка и компилятора для нового компьютера Titan (позднее Atlas 2 [TITAN]), который лаборатория должна была получить через два года [Camp85]. Переход Стрейчи из консультантов в академики плохо сказывался на его доходах, но это должна была скомпенсировать уникальная возможность поучаствовать в проектировании и имплементации опередившего свое время языка. Именно поучаствовать в проектировании и имплементации, а не спроектировать и имплементировать, потому что, забегая вперед, этого так и не произошло. Язык опередит и существующие в то время возможности его имплементировать. + +Первый пропозал CPL (Cambridge Programming Language) был написан Стрейчи, Девидом Барроном (David W. Barron) и Девидом Хартли (David F. Hartley) (которого Стрейчи при первой встрече за несколько лет до того отговорил имплементировать ALGOL 60 [Hart2000]) в августе 62-го. Но уже осенью, по инициативе Баррона и Хартли [Hart2000] [Hart13], начинается сотрудничество с университетом Лондона (London University Computer Unit), который ожидал старшую версию компьютера из той же линейки - Atlas [ATLAS]. К авторам присоединяются Эрик Никсон (Eric Nixon) и Джон Бакстон (John N. Buxton), а CPL становится Combined Programming Language. О Питере Ландине вспоминают как о участнике разработки языка [Rich2000], хотя официально он над языком не работал. +В феврале 63 готова статья “The Main Features of CPL” [Barr63]. +Не смотря на схожесть компьютеров, каждый университет пишет свою имплементацию CPL [Rich13]. Лондон - более традиционную, а Кембридж - более экспериментальную, основанную на идеях Ландина, видимо о компиляции через промежуточный язык "аппликативных выражений". +Но с октября 63-го обе команды имплементаторов совещаются каждый месяц. +Встречи комитета частые, долгие и запоминающиеся [Rich2000]. +В Кембридже имплементацию планируют закончить в начале 64-го, ко времени получения Titan. + +Но Стрейчи все больше переключался на формальное описание семантики, которым заинтересовался под влиянием Ландина. Баррон, один из самых активных участников сначала, тоже переключился на другие дела, хотя какое-то время участвовал в совещаниях, но компилятором уже не занимался. Над компилятором работали аспиранты, года по три, причем последний из трех в основном над своей диссертацией [Rich13] [Hart13]. + +Летом 64-го Уилкс, недовольный медленной работой над компилятором, назначил руководителем команды имплементаторов Дэвида Парка (David Park), работавшего в Оксфорде и МТИ и имевшего опыт имплементации ЯП. Это не помогло. Вклад Стрейчи все сокращался, ему было не интересно работать над практичной имплементацией, да она и не получалась: предварительная версия компилятора работала медленно, в чем обвинили новаторский подход. +Только вот лондонская имплементация тоже не была завершена, удалось имплементировать только непрактичный компилятор подмножества CPL. +Фактически, над обеими имплементациями более-менее постоянно работало по одному человеку: компилятор в Лондоне писал Джордж Кулурис (George Coulouris) [Coul], а кембриджский компилятор писал Мартин Ричардс (Martin Richards) - важный герой нашей истории, к которому мы еще вернемся. +В 65 Стрейчи уходит из Кембриджа в МТИ на год, а потом в апреле 66-го в Оксфорд. Дэвид Парк тоже уходит из Кембриджа работать со Стрейчи в Оксфорде. +Все это время, впрочем, Стрейчи работает над описанием CPL, что идет не очень хорошо, потому что он годами может взаимодействовать с прочими авторами только по переписке. +В июне 66 комитет назначает Стрейчи редактором мануала, после чего большинство участников уходят из комитета официально потому, что уже и так занимались другими вещами. +Этот мануал не был опубликован, но циркулировал как самиздат [Stra66b]. Проект в Кембридже завершился в декабре 66-го [Rich13]. Тогда же, в декабре 1966, состоялась последняя встреча комитета, на которой договорились прекратить работу. [Hart13] +Джордж Кулурис в Лондоне написал компилятор подмножества CPL под названием CPL1 к осени 67-го [Coul68]. Этот компилятор описывают как более-менее законченный, но компилирующий только небольшие программы и непригодный для практического использования [Hart13]. Кембриджский компилятор описывают как непрактичный в лучшем случае [Rich13] или как вовсе не законченный [Hart13], как полный провал по мнению директора лаборатории [Rich2000]. Те, кому не нравится считать CPL "полным провалом", находили утешение в том, что CPL хоть и был неудачей по обычным стандартам, вроде известности, соблюдения сроков и эффективности имплементаций, но повлиял на другие языки [Camp85] [Rich2000]. Утешение в этом будут находить и следующие поколения дизайнеров и имплементаторов ФЯ. +Участники проекта в своих воспоминаниях объясняют провал проекта потерей интереса: Баррон и Хартли занялись ОС для Titan/Atlas 2, а Стрейчи и Парк - формальной семантикой ЯП [Hart13]. Только вот это была не первая и не последняя неудачная попытка сделать компилятор ФЯ. Что если не прогресс остановился из-за потери интереса, а интерес был потерян из-за остановки прогресса по другим причинам? К выяснению того, что это были за причины мы еще вернемся. +CPL задумывался как единый универсальный язык общего назначения [Hart13], но комитет, не смотря на практически полное согласие со Стрейчи [Hart2000] [Rich2000], мало что функционального специфицировал, а имплементировано было еще меньше. И CPL распался на диалекты: тот, что удалось имплементировать в Кембридже, тот, что удалось имплементировать в Лондоне. Для написания имплементации в Кембридже в 65-66гг. выделили самое простое подмножество из всех - практически виртуальную машину [Rich13]. Наконец, самый известный и влиятельный CPL - тот, что имплементировать было непонятно как, да и не стали пытаться. + +### псевдоCPL + +Летом 63-го года Стрейчи, Баррон и Филип Вудвард (Philip Woodward) из Королевского института радиолокации прочли несколько лекций по программированию с примерами на "CPL" [Wood66] [Stra66]. Эти лекции были изданы в сборнике в 1966 [Fox66]. + +Важно уточнить, что код и в этих статьях, и даже в статье, называющейся "Основные фичи CPL" [Barr63] не является кодом на какой-то версии CPL, которая была описана комитетом и тем более имплементирована. Интересно, что и автор лондонской имплементации Кулурис посчитал нужным явно написать, что нет, написать примеры с помощью CPL из мануала [Stra66b] или имплементированного в Лондоне CPL1 нельзя [Coul68]. + +Это был гораздо более впечатляющий CPL, который был настолько хорошим языком, насколько Стрейчи хотел, чтоб он был, без всяких рамок которые могла накладывать имплементация или даже непротиворечивость самого языка, который мы будем называть _псевдоCPL_. +Стрейчи просто писал `map` и `foldr` (который называл `Lit`) + +```haskell +let Map[f, L] = Null[L] -> NIL, + Cons[f[Hd[L]], Map[f, Tl[L]]] + +Map[F, (1, 2, 3)] where F[x] = x * y where y = 1 + +let Lit[F, z, L] = Null[L] -> z, + F[Hd[L], Lit[F, z, Tl[L]]] + +Lit[F1, 1, (1, 2, 3)] where F1[x,y] = x * y +``` + +не зная как его можно имплементировать и можно ли вообще. +Как, сделать так, чтоб в коде типизированного языка не было аннотаций типов? Стрейчи даже не написал, что знает как это сделать, но алгоритм не поместился на полях. Что поделать, выхода у будущих имплементаторов ФЯ не было, пришлось придумать способ и не один. +И тот, кто хотел аннотировать типы и рекурсию и тот, кто не хотел, могли помнить о псевдоCPL как о языке, который им нравился, и который они хотели бы повторить. Со временем, так научатся делать и с имплементированными языками. Но до этого пока далеко. + +Эта идея не была новой. За три года до того уже выходила статья с примерами кода на языке, название которого совпадало с названием языка, который в это время имплементировался. Разумеется, эта имплементация также не поддерживала код из статьи: + +```haskell +maplist[x; f] = [null[x] -> NIL; + T -> cons[f[car[x]]; maplist[cdr[x]; f]]] + +maplist[(1,2,3); λ[[x]; x + y]] +``` + +(пример в статье отличается отсутствием вызова car) + +Мечты о ФП коде в лекциях Стрейчи и д.р. - итерация похожих мечт из этой статьи про Лисп [McCa60], которой оказалось для многих достаточно, чтоб считать Лисп первым ФЯ. +Псевдокод не похож на Лисп? Ну да, Лисп-первый-ФЯ не состоялся. Планировалось, что Лисп будет выглядеть так. Это M-выражения, которые не были ни имплементированы, ни даже специфицированы потому, что имплементация фронтенда все затягивалась, и лисперы привыкли писать на промежуточном коде - S-выражениях. В результате, написание транслятора из M в S выражения было заброшено. Но для имплементации ФЯ важнее то, что не заработала как надо передача функций в функции [McCa78]. К этой проблеме мы еще вернемся. + +Можно было бы сказать, что основная новация CPL-ной итерации в том, что работать со списками теперь собирались в типизированном языке. Но сложно назвать инновацией одно только намерение это сделать. Ни во время чтения этих лекций, ни во время их издания еще толком не понимали как такой код типизировать. Если посмотреть на расширения ALGOL 60, которые в то время имплементировали в Королевском институте радиолокации [Wood66] + +```pascal +list procedure Append(x, y); list x, y; +Append := if Null(x) then y else + Cons(Hd(x), Append(Tl(x), y)) +``` + +то видно, что тип списка просто `list` без параметра. Но во времена ALGOL 60 не знали и как функции нормально типизировать, там и тип функции параметризован только по возвращаемому типу, например `real procedure`. Правда, в 1967-ом кое-какие идеи на этот счет у Стрейчи появились. + +К сожалению, не на всех производит впечатление псевдокод в статьях, многие важные имплементаторы ФЯ вспоминают, что на них произвели впечатление работающие вещи. [Turn12][TODO Леннарт Августссон, Марк Джонс, Ксавье Леруа] Но эти работающие вещи для них сделали те, кто просто не мог смириться с тем, что такой фантастический язык остается фантастическим. И позднее они придумали и имплементировали языки, на которых можно писать `map` именно так, а можно и лучше. + +И писать так `map` они хотели достаточно долго, чтоб атавизмы этого стиля вроде `head` и `tail` попали в ФЯ и дожили до наших дней, хотя сам стиль устарел еще до первой практической имплементации. + +### Следующие 700 непопулярных языков + +> <Я> надеялся <...> сдержать создание сомнительных языков программирования, называемых (как группа) OWHY, что означает "Or What Have You". Никто не понял шутки, и попытка была обречена. +> Дана Скотт, A type-theoretical alternative to ISWIM, CUCH, OWHY, 1993 [Scot93] + + +CPL разрабатывался в первую очередь как практичный императивный ФЯ с добавленными функциональными фичами, в реальность которого верили и имплементацию ждали [Wood66], но фантазии про который сильно опередили его имплементацию. +Параллельно с ним, в обоекембриджской программе развивался второй язык - ISWIM, который двигался в противоположном направлении. + +#### Следующие 700 исписанных листов + +ISWIM (или YSWIM в черновиках [Landin]), что означало "If you See What I Mean", описывал в серии статей Питер Ландин. +Сначала как чисто функциональный псевдокод для описания спецификаций, транслируемый в лямбда-исчисление [Land64]. Затем как императивный псевдокод, транслируемый в лямбда-исчисление, расширенное добавлением мутабельности и средствами захвата продолжения для имплементации произвольного потока исполнения [Land98]. И все еще для описания спецификации, теперь уже ALGOL 60 [Land65a] [Land65b]. +Наконец, как "попытку сделать систему общего назначения для описания одних вещей в терминах других вещей, которая может быть проблемно-ориентированной при подходящем выборе примитивов." [Land66]. Неостановимую фабрику ФЯ, следующие 700 языков программирования. +Эти встречные движения привели к тому, что псевдоCPL и ISWIM, а точнее их функциональные подмножества оказались примерно в одном и том же месте и разбор отдельных фич для каждого был бы слишком повторяющимся, так что собран в одной главе. +На ISWIM написаны, по видимому, более крупные фрагменты ФП псевдокода, чем на CPL. +Самый крупный из них - парсер ALGOL 60 в абстрактное синтаксическое дерево, его описание, компиляция этого дерева в императивные аппликативные выражения IAE. Всего ~500LOC функционального псевдокода [Land65b]. +Предполагалось, что такое описание семантики можно "исполнять" вручную, с помощью ручки и бумаги, но на практике это, конечно, едва ли осуществимо. +Такой подход к описанию семантики - как наивного интерпретатора написанного на ФЯ - имеет смысл только при механизации этой "семантики", когда интерпретатор действительно работает, как у авторов Clean в восьмидесятые или, например, у Россберга в десятые. +Так что, по крайней мере попытка имплементации была неизбежна. И именно от этой неизбежной механизации "семантики" останется литература по имплементации ФЯ, но о ней позже. +Сложно точно установить что на что повлияло в случае функциональных подмножеств CPL и ISWIM. +Ландин написал статьи в первом приближении еще во время работы у Стрейчи [Land65b], т.е. в 60-62 годах [Camp85], прочитал по ним лекции в 63-ем [Fox66], но опубликовал позднее, когда работал в Univac подразделении Sperry Rand и МТИ. +Функциональное подмножество ISWIM может быть старее CPL, но выглядит современнее и с меньшими странностями. Но современнее выглядит просто то, что больше понравилось авторам современных языков. +Очередная функция `map`, на этот раз на ISWIM 65 [Land65b] + +```haskell +rec map f L = null L -> () + else -> f(h L) : map f (t L) + +map f (1, 2, 3) where f x = x * y and y = 1 +``` + +В Univac к ISWIM проекту присоединился Уильям Бердж (William H. Burge), описывающий типизированные аппликативные выражения TAE [Burg64] [Burg66]. + +#### Следующие 700 вариаций семи фич + +CPL разрабатывался на основе ALGOL 60 [Rich13] [Hart13]. В описании ISWIM ссылаются на псевдоLISP 60-го года и ALGOL 60 [Land66]. +"Основа Алгола" в это время - лексическая видимость и поддержка рекурсии. То, что обычно авторы языков и заимствовали из Алгола. Для заимствования этих идей не нужно знать ALGOL 60, если знать про лямбда-исчисление, но многие изобретатели ФП ссылаются на влияние и ЛИ и ALGOL 60. Почему так происходило нам не понятно. Свойства ALGOL 60 по всей видимости не являются изобретенными независимо от ЛИ, а являются следами частично успешной борьбы меньшинства комитета за то, чтоб сделать ALGOL ФЯ [Naur78]. Эта борьба не закончилась на ALGOL 60 и к ней мы еще вернемся. Возможно, ссылающимся на ALGOL была важнее доказанная практикой возможность имплементации. +Синтаксические решения из ALGOL 60 в ISWIM и CPL в основном не попали, о некоторых исключениях - ниже. Не попали и непопулярные, среди заимствующих из Алгола, фичи вроде передачи параметров по имени. +CPL - язык со множеством императивных фич, которые оказали серьезное влияние на мейнстрим. Также, со множеством странных фич. Вроде двух разновидностей имен - однобуквенных из строчной буквы и нуля и более праймов и многобуквенных, начинающихся с заглавной и могущих включать в себя числа. Для чего? Чтоб умножение могло быть не оператором, а отсутствием оператора. + +``` +Square[x] = xx +``` + +Но не все странные фичи CPL остались в CPL, некоторые присутствовали и в ISWIM и оказали влияние на Эдинбургскую исследовательскую программу. + +##### больше не фичи + +Этот класс странных фич как раз демонстрирует происхождение Эдинбургских языков от CPL/ISWIM, странными они стали только исчезнув из этих языков в 80-х и 90-х годах. Какой еще язык претендует на то, что от него произошли языки эдинбургской программы, и эта претензия более-менее правдоподобна без учета этих странных фич? Не все сразу! + +###### отсутствие лямбд + +Отсутствие лямбды довольно неожиданная особенность для ФЯ, и за пределами круга разработчиков CPL/ISWIM идея захватила только еще одного автора ФЯ. +И не смотря на то, что это автор нескольких влиятельных языков, и многие имплементаторы ФЯ хотели делать языки как у него, всего этого влияния не хватило для того чтоб популяризировать ФЯ без лямбд. И едва ли для кого-то такой результат покажется неожиданным. +Нам не удалось установить почему лямбда не попала в CPL/ISWIM. Возможно, это наследие ALGOL 60. Возможно потому, что авторы увлеклись эквивалентной конструкцией, которую считали одной из основных фич обоекембриджских языков и хотели использовать только ее. Эта фича - + +###### выражение `where` + +Что делать, если лямбд нет, а хочешь написать + +```haskell +Quad [a, b, \x -> G[x, y]] +``` + +? Нужно писать [Barr63] + +```haskell +Quad [a, b, F] where F[x] = G[x, y] +``` + +Выражение `where` - полный аналог современного `let` `in`, а не производная и гораздо более новая фича `where`-как-часть-других-конструкций, известная по Haskell. +В наши дни выражение `where` полностью заменено на `let` и `where` как часть других конструкций. И хотя уже в 66-ом в описании [Stra66b] обсуждается какая-то смутная неудовлетворенность `where`-выражением и неопределенные планы сделать `where`-как-часть-декларации, фича существовала десятилетиями. Причем `where`-выражение не осталось только в мертвых языках, не попав в современные, а успело побывать в дизайне и имплементациях Haskell, Standard ML и Caml. Редкий пример фичи, которая попала в эти языки и не осталась в них навсегда. +Авторы CPL не претендовали на изобретение `where`, утверждая, [Barr63] что позаимствовали конструкцию из калькулятора GENIE [Ilif61], но не в точности. В GENIE вместо ключевого слова `where` использовалась запятая. + +###### тернарный оператор ветвления + +попал в мейнстрим, но в ФЯ вытеснен конструкциями из ключевых слов и частью других конструкций - гардами, на синтаксис которых он, вероятно повлиял, но не так сильно как синтаксис ветвления в псевдолиспе из [McCa60], возможно через синтаксис ISWIM 64. + +```haskell +let rec fact(n) = n = 0 -> 1, n * fact(n-1) +``` + +В первом коде на ML (LCF 77) почти всегда используется тернарный оператор, а не `if then else` конструкция. + +##### пока еще фичи + +###### конструкция `let` + +В CPL `let` это часть синтаксиса деклараций, и `where` определяется через декларацию в возвращающем ссылку на значение блоке [Stra66b]. + +```haskell +E where D === ref of § let D; result is E § +``` + +Но в ISWIM `let` - выражение [Land65a], вводится как сахар для `where` [Land66] + +```haskell +let x = M; L === L where x = M +``` + +###### аннотация рекурсии + +Одна из первых идей для CPL еще из критики ALGOL 60 [Stra61], которая повлияла на языки Эдинбургской программы - аннотация рекурсии. В своей конечной и наиболее популярной форме - с помощью ключевых слов `rec` и `and`. +Для ALGOL 60 такие аннотации рассматривали, но отклонили с небольшим перевесом [Naur78] + +###### отсутствие аннотаций рекурсии + +Не смотря на то, что Стрейчи критиковал ALGOL 60 за отсутствие аннотаций рекурсии, в работах, в которых "CPL" используется как псевдокод, он эти аннотации обычно пропускал [Stra66]. +Так что не важно, есть в вашем ФЯ аннотация рекурсии или нет - избежать влияния CPL не удалось. Как и влияния ALGOL 60, разумеется. + +###### отсутствие аннотаций типов + +Не аннотировать типы в псевдокоде легко, в имплементированном языке уже сложнее. Самое сложное, конечно, когда типы есть и их не надо аннотировать, но можно сделать и проще, если аннотировать нечего или если замести аннотации куда-нибудь не на очень видное место. В каждом из трех первоначальных языков Эдинбургской программы будет один из трех основных способов: хороший, плохой и отвратительный. + +###### обозначение блоков отступами + +В ISWIM [Land64] [Land65a] + +```haskell +let x = M; L +``` + +то же, что и + +```haskell +let x = M +L +``` + +###### "одновременные"/"параллельные" декларации + +Зачатки паттерн-матчинга, пока без вложений и ветвлений. + +В ISWIM [Land64] + +``` +(u,v) = (2*p + q, p - 2*q) +``` + +В псевдоCPL [Stra66] и ISWIM 65 [Land65b] + +```haskell +let u, v = 2*p + q, p - 2*q +``` + +##### обсуждавшиеся фичи + +За считанные месяцы до окончательного прекращения всех работ по CPL Стрейчи делает доклад [Stra67] в котором рассуждает о системе типов, о полиморфизме и различиях между параметрическим и ad-hoc, о том как, возможно, будут сделаны составные типы и что будет, возможно, решено делать ли в CPL параметрические типы и если да, то как. Никто это не решал и не имплементировал, но, благодаря этим анонсам и рассуждениям, Стрейчи стали считать одним из изобретателей полиморфизма. + +###### параметрический полиморфизм + +В докладе Стрейчи наконец-то представляет тип всех тех псевдокодовых `map` [Stra67]: + +``` +(α ⇒ β, α list) ⇒ β list +``` + +Лекции были опубликованы только в 2000 году, так что префиксная форма полиморфных типов может быть более поздней нотацией "как в ML". Но, по видимому, происходит от постфиксных аннотаций вроде `string ref`, которые были имплементированы [Coul68] и вероятно происходят от типов из ALGOL 60. Таких как `integer array`. +В типизированном ISWIM Берджа [Burg64] эта сигнатура выглядела бы + +``` +map ∈ (A -> B) -> (A-list -> B-list) +``` + +но именно такой сигнатуры мы в его работах не видели. +Эдинбуржцы ссылаются на работу Стрейчи по полиморфизму [Miln78], но в такой тривиальной форме идея параметрического полиморфизма была, по всей видимости, переоткрыта независимо. Например авторами языка CLU, на который эдинбуржцы также ссылаются. Возможно, переоткрыта даже в рамках Обоекембриджской программы. +Интересно, что автор языка CLU Барбара Лисков (Barbara Liskov) пишет [Lisk93], что параметризованные типы в CLU появились из идеи сделать пользовательские типы вроде упомянутых уже выше массивов или функций ALGOL 60. Т.е. как очевидный шаг, которому не уделяется особого внимания в статьях по CLU и его истории. В отличие от ограниченного "интерфейсами" параметрического полиморфизма, который описывается как интересная и сложная идея и проблема, потребовавшая долгие годы для дизайна и имплементации. +И для интересных проблем, касающихся полиморфизма, от обоекембриджцев решений эдинбуржцам не осталось. + +###### Изобрел ли Ландин АлгТД? + +Тернер утверждает, что да [Turn12], и на первый взгляд он прав. Уже в статье [Land64] 64-го года и, вероятно, в лекциях для летней школы 63-го года [Fox66] Ландин использует неформальную нотацию для определения структур данных: + +``` +A list is either null + or else has a head (h) + and a tail (t) which is a list. +``` + +Описание у Ландина достаточно неформальное, чтоб не связываться с проблемами не изобретенного еще как следует полиморфизма и вообще избегать указывать что представляет из себя поле `head` (`h` там не параметр, а сокращенное имя поля). Бердж описывает список как параметрический [Burg64], или, может быть, как конвенцию по именованию списков + +``` +An A-list is either null and is a nullist +or has a head (or h) which is an A and a tail +(or t) which is an A-list +``` + +Если на первый взгляд Тернер прав, то на второй и следующие взгляды сходство уже не так очевидно. Такая декларация объявляет не конструкторы АлгТД, которые могут использоваться и как паттерны. Паттерн-матчинга в ISWIM еще нет. Декларация объявляет предикаты и селекторы, имена которых указывает программист, а также конструкторы, имена которых формируются по правилам из имен предикатов и имени всей структуры: + +``` +null(constructnullist()) = true +null(constructlist(x, L)) = false +h(constructlist(x, L)) = x +constructlist(h L, t L) = L +``` + +В данном случае имя для `cons` вообще не написано (понятно, что такой предикат избыточен, хватит и `null`), но в более поздних статьях списки определены с `cons` [Land66]. +Ладно, паттерн-матчинга нет, но хотя-бы общая идея сумм произведений-то была изобретена? Вроде бы, со вторым, более узким определением Тернер прав. Идея сумм произведений ясна. +Но нам эта идея ясна потому, что мы уже знаем АлгТД как суммы произведений. Посмотрим на более сложную структуру, например описание абстрактного синтаксического дерева ISWIM [Land66]: + +``` +an aexpression (aexp) is + either simple, and has + a body which is an identifier + ... + or conditional, in which case it is + either two-armed, and has + a condition, which is an aexp, + and a leftarm, which is an aexp, + and a rightarm, which is an aexp, + or one-armed ... +``` + +Так, что тут у нас, сумма произведений сумм произведений ... +Нотация для псевдокода, для которого пока особо не придумывали как объявлять типы, а декларации АлгТД должны ведь и типы объявлять. И суммы произведений позволяют это удобно делать. А нотация Ландина - не позволяет, по крайней мере в неизменном виде. В современном ФЯ это выглядело бы как-то так: + +```haskell +data AExp + = Simple { body :: Identifier } + ... + -- а как называется тип с конструкторами TwoArmed и OneArmed? + | Conditional ( TwoArmed { condition, leftArm, rightArm :: AExp } + | OneArmed ...) + ... +``` + +Если посмотреть на то что получилось, когда обоекембриджцы попытались перейти от нестрогого описания к чему-то более точному и типизировать это, то тут-то появляются уже серьезные основания сомневаться в том, что суммы произведений уже были изобретены. +Отступив от бесконечно вложенных сумм и произведений слишком далеко назад они получили отдельные декларации для произведений и для сумм. Или не отступили, а просто позаимствовали эту систему из другого языка, к которому мы еще вернемся. +Ричардс вспоминает, что авторы CPL много обсуждали как сделать композитные типы но так и не остановились ни на чем до того, как он покинул проект [Rich13]. Этот процесс, по всей видимости, не документирован. Известен только конечный результат из того самого доклада Стрейчи [Stra67], в котором он рассказывал про полиморфизм. + +``` +node Cons is LispList : Car + with Cons : Cdr + +node Atom is string : PrintName + with Cons : PropertyList + +element LispList is Atom + or Cons + +``` + +(да, тип и имя поля на неправильных сторонах `:`) + +Декларация вводит три новых типа `Cons`, `Atom` и `LispList` +и их конструкторы с селекторами. +`element` объявляет не тип сумму, а или-тип, конструктор `LispList` перегружен, параметр может принимать и объекты типа `Atom` и `Cons`. Тип, а не имя конструктора определяет что за значение конструируется. +Конструктор для пустого списка не определен потому, что в этом пропозале для CPL есть специальные конструктор `NIL` и предикат `Null`. Да, для того чтоб делать то, что обожают делать в мейнстримных языках, а вот в ФЯ Эдинбургской программы - не особенно. +Схожая "одноуровневая" система будет и в ISWIM. Далее мы увидим, что один из изобретателей ПМ по АлгТД уже в Эдинбурге понимал типизированную версию Ландинской нотации так же, как и авторы CPL. + +### Следующие 700 не самых быстрых имплементаций + +> Мы здесь рассматриваем возможность, а не практичность. Тем не менее, то, что написано ниже, может быть принято за отправную точку для разработки эффективной имплементации. +> Питер Ландин, ЛИ подход [Land66b] + +Основная литература по имплементации ФЯ от обоекембриджской программы осталась от ISWIM - языка спецификации, который предполагалось "выполнять" с помощью ручки и бумаги. Это ставило реальную цель. Цель была достигнута и первые доклады сделаны Ландином в 63-ем и статья опубликована в 64-ом [Land64]. +CPL был амбициозной попыткой имплементировать ФЯ общего назначения, но имплементация функциональной части не особенно продвинулась. +Про имплементацию CPL в Кембридже мало что опубликовано. Раз уж попытка закончилась неудачей, публиковать было нечего. +Мы бы не хотели создать впечатление, что цель Ландина была скромной, или что ее достижение было незначительным результатом. Цель была просто реалистичнее, чем цель CPL-щиков. Которые планировали имплементировать за два года ФЯ не только на самом этом ФЯ, но и как единственный язык для новой машины. Единственный потому, что должен был подходить для любых задач. +Сомнительно, что такую цель кто-то достиг бы и сегодня, при том, что мы знаем как делать ФЯ. А в 62-ом не знал никто. Но, правда, были уже две школы ФЯ-строения, которые либо не знали, что они не знают, либо не очень и хотели делать ФЯ. Причем не знали/не хотели они разные вещи. К этим направлениям недо-ФП-мысли мы вернемся позже. +Ландин всего-навсего был первым, кто придумал работающую идею. Вернее потенциально работающую. В предисловии мы определили практическую имплементацию как компилятор, способный компилировать хотя-бы компилятор ФЯ или что-то схожее по сложности. И наработки Ландина никогда не достигли этого уровня практичности. Но базирующиеся на них - достигли. +До Ландина механизация вычисления лямбда-выражений, которые действительно вычисляются как лямбда-выражения, была только в форме переписывания последовательностей символов или деревьев, как, например, оптимизатор компилятора переписывает код [Gilm63]. +Ландин описал стековую машину, что гораздо практичнее, но не без проблем. +Стековая машина - это то, что до Ландина уже позволяло имплементировать язык с рекурсивными функциями [Dijk60] первого порядка, и даже решить половину проблем с функциями высшего порядка, а именно передавать функции в функции [Rand64]. +Оставалось решить проблему с возвращением функций из функций. +Для этого Ландин использовал замыкания, сам термин введен им. Он, правда, не изобрел замыкание как структуру данных. Похожие структуры из ссылки на функцию и полей для её свободных переменных использовались, только на стеке, для имплементации передачи аргументов по имени в ALGOL 60 и назывались PARD [Dijk62]. Ландин применил их для возврата функций из функций, аллоцируя в куче со сборщиком мусора, который к тому времени уже применили в Лиспе [McCa60]. +У Ландина получилась машина с четырьмя регистрами, названная SECD по именам регистров: Stack, Environment, Control, Dump. +C - программа, список аппликативных выражений, которые машина редуцирует. +S - стек, нужен для имплементации функций. +D - хранит снапшот полного состояния машины (S,E,C,D) и нужен для имплементации лямбд. +E - окружение. Нужно для того, чтоб у лямбд могли быть свободные переменные. +Первая опубликованная версия SECD-машины имплементировала лямбда-исчисление, в которое транслировалась чисто функциональная версия ISWIM [Land64]. +В 64-ом Ландин расширил язык, добавив к ЛИ присваивание и операцию захвата продолжения. Это расширенное ЛИ он назвал IAE - императивные аппликативные выражения, а новую версию SECD-машины - sharing machine [Land65a]. Для имплементации захвата продолжения в этой машине добавлена еще одна разновидность замыкания "программа-замыкание", которое добавляет к функции не только окружение E, но все состояние машины D. +Версия статьи про SECD [Land64] 66-го года в сборнике [Fox66] уделяла больше внимания имплементации SECD с помощью компьютера, а не ручки и бумаги [Land66b]. +Понятно, что имплементация рекурсии с помощью стека - не самый практичный способ имплементировать ФЯ. Обязательное использование сборщика мусора, практичной имплементации которого еще не существовало, было даже большей проблемой. Но эти проблемы были решаемы, и были решены позже, что мы в дальнейшем рассмотрим подробнее. +Пытались ли имплементаторы CPL использовать идеи Ландина? В воспоминаниях участников упоминается, что для имплементации используются "аппликативные выражения" [Camp85] как у Ландина. Но это скорее всего не означает трансляции в лямбда-исчисление. В описании имплементации нефункционального языка BCPL [Rich69] тоже говорится об аппликативных выражениях, но в данном случае это точно не лямбда - просто промежуточное представление в виде дерева. +Во время работы в Univac Ландин и Уильям Бердж имплементировали прототип SECD [Land66] для Univac 1107 [Burg64]. Эта попытка имплементации по всей видимости не была успешной, потому что от нее осталось только пара упоминаний в статьях и перечне документов из личного архива Ландина [Landin]. +И Ландин и Бердж предпримут еще по одной попытке, но уже не в Univac. + +В другом Кембридже +------------------ + +Возможно, что вклад CPL в историю языков программирования пока не выглядит выдающимся. Но, как это не удивительно, уже описанного хватило для того, чтоб впечатлить некоторых участников Эдинбургской исследовательской программы. Чтобы впечатлить остальных, а сверх того, еще и сделать CPL непосредственным предком многих популярных современных языков, понадобились еще и труды исследователей и имплементаторов в другом Кембридже. Точнее, в основном, одного имплементатора - Мартина Ричардса. + +### Мартин Ричардс + +Мартин Ричардс (Martin Richards) с 1959 изучал математику в том самом Кембридже. В октябре 1963 он приступил к работе над диссертацией, повстречался со Стрейчи, и его затянуло в CPL проект [Comp17]. Стрейчи не мог быть его научруком официально, и им стал Баррон, а после того, как тот потерял интерес к CPL через год - Дэвид Парк [Rich2000], интересы которого тоже со временем поменялись. +Три года, до декабря 1966, Ричардс был практически единственным человеком в Кембридже работавшем только над CPL, занимаясь имплементацией его минимального подмножества Basic CPL, фактически виртуальной машины, которое должно было быть использовано для имплементации полнофункционального компилятора. Который, как мы помним, не имплементировали. +Вырвавшись из трясины обреченного проекта, которым никто кроме него не занимался, Ричардс отправился в МТИ. +Перед этим он заверил Стрейчи, что уж там-то он развернет работу над имплементацией CPL по настоящему. +Или, по крайней мере, Стрейчи заверял что план был именно такой [Strachey]. Еще осенью 67-го он рассказывал в докладе [Stra67], что портабельный компилятор CPL скоро будет готов. +Казалось бы, после того как, брошенный своими научруками, Ричардс провалил написание кембриджского компилятора, можно ли было ждать от него успехов в МТИ? Но успехи не заставили себя ждать. + +### BCPL + +> BCPL - это просто CPL из которого удалены все сложные части. +> Мартин Ричардс, Как BCPL эволюционировал из CPL [Rich13] + +> Ряд синтаксических и лексических механизмов BCPL элегантнее и последовательнее, чем в B и C. +> Деннис Ритчи, позаботившийся об этом [Ritc93] + +В МТИ Ричардс работал вместе с Ландиным под началом Джона Возенкрафта (John "Jack" Wozencraft) [Rich2000] с декабря 66-го по октябрь 68-го. [Rich2000] [Comp17] +В МТИ Ричардс написал компилятор Basic CPL на AED-0 [Rich2000], похожем на ALGOL 60 языке, но с указателями и прочими фичами для системного программирования [Ross61]. Затем он написал компилятор BCPL на BCPL. Начальная версия компилятора в 1K строк была готова в начале 67-го года [Rich2000] для CTSS на IBM 7094 [Rich13]. За следующие 10 лет компилятор вырос до 5K строк и еще столько же строк тулинга вроде отладчика и редактора [Atki78]. +Получившийся язык не был уже минимальным подмножеством CPL или ВМ для его имплементации. Это был достаточно большой язык со многими фичами позволяющими делать одно и то же разными способами - управляющими структурами и т.д. [Rich74], который сам использовал стековую виртуальную машину Ocode для удобства портирования компилятора [Rich69]. BCPL отличался от CPL тем, что все что в CPL было сложно имплементировать в BCPL не попало. +И все что нам, как историкам ФП, интересно - было сложно имплементировать. Понятно, что в BCPL нельзя было вернуть замыкание, и, соответственно, не требовался сборщик мусора. Но даже ограниченной ФП-функциональности ALGOL с передачей замыкания вниз по стеку не было. Свободные переменные могли быть только статическими [Rich74]: + +```f# +let a, b = 1, 2 +// Так нельзя +let f (x) = a*x + b +``` + +```f# +// Только так можно +static $( a = 1; b = 2 $) +let f (x) = a*x + b +``` + +Ричардс оставил в BCPL один тип - слово. Не стал имплементировать выражение `where`, опередив на четверть века моду на выкидывание выражения `where` из ФЯ. Убрал аннотации рекурсии. +Не смотря на все эти урезания, BCPL все равно сложный язык, со вложенными функциями, возвращающими значения блоками-выражениями. Компилятор BCPL строил абстрактное синтаксическое дерево [Rich69] и потому имел серьезные для того времени требованиями к памяти. Поэтому Кен Томпсон не мог его использовать на той машине, на которой писал Unix и сделал еще более урезанную версию языка без всех этих вложенностей: B [Ritc93]. Томпсон сделал и некоторые изменения не связанные с экономией памяти. B - один из ранних примеров декембриджизации. Это процесс изменения синтаксиса происходящего от обоекембриджской ветви на любой другой, лишь бы только иначе выглядящий, широко практиковался в наши дни авторами Scala, Rust, Swift и др. +От B произошел C и далее большая часть ЯП-мейнстрима, каким мы его знаем и любим. +Сам BCPL использовался до 80-х годов. В том числе и некоторыми имплементаторами ФЯ. +В апреле 68-го Стрейчи получил письмо от Ричардса о том, что тот передумал имплементировать CPL, а решил вместо этого дальше развивать BCPL [Strachey]. + +### PAL + +> Поскольку никто не пишет реальные программы на PAL, мы можем позволить себе неэффективную имплементацию, которая иначе была бы неприемлемой. +> Артур Эванс, PAL - язык, спроектированный для преподавания. [Evan68] + +Создав первый практический (он же первый нефункциональный) язык обоекембриджской программы, Ричардс не остановился на достигнутом и приступил к имплементации функционального языка с ограниченной практичностью. +Этим языком ограниченной практичности был PAL - педагогический алгоритмический язык [Evan68b], разрабатываемый специально для курса 6.231 МТИ "Programming Linguistics". +Первыми его имплементаторами были теперь работающий в МТИ Ландин и Джеймс Моррис (James H. Morris, Jr.). Они написали на Лиспе то, что конечно же было ISWIM-ом с незначительными изменениями. Но имплементация, видимо, получилась слишком непрактичной даже для языка, непрактичность которого пытались представить как фичу, позволяющую ему имплементировать всякие сумасшедшие вещи вроде лямбда-исчисления. Поэтому вторую версию в 68 году имплементировали Мартин Ричардс и Томас Баркалоу (Thomas J. Barkalow) на BCPL для IBM 7094 под CTSS. Компилятор в байт-код к 70-му году был 1.3KLOC, а интерпретатор 1.5KLOC. +PAL имплементирован с помощью SECD машины, которую авторы PAL почему-то называют CSED-машиной. +Проблема неуказывания типов в псевдокодах решена тем, что язык "динамически" типизирован. +Описывающие PAL говорят, что вторая имплементация отличалась от ISWIM несколько больше [Evan68] [Rich13], но не говорят чем. +Мы рискнем предположить, что это за отличие, тем более, что это отличие практически единственное заметное: в ISWIM добавили лямбду. + +```haskell +def rec map f L = Null L -> nil + ! let h, t = L in (f h, map f t) + +map (ll x. x + y) (1, (2, (3, nil))) where y = 1 +``` + +Имплементация поддерживалась до 70-го года и язык несущественно менялся. +С 69-го он выглядел так [Woze71]: + +```haskell +def rec map f L = Null L -> nil + | let h, t = L in (f h, map f t) + +map (fn x. x + y) (1, (2, (3, nil))) where y = 1 +``` + +Помимо уже перечисленных имплементаторов, в авторы языка записаны Артур Эванс (Arthur Evans), который написал большую часть статей, репортов и мануалов, Роберт Грэм (Robert M. Graham) и Джон Возенкрафт. Просто поразительно, конечно, сколько авторов может быть у идеи добавить лямбду в функциональный язык. +Следующая по очевидности, после добавления лямбды в ФЯ, идея, которая пришла бы в голову современному человеку, конечно, такая: у нас есть непрактичный, медленный ФЯ PAL и практичный язык на котором он имплементирован BCPL. Что если мы будем "склеивать" в коде PAL-скрипта вызовы быстрых функций, написанных на BCPL? Но нет, никаких следов такого рода идей в 1968 году мы не видели. Такие идеи начнут завоевывать умы только к концу 70-х. +В Австралии 90-х годов именно эта идея будет воплощена в жизнь владельцем софтверной компании Lennox Computer Дугласом Ленноксом. Он использует сочетание придуманных и имплементированных им клонов ISWIM/PAL называемого GTL (General Tuple Language), сильно отставшего от своего времени, и C называемого (не тот)D [Lennox]. Это только первый из описываемых нами здесь, но не последний из ряда случаев, когда какой-то ФЯ или его имплементация находят неожиданное продолжение жизни у антиподов. +В PAL пытались имплементировать Ландинскую аннотацию для описания структур, со всеми особенностями вроде неограниченной степени вложенности + +``` +def rec LIST which + is ( HEAD which IsLIST else IsATOM + also TAIL which IsLIST else IsATOM) + else IsNIL +``` + +и сложный правил для именования конструкторов и предикатов, которые генерировались по описанию. + +``` +def LIST which is + (HEAD also TAIL) + else IsNIL +``` + +поскольку тут только один "конструируемый объект" `HEAD also TAIL` именем его тега будет имя всей структуры, т.е. `LIST`, а именем его конструктора `MakeLIST` [Zill70]. Но эта работа не была завершена. +На PAL было написано кода не сильно больше, чем псевдокода на ISWIM. Это были в основном учебные интерпретаторы и другие примеры из курса для которого PAL и создавался [Woze71]. Для каких-то других целей он, судя по отсутствию следов, не использовался ни в МТИ, ни где-то еще. На то, что делали в МТИ язык оказал минимальное влияние. Другой Кембридж не принял ФП в этот раз. +Имплементация PAL самый старый артефакт в этой истории, для которого сейчас доступен исходный код [PAL360]. Исходный код был доступен с самого начала, как и для многого другого разработанного в МТИ на госденьги и являющегося поэтому общественным достоянием. Кроме того, это был код на языке, компилятор которого был переносим на другие машины и был перенесен. Что было редкостью в то время вообще, и особенно в МТИ, где практически все остальные имплементации языков писали на ассемблере, а потом заново, когда меняли машины. Не смотря на все эти качества которые, казалось бы, делали PAL хорошей платформой для разработки ФЯ, он в качестве такой платформы не использовался. +За одним исключением. +Как мы помним, Стрейчи после CPL-проекта стал работать в Оксфорде. И там он был научруком у человека, который попытался имплементировать PAL эффективно. Чтоб PAL можно было использовать как язык общего назначения. Это ему не удалось. Не то чтоб ему с тех пор хоть раз удалось эффективно имплементировать ФЯ. Но ФЯ, которые он разработает будут хотеть эффективно имплементировать другие люди и он будет далее одним из главных героев этой истории функционального программирования. + +### McG + +Уильям Бердж, работавший с Ландином в Univac, после этого работал в IBM Research в Йорктаун Хайтс. Там он вместе с Бартом Левенвортом (Burt Leavenworth) в 1968 году имплементировал на основе SECD-машины еще один язык мало отличимый от ISWIM - McG [Emde06] [Burg71]: + +```haskell +def rec map f x + = null x ? () + | pfx (f(h x))(map f(t x)); + +let y = 1; let f x = x + y; map f (1,2,3); +``` + +И вот он-то повлияет на отношение к ФП в Йорктаун Хайтс. И Бердж использует его для ФП исследований в 70-е годы. + +------------------- + +Пришла пора прощаться с основными действующими лицами обоекембриджской истории. Впереди 70-е годы, такой важной роли в истории ФП они уже играть не будут. Ландин занялся административной работой. Ричардс забросил ФП и сконцентрировался на развитии BCPL. Стрейчи умер. +Мы отправляемся на север, в новую основную локацию и знакомимся с новыми главными героями: аспирантом Стрейчи, знакомым Ландина и знакомым знакомого Стрейчи. + +Эдинбургская исследовательская программа +======================================== + +## От исследовательского программирования к экспериментальному. + +Мартин ван Эмден как-то раз вычитал в журнале New Scientist сумасшедшую историю. В Эдинбурге некие Бурсталл и Поплстоун бросили вызов МТИ, написали многопользовательскую ОС на собственном языке для интерактивного программирования! И как раз в этом, 1968-ом году в Эдинбурге проходит конференция IFIP. Конференция не по функциональному программированию и даже не по программированию вообще, а по всему что связано с использованием компьютеров. Связано с этим пока не очень много, так что одной конференции достаточно для всего. Ван Эмден пока еще только сдавал программы на Алголе на бумажной ленте, чтоб их когда-нибудь запустили и очень хотел увидеть интерактивное программирование и более продвинутый чем Алгол язык. И Математический Центр Амстердама отправил ван Эмдена на конференцию. +Конечно же, на конференции было запланировано демо эдинбургской системы. Только кроме ван Эмдена на него никто не пришел. И демо не состоялось из-за проблем с телефонным соединением. Ну что ж. Тем больше времени у разработчиков чтоб рассказать ван Эмдену о проекте. Путь к этому (частичному) успеху, запустившему Эдинбургский университет в TOP4 центров по разработке ИИ, не был прямым [Emde06]. + +### Род Бурсталл + +Род Бурсталл (Rodney Martineau Burstall) - физик [Burstall] сначала работавший кем-то вроде тех, кого сейчас называют "квант", а затем программистом [Ryde2002]. Бурсталл как-то раз разговорился с другим покупателем в книжном магазине. Этот другой покупатель - Мервин Прагнелл (Mervyn Pragnell) - организовывал подпольный семинар по логике в Лондоне. Подпольный потому, что что официально Лондонский Университет не имел к нему никакого отношения. На этих семинарах в 61-ом году Бурсталл познакомился с Ландином, а Ландин научил его лямбда-исчислению и функциональному программированию в пабе "Герцог Мальборо". +Связи в функциональном подполье не обошлись без последствий. Несколько лет спустя, в 1964-ом году Бурсталлу позвонил некий Дональд Мики (Donald Michie) и предложил работу в "Группе Экспериментального Программирования", или может быть в экспериментальной группе программирования в Эдинбургском университете. Бурсталл согласился. +Вскоре выяснилось, что кандидатуры рекомендовал Стрейчи, имея в виду, что первые месяцы будущий сотрудник будет работать у него над CPL [Burs2000], тем более, что у экспериментальной группы пока что не было компьютера, так что экспериментальное программирование в ней откладывалось. +Работа Бурсталла над CPL, как это обычно бывало с работой над CPL, не имела особо хороших последствий для CPL. Но прежде чем отправиться работать над CPL в октябре 65-го, Бурсталл познакомился со своим коллегой Поплстоуном. + +### Робин Поплстоун + +Робин Поплстоун (Robin John Popplestone) - математик, увлекшийся программированием после того, как впервые попробовал программировать в университете Манчестера на одном из тех самых Атласов. Поплстоун хотел имплементировать логику, но обнаружил, что для удобной работы с деревьями мало аллокаций только на стеке. Чтоб разобраться, как программисты решают такие проблемы, он посетил ту самую летнюю школу по "нечисленным вычислениям", которая популяризировала псевдоCPL [Fox66] (Бурсталл также был знаком с этими лекциями, он писал рецензию на книгу [Burs67]). Наибольшее впечатление на него произвели доклад Стрейчи по CPL, LISP, расширения ALGOL 60 для работы со списками от радиолокационного института, доклад Ландина про то, как использовать стек и сборщик мусора для вычисления выражений. С этого времени Поплстоун стал последовательным пропонентом использования сборщика мусора. +Проблема была в том, что если когда-то у него был доступ к компьютеру, но не было нужных знаний и идей, то сейчас знаний и идей было достаточно, но уже не было доступа к компьютеру, на котором он мог бы их воплощать в жизнь. Теперь он преподавал математику в университете Лидса и программировал на существенно более ограниченной университетской машине. Настолько, что выражения на Лиспе не помещались в "быструю" часть памяти, и Поплстоун придумал и имплементировал стековый язык COWSEL. Разумеется, это будет бесполезно на следующих машинах, но недостатки будут жить и десятилетия спустя. +Кстати, о следующей машине: вот и она. Новая университетская машина, правда, работала только в пакетном режиме, а Поплстоун привык к интерактивной разработке и не обладал важной для программиста того времени способностью писать все правильно с первого раза. Или хотя-бы с сотого. Это означало, что то что Поплстоун делал на предыдущей машине за вечер, теперь могло требовать месяцы. Поплстоун так и не смог освоить старый подход к программированию и перенести на новую машину свой язык и пользоваться улучшенной производительностью. +Ситуация складывалась не радужная. Поплстоун преподавал в Лидсе математику, к которой испытывал все меньше интереса и выпрашивал машинное время на более совместимой с ним машине за пределами университета для своих экспериментов. К счастью, на дворе весна 65-ого года и некий Дональд Мики заглянул в Лидс в поисках каких-нибудь "нечисленных вычислений". Вычисления были обнаружены и Поплстоун приглашен сделать доклад в Эдинбурге. Там он познакомился с Бурсталлом, который уговорил его экспериментально программировать в экспериментальной группе, что удалось очень легко [Popplestone]. + +### Энергия маленькой целевой машины. + +> Для измученного имплементатора с маленькой целевой машиной мысль о том, что ему придется писать код для поиска свободных переменных в теле процедуры, и организовывать присвоение им значений перед выполнением этого тела, казалась слишком сложной. +> Поплстоун, Р. Дж. Ранняя разработка POP [Popplestone]. + +Экспериментальная группа Дональда Мики была задуман им как воспроизведение другого, существенно более масштабного эксперимента "Project MAC". В разные времена аббревиатура MAC расшифровывалась по разному, а в обсуждаемое время в первую очередь как Multiple Access Computer. Одна из первых (если не первая) система разделения времени была крупным успехом МТИ, привлекшим финансирование, которое было использовано для следующих, не таких успешных проектов. Мики посетил МТИ, и успехи проекта MAC, а также ростки будущих неуспехов проекта MAC произвели на него сильное впечатление. Он решил организовать что-то похожее в Великобритании, но поменьше и подешевле. +Проект мини-МАК - и в узком и в расширенном смысле - должен был не просто воспроизвести большой МАК, а должен был сделать это другим путем. Это было хорошо для всего, что не любили в проекте МАК, например решатели теорем и логическое программирование [Emde06]. Хорошо и для того, к чему там были просто равнодушны, как к функциональному программированию. Проблема была в том, что некоторые вещи необходимые для ФП, такие как сборка мусора, в проекте МАК (пока что) любили. И, разумеется, в экспериментальной группе собирались все писать на ALGOL 60. +Это угрожало полной катастрофой всем замыслам Поплстоуна. Он доказывал, что использование языка без сборщика мусора - ошибка, а добавить сборщик в имплементацию Алгола сложно потому, что не известно где на стеке указатели. Особых результатов это не принесло, но тут - в начале 66-го - экспериментальная группа наконец-то получила компьютер Elliott 4120. Поплстоун сразу же начал тайную имплементацию своего языка на нем, и вскоре имплементировал достаточно, чтоб языком заинтересовался Бурсталл. +Бурсталл организовал для Мики демонстрацию, на которой тот попросил Поплстоуна написать небольшую функцию прямо у него на глазах, что тот легко сделал. Увидев интерактивное программирование, Мики стал энтузиастом языка. Не понравилось Мики только название COWSEL, так что как-то раз Поплстоун вернувшись из отпуска обнаружил, что язык уже называется POP (Package for Online Programming). +Вскоре после того, как первая версия была готова и описана в апреле 66-го, а сборка мусора и интерактивное программирование завоевали себе место в мини-версии проекта MAC, Поплстоун и Бурсталл приступили к созданию POP-2. +Примечательно то, что проработавший три года программистом в индустрии Бурсталл играл роль "теоретика", а программировавший в свободное от преподавания математики время Поплстоун - "практика". Видимо оба очень хотели передохнуть от того, чем занимались на работе. +Разработку сначала планировали начать с нуля, чего не произошло. POP-2 унаследовал, по видимому, самую необычную фичу POP-1 - "открытый" стек. Как авторы POP, так и авторы CPL утверждают, что POP-2 произошел от CPL [Camp85] [Rich2000]. И это утверждение не так легко объяснить, даже с учетом крайне разреженного дизайн-пространства языков в те годы. Даже если выбирать из двух языков со сборкой мусора, одного имплементированного частично и с ошибками и второго, по большому счету, вообще воображаемого, то LISP выглядит как более правдоподобный предок, чем CPL. Изначально, возможно, планировалось сделать язык похожий на CPL, но ряд ключевых решений оттуда были отвергнуты Поплстоуном по "практическим" соображениям и заменены решениями, которые сам же Поплстоун впоследствии называл "ошибками" и "хаками". +В сначала POP-2 планировали лексическую видимость как в ALGOL 60 (и CPL), но решили сделать динамическую. Это решение Поплстоун обосновывал проблемами имплементации: для того чтоб сделать и "открытый" стек и лексическую видимость на целевой машине было слишком мало регистров. Также Поплстоун считал, что язык с динамической видимостью проще отлаживать. +Поплстоун еще и планировал имплементировать функции высшего порядка с помощью метапрограммирования, как в LISP, для чего достаточно дать программисту доступ к компилятору как функции. Что было не как в LISP, так это то, что Бурсталл знал, что этого недостаточно. + +``` +function funprod f g; + lambda x; f(g(x)) end +end; +``` + +Эта имплементация композиции функций не сработает без лексической видимости: `f` и `g` свяжутся с функциями видимыми там, куда возвращается анонимная функция из `funprod`. +Но аннотацией видимости не обошлось, Поплстоун решил, что поиск свободных переменных в лямбде и конструирование замыкания будет требовать слишком много ресурсов и будет слишком сложно имплементировать. Поэтому программисту нужно делать лямбда-лифтинг и инициализацию замыкания вручную: + +``` +function funprod f g; + lambda x f g; f(g(x)) end(%f,g%) +end; +``` +[Burs68] +И наш традиционный пример будет выглядеть так: + +``` +function map f l; + if null(y) then nil else cons(f(hd(l)), tl(l)) close +end; + +vars y; 1 -> y; +map (lambda x y; x + y end(% y %)) [1 2 3] +``` + +Поплстоун позднее вспоминал, что математика была их с Бурсталлом общим языком, который позволил им согласовать их цели в разработке POP-2 и достичь того, что язык, при всех его странностях можно было использовать в функциональном стиле. Но их сотрудничество ограничивало то, что Бурсталл интересовался применением математических идей более последовательно, а Поплстоун неохотно поддерживал его формальный подход [Popp2002]. +Можно ли его было использовать в функциональном стиле? К этому мы вернемся позже, когда будем обсуждать рождение функционального стиля. Но можно сказать определенно, что языки Эдинбургской программы оказались основаны не на нем, а на тех языках, использовать которые в функциональном стиле было удобнее. +Компромиссные фичи, срезания углов и остатки POP-1 сильно подорвали модернизационный потенциал POP-2. Например, в Эдинбурге добавили статическую типизацию к ISWIM, но добавить ее к POP-2 оказалось слишком сложно: попытавшийся это сделать позднее Поплстоун обнаружил, что из-за открытого стека сигнатуры функций будут тайплевел-парсерами и посчитал, что не стоит связываться с такими сложностями [Popp2002]. +Важны ли были эти компромиссы для практичности? Сомнительно. Сейчас нам известно более-менее достоверно, что тот, кто может позволить такие дорогие фичи как сборка мусора или динамическая "типизация" - может позволить и лексическую видимость и компилятор, который сам ищет свободные переменные в лямбдах. +Получился ли язык практичным? Он определенно применялся для написания программ больше, чем PAL и прочие ISWIMы того времени, но это не очень высокая планка. На нем была написана первая версия решателя теорем Бойера-Мура, одной из немногих программ, которая была переписана на LISP, а не с LISPа на какой-то другой язык. Первая имплементация POP-2 и более поздние для PDP-10 использовались для имплементации языков Бурсталла. Имплементация 80-х годов служила бэкендом для компилятора SML. Установленным в первой главе критериям практичности он соответствует. И для этого не понадобилось приносить все функциональные фичи в жертву как в BCPL. Это шаг вперед, по сравнению с обоекембриджской программой. Но не все в POP-2 было шагом назад по сравнению даже с планами для CPL. Но о прогрессивных фичах POP-2 и его влиянии на языки Эдинбургской программы мы подробнее расскажем ниже в соответствующих главах. +Конечно проблемы с практичностью у POP-2 были, сборка мусора мешала программировать роботов, а из-за динамической "типизации" компилятор генерировал медленный код, который проверял теги и вызывал функции для каждой арифметической операции, что мешало писать на нем код для распознавание образов. Но такие проблемы не решены полностью и у современных "динамических" языков со сборкой мусора. [Popp2002] +Почему на POP-2 вообще пытались писать код для управления роботами и распознавания изображений? Потому что он стал тем, чем хотели сделать CPL - единственным языком единственной машины, используемой в экспериментальной группе. И был он единственным не потому, что лучше всего подходил для всего "экспериментального программирования", которым в ней занимались. Операционная система Multipop68, ставшая ответом мини-MACа MACу большому, была системой основанной на языке, безопасность в которой обеспечивается инкапсуляцией и проверками языка. В данном случае проверками тегов в рантайме. +Первоначальный план с аппаратной изоляцией процессов не удалось осуществить потому, что университет не смог приобрести подходящие жесткие диски для свопа. Со временем в Эдинбурге сделали более традиционную систему разделения времени с аппаратной защитой и свопом Multipop 70, но пользователи не любили её за медлительность и предпочитали продолжать пользоваться Multipop68 до получения новой машины PDP-10 в середине 70-х. +Пока POP-2 был обязательным к нему привыкли, он был портирован на другие машины и внедрен в других университетах, стал популярным языком в британском ИИ, как Лисп в американском [Popplestone] [Popp2002]. Мики в 1969-ом даже основал компанию Conversational Software Ltd, которая пыталась POP-2 коммерциализировать [Howe07]. + +### Filtrage de motif + +Получив работу в Эдинбургском университете, Робин Поплстоун отправился из Лидса в Эдинбург на катамаране собственной конструкции. В Северном море его судно пошло ко дну. Совершенно точно не вместе с рукописью диссертации Поплстоуна, которого подобрал проходящий траулер [Popplestone]. По какой-то причине и Бурсталл и Лессер считали важным упомянуть, что рукопись диссертации была утрачена каким-то другим (не указанным) способом [Burs04]. Мы же считаем важным упомянуть, что в эти роковые минуты судьба одной из наиболее узнаваемых фич ФЯ висела на волоске. +Три года спустя, в 1968 Род Бурсталл написал статью "Доказательство свойств программ с помощью структурной индукции" [Burs69]. Но прежде чем приступить к доказательствам, он расширил синтаксис используемого им как псевдокод ISWIM для "более удобной манипуляции структурами данных". +Более удобной, чем в Лиспе, подход которого до этого момента воспроизводили в коде на ISWIM/CPL/PAL/McG/POP-2: + +```f# +let rec map(f, xs) = if null(xs) then nil() + else let x = car(xs) and xs1 = cdr(xs) + cons(f(x), map(f, xs1)) +``` + +Бурсталл еще не описал свои расширения ISWIM, а он уже выглядит иначе? Ну, ISWIM выглядит иначе в каждой статье, в которой его используют. +ISWIM позволяет связывать несколько переменных с элементами туплов + +```haskell +let rec map(f, xs) = if null(xs) then nil() + else let (x, xs1) = splitup(xs) + cons(f(x), map(f, xs1)) +``` +и даже вложенных `(x,(y, z))` (но имплементированные в то время языки вроде PAL так не могут). Бурсталл решил это обобщить на все структуры, которые объявляются Ландинской нотацией. +Предположим, у нас есть список, в котором α - любой объект +``` +An α-list is either a cons or a nil. +A cons has an α and a list. +A nil has no components. +``` +Эта нотация декларирует множество функций, с генерируемыми по правилам названиями, которые предполагается использовать программисту. +Каждой "конструирующей операции" (construction operation) вроде `cons` и `nil` соответствуют три функции: +* функция-конструктор, принимает компоненты, возвращает структуру +* функция-деструктор, принимает структуру, возвращает компоненты как упорядоченный тупл +* предикат, проверяет, что объект был сконструирован данной "конструирующей операцией" + +Бурсталл предложил использовать один идентификатор - тот, что пишет сам программист, например `cons` - для всех, а различать какая именно из трех функций используется по контексту. Возможно, что эта идея происходит от дублетов из POP-2, в котором к функции, работающей как геттер, можно прицепить функцию работающую как сеттер. Обе имеют одно имя, а когда вызывается одна или другая - определяется тем, с какой стороны оператора присваивания находится вызов [Burs68]. +Расширенный ISWIM транслируется в обычный ISWIM так: +`let x = cons (a, y)` в `let x = cons(a, y)`, +`let cons (a, y) = x` в `let (a, y) = decons(x)`, +`x is cons` в `compound(x)` и, наконец, +`f(cons)` это `f(cons, decons, compound)`. +Да, когда конструкторы АлгТД были изобретены, они были первоклассными. +Было бы странно, если бы не были. ISWIM - функциональный язык. Почему же тогда они перестали такими быть? Всему свое время. +Изобретя конструктор АлгТД, Бурсталл пока что не изобрел АлгТД как суммы произведений. Он, как и разработчики типов для CPL годом раньше, считает, что каждая "конструирующая операция" вводит новый тип, их дизъюнкция тоже. Вместо одного типа списка `cons` - конструирует значение одного типа, `nil` - второго, а `list` - третий тип. +Вот что получилось у Бурсталла после всех этих улучшений: +```haskell +let rec map(f, xs) = if xs is nil then nil() + else let cons(x, xs1) = xs + cons(f(x), map(f,xs1)) +``` +О нет! Конструкторы АлгТД используются не так. +И что толку, что разрешено вложение +```haskell +let cons(x1, cons(x2, xs2)) = xs1 +``` +применять такое удобство на практике нельзя, ведь нужно сначала проверить второй `cons`, а вложения `is` проверок нет. +К счастью, проходящий мимо Робин Поплстоун спас функциональное программирование, как до того проходящее мимо судно спасло Поплстоуна. Избыточность нотации, повторения имен не понравились ему, о чем он честно сообщил Бурсталлу. +```haskell +if xs is cons then let cons(x, xs1) = xs; ... +``` +Нельзя ли использовать тут `cons` и `xs` по одному разу? +Бурсталл продолжил поиск, вспомнил предложенный в 67-ом Мартином Ричардсом для CPL `case` по типам, и получил современную нотацию: + +```haskell +let rec map(f, xs) = cases xs: + cons(x, xs) : cons(f(x), map(f, xs)) + nil() : nil() +``` +Бурсталл также предложил использовать инфиксные "конструирующие операции", например `::` вместо `cons` +```haskell +let rec map(f, xs) = cases xs: + x :: xs: x :: map(f, xs) + nil() : nil() +``` +Авторы языков программирования не часто рассказывают чудесные истории о том, как кто-то сказал им однажды: "остановись, подумай, нельзя ли здесь сделать покороче?" и они заметили это и сделали. Судя по тому, как языки программирования выглядят, и происходят такие истории нечасто. +Это тем важнее, что похоже, что такая нотация не была больше переизобретена независимо. +Важно, правда, отметить, что это пока что псевдокод, до первой имплементации еще годы, а до первой статьи о том, как это компилировать еще больше. +Также примечательно, что в статье Бурсталла, как и в этой главе, не упоминаются два английских слова, которыми такую нотацию теперь называют. + +За возвращение функций еще никого не увольняли +------ + +После неудачной демонстрации Multipop Ван Эмдену в 68-ом году, тот возвращался в Эдинбург каждое лето. И уже в 69-ом все, что не работало раньше - теперь отлично работало. За этот и следующий визит Ван Эмден освоил POP-2 и интерактивное программирование. Но пришла пора ему связать историю ФП в Эдинбурге с другим ответвлением Обоекембриджской программы. +В конце лета 71-го Ван Эмден отправился в IBM research, Йорктаун Хайтс. Там Ван Эмден познакомился с тем, что он описывает как вторую исследовательскую программу, произошедшую от работ Стрейчи и Ландина. В обсуждаемые времена, скорее даже первую, если судить по более впечатляющим успехам. Вместо слабой четырехпользовательской машины в Эдинбурге - в каждом офисе терминал, подключенный к CP/CMS системе на IBM 360-67 [Emde06]. Вместо изуродованного "практичностью" POP-2 - McG. Настолько полноценный ФЯ, насколько ФЯ вообще мог быть полноценным в 72-ом году. Да, это не очень высокая планка. Пройдет не так много времени, и ситуация изменится на противоположную. В Эдинбурге изобретут первый функциональный язык, как мы их сейчас знаем, а McG так и останется пиком ФП в IBM. По какой-то неизвестной нам причине, начиная со статей 72-го года, код на McG и даже упоминания этого языка исчезают. Возможно произошла какая-то ФП-катастрофа в IBM, и Ван Эмден был свидетелем ФП-утопии в последний год её существования. Но ФП-катастрофа точно не была полной и окончательной. Технически Йорктаунская ветка истории ФП существует и сейчас, но её главный и еще более-менее живой продукт Axiom - не совсем язык программирования и не особенно впечатляющ как ФЯ. Главный итог Йорктаунской ветки не в нем, а во влиянии, которое Йорктаунская программа оказала на Эдинбургскую и некоторые другие. Изобрести функциональный язык, как мы его знаем в Йорктаун Хайтс не смогли. Зато смогли изобрести функциональное программирование, как мы его знаем. + +### Уильям Бердж + +Функциональное программирование занес (в 65-ом) в Йорктаун Хайтс Уильям Бердж, англичанин, который, как мы уже знаем, работал с Питером Ландином в Univac (в 63-ем) и, как вспоминает Ван Эмден, с Родом Бурсталлом [Emde06]. Ван Эмден, впрочем, не вспоминает где именно и в чем заключалась эта работа. +Бердж закончил Кембридж (в 55-ом) и научился программировать в Королевском Радиолокационном Институте (в 54-ом) [Burg75], т.е. скорее всего до того как в этих местах зародился интерес к функциональному программированию. +Бердж, в отличие от Хартли, имплементировал ALGOL 60. Может быть не успел познакомится со Стрейчи. Может быть успел, но Стрейчи не смог его отговорить имплементировать ALGOL 60. Может быть, если бы Стрейчи не отговорил Хартли имплементировать Алгол 60, то Хартли бы потом смог имплементировать ФЯ, как смог Бердж. +Бердж и Барт Левенворт имплементировали McG (называемый также McG360), ISWIM-подобный язык на основе SECD машины в 1968. +Нам мало что известно об этом языке. Это, в основном, полная противоположность другой имплементации ISWIM - PAL. Внутренний продукт IBM, который за пределами лаборатории не мог видеть и исходники которого не были открыты. Описан во внутренних отчетах, которые не были отсканированы и выложены в интернет. Во всех работах его авторов за парой исключений [Burg71] [Leav71] вместо примеров на нем - псевдокод, который отличается в деталях. +По немногочисленным примерам кода и REPL-сессий [Burg71] можно заключить, что он больше походил на ISWIM чем PAL отсутствием лямбд и меньше - отсутствием `where`-выражений. Отсутствие и того и другого вполне может быть объяснимо предпочтениями автора кода. +Пользователи McG в IBM research также были противоположностью пользователей PAL в МТИ. McG в отличие от PAL использовали для экспериментов с имплементацией ФЯ. Левенворт дал пользователю языка возможность писать функции, которые могут читать и изменять структуры, описывающие состояние SECD машины. Хотите имплементировать корутины, продолжения? Пожалуйста! [Leav71]. Понятно, что такая идея требовала слишком наивную имплементацию даже для интерпретатора чтобы работать и не имела будущего, но Бердж, видимо, осуществил и уж точно описал эксперимент который будущее имел и к которому мы еще вернемся. +Но самым важным для нашей истории отличием Берджа и прочих пользователей McG от, по большому счету, всех остальных пользователей ФЯ того времени было желание писать код в функциональном стиле. + +### Наука переполнять стек + +> Тот факт, что большинство функций являются константами, определенными программистом, а не переменными, которые изменяются программой, не является следствием какой-либо слабости системы. Напротив, он указывает на богатство системы, которое мы не умеем хорошо использовать. +> Дж. МакКарти, Руководство программиста на LISP 1.5 + +То программирование, про которое писали в 60-х авторы и первые пользователи будущих ФЯ было больше про рекурсивные функции, чем про функции высшего порядка. Что обычно подчеркивается в названии работ вроде "Рекурсивные техники в программировании" [Barr68] или "Рекурсивные функции символьных выражений и их вычисление машиной" [McCa60]. Что странно, по крайней мере, по двум причинам. Во-первых потому, что эффективная имплементация рекурсии еще не используется, хотя изобретена Вейнгаарденом. Но, как и обычно бывает в нашей истории, еще не понята и не принята [Reyn93]. К этому мы еще вернемся. Во-вторых функции высшего порядка появились в упомянутых нами ранее псевдокодовых фантазиях. До того, как были имплементированы языки, на которых их можно писать. Поэтому можно было бы ожидать, что тем, кто получил наконец-то возможность поэкспериментировать с такими функциями, немедленно захочется этим заняться. Но нет. Судя по дошедшим до нас следам, желающих было немного. +ФВП как псевдокод появились в первой же публикации о Лиспе 1960-го года. Строго говоря, ФВП они не были, а были применением метапрограммирования в языке первого порядка. К проблемам такого подхода мы еще вернемся. Если исключить функции анализирующие код, который в них передавали, вроде `eval` и `apply` оставались еще те, которые можно было бы имплементировать как ФВП. Таких "ФВП" было всего две: `maplist` и `search`. Влияние этой первой публикации на ФП несомненно, но ни в какой момент эти ФВП не были идентичны тем, что появились в публикациях обоекембриджцев. С самого начала были как менее важные различия вроде порядка аргументов, не имеющего пока что никакого значения из-за некаррированности функций, так и более значительные. +Главная разница была в том, что обходящие список ФВП применяют принимаемую функцию поочередно не к отдельным элементам списка, а к остаткам [McCa60]. Не позднее 66-го года появляются версии этих функций работающие с элементами [Bobr66], но влияние обоекембриджской программы сомнительно. Вероятно, они появились потому что лисперы обнаружили что часто приходится начинать передаваемую функцию с применения `head` (в Лисп обычно `car`), отсюда именование новых модификаций вроде `mapcar`. Но идея о том, что пользователь функции `map` может выбирать с какой частью остатка списка работать не умерла и даже получила развитие - появились версии `map` принимающие функцию, вызываемую вместо `tail` (в Лиспе `cdr`) для управления обходом списка [Teit74]. +Примечательно, что `search` помимо предиката принимала функции, которые вызывались в случае, если подходящий элемент найден и если нет. Один из изобретателей продолжений Локвуд Моррис позднее писал, что главным вдохновением для него был этот паттерн для обработки ошибок. Моррис не припоминает, чтоб он видел как МакКарти или еще кто-то в то время писал нетривиальный код в таком стиле [Reyn93]. И, вероятно, видеть было нечего, потому что паттерн не стал популярным и сменился на возвращение из функции пустого списка. +Пара "ФВП" из первой публикации о Лиспе не была просто примером, выборкой из многих придуманных лисперами функций. За статьей последовали мануалы и там идей не стало больше, из полутора сотен функций стандартной библиотеки LISP 1.5 было только несколько ФВП - вариации `maplist` и `search` и пара функций применяющих продолжения для обработки ошибок [McCa62]. + +```haskell +maplist[x;f] = [null[x] -> NIL;T -> cons[f[x];maplist[cdr[x];f]]] + +search[x;p;f;u] = [null[x] -> u[x];p[x] -> f[x];T -> search[cdr[x];p;f;u]] +``` + +Разумеется `maplist` описывалась функциональным псевдокодом только в мануале, а в библиотеке была реализована как сотня строк ассемблерного кода. За все последующие 60-е годы и начало 70-х лисперы продолжали инкрементальную модификацию `maplist`, самая интересная из которых была аналогом `concatMap`, и добавили пару функций, принимающих предикат вроде сортировки. +Никаких следов влияния всей этой работы на Эдинбургскую программу мы не видели. Многие наработки и не могли быть перенесены в будущие ФЯ из-за типизации. Например, в конце 60-х - начале 70-х в MacLISP семейство функций `map` сделали принимающими произвольное количество списков и работающими как обобщенные `zipWithN`. Примерно в то же время лисперы перестали документировать свои функции тем псевдокодом из первой статьи [Whit70] [Moon74]. +POP-2 похож на Лисп не только некоторыми языковыми решениями, но и библиотекой ФВП [Dunn70], правда, помимо `maplist` там были кое-какие интересные идеи о которых ниже. +Обоекембриджская программа подарила нам привычные `map` и `filter` (тогда назывался `select`) в работах Ландина по описанию семантики ALGOL 60, которые писались в начале 60-х и были опубликованы в 65-ом [Land65b] + +```haskell +rec map f L = null L -> () + else -> f(h L) : map f (t L) + +rec select(p)(L) = null L -> () + p(h L) -> h L:select(p)(t L) + else -> select(p) (t L) +``` + +Скобки вокруг аргументов в коде Ландина появляются или нет бессистемно. +Также, в одной из этих работ [Land65b] можно увидеть пару странных ФВП для моделирования циклов, которые неработоспособны в последующих ФЯ и вводятся только как неправильное решение проблемы, к правильному решению которой мы еще вернемся. +Стрейчи по всей видимости представил `map` на летней школе в 63-ем и опубликовал в 66-ом [Stra66]. Аргументы у map в современном порядке, в отличие от лиспового, но это скорее всего просто случайность потому что функции некаррированы. +Стрейчи делает шаг вперед по сравнению с лисперами: пытается найти более фундаментальное представление для обхода списка и изобретает `foldr` (у Стрейчи называется `lit` - List ITeration), приводит ряд примеров выражения функций через `Lit` (пока что) полным применением [Stra66]. + +```haskell +let Lit[F, z, L] = Null[L] -> z, + F[Hd[L], Lit[F, z, Tl[L]]] + +let Map[g, L] = Lit[F, NIL, L] where F[x, y] = Cons[g[x], y] + +let Rev[L] = Lit[F, NIL, L] where F[x,y] = Append[y, x] + +let Product[L] = Lit[f, List1[NIL], L] + where f[k, z] = Lit[g, NIL, k] + where g[x, y] = Lit[h, y, z] + where h[p, q] = Cons[Cons[x,p], q] +``` + +Что отличается от псевдокода лисперов, которые заново описывают раз за разом рекурсивные обходы списков. +Выражение функций через `Lit` произвело позднее сильное впечатление на деятелей Эдинбургской программы. Аспиранты Бурсталла Майкл Гордон (Michael Gordon) и Гордон Плоткин (Gordon David Plotkin), более известный другими своими работами, в 72 задались вопросом о том, что можно, а что нельзя выразить через `Lit`. Майкл Гордон даже определил язык, трансформирующийся в комбинацию `Lit`-функций (май 73) [Gord2000b]. +Но все эти наработки в Обоекембриджской программе так и остались псевдокодом. В библиотеке PAL функций высшего порядка не было вообще [Evan68b], так что не было и никакой итеративной работы по совершенствованию описанных Стрейчи и Ландином наборов функций, вроде той что была у лисперов. Показательно, что представленные в этой истории функции `map` на псевдоCPL и ISWIM написаны их авторами, а `map` на PAL написана нами, не смотря на то, что это реально существовавший язык для которого доступны исходные коды, мануал, учебный курс и исходники примеров для курса. Почему пользователи PAL не хотели писать код в функциональном стиле? Или, если писали, то почему не оставили никаких следов этой деятельности? Нам не удалось разгадать эту загадку. Можно понять нежелание писать ФП код у пользователей лиспов (из-за имплементации "ФВП" с помощью метапрограммирования) и пользователей POP-2 (из-за неудобства лямбд), но у PAL таких проблем нет, и для обсуждаемых экспериментов даже несерьезной имплементации достаточно. Да, в МТИ тогда не было особого энтузиазма по отношению к функциональному программированию, а энтузиазма по отношению к PAL не было никогда, но к исходникам PAL получали доступ, например, аспиранты Стрейчи, будущие активные участники Эдинбургской программы, для которых ФП должно было бы быть гораздо интереснее. +Но если с функциями, принимающими другие функции, дела обстояли не особенно весело, то с еще одной разновидностью ФВП все было еще печальнее. В мануале LISP 1.5 функции вроде `maplist` называются "функционалами". Есть там и определение функционала - это "функция, принимающая другие функции" [McCa62]. Как же они называли функции, которые возвращают другие функции? - спросите вы. Что-что делают? - спросят в ответ лисперы. + +### Комбинаторные основания функционального программирования + +> Возможные варианты использования для практических целей функций, производящих функции, на сегодняшний день в значительной степени не изучены. +> У. Бердж, Техники рекурсивного программирования [Burg75] + +Что если вернуть функцию из функции? В 60-е годы так мало задавались этим вопросом, что бывало замечали только через годы обсуждения, что язык, в котором есть "лямбды", не позволяет их использовать как лямбды. Рассказ об этом языке еще впереди. Справедливости ради, сейчас не 60-е годы и для широких направлений программистской мысли это все еще не самый очевидный вопрос и многие популярные языки вроде C++ или Rust не очень хорошо приспособлены к таким потребностям программиста. Передача функций в функции прочно закрепилась в мейнстриме, но возвращение функций из функций и сегодня экзотическая техника, популярная только в рамках Эдинбургской программы. Не смотря на распространенность ФЯ сегодня, большинству интереснее передавать функции в функции, а не возвращать их. +На заре Эдинбургской программы, эта техника в основном ограничивалась даже не одной исследовательской программой, а работами с участием одного человека: главного героя этой части нашей истории Уильяма Берджа. +Заметно, что придумать простую, но более-менее общеполезную функцию, возвращающую другую функцию авторам ФП-учебников того времени дается не легко. И в учебном курсе на PAL [Woze71] и в мануале POP-2 [Burs68] для демонстрации ФВП используется функция + +```haskell +let Twice f x = f(f x) +``` + +а не, например, реалистичная функция `(.)`, которую гораздо позднее использовал Поплстоун для иллюстрации проблем POP-2 [Popplestone], как и мы в этой работе. +Бердж объявлял и использовал композицию функций не позднее статьи 64-го года [Burg64] в псевдокоде вполне современного вида: + +```haskell +(f . g) = f (g x) +``` + +И тогда же ссылался на "Комбинаторную Логику" Карри [Curr58]. +Статья содержит, видимо, первый нетривиальный ФП-код (ISWIM-псевдокод) в котором функции возвращают функции - комбинаторный парсер. Точнее матчер, разбирающие строку функции не возвращали значения, только успешность разбора и остаток. + +```haskell +nullist x = (true, x) + x = null x -> (false, x) + else -> eq (h x) 'I' -> (true, t x) + else -> (false, x) + +V f g x = 1st(f x) -> f x + else -> 1st(g x)-> g x + else -> (false, x) + +C f g x = 1st(f x) -> 1st(g(2nd(f x))) -> g(2nd(f x)) + else -> (false, x) + else -> (false, x) + +* = V (C (* )) nullist +``` + +Единственный нетривиальный ФП-код с возвращением функций функциями, произведенный обоекембриджской программой был опубликован Ландином, когда он работал в Univac, где Бердж работал начальником небольшой исследовательской группы, которая занималась семантикой ЯП [Burg75]. Ландин столкнулся с проблемой моделирования Алгольных циклов с помощью функций строгих списков и применил возвращение функций из функций для описания "стримов" [Land65a]. + +```haskell +nullist* = λ().() +L :* M = λ().(L,M) +null*(S) = null(S()) +h*(S) = 1st(S()) +t*(S) = 2st(S()) + +rec while*(e, p) = λ().p' -> [e', while*(e, p)] + else -> () + where e', p' = e(), p() +``` + +Эта работа из двух частей также одна из немногих работ обоекембриджской программы, в которых используется композиция функций и частичное применение. Правда, так мало, что можно пропустить если моргнуть не вовремя. +Нужно заметить, что статья не посвящена стримам, они просто один из инструментов, которые используются в статье об описании семантики Алгола. Бердж вспоминает, что Ландин рассказал ему о стримах в 62-ом году [Burg75b], но статьи о них Ландин не писал. Позднее Бердж пишет про стримы больше и подробнее [Burg71] [Burg75] [Burg75b]. +Бердж мог изобрести комбинаторные парсеры под влиянием Ландиновских стримов, но и наоборот, на изобретение стримов Ландином могли повлиять комбинаторные парсеры Берджа. Ландин, описывая [Land98] технику индикации успешной/неудачной работы через возвращаемый результат, ссылается на статью о комбинаторных парсерах [Burg64]. Эта техника выглядит как что-то, что изобреталось независимо бессчетное количество раз, но в ретроспективе многие изобретения кажутся очевидными. +Статьи про семантику Алгола и про комбинаторные парсеры к тому же производят впечатление использования одного "словаря" функций. Например, `zip` в статье Берджа и `unzip` в статье Ландина. +Стримы, конечно, не являются решением проблемы контрол-структуры, ведь результаты стрима не сохраняются и их может быть нужно перевычислять. Так что через несколько лет изобретение получило развитие в POP-2. +Авторы POP-2 изобрели "динамические списки". Динамический список производился ФВП `fntolist` из функции, возвращающей элементы последовательности. Получалась пара из ссылок на булево значение и функцию. Функции получения головы и хвоста списка проверяли cons-ячейку на то что она имеет такую специальную форму и вызывали функцию вычисления следующего элемента. Если функция возвращала нулевое значение - cons-ячейка переписывалась в кодирующую пустой список. Если возвращался результат - в cons-ячейке переписывали на месте ссылку на булево значение на результат вызова функции, а ссылку на функцию - ссылкой на новую специальную cons-ячейку. +Вся эта машинерия была имплементирована в библиотеке [Burs68]. + +```js +function fntolist f; cons(false, f) end + +function solidified l; vars f x; + if isfunc(back(l)) + then back(l) -> f; f() -> x; + if x = termin then true -> front(l) + else x -> front(l); conspair(false, f) -> back(l) + close; l + else l + close +end; + +function hd l; front(solidified(l)) end; +lambda i l; i -> front(solidified(l)) end -> updater(hd); +``` + +В 70-е Бердж ссылается на это изобретение, но сам его применяет существенно позже [Burg89]. +Помимо ISWIM, на котором Бердж писал комбинаторные парсеры, он знает [Burg66] еще один неимплементированный еще язык с возвращающими функции функциями - CUCH. CUCH (CUrry CHurch) - это эзотерический язык Коррадо Бёма (Corrado Böhm), более известного другими своими работами и другим эзотерическим языком [Böhm72]. На нем Бердж комбинаторные парсеры не писал, этот язык хуже подходил для написания более-менее практичного кода из-за однобуквенных констант и параметров и прочих ограничений. Язык разработан в 64-ом году [Card2006], но Бердж ссылается на него впервые в работе 66-го года. Как выглядел CUCH: + +``` +I = (λxx) +K = (λx(λyx)) +``` + +Другой язык, который мог бы повлиять на комбинирование комбинаторов Берджем - APL. Про который тот, конечно, знал, работая в IBM и на который ссылался в 72-ом году [Burg72]. Мы, правда, не видим особого сходства и Бердж не уделил особого внимания сравнению. CUCH-программа, правда, некоторое внимание уделила. В 1962-64 Мариса Вентурини Дзилли (Marisa Venturini Zilli) занималась энкодингом операций APL с помощью комбинаторов, а в 82-ом Бём делал это для родственного языка FP [Card2006]. Еще одна задача для будущих историков идей. +С 65-ого года Бердж работает в IBM Research, где изучает "методы упрощения программирования использованием высокоуровневых языков" [Burg75]. Высокоуровневый язык, правда, готов только в 68-ом году. В отличие от всех тех, кто с того же самого года имел доступ к имплементации PAL, Бердж решил опубликовать свои идеи о том, как ФП код может и должен выглядеть. В частности, что можно сделать, если язык позволяет возвращать функции. +В статье 71-го года [Burg71] Бердж приводит примеры REPL-сеансов и кода на языке McG. Бердж выделил повторяющийся код в функциях работы со списками: + +```python +def rec list1 a g f x = + null x ? a + | g (f(h x))(list1 a g f (t x)); +``` + +Бердж не ссылается на статью Стрейчи в которой тот описывает `Lit`. Кроме того, его функция отличается названием, порядком и числом параметров, так что вполне можно допустить независимое изобретение. Бердж работал вместе с Ландином, который скорее всего был знаком с этой функцией, но возможно просто не придавал изобретению такого значения, которое придавали ему в Эдинбурге. +Обратите внимание на один ненужный параметр, вместо которого можно композицию функций использовать. Что довольно странно, если учесть что Бердж если не первый, то один из первых, кто начал использовать композицию. +Бердж объявил комбинаторы из книги Карри: + +```python +def i x = x; +def k x y = x; +``` + +И использовал их далее для определения функций частичным применением ФВП: + +```python +def plus x y = x + y; +def sum = list1 0 plus i; +def length = list1 0 plus (k 1); +def map f = list1 () pfx f; +``` + +Бердж называет три языка, которые поддерживают программирование в таком стиле: McG, PAL и POP-2. Но примеры на PAL и POP-2 не приводит. И POP-2 не тот язык на котором такой стиль будет хорошо выглядеть. [TODO] +И если `foldr` называется `list1`, то вы наверное уже догадалась какая функция называется `list2`. В статье 71-го года для `foldl`, видимо, не нашлось места, и её код появляется в статье 72-го года [Burg72]. + +```python +def list2 a g f x = + if null x + then a + else list2 (g(f(h x))a) g f (t x) + +def reverse = list2 () prefix I +``` + +Бердж отмечает, что `list2` может быть имплементирована как цикл, т.е. эффективнее, чем в рекурсивной форме. `list1` может быть выражена как двойной проход + +```python +def list1 a g f x = list2 a g f (list2 () prefix I x) +``` + +что все еще может быть эффективнее, чем рекурсивная имплементация. Это изобретение будет использоваться в библиотеках начиная с первых же имплементаций ФЯ до относительно современных вроде mlton. +Это не код на McG, а ISWIM-псевдокод - со статьи 72-го года начинается упомянутое выше McG-стирание. +В статье 71-го года Бердж описал еще несколько функциональных EDSL. Комбинаторы парсеров теперь именно парсеры, а не матчеры, возвращают значение. Стримы называются "последовательностями", но все те же, что и в статье Ландина [Land65a]. Бердж, по всей видимости, положил начало популярной у функциональщиков демонстрации стримов с помощью нерешета Неэратосфена, но использовал не ту версию, которая используется в наши дни. +Бердж предложил параметризовать алгоритм функцией обхода структуры + +```python +def summing f = f 0 plus i; +def sum = summing list1; +``` + +Но не только потому, чтоб выбирать суммировать список обходя его `list1` или `list2`, а потому, что Бердж использует несколько структур, как современные функциональные программисты, не только одни односвязные списки, как было популярно у "рекурсивных программистов" того времени. +Но как описывать структуры в ФЯ того времени? Имплементировали ли в McG Ландинскую нотацию для сумм и произведений? Нет, но Бердж придумал ФП-EDSL для их описания. Описания структур собираются функциями `du` (direct union) и `cp` (cross product). + +```python +def rec tree = du(int,cp(tree,tree)); + +def atom, nonatom = predicates tree; +def atomclass, compoundclass = partition tree; +def left, right = selectors compoundclass; +def ctree x y = make compoundclass (x,y); +``` + +автоматизация генерации конструкторов, предикатов и геттеров, конечно, оставляет желать лучшего. Чтоб получить их для произведения приходится разбирать сумму функцией `partition`. +Статья 72-го года [Burg72] уделяет описанию структур больше внимания, к тому же у них появляются параметры. + +```python +def rec list A = du(cp(), cp(A,list A)); + +def null, nonnull = predicates (list A); +def nullc, nonnullc = parts (list A); +def h, t = selectors nonnullc; +def prefix x y = construct nonnullc (x, y); +def nullist = construct nullc (); +``` + +Да, в описаниях геттеров и предикатов A не определено. Это, правда, ISWIM-псевдокод, в статье с примерами на McG таких параметризованных определений не было. +Добавив к этим материалам подробностей, теории по ЛИ и комбинаторам и сведений об имплементации ФЯ Бердж написал первую современную книгу о функциональном программировании [Burg75], хотя по традиционному названию про рекурсию этого не скажешь. Книга оказала важное влияние на Эдинбургскую программу, к которому мы еще не раз вернемся. Переведена на русский язык [Берд83]. +В книге Берджа, по всей видимости, впервые опубликована жемчужина функционального программирования, с которой с тех пор неразрывно связана наша история. Но еще до издания книги, Ван Эмден привез эту жемчужину в Эдинбург. Ван Эмден был одним из многих героев нашей истории, которые приехали работать в Эдинбург (и его окрестности) в это время. Но в отличие от большинства из них, Ван Эмден вскоре стал героем не нашей истории. В одно темное утро в октябре 72-го года в Эдинбурге Боб Ковальски предложил Ван Эмдену показать программирование на логике. + +### Разделение + +Тот самый "квиксорт", который мы, функциональные программисты, так любим ненавидеть и ненавидим любить, показал Ван Эмдену имплементатор McG Левенворт. В 1972-ом году, в Йорктаун Хайтс [Emde06]. Самая ранняя версия, которая была опубликована, датируется 1975-ым годом [Burg75]. + +```haskell +def rec qs x = + if null x + then () + else let d = h x + let y, z = partition d (t x) + concat (qs y, u d, qs z) + where rec + partition d x = + if null x + then (), () + else let y, z = partition d (t x) + if h x < d + then h x:y, z + else j, h x:z +``` + +В книге Берджа он вводится как трисорт, в котором процесс построения (несбалансированного) дерева и его сплющивание в список объединены. Сортировка, в которой эти процессы происходят последовательно, описывалась и раньше [Barr68]. Почему же этот фьюзед-трисорт называют квиксортом в этой книге и позднее? Это все еще остается загадкой. +Трудно поверить, но существует разновидность программистов, на которых этот "квиксорт" произвел еще большее впечатление, чем на функциональных программистов. +Летом 70-го, во время очередного посещения, Ван Эмден стал свидетелем того, как в Эдинбурге завоевала для себя плацдарм еще одна исследовательская программа. Единственная разновидность программирования, к которому программисты вообще и в проекте МАК в частности относятся еще хуже, чем к функциональному: логическое программирование. +Неприязнь к зарождающемуся логическому программированию была в МТИ такой жгучей, что они не могли держать ее в себе и решили сделать о ней доклад в Эдинбурге. Доклад "Нерелевантность Резолюции" (The Irrelevance of Resolution) должен был прочесть Сеймур Пейперт (Seymour Papert), но он опоздал, и доклад сделал один из наших будущих героев, студент Джеральд Сассман (Gerald Jay Sussman). К концу доклада Сассмана приехал Пейперт и сделал доклад снова, еще раз. Двойное послание не могло быть более ясным: это не то. Сюда лучше не лезть. Серьезно, любой из вас будет жалеть. Лучше закройте тему и забудьте что писалось у Алана Робинсона. +После того, как позиция большого МАКа была выражена так недвусмысленно, у малого МАКа просто не осталось выбора. Чтоб разобраться, что же именно по мнению из МТИ лучше не делать, исследователи в Эдинбурге попытались изучить их собственные наработки в этой области. Знакомый Ван Эмдена Роберт Ковальски (Robert Kowalski), пострадал больше многих, ему пришлось разбираться с написанными на Лиспе PLANNER и CONNIVER, а он не знал и ненавидел Лисп. +К счастью, всего через несколько лет пришла помощь из Франции. Сначала ее анонс. В 1973 Ален Колмероэ (Alain Colmerauer) и Филип Руссель (Philippe Roussel) посещают Эдинбург и рассказывают о своей имплементации Пролога. Посетители из Марселя показали как писать конкатенацию списков, но, в общем-то и все. Как программировать на логике пока еще не знали. Но Ван Эмден придумал как написать на логике "квиксорт". И это была настоящая революция. Первая нетривиальная программа, _настоящий_ алгоритм. Теперь антирезолюционщики из МТИ увидят как они ошибались! +Имплементации логического программирования в Эдинбурге не было, но Мики решил попробовать запустить "квиксорт" на доказателе Бойера-Мура, и был доволен и горд, что решатель теорем смог сортировать список. +Только в феврале 74-го, когда в Эдинбурге уже появилась новая машина, Дэвид Уоррен привез из Марселя коробку с картами - имплементацию Пролога на Фортране. Возможность запускать программы имела большое значение и Ван Эмден написал и испытал много маленьких программ [Emde06], которые стали примерами в книге Элдера Коэлью (Helder Coelho) "Как решить это на Прологе", напечатанной намного позже, но до этого циркулировавшей как самиздат [Coel82]. Логический "квиксорт" ван Эмдена 73-го года в версии 75-го [Warr75] выглядел так: + +``` ++LET( + +QSORT(*L.*R,*Y,*Z):_ + SPLIT(*R,*L,*R1,*R2)& + QSORT(*R1,*Y,*L.*W)& + QSORT(*R2,*W,*Z); +QSORT(NIL,*Z,*Z) + +).+LET( + +SPLIT(*L.*R,*X,*L.*R1,*R2):_ + *L LE *X & SPLIT(*R,*X,*R1,*R2); +SPLIT(*L.*R,*X,*R1,*L.*R2):_ + *L > *X & SPLIT(*R,*X,*R1,*R2); +SPLIT(NIL,*X,NIL,NIL) + +). +``` + +Мы не ставим перед собой цели написать историю логического программирования, и временно прощаемся с ним, но раскол протоэдинбургской программы - не последнее влияние которое оно окажет на функциональное программирование. + +Следующие 700 изобретений продолжений +------------------------- + +> В ходе ранней истории продолжений основные понятия открывались независимо друг от друга выдающееся число раз. +> Дж. Рейнольдс, Открытия продолжений [Reyn93]. + +> Лучше бы я, конечно, выбрал название покороче. +> К. Вадсворт, Continuations Revisited [Wads2000] + +Связи между большинством остальных будущих участников Эдинбургской программы нам поможет установить Джон Рейнольдс (John C. Reynolds), написавший историю изобретения продолжений. + +### Рейнольдс и GEDANKEN. + +Физик, увлекшийся компьютерными науками [Broo14], Рейнольдс более известен другими своими работами, но в конце 60-х он был одной из упоминавшихся выше необоекембриджских исследовательских программ, независимо получивших ФП из Алгола и ЛИ, но состоящей из одного человека. Программа породила один ФЯ - GEDANKEN. Встречается мнение, что язык относится к ветви обоих Кембриджей и происходит от PAL, но Рейнольдс утверждает, что язык разработан большей частью до того, как его автор узнал о PAL [Reyn2012] [Reyn69]. Лучшим подтверждением этого является несомненная самобытность GEDANKEN [GEDANKb]: + +```haskell +MAP ISR #(F, L) IF L = NIL THEN NIL + ELSE CONS(F(L 1),MAP(F, L 2)); + +Y IS 2; +MAP(#X ADD(X,Y), (1, (2, (3, NIL)))); +``` + +Символ для лямбды `#` в статьях обычно заменен на `λ`. +Но только большей частью, Рейнольдс узнал о PAL еще до того как описал GEDANKEN в отчете [Reyn69] и позаимствовал из PAL энкодинг функций многих переменных с помощью функций одной переменной и туплов. +Все началось с того, что Рейнольдс придумал как описать структуры данных с помощью функций, но не так как мы (и, возможно, вы) подумали, а так: + +``` +CONS IS #(X, Y) #Z IF Z = 1 THEN X ELSE Y; +CAR IS #X X 1; +CDR IS #X X 2; +``` + +такая техника не работала в ALGOL и скомпилированном LISP 1.5 [Reyn2012] [Reyn69] и Рейнольдсу пришлось изобретать язык с лексической видимостью в котором можно возвращать функции. +Рейнольдс имплементировал GEDANKEN в апреле 69-го для того, чтоб проверить не допустил ли он ошибок в операционной семантике языка для статьи [Reyn70], просто переписав семантику максимально близко к её тексту на стенфордском LISP360, уложившись в 500LOC [GEDANK]. Описание семантики, впрочем, так в статью и не попало. Имплементация требовала заметно больше памяти, чем нужно и была "медленной как ледник". +В начале 70-х Рейнольдс предпринял было попытку более серьезной имплементации, но был "деморализован" тем, что не смог придумать как решить проблемы производительности. После появления Scheme и ML, Рейнольдс решил, что страдать с GEDANKEN больше нет смысла. Так закончилась его история, и история всей этой исследовательской программы. + +### Изобретено независимо. + +Более полезным для ФП были уже те работы, которыми Рейнольдс как раз известен, о параметрическом полиморфизме [Reyn74]. Подход Рейнольдса был более формальным, чем у Стрейчи, он не ограничился парой примеров на псевдокоде, а смог описать что параметрический полиморфизм означает и как работает. Работает он как лямбда-исчисление: + +``` +(Λt. λf ε t -> t. λx ε t. f(f(x)))[integer] +λf ε integer -> integer. λx ε integer. f(f(x)) +``` + +Работа Рейнольдса была проделана независимо от работы Жана-Ива Жерара (Jean-Yves Girard) о том же [Gira72]. Похоже, что работать над чем-то независимо от вас намного легче, если вы публикуетесь на французском языке. Хенк Барендрегт (Hendrik Pieter Barendregt) называет и другую причину: Жерар работал над параметрическим полиморфизмом для теории доказательств, а Рейнольдс для описания семантики языков программирования [Bare92]. +Эта же причина, уже по мнению Рейнольдса, была у множественного независимого переизобретения продолжений. Продолжения изобрели имплементаторы ЯП, изобрели занимающиеся операционной семантикой ЯП, изобрели занимающиеся денотационной семантикой ЯП. +Но что означают эти переизобретения? Что именно они переизобретали? Все эти люди читали и ссылаются на работы Ландина про J-оператор. Проблема в том, что Ландин изобрел продолжения как фичу интерпретатора, определенную через изменения состояния интерпретатора. И те, кто определяли семантику языков через интерпретатор вслед за Ландином, хотели интерпретатор попроще. Для занимающихся денотационной семантикой интерпретатор был проблемой в принципе. То что все они хотели изобрести - это энкодинг `goto` и вызова по значению в ЛИ, т.е. CPS. Имплементация языков программирования с помощью CPS в Эдинбургской программе не будет применяться еще долго, так что мы вернемся к ней в рассказе о других попытках сделать ФЯ, результаты которых Эдинбургская программа в конце концов все же позаимствовала. +Итак, Рейнольдс делает вывод о том, что продолжения переизобретали в меньшей степени из-за плохой коммуникации между исследователями, а больше из-за разнообразия областей в которых продолжения посчитали полезными [Reyn93]. Но из описываемых им самим событий напрашивается противоположный вывод. +CPS-преобразование изобрел научрук Дейкстры и Ван Эмдена и автор алголов Адриан Ван Вейнгаарден (Adriaan van Wijngaarden) и сделал о нем доклад в 1964. И на этом докладе присутствовали как операционщики, например Ландин, так и денотационщики, например Стрейчи. Но благополучно забыли и/или не поняли о чем речь. Реакция имплементаторов была еще хуже, так что к этому эпохальному моменту мы еще вернемся. +В ноябре 70-го Локвуд Моррис (Lockwood Morris), защитивший диссертацию в Стенфорде и преподающий в Эссекском университете, выступил с докладом "Следующие 700 формальных описаний языков" [Morr93] по приглашению Ландина в Лондонском колледже (сейчас - университете) королевы Марии (Queen Mary College (University of) London). Моррис работал в Стэнфорде с МакКарти, который слушал доклад Вейнгаардена, но, видимо, забыл про него. Так что Моррису пришлось переизобрести продолжения самостоятельно. Нельзя сказать, что МакКарти вовсе не помог ему. Как мы уже упоминали выше, одной из идей вдохновивших Морриса была техника обработки ошибок из LISP 1.5 [McCa62] и статьи МакКарти [McCa60]. Другое влияние - механизм обработки ошибок в Snobol. +Моррис называл продолжения "дампы" (по названию регистра SECD-машины) и "значения-метки". +Рейнольдс слушал доклад, первый раз увидел использование продолжений. Из этого произошла его собственная работа над определяющими интерпретаторами, которая популяризировала продолжения [Reyn72]. Еще до издания статьи Рейнольдс популяризировал продолжения докладами. В 71-ом Левенворт (McG) приглашает Рейнольдса и Артура Эванса (PAL) провести семинар по применению лямбда-исчисления в программировании [Reyn98]. +Рейнольдс отмечает, что во время обсуждения доклада Морриса Ландин ничего не говорит о работе Вейнгаардена, хотя еще в середине шестидесятых помнил о ней и ссылался на неё. +В декабре 70-го Рейнольдс посещает университет Эдинбурга. Там он рассказывает про доклад Морриса Бурсталлу и Крису Вадсворту (Chris Wadsworth), пишущему в это время диссертацию под руководством Стрейчи в Оксфорде. Вадсворт в свою очередь рассказал, как переизобрел продолжения он сам. Буквально вот только что. +В октябре 1970 Стрейчи показал Вадсворту препринт “Proving algorithms by tail functions” Мазуркевича (Mazurkiewicz), которую он получил на встрече IFIP WG2.2. Напечатали статью только в 71-ом. Рейнольдс затрудняется назвать её переизобретением продолжений, но считает её как минимум шагом к такому переизобретению [Reyn93]. +Но Вадсворту одной фразы "tail functions" в заголовке оказалось достаточно - все встало на свои места [Wads2000]. До этого они со Стрейчи два года безуспешно пытались изобрести энкодинг `goto` и меток с помощью лямбд. Разумеется, Стрейчи тоже слушал Ван Вейнгаардена, но это не помогло. Вадсворт называл продолжения продолжениями - именно его термин стал ходовым. С ноября они распространяли рукопись, но Стрейчи считал что не стоит торопиться публиковать статью, она должна настояться. Да, именно поэтому некоторые из статей Стрейчи, на которые мы тут ссылались, изданы через десятилетия после его смерти или не изданы вовсе. Эта статья, правда, была напечатана еще при жизни Стрейчи в 74-ом, после того как Вадсворт защитил свою диссертацию на более важную для Эдинбургской программы тему, чем продолжения. +Хотя в Эдинбургской программе соберутся использовать CPS-преобразование для имплементации ФЯ еще не скоро, там собрались имплементировать продолжения как фичу языка. И без первого результаты второго им не особенно понравились. +Бурсталл хотел имплементировать J оператор Ландина и урезанная версия продолжений, соответствующая по мощности исключениям была в POP-2 с самого начала. Полноценные продолжения имплементировали в 1970, но сохранение стека копированием в хип было дорогим [Popp2002]. Не могло быть и речи об использовании этого как основной управляющей конструкции. +В Оксфорде Стрейчи - научрук Дэвида Тернера (David Turner), посоветовал ему имплементировать PAL эффективно, что Тернер безуспешно пытался сделать три года с 69-го по 72-й. И главной проблемой с производительностью Тернер посчитал первоклассные метки в PAL, т.е. продолжения [Turn19]. Позднее Тернер найдет, что использовать вместо продолжений: наработки из диссертации Вадсворта и книги Берджа. +Для участников Эдинбургской программы продолжения - сложная в реализации и медленная фича, а использование CPS-преобразования для имплементации ФЯ - экзотическая и непопулярная техника. В результате, не смотря на количество переоткрывателей продолжений в Эдинбургской программе, продолжения не найдут себе в ней важного места. +Из воспоминаний Ван Эмдена и Рейнольдса мы знаем, что какая-то связь между будущими компонентами эдинбургской программы поддерживалась. Достаточная для того, чтоб узнавать о чем-то до публикации, но недостаточная для того чтоб сразу обнаруживать что работа ведется над одним и тем же. Да, в отличие от обоекембриджцев они хотя-бы слушали, что говорят другие, находящиеся в той же комнате. Но они не так и часто бывали в одной комнате, так что коммуникация все ещё оставляла желать лучшего. +К счастью, приближается момент, когда это взаимодействие улучшится настолько, что можно будет уже говорить о единой Эдинбургской исследовательской программе, внутри которой переизобретение велосипедов минимально. И в эту доинтернетную эпоху такой результат мог быть достигнут только одним способом - перемещением основных участников в одну местность. Что они, по счастливому совпадению, и сделали. +Читая все эти воспоминания о ранних годах Эдинбургской программы, мы никак не могли избавиться от странного ощущения. Чего-то в них не хватает такого, что там точно должно быть. Но нет. +Что странно во всех этих воспоминаниях - это отсутствие упоминаний Робина Милнера. + +Типо-теоретическая альтернатива +--- + +### Робин Милнер + +> Я посчитал программирование довольно неэлегантным. <..> Мне показалось, что программирование - это не очень красивая вещь. Поэтому я решил, что больше никогда в жизни не подойду к компьютеру! <..> И устроился в Ferranti, где я работал программистом три года. <..> Да, я взялся за это без особого энтузиазма, но я решил, что надо же найти какую-то работу. +> Робин Милнер, интервью 2003-го года. [Miln2003] + +На протяжении всей этой истории Робин Милнер был с нами: как многие наши герои, он учился в Кембридже [Miln2003]. Как многие наши герои работал программистом в Ferranti [Miln93] [Plot2000], как многие наши герои напрограммировался и ушел в академию. Милнер пару раз посещал лондонский подпольный семинар [MacQ15], когда преподавал в Лондонском городском университете (City, University of London) в 63-ем. Познакомился с Ландином, Стрейчи и Бурсталлом [Plot2000] [Plot10]. Интересовался CPL, дружил с Дэвидом Парком [Miln2003]. Но герои и наблюдатели за героями тех времен не рассказывают как кто-то научил его лямбда-исчислению в баре, или как он научил кого-нибудь функциональному программированию в баре. Никто не придумывает историю о том, как собака съела его диссертацию, и поэтому он ее не защитил (как многие наши герои). Обоекембриджцы и ранние Эдинбуржцы не заметили самого важного, по мнению многих, отца функционального программирования. +Которым он мог бы и не стать. Во время работы в Университетском колледже Суонси (University College of Swansea, сейчас Университет Суонси) в 68-70гг. Милнер увлекся резолюционизмом, восхищался результатами Робинсона, и мы могли бы потерять его как Ван Эмдена. К счастью, Милнер написал автоматический доказатель теорем и тяжелый опыт безуспешных попыток делать с его помощью что-то интересное убил в Милнере резолюциониста. Или, по крайней мере, на какое-то время обезвредил [Miln93] [Miln2003]. +После Суонси, в 71-ом году Милнер отправился работать в страну, из которой в Великобританию приезжали все те выдающиеся мыслители чтоб отговорить от занятий всеми этими робинсонизмами - США. Отправился в Стенфорд, к МакКарти, который перешел туда из МТИ. + +### Для вычислимых функций + +В Стенфорде Уитфилд Диффи (Whitfield Diffie), более известный другими своими работами, научил Милнера писать на Лиспе [Gord2000], но не научил любить Лисп. +По собственному заявлению Милнера, Лисп повлиял на дизайн его будущего ФЯ [Miln93], но в основном не примером того, как надо делать, а примером того, что надо делать по другому. Что, как мы увидим дальше, будет довольно обычным подходом в Эдинбургской программе. +В Стенфорде хотели сделать что-то практическое и работающее, хотя бы в прямом смысле - выполняющееся на компьютере. +И, по мнению Милнера в это время, практическое и работающее - это инструмент проверки доказательств для логики Скотта [Miln93] [Miln2003]. +"Инструмент проверки" означает, что над доказательством работает в основном человек, причем от него требуются не только интересные идеи, но и много, много рутинного труда [Gord79]. +Логика Скотта, которую Милнер назвал Logic for Computable Functions или LCF - это неопубликованная в свое время работа Скотта "Типо-теоретическая альтернатива для ЕВПОЧЯ, КАЧЕ, ИВТП" [Scot93]. Основную часть статьи Дана Скотт (Dana Stewart Scott) написал в октябре 1966 во время посещения исследовательской группы по программированию (Programming Research Group) Стрейчи в Оксфорде. В ней Скотт посмеивается над нетипизированным подходом, который используют Бём, Стрейчи и Ландин. Как мы помним, Стрейчи советовал побольше тянуть с публикацией на тот случай, если со временем выяснится, что статью лучше не публиковать [Wads2000]. Скотт не вспоминает, советовал ли ему так делать Стрейчи, но все сработало как было задумано. +Долго настаивать статью не понадобилось. Уже через месяц у самого Скотта появились кое-какие нетипизированные идеи. Перечитывать собственные шуточки над нетипизированной логикой Скотту стало стыдно и он решил статью и не публиковать [Scot93]. +Но у читавших препринт Милнера и Плоткина отношение к статье не изменилось и они продолжали распространять копии. В результате в 93-ем её называют самой известной неопубликованной рукописью в PLT [Gunt93], и в том же году её конечно же опубликовали. +Скотт переписывался с Милнером и рекомендовал ему отказаться от типизированной логики, которая больше не нужна. Это не помогло. Милнер ответил, что имплементация уже почти написана, не переписывать же ему все заново [Gord10]. +В 71-72гг Милнер и присоединившиеся к нему Ричард Вейраух (Richard Weyhrauch) и, позднее, Малкольм Ньюи (Malcolm Newey) написали LCF [Miln82] [Gord2000] на MLISP2 на PDP-10 [Miln72]. MLISP 2 - это "алголообразный" синтаксис для Лиспа, для тех, кто не любит скобки [Smit73]. Что-то вроде POP-2. + +``` +EXPR MAP(F,L); +IF NULL(L) THEN L +ELSE F(CAR L) CONS MAP(F,CDR TERM); +``` + +В Суонси Милнер хотел попробовать верифицировать какую-нибудь "реальную" программу, и программа которую он нашел была написана химиком на Фортране, с матрицами, хранящимися в одном массиве [Miln2003] и другими характерными Фортран-вещами, верификация которых не задалась. Так что на этот раз он верифицировал очень простой компилятор очень простого языка в код очень простой машины, что получилось гораздо лучше [Miln93] [Miln2003]. +В своей диссертации (выпущенной как отчет в январе 75-го) Ньюи описывает планы на будущий LCF2 [Newe75]. +LCF был по большей части системой проверки доказательств с не особенно развитой автоматизацией в виде нескольких предопределенных простых команд. Такой инструментарий использовать утомительно, так что в LCF2 нужно двигаться в сторону генерации доказательств, для этого нужен высокоуровневый командный язык с фичами высокоуровневых ЯП. Насколько высокоуровневых? Первоначальные планы не выглядят амбициозными. Идея о том, что доказатель должен управляться императивным кодом не нова. Это, по большому счету то, что МИТ продвигал как альтернативу резолюционизму, т.е. (Micro)PLANNER [Hewi09]. Но авторы LCF не планируют, что этот императивный язык будет Лиспом. Они хотят типизированный язык. Ньюи предлагает по крайней мере четыре типа для объектов доменной области, процедуры, функции, операторы ветвления и циклов, блоки кода. Может быть даже функциональные параметры у процедур. +Поскольку после типизированной логики Скотт стал продвигать нетипизированную, для Ньюи это еще вопрос, будет ли язык в следующем LCF типизированным или нет. Но скорее всего будет. Просто потому, что первый LCF типизирован. Нужно заметить, что ранее, в январе 72-го года такой вопрос не стоял. Милнер писал [Miln72], что само собой разумеется, что в следующей версии логика будет нетипизирована, ведь Скотт перешел на нетипизированную. Это, впрочем, не должно было оказать прямого влияния на то, будет ли типизирован новый командный язык. +Милнер не хотел больше жить в США, хотел жить и работать в Великобритании, желательно в Оксфорде, где он бывал и где познакомился с работами Стрейчи и Скотта. Так что окончательная консолидация Эдинбургской программы могла бы и не состояться. Но еще больше Милнер хотел жить на одном месте, а не менять университет каждые пару-тройку лет. И постоянную позицию давал Эдинбургский университет, а не Оксфорд [Gord10]. + +### Три мили разделения + +Робин Милнер начал работать в Эдинбургском университете в 73-ем [Gord2000] [Plot2000] [Miln2003]. Но не с нашими старыми знакомыми Бурсталлом и Поплстоуном в Отделе экспериментального программирования, который в это время назывался Департамент машинного интеллекта (Department of Machine Intelligence) [Howe07]. И хорошо, что не там, но об этом позже. +Милнер работал в Департаменте компьютерных наук в Кингс Билдингс, а бывший Экспериментальный отдел программирования располагался на Хоуп Парк Сквер [Ryde2002] в трех милях от него. +Нельзя просто так взять и пройти/проехать три мили. МакКвин называет это расстояние серьезным барьером между двумя функциональными сообществами. Сообщества поддерживали связь с помощью того, что МакКвин называет "обществом памяти Боба Бойера". Бойер (Robert Stephen Boyer) не умер, а ушел из университета работать в SRI. Общество памяти собиралось раз в две недели дома у Бурсталла или у Милнера. Потому, что все остальные "ютились в лачугах", вспоминает МакКвин [MacQ15]. +Итак, Милнеру не понравился ни доказатель теорем, который все делает сам, но ничего интересного не может доказать, ни доказатель, который что-то может проверить, но ничего не интересного не делает сам. Милнер с Ньюи еще в Стенфорде решили, что пользователь должен иметь возможность автоматизировать рутинные действия программами на высокоуровневом языке. +И в Эдинбурге проект этого языка стал существенно амбициознее. По всей видимости потому, что изменилось отношение Милнера к автоматизации. Гордон считает, что Милнер поверил в более амбициозную автоматизацию, ознакомившись в Эдинбурге с работами Бойера и Мура (J Strother Moore) [Gord10]. Мы не видели прямого утверждения Милнера об этом, но предположение косвенно подтверждается ссылками Милнера на их доказатель теорем как пример того, что можно было бы имплементировать на скриптовом языке для нового LCF [Miln76] [MilnBird84]. +Бойер и Мур тоже занимались резолюционизмом, как и полагалось в Эдинбурге. С помощью своей резолюционной системы Baroque они могли доказать существование списка из трех элементов и прочие не особо интересные вещи. Так что летом 72-го года они решили автоматизировать индукцию и в 73-ем году получили не только автоматический, но и быстрый доказатель, который за несколько секунд доказывал что-нибудь вроде ассоциативности конкатенации списков [Boye75] [Moor13]. Проблема в резолюционизме, а не в автоматизации. +Раз уж автоматизация это хорошо и быстро, то нужен скриптовый язык подходящий для написания более-менее серьезных объемов более-менее сложного кода, вроде POP-2 на котором писали Бойер и Мур (вынужденно, больше не на чем было, как мы писали выше). +Сыграла роль и ФП культура в Эдинбурге и бэкграунд участников проекта. Проект по созданию следующей версии LCF - Edinburgh LCF был начат в 1974-ом году [Miln82] [Miln93]. Первыми ассистентами Милнера были работавший с ним в Стенфорде Малкольм Ньюи и, упоминаемый нами раньше, один из изобретателей продолжений Локвуд Моррис [Miln82]. Эти трое и являются основными авторами первой версии языка [Gord2000]. + +### Метаязык + +В 82-ом году Милнер описал радикальный взгляд на MetaLanguage (ML). На самом-то деле это DSL: язык, спроектированный для более-менее одной задачи, в отличие от других ФЯ. Но вышло так, что ML оказался хорошим языком общего назначения. Этот факт открыли не его авторы, а посмотревший на ML свежим взглядом будущий герой нашей истории Карделли. +Милнер заявляет, что дизайн языка определен его целью в такой степени, что едва ли получится сделать хороший язык для скриптования системы доказывания теорем, который бы существенно отличался от ML [Miln82]. Это утверждение выглядит странным хотя-бы потому, что в той же статье рассматриваются дизайн-решения, которые сам же Милнер считает сомнительными, ну или по крайней мере не несомненными. И эти решения из тех, что существенно влияют на дизайн языка. Сам Милнер со временем изменит свое мнение об одном из этих решений и будущий ML станет таки заметно отличаться от Edinburgh LCF версии ML-я. +В материалах 78-79гг о ML фрейминг противоположный. ML - это язык общего назначения, уверяют авторы. Ладно, это не совсем так, но специализирован он не потому что в нем чего-то нет, а потому что в нем есть цитаты и антицитаты для конкретного синтаксиса логики нового LCF - PPλ (Polymorphic Predicate λ-calculus): + +``` +"F X == Y & ^(mkinequiv("Z:^t ", x))" + where t = ":tr" and x = "X" +``` + +отчего ML и называется "метаязык". Также, в нем есть встроенные структуры для абстрактного синтаксиса PPλ. Да, встроенные объекты предметной области. Но это уже исправлено добавлением возможности описывать такие структуры программисту, так что можно считать, что язык общего назначения [Gord78] [Gord79]. +Хотя исследования в области дизайн языков программирования не были главной целью создания языка, авторы считают, что новые фичи в языке подходят для использования. Авторы отмечают, что когда говорят про "эксперименты" и "исследования" фич языка, это значит, что не то чтоб они проводили какие-то исследования, хотя бы даже только и сравнительные с другими языками. Они просто имплементировали новые фичи, и пользователи языка вроде как быстро осваивают их и довольны [Gord79]. Спасибо за честность, но авторы языков обычно об этом и говорят, когда говорят про "эксперименты". +И раз уж исследования в области дизайн языков программирования не были главной целью создания языка, авторы решили не экспериментировать со многими другими фичами. +Другими словами, ML - это ISWIM с некоторыми важными инновациями и инструментарием для манипуляции PPλ-кодом. Но, как мы помним, ISWIM в каждой статье и в каждой имплементации разный. Это ISWIM с лямбдами как PAL или с каррингом как McG? ISWIM с `if then else` как в статьях Бурсталла, или с CPL-ным тернарным оператором как в статьях Ландина? ISWIM с туплами или со списками? +Да. + +### Предыдущие 700 ISWIMов в одном + +Авторы заявляют, что ML это ФЯ "в традиции" ISWIM [Gord78] [Gord79] и PAL [Gord78], что хорошо видно: + +```haskell +letrec map f l = null l => nil + | f(hd l) . map f (tl l);; + +map (\x. x + y) [1; 2; 3] where y = 2;; +``` + +Также, "в традиции" GEDANKEN и POP2 [Gord78] [Gord79], чего не видно вовсе. +И если язык несомненно "в традиции" PAL, то стандартная библиотека LCF/ML вовсе не в традиции PAL и содержит богатый набор ФВП. +Милнер разработал полностью функциональный дизайн в стиле Берджа (на книгу которого Милнер и др. ссылаются [Miln78] [Gord78] [Gord79]) со всеми соответствующими требованиями к языку. +Пользователь системы применяет к целям тактики. Тактика возвращает набор подцелей и валидацию: функцию, для того чтоб из достигнутых подцелей получить цель. Валидации - функции из теорем в теоремы - должны быть объектами как и цели. Так что тактика - функция, возвращающая функцию. Валидация в общем случае создает замыкание, поэтому нужна лексическая видимость. +Пользователь строит тактики из других тактик с помощью комбинаторов тактик. Часто не только тактик, а просто функций. Функций принимающих и возвращающих функции [Miln82]. +Эти стандартные комбинаторы помимо обычных `(.)` (в LCF/ML называется `o`), `id` (`I`) и `const` (`K`) включают гораздо более экзотические, соответствующие функциональным специализациям функций стрелок и аппликативов вроде `(&&&)` (`commaf`), `(***)` (`#`) и `liftA2` (`oo`). Есть даже отсутствующий в хаскельной стандартной библиотеке комбинатор для композиции функций вроде `concat` и `map`: + +```ml +let $o2 (f,g) x y = f(g x y);; +``` + +[LCF77] `$` тут означает, что объявлен инфиксный оператор. +Другими словами, с точки зрения доктрины Милнера 82-го года, функциональность ML была предопределена тем, что у Edinburgh LCF функциональный дизайн. Это логично, но рассуждение тут циркулярное. Сам функциональный дизайн для системы доказания теорем вовсе не неизбежен. Так, в первой версии его в основном удалось избежать. Мы не утверждаем, что нефункциональный дизайн был бы хорошей идеей, но идеи не обязаны быть хорошими. +Милнер в 60-е увлекался "симуляцией", что в основном было тем, что сейчас называют ООП [Miln2003]. Так что мы вполне могли бы потерять Милнера, как мы потеряем одного из будущих главных героев нашей истории. Угроза ООП вполне реальна. +До того, как Милнер решил, что все было предопределено самой целью сделать скрипт для LCF, ML был ФЯ просто потому, что это то, что делают авторы языков с таким бэкграундом в такие времена и в таком месте. +Что до предопределенности, то решение сделать скрипт не то что функциональным языком, а еще и ISWIMом, на самом деле, серьезно мешает имплементировать более важное свойство скрипта, с которого его изобретение и начиналось. +Ньюи пишет о типизированном скрипте в первую очередь, а о функциональных параметрах только по возможности [Newe75]. И эта возможность в то время была совсем не очевидна. Типизированные ФЯ в то время не то чтобы далеко продвинувшееся направление, но типизированный ISWIM это еще более сложный вызов. Как воплотить весь этот псевдокод без аннотаций типов в реальность? + +### Невыносимая тяжесть аннотации + +Почему скрипт для LCF должен был быть типизированным? +Важно, чтоб пользователь мог применять валидации только к теоремам, а не любым объектам, которых получилось много видов. Нетипизированный язык обходится дорого, утверждает Милнер, нужно тратить время на поиск ошибок. Так что нужны типы [Miln82]. +Гордон обычно акцентирует внимание не на том, что нужно тратить время, а на том что надо тратить память. +Только процедура доказательства может производить значения типа `thm` так что, пока система типов обеспечивает безопасность, нельзя получить, например, утверждение `True == False` типа `thm`. Это освобождает от хранения в памяти деревьев для доказанных утверждений. Что было проблемой для Stanford LCF [Gord79] [Gord10]. +Функциональная архитектура Милнера не требует какой-то продвинутой типизации: + +```ml +deftype goal = form # (simpset # form list) + and proof = thm list -> thm;; + +deftype tactic = goal -> goal list # proof;; +``` +[LCF77] + +Композить эти функции можно также специализированными комбинаторами с простыми типами. Очередной случай, когда неизбежность ML сильно преувеличена. То, чего хватило бы LCF-скрипту, Милнеру не достаточно. +Решено делать функциональный язык. И, отмечает Милнер, это предполагает определение функций, которые хорошо работают с широким разнообразием объектов. Эта гибкость практически необходима для такого стиля программирования. Типизация как, например, в ALGOL 68 запрещает эту гибкость и следовательно запрещает весь такой подход к программированию [Miln78]. Декларировать новую функцию `map` для каждого нового типа "невыносимо" [Miln82]. Милнер ссылается на доклад Стрейчи про параметрический полиморфизм [Stra67]. Это как раз то, что нужно! Но Милнеру нужно больше. Для него "невыносимо" даже явно указывать тип параметра для параметрически полиморфной функции `map` [Miln82]. Все типы должны выводиться. +Это очень высоко установленная планка, практически все авторы других языков с параметрическим полиморфизмом, которые появлялись в то время вполне могли это вынести. И уже второй язык в котором применили наработки Милнера требовал указывать типы функций. Справедливости ради, это использовалось для имплементации фичи, которой не было в ML, и это не был командный язык в REPL доказателя теорем, для которого не указывать типы важнее. Тем не менее, вывода типов в ФП без этих аномально высоких требований могло бы и не быть, не смотря на то, что алгоритм вывода типов был изобретен независимо несколько раз. Дело в том, что он обычно изобретался не программистами, и никакой программист кроме Милнера не смог довести изобретение до конца. +Первым изобрел алгоритм, частично предвосхитивший остальные обсуждаемые тут, Максвелл Ньюман (Maxwell Herman Alexander Newman) в 42-ом году. Скорее всего, ни один из прочих изобретателей алгоритма не видел этой работы на момент своего изобретения той или иной степени независимости. Хиндли узнал об этой работе Ньюмана только в 2005-ом году [Hind07]. +Ключевую часть алгоритма разработал не позднее 54-го года Кэрью Мередит (Carew Meredith), он был использован в статье вышедшей в 57-ом, но не определен в ней формально. Двоюродный брат Кэрью Дэвид Мередит запрограммировал алгоритм на UNIVAC1 в том же году. +Карри продемонстрировал алгоритм вывода типов на примерах и описал неформально [Curr58] в 58-ом году. С этой работой знакомы, наоборот, все последующие изобретатели. Карри описал алгоритм формально и доказал его корректность в 66-ом, но опубликовал только в 69-ом. Другой алгоритм, использующий алгоритм унификации был изобретен Хиндли (J. Roger Hindley) в 67-ом и опубликован в 69-ом [Hind07]. Карри и Хиндли знали о работах друг друга и общались во время их написания. Различные, но эквивалентные алгоритмы в этом случае осознанный выбор, а не результат независимого открытия [Card2006]. +Об этом изобретении Милнер узнает уже после собственного изобретения [Miln78] [Miln82]. Гордон считает, что Милнер и Хиндли практически наверняка знали друг друга, когда работали в Суонси, но, видимо, не говорили про типы [Gord10]. Это, наверное, довольно нормально. Например, Рейнольдс в апреле 74-го выступал с докладом о своем типизированном ЛИ в Париж VII где преподавал Жерар, но никто там не сказал ему, что вот у нас тут Жерар тем же самым занимается [Card2006]. +Работой, о которой Милнер знал, была диссертация Джеймса Морриса. + +### Смотрите, не перепутайте Моррисов + +Джеймс Моррис (James Hiram Morris, Jr.), дальний родственник Локвуда Морриса [Reyn93], уже появлялся в нашей истории. Он писал вместе с Ландином первую имплементацию PAL. Моррис один из тех загадочных авторов PAL, которые проводили много исследований в области ЯП, но не использовали для этого PAL. Впрочем, обсуждаемая работа Морриса частично решает эту загадку: ее результаты не могут быть использованы в таком языке как PAL. +В своей диссертации 68-го года [Morr68] в МТИ Джеймс Моррис описал алгоритм вывода типов для типизированного ЛИ на основе решения уравнений в стиле Карри. Моррис знал о работе Карри 58-го года [Curr58] но не о работах Карри и Хиндли 66-69гг [Card2006] [Hind07]. +Независимое изобретение вывода типов только одно из аналогичных независимых изобретений Морриса. Он также изобрел равенство по Лейбницу независимо от Лейбница и алгоритм Кнута-Морриса-Пратта независимо от Кнута. Как и значительная часть героев этой истории, Моррис независимо изобрел продолжения, к чему мы еще вернемся в главе про изобретения продолжений имплементаторами. Почему же мы читаем о Моррисе в главе про Милнера, а не о Милнере в главе про Морриса? Моррис, как обычно бывает у обоекембриджцев, не был особенно успешен в практическом плане. +Алгоритм Морриса может выводить типы для типизированного ЛИ, расширенного операторами над типами их конструкторами для построения композитных структур (к которым мы еще вернемся). На первый взгляд это то, что нам нужно как основа для ISWIM с типами, но Моррис обнаружил ряд проблем, которые делали его расширенную лямбду слишком скучной для языка программирования. +Начнем с того, что Y-комбинатор на этом языке не проходит проверку типов. + +```haskell +> y = \f -> (\x -> f(x x))(\x -> f (x x)) +cannot construct the infinite type: t0 ~ t0 -> t +``` + +Хуже того, если мы попробуем дать имя какому-нибудь подвыражению, то выражение может перестать проходить проверку типов. Например, когда функция, которой мы дали имя, применяется к значениям разных типов. + +```haskell +> (\twice -> (twice tail "FOO", twice not True))(\f x -> f(f x)) +Couldn't match type `Bool' with `[Char]' +``` + +Но что это за функциональное программирование, если мы не можем объявить полиморфную функцию? +Погодите-ка, эти примеры не работают и в современных ФЯ, в которых можно объявлять рекурсивные и полиморфные функции. Может быть система Морриса не так и сильно от них отличается, и он в паре-тройке шагов от успеха? Так и есть, и он даже сделал один из этих шагов. +Моррис расширил свой язык операцией `rec`, объявлять Y-комбинатор больше не нужно. Решение, правда, только частичное. Бесконечные типы можно получить и другими способами, в коде для стримов, например. Любые рекурсивные типы в языке Морриса запрещены, S-выражениями с типом + +``` +S = A + S x S +``` +пользоваться нельзя, хотя Моррис считал, что ограничение можно ослабить. +Проблема полиморфизма (Моррис, конечно, ссылается на Стрейчи [Stra67]) так просто ему не далась. Точнее, проблема полиморфных функций, определяемых программистом, как сам Моррис её сформулировал. Встроенных полиморфных функций он добавил целую кучу, все эти конструкторы и селекторы для композитных типов, а потом еще и `rec`. Все что придумал для решения этой проблемы Моррис - это поредуцировать немного выражение прежде чем типизировать его, изобретя таким образом шаблоны C++ независимо от Страуструпа. Не видели, чтоб кто-то приписывал ему это изобретение. Но может это и хорошо, когда вам не приписывают изобретение шаблонов. + +```haskell +> ((\f x -> f(f x)) tail "FOO", (\f x -> f(f x)) not True) +("O",True) +``` + +Сработало! Но какой ценой? Моррису и самому такое решение не особенно понравилось. +К концу диссертации Моррис совсем пал духом и пишет, что может типизация это не для ФЯ? Да, в каком-нибудь BCPL любое применение функции не к тому значению - неопределенное поведение. В LISP не всегда, там как повезет. Но если не повезет, то только держись. Если применить списочные селекторы к атому, можно получить список - таблицу его свойств и продолжить весело обходить её, например. Надо просто проверять все в рантайме и поскорее падать как в PAL, если что не так. Может быть этого будет достаточно? +Такое пораженчество Милнера, конечно, не устроило. +Проблему определяемых программистом полиморфных функций решил Рейнольдс: + +```haskell +> (\(twice :: forall a. (a -> a) -> a -> a) -> (twice tail "FOO", twice not True))(\f x -> f(f x)) +("O",True) +``` + +Но, как мы помним, аннотировать типы "невыносимо". +Вы, вероятно, недоумеваете, к чему все эти сложности. Функциональный программист объявляет функции не так, а вот так: + +```haskell +> let twice f x = f (f x) in (twice tail "FOO", twice not True) +("O",True) +``` + +Но для обоекембриджца `let f arg = body in ... f ...` это то же самое, что `(\f. ... f ...)(\arg. body)`, как завещал Ландин. В языке Эдинбургской программы это не одно и то же. Что помешало Моррису использовать то же самое решение, которое он уже применил к проблеме Y-комбинатора? Сделать специальный `let` со своим правилом типизации, как он сделал специальный `rec`? Мы не знаем. Но потому, что он этого не сделал, а Милнер - сделал, вы сейчас читаете параграф о Моррисе в главе про Милнера, а не параграф о Милнере в главе про Морриса. + +### Возвращение резолюционизма + +Милнеровский опыт резолюционизма в очередной раз повлиял на историю ФЯ, когда Милнер не стал использовать подход Морриса с решением уравнений. Милнеровский алгоритм W основан на алгоритме унификации Робинсона. Проблема в том, что алгоритм W медленный. Дорогая операция подстановки применяются слишком часто. Медленные алгоритмы - это нормально для резолюционистов, но потому они и наработали массу способов делать медленные вещи быстро. Милнер использует идею, знакомую ему из литературы по доказателям теорем, основанных на методе резолюций. Подстановки композируются, но применяются только тогда, когда это необходимо. Получается алгоритм J, который и использован для вывода типов в LCF/ML [Miln78]. +Хиндли придумал использовать алгоритм унификации раньше, о чем Милнер узнал не позднее 77-го года [Miln78]. А мог бы на десятилетие раньше, если бы интересовался тем, над чем работают его коллеги по университету [Gord10]. Хиндли, правда, не изобретал полиморфный `let`, изобретя который Милнер решил главную проблему системы Морриса [Miln78]. +Милнер знает, что достигнута не вся "гибкость", которая возможна в нетипизированном языке и даже в языке, где надо аннотировать полиморфные типы. Он знает о работах Рейнольдса [Reyn74], языке EL1, работе Лисков и др. над будущим CLU, к которой мы еще вернемся. Также Милнер ссылается на некоторые языки, публикации о которых появились позже, чем его система типов была имплементирована, так что едва ли эти работы уже могли повлиять на его решение. +Милнер заявляет, что предпочитает так много "гибкости", сколько возможно без явного указания параметров типов и не больше. Заявления заявлениями, но на практике последующие авторы ФЯ будут хотеть немного побольше. И даже сам Милнер в той же статье, в которой он делает это заявление [Miln78]. +Милнер чувствует, что не все разделяют его мнение по "невыносимости" (обязательной) аннотации. "Можно спорить," - допускает он - "что отсутствие аннотаций типов затрудняет понимание". Свой подход он защищает так: + +* Не аннотировать типы удобно, особенно в однострочниках. +* Тайпчекер настолько простой, что обязательное указание типов не сделает его заметно проще. +* Аннотируйте если хотите, возможность аннотировать есть. + +И хотя о необходимости вывода типов "можно спорить", он определенно сыграл важную роль в развитии Эдинбургской программы, произведя впечатление на людей, сыгравших важную роль. Многие наши будущие герои описывают первый опыт с ML REPL как чудо, волшебство [Augu21] [Plot10]. И если уточняют, что было таким волшебным, то говорят про вывод типов, о том как REPL выдает тип функции которую они только что набрали. Чудо выглядело так: + +```ocaml +#letrec map f l = if null l then [] +# else f(hd l).map f (tl l);; +map = - : ((* -> **) --> ((* list) -> (** list))) + +#map (\x.x*x) [1;2;3;4];; +[1; 4; 9; 16] : (int list) +``` +[Gord79] + +Итак, Милнер потребовал от нового языка нечто невиданное, о чем Обоекембриджцы могли только мечтать, и все у Милнера получилось. И Милнер утверждает, что повезло, что система типов, которая понадобилась для этого такая простая, а её имплементация такая элегантная [Miln82]. +Милнер отмечает, что он не работал над преобразованиями типов и перегрузкой, но думает, что это все может быть интегрировано [Miln78]. И, походя отмахнувшись от одной из главных проблем, с которой будут десятилетиями страдать его последователи, переходит к тому, что пошло не так с самого начала. На самом деле не все так просто и элегантно. + +### Mutatis mutandis + +Все (прото)функциональные языки, о которых мы писали до сих пор имели операторы присваивания. История CPL началась с изменяемых ссылок еще до того, как он стал назваться CPL и ISWIM получил оператор присваивания еще до того, как он стал называться ISWIM. +В обоекембриджской программе если и существует дискуссия о том, должна ли в языке быть мутабельность - она не очень развита. Да, у нас есть затруднения с описанием семантики мутабельности и доказательствами корректности кода на языке с мутабельностью, но мы не умеем делать все что нам нужно без мутабельности, так что мутабельность в языке будет [Land66]. +Ко времени разработки ML тут мало что поменялось, так что если он создавался как язык общего назначения, то едва ли есть смысл говорить о каком-то "решении". Справедливости ради, примерно в то же время обсуждалась иммутабельность в CLU, но только потому, что первоначально это должен был быть dataflow-язык [Lisk93]. +С точки зрения доктрины-82 ML - DSL, и уже есть какой-то смысл говорить об обосновании для мутабельности как фичи. И обоснование такое: первый LCF работал с мутабельными деревьями, так что решили, что и в ML мутабельность будет [Miln82]. Это обоснование критикует Гордон. После добавления абстрактных типов большие деревья не нужны [Gord10]. +Даже если мутабельность подается как расширение чисто-функционального подмножества, как в ISWIM и PAL, расширение всегда ограничивается только добавлением присваивания, а не отдельного способа декларации мутабельных объектов. Все является мутабельным, даже туплы: + +```haskell +let t = 1,2,3 in +t 1 := 4; +Print t -- напечатает (4,2,3) +``` + +код, который в PAL-мануале [Evan68b] иллюстрирует "наиболее распространенную ошибку в PAL": + +```haskell +let i = 1 in +let t = 1, 2, i, 4 in +Print t; -- напечатает (1, 2, 1, 4) +i := 4; +Print t -- напечатает (1, 2, 4, 4) +``` + +ну, ничего не поделаешь. +В отличие от Обоекембриджских языков и POP-2, в ML _есть_ разделение на декларации констант и мутабельных ссылок. + +``` +#let x = 1;; +x = 1 : int +#x := 2;; +UNBOUND OR NON-ASSIGNABLE VARIABLE x +TYPECHECK FAILED +``` + +Не для того, чтоб как-то бороться с главной ошибкой PAL, а из-за типизации. По этой же причине Милнер относит решение о добавлении мутабельности в ML к проблемным [Miln82]. +В чем же проблема? После добавления присваивания можно получать значения любых типов из значений любых типов, а значит и типа `thm` - теоремы - из любой формулы `form`. Вот Локвуд Моррис доказывает, что `True == False` [Gord79] + +```ml +let store, fetch = + letref x = [] in + (\y. x := [y]), (\(). hd x);; +store "TT == FF";; +let eureka :thm = fetch();; +``` + +Готово! Да, все труды с добавлением и выведением типов были напрасны. +Добавления специально типизированного `let` было мало, нужен еще один специальный оператор для декларации - `letref`. Но что должно быть специальным в его типизации? +Можно было бы запретить полиморфные мутабельные ссылки вообще, но они нужны. Не то чтобы эффективная имплементация рекурсии в это время была вовсе неизвестна, но уж точно не принята. Поэтому, если писать функции аналогичные левой свертке, то лучше использовать цикл [Gord79], как завещал Бердж: + +```ml +let rev l = + letref l,l' = l,[] in + if null l then l' loop (i,i' := tl l, hd l.l');; +``` + +Цикл здесь это `if then loop`. Невиданная ни до, ни, вероятно, после конструкция структурного программирования, объединяющая (многоветочный) условный оператор и пару разновидностей операторов циклов [Gord79]: + +``` +{if e1 {then|loop} e1' + if e2 {then|loop} e2' + . + . + . + if en {then|loop} en'} +{ {else|loop} en''} +``` + +Это основное отличие ML от ISWIM после типизации. Эдинбургская программа отвергла продолжения как основную управляющую структуру из-за медлительности. И большинство дизайнеров ML отвергли `goto` как в Лиспе, за которое выступал Ньюи [Gord10]. +В это время неумение имплементировать рекурсию, отказ от продолжений и структурное программирование (отказ от `goto`) идут в одном пакете. И если вы затрудняетесь увидеть связь, то это нормально, недопонимание их и связывает. К этой истории мы еще вернемся. +Итак, для имплементации полиморфных функций с помощью циклов нужны полиморфные мутабельные ссылки. Или какие-то еще более невиданные циклы, с изобретением которых эмелисты пока решили не связываться. Они решили связываться с изобретением хаков для типизации мутабельных ссылок, чем они и занимались в последующие десятилетия. +И, в случае LCF/ML, они пока остановились на запрете присваивания полиморфным _нелокальным_ ссылкам в функциях, что заодно потребовало аннотации типа в некоторых случаях. Милнер доказал надежность системы типов только для подмножества без мутабельных ссылок. Милнер и Рейнольдс не позднее 77-го решили, что нужно разработать язык с контролем эффектов [Miln78], но Милнер не стал заниматься этим, а Рейнольдс занимался без особого успеха. +В 79-ом Гордон и другие пишут, что мутабельные ссылки особо не пригодились, редко используются и не ясно, стоило ли их добавлять вообще [Gord79]. В коде LCF 77 один `letref` на ~50 прочих объявлений или один на сто строк, что может быть и можно назвать редким. Но не потому, что мутабельные деревья не нужны или чего-то вроде того. Причина гораздо хуже. +Мутабельность и циклы нужны для имплементации функций вроде `foldl` (в LCF/ML `revitlist`) и `reverse` (`rev`), но в LCF 77 они написаны на Лиспе. Код на ML используется только для каррированной обертки: + +```haskell +let revitlist f l x = revitlist(f,l,x);; +``` + +и `foldr` (`itlist`), двупроходная как у Берджа, имплементирована с использованием этих двух лисповых функций: + +```haskell +let itlist f l x = revitlist(f, rev l, x);; +``` + +Так имплементированы большинство функций списков. Вот так выглядит библиотечный `map`: + +```haskell +let map f l = map(f,l);; +``` + +в другом файле заголовок на лиспе с типом и арностью: + +```lisp +(PUTPROP (QUOTE map) 2 (QUOTE NUMARGS)) +(PUTPROP (QUOTE map) (MKTIDY (QUOTE (((%a /-> %b) # (%a list)) /-> (%b list)))) (QUOTE MLTYPE)) +``` + +в третьем файле обертка для библиотечной лисповой функции `MAPCAR` + +```lisp +(DEFPROP map + (LAMBDA (%%F L) (MAPCAR (FUNCTION (LAMBDA (X) (AP %%F X))) L)) +EXPR) +``` + +которая имплементирована как сотня строк ассемблера. +Имплементация большинства этих функций на ML написана, но только как документация в мануале. Как Лисперы в 60-е использовали свой M-псевдокод. +Диалекты LCF/ML без мутабельных ссылок еще появится, самый первый - в 80-е годы, а самый новый в 2011г. (последний релиз - в 2012)[EventML]. + +### Исключения подтверждают правило + +Авторы LCF/ML считают исключения одной из трех важнейших фич ML (вместе с выводом типов и поддержкой ФП)[Gord79] и одной из двух основных инноваций (вместе с выводом типов) [Miln82]. +С точки зрения Милнера 82-го года, это одна из неизбежных фич ML потому, что тактика сама определяет свою успешность, и должна иметь средство сигнализировать о неуспешности. Да, стримы, комбинаторные парсеры и некоторые стандартные лисповые функции, которые в Эдинбургской программе уже известны, сигнализируют о своем (не)успехе возвращаемым значением. +Милнер комментирует, вероятно, именно их, когда заявляет, что не хочет, чтоб тип населяли какие-то еще посторонние значения. И справедливости ради, некоторые из обсуждаемых решений сообщают о результате таким способом, который работал бы только в нетипизированном языке или языке с `null`. Понятно, что Милнер не хочет ничего такого. Но! Во-первых, не все решения такие. И во-вторых, наличие плохих значений типа не вполне решается непроверяемыми исключениями. +Ну, с неизбежностью все понятно, а что насчет инновационности? Отвергая продолжения как неимплементируемую эффективно фичу, Эдинбургская программа хорошо относится к их ограниченной форме - исключениям. Мы уже упоминали об исключениях в POP-2, и авторы ML даже упоминают о влиянии POP-2, но исключения из этого языка если и повлияли на ML, то разве что как пример того, как делать не надо. +В стандартной библиотеке LCF/ML есть функция `tryfind`, которая применяет функцию к списку и возвращает первый результат после успешного завершения. Если функция бросает исключение, она применяется к следующему значению из списка. Попробуем имплементировать её с исключениями из POP-2, если б они были в ML (их не было). +Начнем с того, что и `tryfind` и та, которую `tryfind` применяет к списку должны принимать функцию, бросающую исключение. Почему? Исключения в POP-2 это урезанный J-оператор Ландина, такая вот ФВП: + +``` +jumpout(функция_обработчик, сколько_кладет_на_стек) -> функция_бросающая_исключение +``` + +Забудем про число результатов на стеке, в ML нам это не пригодится. `функция_бросающая_исключение` принимает исключение, завершает функцию, в которой вызвана `jumpout`, пропускает исключение через `функция_обработчик` и возвращает из функции, в которой вызывали `jumpout` результат `функция_обработчик`. Хорошо, если обработчик может вернуть какое-то дефолтное значение, а если нет? Ну, придется использовать ту самую сумму, которую не хотел использовать Милнер. +Получаем что-то такое: + +```haskell +letrec tryfind f fail l = + null l => fail [] + | let g x = let fail = jumpout(K(tryfind f (tl l))) + in f fail (hd x) + in g l;; +``` + +что используется так: + +```haskell +let fail = jumpout(I) in tryfind (\fail x. [... fail[] ...]) ... +``` + +В POP-2 передавать функцию, бросающую исключения было не нужно, там `jumpout` использовался бы примерно так: + +```haskell +let fail = jumpout(I);; +letref fail2 = I; + +letrec tryfind f l = + null l => fail [] + | let g x = fail2 := jumpout(K(tryfind f (tl l))); + f (hd x) + in g l;; + +tryfind (\x. [... fail2[] ...]) ... +``` + +Но в LCF/ML, как мы помним, присваивать нелокальной полиморфной ссылке из функции нельзя. Не то чтобы использование как в POP-2 выглядит лучше, впрочем. +Возможно, мы просто не в состоянии правильно использовать этот инструмент. Но выглядит так, что он не очень подходит для использования. По крайней мере для такого использования, которое задумано в LCF. +Исключения, которые придумал Милнер, позволяют имплементировать и применять функцию так: + +``` +letrec tryfind f l = null l => fail + |(f(hd l) ? tryfind f (tl l));; + +tryfind (\x. ... fail ...) ... +``` + +(Позволяют. Но она, разумеется, имплементирована на Лиспе. Такой код только в документации). +Исключения - единственная фича LCF/ML, которую Милнер записывает и в список интересных инноваций, и в список основных фич и в список фич проблемных и недоработанных [Miln82]. В чем же недоработка? Как бы плохо не подходила функция `jumpout` для обработки ошибок, она позволяет делать раннее завершение и возвращать результат, которым может быть произвольное значение. Милнеровские исключения такого не позволяют, исключением может быть только токен, т.е. массив символов. Как бросать другие значения и как это типизировать авторы LCF/ML не придумали. +Такое ограничение может показаться не очень страшным, если использовать исключения для обработки ошибок, а не как управляющую структуру для обычного кода, но эмелисты определенно собираются использовать исключения именно так. Вот реальный пример из мануала [Gord79]: `switch` в языке не нужен, просто кидайте строку и обрабатывайте это "исключение" вот так + +``` +let termvars t = + failwith phylumofterm t + ?? ``const`` nil + ?? ``var`` [t] + ?? ``abs`` (let x,u = destabs t in ... + ?? ``comb`` (let u,v = destcomb t in ... +``` + +Отказ от сигналов через результат в LCF/ML последователен. Функции, которые в современных ФЯ возвращают `Maybe`, в LCF/ML бросают исключения, что не особенно хорошо сказывается на информативности их типов. Аналог функции `mapMaybe` имеет такой тип [HOL88]: + +``` +mapfilter : (* -> **) -> * list -> ** list +``` + +у этого есть и положительная сторона, частичные функции вроде `head` и `tail` нормально комбинируются с другими, а не являются странными реликтами прошедших эпох, как в современных языках: + +``` +#mapfilter hd [[1;2;3];[4;5];[];[6;7;8];[]];; +[1; 4; 6] : int list +``` + +Вот только нормально интегрируются не все частичные функции. Если ошибка возникает в лисповом коде, который получен трансляцией из ML, то происходит выпадение из LCF-REPL в REPL лисповый. Например, в случае переполнения целых чисел. +Нежелание или неспособность изобрести `Maybe` интересно еще и тем, что это практически единственный полезный параметризованный тип, который можно было использовать в, по крайней мере ранней версии, системы типов ML. + +### Не могу сконструировать бесконечный тип + +Стараясь сделать из типизированной лямбды более практически интересный язык, Джеймс Моррис расширил ее примитивами для создания, использования и типизации композитных типов, которые он, по большей части, позаимствовал из работы МакКарти [McCa61]. МакКарти ввел декартово произведение (двухместный кортеж) и прямое объединение (`Either`) как способы композиции элементарных типов и соответствующие конструкторы, предикаты и селекторы (для объединений - частичные функции). Никакие параметризованные сложные типы с их помощью он не конструировал, но приводил пример рекурсивного типа для для S-выражения: `S = A(+)SxS` где `A` - атом. Мы уже встречались с производной от этой системой - функциональный EDSL Берджа для конструирования и разбора структур данных. +Моррис [Morr68], позаимствовав общую идею, дал конструкторам и селекторам произведений более запоминающиеся названия (у МакКарти они имели названия вроде `i` и `j`, `p` и `q`, `r` и `s`. Попробуйте угадать что из них что), а от предикатов и частичных селекторов отказался вовсе, введя конструкцию `switch`, аналогичную хаскельному оператору `(|||)` + +``` +switch x into (\y.y+1) or length +``` + +Эту конструкцию Милнер заимствовать не стал. Зачем, когда можно обрабатывать исключения, бросаемые частичными селекторами? Пришлось изменить и названия операций. Моррис называл головой и хвостом первый и последний элементы двухместного кортежа, а Милнеру они были нужны для еще одного встроенного типа - `list`. +В ISWIMах обычно были или только туплы, как в PAL, и списки нужно было собирать из них. Или только списки как в McG, и нужно было использовать их вместо туплов. Соответственно, только для какой-то одной конструкции был синтаксис, использующий запятые и скобки. Скобки в ISWIMах часто можно было использовать и круглые и квадратные, в зависимости только от того, что программисту кажется более читаемым в данном случае. Конечно, пары скобок должны быть одного вида. +Наличие типов в LCF/ML сделало использование туплов вместо списков и наоборот затруднительным, так что потребовалось изобрести разные синтаксисы для них. Возможно, что впервые потребовалось. В LCF/ML синтаксис для туплов это запятые с опциональными круглыми скобками, а синтаксис для списков - `;` с обязательными квадратными скобками. Почему бы не использовать запятые в обоих случаях? Первопроходцы разделения синтаксисов для туплов и списков могли просто не задать этот вопрос. Также, вероятно существует причина связанная с имплементацией, к которой мы еще вернемся. Многие ФЯ, в том числе и некоторые ML-и, пересмотрят это, любимое многими программистами на Ocaml и F#, решение. Авторы LCF/ML также выбрали `.` как инфиксный `cons`. Как в Лиспе [McCa62] и Прологе того времени [Colm96] [Warr77]. Это решение не стало популярным в Эдинбургской программе. Даже в её расширенном толковании: в Прологе `(H.T)` позднее заменят на `[H|T]`. +В LCF/ML также есть и синтаксис для разбора и туплов `v1,v2` и списков: + +``` +v1.v2 +[] +[v1;v2 ... ;vn] +``` + +Но "паттернов" для объединений нет. +Это один из первых _имплементированных_ ISWIMов, в которых такие конструкции могут быть вложенными. Но нет никакой `switch`/`case` образной структуры как у Бурсталла [Burs69]. Эта конструкция, возможно, пала жертвой исключений так же как конструкция Морриса для элиминации объединений или любой другой из известных к тому времени свитчей. Неудачное сопоставление с "образцом", которые эмелисты называют "varstruct" выбросит исключение, которое надо обработать. Техника реально использовалась в коде LCF [LCF77] (да, не все функции списков написаны на Лиспе): + +```haskell +letrec split l = (let (x1,x2).l' = l in + (x1.l1',x2.l2') where l1',l2' = split l' + ) ? (nil,nil);; +``` + +что примерно соответствует такому коду: + +```haskell +split ((x1,x2):l') = (x1:l1',x2:l2') where (l1',l2') = split l' +split _ = ([],[]) +``` + +Наш обязательный `map` в таком стиле: + +```haskell +letrec map f l = (let x.xs = l in f x . map f xs) ? [] +``` + +Это современный код, ни в коде LCF, ни в мануале [Gord79] такого `map` нет. +Как вы, наверное, догадываетесь, такой подход не стал популярным в ФЯ. Но он мог повлиять на работы по компиляции паттерн-матчинга или быть переизобретен заново. Например, при компиляции ПМ методом Вадлера [SPJ87] на определенном этапе в промежуточном представлении ветви ПМ могут содержать операцию аналогичную `fail` и скомбинированы оператором аналогичным `?`, но это промежуточное представление не должно компилироваться в выбрасывание и обработку исключений. Еще одна недоработка по сравнению с ISWIM-псевдокодом Бурсталла [Burs69] - невозможность декларировать конструкторы вроде `,` и `.`, разбираемые таким "матчингом". +В 82-ом году Милнер назовет неиспользование наработок Бурсталла проблемой, еще одним сомнительным решением. Но, как и в случае с мутабельностью, есть основания сомневаться в историчности такого "решения". На тот момент, когда "решение" должно было быть принято, едва ли можно говорить о том, что ПМ готов для использования. Так что и решать нечего. +Сложнее понять почему нет менее амбициозных свитч-конструкций. LCF/ML не старались делать минимальным языком и обсуждаемый ранее if-then-loop не заменен циклом, управляемым исключениями, который в LCF/ML, разумеется, тоже есть. Практически все в LCF/ML можно делать многими способами. Это, видимо, самый большой ФЯ на момент своего появления. Определенно самый большой из имплементированных. Но вполне сравним даже с самым воображаемым из воображаемых CPL-ей, хотя воображаемость мешает установить это точно. Только ключевых слов для деклараций в LCF/ML почти два десятка, хотя в основном потому, что комбинации ключевых слов вроде `let rec` заменены их конкатенациями. +Но является ли `list` встроенным типом только из-за специального синтаксиса? Специальный синтаксис может поддерживать совсем не специальные типы объявленные в библиотеке. +И в LCF/ML есть система МакКарти для композитных типов. Которая, на первый взгляд, производит впечатление мощной и удобной. Первоклассные комбинаторы типов! +МакКарти предполагал, что ей можно типизировать S-выражения, а мы попробуем сделать списки: + +```haskell +> nil = Left () +> isNil = isLeft +> cons h t = Right(h,t) +> hd l = fst(fromRight (error "hd") l) +> tl l = snd(fromRight (error "tl") l) +``` + +пока все хорошо... + +```haskell +> map f l = if isNil l then nil else cons (f (hd l)) (map f (tl l)) +error: cannot construct the infinite type ... +``` + +Ох. Система МакКарти полностью бесполезна для языка с Милнеровским выводом типов. С любым рекурсивным типом проблема та же, что с написанием Y-комбинатора. Решение Морриса с `rec` не решило проблему полностью. Что же делать? + +### Против методологии + +> Типы - это множества значений. <...> Постулат ни оригинальный, ни спорный. +> Джеймс Моррис, ЛИ модели языков программирования [Morr68]. + +> Типы - это не множества. +> Джеймс Моррис, Типы - это не множества [Morr73a]. + +Барбара Лисков (Barbara Liskov) разочаровалась в методологии программирования к осени 1972-го года. Работы по методологии оперировали туманными определениями сущностей, которые программист должен был находить, но не объясняли как. Давали рекомендации, которым трудно было следовать на практике. Например, методология рекомендует запрещать программистам читать неинтерфейсную часть кода, который они используют (не шутка). +Какой должна быть методология? Лисков считает, что лучший способ разработать методологию - это разработать язык. Такой, что решения, использующие методологию - это программы на этом языке. А значит нет проблем с отображением дизайна на программы. Язык точно определен, а значит и методология определена точно. Язык - инструмент для объяснения и понимания методологии, можно продемонстрировать что и как (не)работает [Lisk74] [Lisk93]. +Нужно обсуждать не невнятные рекомендации, а конструкции языков, которые способствуют написанию программ с хорошим дизайном. Если связать невнятные "единицы инкапсуляции" с типами данных, то не будет проблем с их идентификацией и использованием, ведь абстрактные типы данных имеют четкое определение и компилятор может проверить правильность их использования [Lisk93]. +Лисков выступила с докладом об этих идеях в апреле 73-го и нашла единомышленника - Стефана Циллеса (Stephen N. Zilles). Мы уже знакомы с Циллесом, он работал над попыткой адаптации нотации Ландина для PAL [Zill70]. К осени 73-го они разработали абстрактный тип как языковую конструкцию алголоподобного языка и опубликовали свои наработки в сентябре 73-го. Более известна версия этой статьи, опубликованная в апреле 74-го [Lisk74]. Между этими публикациями в октябре 73-го года состоялась встреча исследователей, занимающихся такими проблемами в Гарварде. Из тех, кто позднее обсуждал дизайн ML с его авторами там были Рейнольдс и более известный другими своими работами Хоар (Charles Antony Richard Hoare) [Gord79]. И авторам ML должно было быть интересно то, что они могли рассказать. "Непрозрачность" типов не только позволяет определять самому структуры данных вроде встроенных в ML "теорем", но и решить проблему с рекурсивными типами. +Лисков и Циллес не единственные, кто начал работать над абстрактными типами данных, и даже не единственные из тех, о ком знали в Эдинбурге [Miln78]. Но работа Лисков отличается от большинства работ того времени практическим подходом. Лисков хотела имплементировать языковую конструкцию, получить работающий язык с ней как можно скорее. Поэтому первым делом было выброшено то, над чем работающие над АТД в основном и работали - описание спецификации АТД. Лисков решила, что спецификацию точно не удастся проверить компилятором в ближайшей перспективе. Циллес, как и большинство исследователей, как раз интересовался спецификацией и потому не принимал в дальнейшей разработке языка особого участия. +Разработка нового языка не лучший способ получить что-то работающее, так что для начала Лисков хотела модифицировать существующий язык. Студент Лисков написал обзор языков, в которых есть похожие на АТД конструкции [Aiel74], и это скорее обзор языков, в которых нет таких конструкций. Наибольшее сходство было найдено с классами Simula 67. В Simula 67 не было инкапсуляции, что означает не очень сильное сходство классов со средством для инкапсуляции, разработкой которого занималась Лисков. Хуже того, добавить инкапсуляцию в Симулу было недостаточно. Лисков хотела, чтоб АТД были средством добавления в язык типов аналогичных встроенным, таких как массивы. И массивы параметризованы, а классы Симулы - нет. С существующими языками с параметрическим полиморфизмом дела обстояли неважно. Так что было решено разрабатывать новый язык - CLU. +Нет, Лисков не нашла никого работающего над языком с параметрическим полиморфизмом. Про ML она узнала только в конце 70-х годов. +Единственной более-менее (и скорее менее) практической работе по сокрытию, известной Циллесу и Лисков в 73-ем году, была работа Джеймса Морриса. Да, снова Моррис, может быть это все-таки должна была быть глава про него? +Моррис один из тех загадочных авторов PAL, которые проводили много исследований в области ЯП, но не использовали для этого PAL. Эта его работа 1971-го года (но не опубликованная до 73-го) могла бы использовать PAL. Но использует GEDANKEN [Morr73b]. Не то чтобы Моррис забыл про PAL, эта работа упоминает PAL, как и его работа о выводе типов. +Для начала, Моррис описывает интервал на GEDANKEN "неправильно": + +```f# +[Createint IS #(X, Y) IF X <= Y THEN X, Y ELSE Y, X; + Min IS #Z Z(1); + Max IS #Z Z(2); + Sum IS #(X,Y) (X(1) + Y(1)), (X(2) - Y(2)); + Createint, Min, Max, Sum] +``` + +Разумеется, программист может сконструировать некорректный интервал без помощи `Createint` и писать код, который зависит от внутреннего устройства интервала. +Моррис отмечает, что программист, пишущий функцию, принимающую такое значение, может оборонительно проверять его целостность, но это непрактично для нетривиальных структур. Отсортированных массивов, например. Проблема та же, что решали авторы LCF, только они делали акцент на память, а Моррис - на время. Вопрос нужно ставить не "что представляет из себя значение?", а "кем сконструировано значение?" или "откуда значение пришло?", настаивает Моррис [Morr73a]. +Чтобы воспрепятствовать неправильному использованию данных, предлагает Моррис, нужно конвертировать между структурой для которой определены селекторы и конструкторы вроде тупла в обсуждаемом случае и объекта с другим тегом, который эти селекторы не распознают и вызовут ошибку. + +```f# +[Seal, Unseal IS Createseal(); + Createint IS #(X, Y) Seal (IF X <= Y THEN X, Y ELSE Y, X); + Min IS #P(Unseal(P))(1); + Max IS #P(Unseal(P))(2); + Sum IS #(P,Q) [P' IS Unseal(P); + Q' IS Unseal(Q); + Seal((P'(1) + Q'(1)),(P'(2) + Q'(2)))]; + Createint, Min, Max, Sum] +``` + +Моррис использует замыкания для ограничения доступа к этим функциям запечатывания и распечатывания данных. Только нужные функции захватят ссылки на правильные экземпляры `Seal` и `Unseal`. +Это не новая идея. Замыкания первоклассных функций могут ограничивать доступ к своей внутренней структуре. Или не ограничивать. В POP-2 были обе разновидности замыканий. Ограничивающие доступ замыкания использовались для обеспечения безопасности в Multipop68. Но Джеймс Моррис не ссылается на эти Эдинбургские наработки. +Замыкания также используются Моррисом для защиты генератора новых тегов, используемого функцией `Createseal`, создающей пары из запечатывателя и распечатывателя. Моррис также разрабатывает более сложную машинерию со списками доступа для более интересных разновидностей доступа к внутренней структуре объектов. +После чего критикует свою систему как непрактичную из-за медлительности проверок и удержания ссылок на объекты кучи, не способствующих нормальной работе сборщика мусора [Morr73b]. +Поэтому в следующей работе [Morr73a] Моррис описывает расширения для мейнстримного типизированного языка. В мейнстримном языке нет замыканий, так что как основу для системы сокрытия он изобретает "модуль". Это не модуль в привычном нам смысле, он не образует пространство имен и сам не имеет имени, это скорее аналог конструкции `local in end` в SML. +Для запечатывания вводятся обертки, проверяемые компилятором и не имеющие представления в рантайме, сходные с `newtype` или запечатыванием в параметризованных модулях: + +``` +type +complex > real array [1:2]; +``` + +Но если динамическая система из первой статьи [Morr73b] описывает имплементацию полностью, то вторая статья - просто набор пожеланий Морриса, как все это имплементировать - не понятно. +По этой причине Лисков и Циллес не были уверены, что инкапсуляция может быть имплементирована полностью статически и были готовы начать с динамической системы Морриса и сделать статически так много проверок как смогут. И оказалось, что смогут все. Идеи Морриса в конце концов не пригодились [Lisk93], так что глава не будет про него. +Не смотря на желание имплементировать АТД как языковую конструкцию, так называемые "кластеры", Лисков и студенты даже не начали имплементацию до того как вышла статья [Lisk74], на основе которой авторы ML разработали АТД для ML [Miln78] [MacQ15]. +Авторы ML ссылаются на другие работы по АТД, но время публикации этих работ, отсутствие описания спецификации и параметризованность не оставляют особых сомнений. +Язык Лисков как и ML - язык с универсальным представлением и сборкой мусора. +Так выглядела бы имплементация списка с помощью "кластера" Лисков: + +``` +list: cluster(element_type: type) + is cons, null, hd, tl; + rep(type_param: type) = oneof(nil: null, + cons: (h: type_param; + t: list(type_param); + e_type: type)) + create ... end + cons: operation(s: rep, v: s.e_type) returns rep; ... end + hd: operation(s: rep) returns s.e_type; ... end + tl: operation(s: rep) returns rep; ... end + null: operation(s: rep) returns boolean; ... end +end list +``` + +А так на ML [Gord78] [Gord79]: + +```ml +absrectype * list = . + * # * list + with nil = abslist(inl()) + and $.(x,l) = abslist(inr(x,l)) + and null l = isl(replist l) + and hd l = fst(outr(replist l)) + and tl l = snd(outr(replist l)) +``` + +Если не понятно, что означает `. + * # * list`, то это `unit + 'a * 'a list` и `() + (a, List a)`. Нотация со звездами обычна для LCF/ML. Выводимые в REPL типы и код LCF использует её. Но не единственная. Синтаксис LCF/ML позволяет записывать параметры типов и похожим на современные ML-и образом (но с `*` вместо `'`), и еще одним непохожим: +``` +* # ** -> * +*a # *b -> *a +*1 # *2 -> *1 +``` +Как мы уже писали, LCF/ML - большой язык. Будущие ФЯ позаимствуют каждый и трех вариантов, но не все варианты одновременно. +Конечно, помимо сходства, между "кластерами" и `abstype` есть и очевидные различия. Лисков пишет, что не хотела неявных преобразований типов в языке, но все преобразования между абстрактным типом и его представлением в "кластере" неявные. Милнер, как мы помним, решил не думать как неявные преобразования будут работать с его системой типов, так что в LCF/ML эти преобразования между АТД и представлением явные, как у Морриса. +"Кластер" образует пространство имен и CLU требует использовать его функции с полной квалификацией и с указанием типа параметра: + +``` +list(int)$cons(1,l) +list(boolean)$hd(l) +``` + +`abstype` пространства имен не вводит, как и изобретения Морриса. +Функции АТД должны иметь как минимум один параметр этого типа, поэтому конструктор - это специальная языковая конструкция. Это странное требование происходит, в основном от страха и непонимания ALGOL 68 [Lisk93]. Милнера это все не волнует, таких требований к функциям, объявленным в `abstype`, нет. И специальных конструкторов нет тоже. +Основная многословность определения "кластера" получается из-за разделения определения типа и его представления списком функций. Лисков считала, что идейно важно, что в декларации АТД `is` список функций над ним, а не `is` внутреннее представление. АТД - это не его представление, а его интерфейс. Но это не важно для Милнера. +Из разговоров с Рейнольдсом, который в это время как раз занимался параметрическим полиморфизмом, Лисков сделала вывод, что статическая проверка возможна в ближайшей перспективе. Но в статье пока что параметру типа соответствует значение в рантайме и поле для него в представлении типа [Lisk74]. Справедливости ради, сложная проблема которую решали авторы CLU - ограниченный полиморфизм. Значение в рантайме позволяет проверить, что к значению типа-параметра можно применить сравнение, например, чтоб имплементировать множество [Lisk93]. Авторы LCF/ML над этой проблемой пока что не думают и множества не имплементируют. +Авторы LCF/ML отмечают, что абстрактные типы в нем и в CLU не "по-настоящему абстрактные" [Gord78], как в работах Циллеса и Гуттага. Милнер позднее делает [Miln82] загадочный комментарий о том, почему наработки Бурсталла не были использованы. Он связывает его работу с работами Гуттага над этими "настоящими" абстрактными типами данных. Что все это значит? Разберемся в следующей главе! +Прототип имплементации CLU был готов раньше, чем имплементация ML. Но готовая для использования имплементация CLU появилась позже. Так что, можно считать, что LCF/ML стал первой законченной имплементацией этой идеи (его авторы не претендуют на это). Понятно, что требования к "законченности" скрипта для доказателя теорем и языка общего назначения существенно отличаются. Компилятор CLU, например, был переписан на CLU за годы до того, как компилятор CLU был "закончен". Имплементацию LCF/ML посчитали законченной до того, как начали писать его компилятор хоть на чем-то, не то что на ML. +Абстрактные типы позволяют объявить список с помощью сумм и произведений, "непрозрачность" решает проблему с бесконечными типами. Теперь на ML можно написать и типизировать практически весь код из третьей главы книги Берджа [Burg75], что посоветовали Милнеру рецензенты его статьи [Miln78]. Наконец-то системы МакКарти и Милнера заработали вместе. Все рекурсивные типы вроде стримов и деревьев больше не нужно встраивать в язык. Нужно отметить, правда, что в библиотеке списки так не объявлены. Заявленная причина: это ухудшило бы производительность [Gord79]. +Списки, сконструированные с помощью сумм и произведений, имеют неэффективное представление в памяти [LCF77]: + +``` + ┌───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ + │ ├──►│*T*│ ├──►│ │ ├──►│*T*│ ├──►│ │ ├──►│NIL│NIL│ + └───┘ └───┴───┘ └─┬─┴───┘ └───┴───┘ └─┬─┴───┘ └───┴───┘ + │ │ + ▼ ▼ + ┌───┐ ┌───┐ + │ 1 │ │ 2 │ + └───┘ └───┘ +``` + +по сравнению со списками как в Лиспе: + +``` + ┌───┐ ┌───┬───┐ ┌───┬───┐ + │ ├──►│ │ ├──►│ │NIL│ + └───┘ └─┬─┴───┘ └─┬─┴───┘ + │ │ + ▼ ▼ + ┌───┐ ┌───┐ + │ 1 │ │ 2 │ + └───┘ └───┘ +``` + +Встроенные списки в LCF/ML именно такие как в Лиспе. +Абстрактные типы как в CLU - это самая новая фича ML из всех, что не были разработаны специально для ML. Можно предположить, что декларации абстрактных типов в ML появились не сразу, потому что все "стандартные" абстрактные типы встроены, кроме одного - структуры данных `simpset`, используемой одной из более-менее сложных тактик. Вполне возможно, что некоторые типы встроены по той же причине, что и списки. МакКартиевская система создает действительно плохое представление в памяти почти для всего и `simpset` это не касается потому, что это обертка над списком. Но `thm` тоже только обертка для другого встроенного типа. Нет причин не имплементировать "теоремы" используя `abstype`, если только `thm` не появился в языке раньше, чем `abstype`. +По той причине или по иной, но в коде LCF 77 `abstype` используется только один раз. Ни параметризованные, ни рекурсивные (`absrectype`) абстрактные типы не используются вовсе[LCF77]. +И особенно богатой истории использования у них и не будет. Их время, как и выражения-`where`, пройдет в 80-е, в новых ФЯ они будут или неиспользуемым реликтом, или вовсе в них не попадут. +Если алгебраические типы данных происходят от нотации Ландина, то путь от неё до АлгТД совсем не выглядит прямым, нотация CPL уже свернула куда-то не туда с разделением сумм и произведений. Моррис считал, что МакКартиевская "сумма" (которую Моррис называет "неоднозначный" тип) - это способ вернуть "свободу" нетипизированных языков в типизированный, и планировал развивать систему в соответствующем направлении, с объединениями типов без "конструкторов" и пересечениями типов [Morr68]. Даже развитие системы МакКарти в ML и обход её проблем с помощью абстрактных типов данных выглядит как продолжение движения не туда. Когда же мы придем к современным АлгТД по этому пути? +Никогда. Современные АлгТД появились иначе. Но это уже другая история. + +### Когда функционального программирования не было + +Милнер, Ньюи и Локвуд Моррис имплементировали LCF на новой (для Эдинбурга) машине DECsystem-10 [Gord79]. Известной также как PDP-10. Той самой, появление которой положило конец монополии POP-2 в группе экспериментального программирования, позволило Ван Эмдену писать программы на Прологе. Да, первый LCF Милнер с Ньюи тоже писали на PDP-10, что многое говорит о том, насколько система была новой (не для Эдинбурга). +Локвуд Моррис дописал транслятор из ML в Лисп за шесть недель до того, как закончил работать в Эдинбурге. Милнер утверждает, что никто не нашел в нем ошибок с тех пор как Моррис закончил работу над транслятором и до того, как он был переписан уже в 80-е [Miln93]. Если эта история про отсутствие ошибок кажется вам неправдоподобной, то подождите, скоро мы раскроем обстоятельства, которые делают эту историю намного правдоподобнее. +Малкольм Ньюи написал парсер ML с помощью техники Вона Пратта (Vaughan Pratt). Пратт провел лето 75-го года в Эдинбурге и научил этой технике местных имплементаторов ФЯ, например МакКвина [MacQ14], нашего будущего героя. Это не обязательно указывает на время начала работ над парсером LCF/ML, потому что работа Пратта была опубликована раньше. Ранее метод захватил связанные с Эдинбургской программой центры, такие как Другой Кембридж и лабораторию IBM в Йорктаун Хайтс [Prat73]. К тому же, Пратт и Ньюи были знакомы еще с Австралии [MacQ15]. +МакКвин считает, что разные разделители в списках и кортежах, а так же `;;` появились в LCF/ML потому, что писать парсер по Пратту проще, когда один и тот же оператор не используется для разных целей [MacQ14]. Вот только другие имплементаторы, включая и самого МакКвина с этим как-то справились. +Имплементация LCF/ML была в первом приближении закончена в 75-ом году [Miln78], и сам LCF использовался с того же или следующего года [Gord78]. Первое описание LCF/ML было опубликовано в сборнике IRIA (да, это будущая INRIA) 75-го года [Miln75], но не отсканировано, так что трудно сказать точно, насколько оно отличается от основных публикаций о LCF 78-79гг. Но судя по обсуждениям работы в [Tenn77], в ML уже есть вывод типов. И Локвуд Моррис поучаствовал в дизайне АТД в ML [Miln78]. А Моррис с Ньюи закончили участие в проекте в 75-ом году. +Локвуд Моррис отправился в Сиракузский университет (Syracuse University) в штате Нью-Йорк [Gord2000] работать с самим Робинсоном и другими резолюционистами над LOGLISP. А Ньюи - в Австралийский национальный университет (Australian National University) и вернется в нашу историю только через десяток лет. +Вместо них ассистентами Милнера стали уже знакомые нам Майкл Гордон и Крис Вадсворт [Miln82], которые занимались в основном LCF, и только какими-то деталями в ML [Miln93] [Miln90] и закончили имплементацию к 1978 году. +LCF/ML - это первая имплементация ФЯ для которой есть не просто исходный код, но история исходного кода. К сожалению, на 70-е годы приходится только одна отметка этой истории: код LCF в октябре 77-го года [LCF77]. +С начала проекта и до конца 77-го года Милнер, Ньюи, Моррис, Гордон и Вадсворт написали 10KLOC на Stanford LISP 1.6 [Gord79], из них 2KLOC на MLISP2 - фронтенде с более мейнстримным синтаксисом для этого Лиспа. И аж 1162 строки на ML. Да, тысяча строк. Тут нет опечатки, никакие цифры не пропущены. Это мы и имели в виду, когда писали, что функционального программирования не было. +И, кстати, мы думаем, что написать тысячу строк на языке, не используя даже все его фичи, вроде параметризованных и рекурсивных АТД - это не самый верный способ обнаружить все ошибки бэкенда компилятора этого языка. +В 1978-ом году проект Edinburgh LCF завершен. Милнер занялся другими проектами [Plot2000]. Вадсворт отправился работать в Лабораторию Резерфорда - Эплтона (Rutherford Appleton Laboratory). Гордон до конца 70-х в Эдинбурге и использует LCF для верификации железа, а в 81-ом году отправляется туда, где наша история началась [Gord2000] - в Кембридж. +История имплементации на этом не заканчивается. У неё впереди десятилетия истории. Самый поздний её релиз - это NuPRL 5 2002-го года [NUPRL2002]. У имплементации будет несколько форков. Пока что это не компилятор, но будет компилятором. Пока что имплементирует не то, что мы определили как ФЯ Эдинбургской программы, но будет имплементировать. +Язык LCF/ML даже в минимально измененном виде доживет до 2012-го года [EventML], но помимо этих около-NuPRL реликтов, будут ML-и, произошедшие от LCF/ML более-менее напрямую, в результате инкрементальных изменений. Некоторые из которых активно развиваются и популярны (по меркам ФЯ) и сегодня. Но, надо заметить, что не все языки с ML в названии произошли в результате инкрементальных изменений. +Как видно, LCF/ML достаточно близко подошел к тому, чтоб стать первым ФЯ в том смысле, какой мы определили в предисловии. Общий предок всех ФЯ, историю которых мы пишем, сильно упростил бы ее и наименование для этих ФЯ. Но ML не стал таким языком. Понадобилось еще два языка Эдинбургской программы, чтоб разобраться с недочетами и спорными вопросами, которые Милнер обсуждает [Miln82] в 82-году. +Так что пришло время вернуться к экспериментальному программированию нашего старого знакомого Бурсталла. + +## Уравнения и неравенства + +> Было решено, что <...> новый язык должен быть как можно ближе к стандартной математической нотации и быть читаемым без особых дополнительных объяснений. +> Хайнц Рутисхаузер, Описание ALGOL 60, том 1 (1967) [Ruti67] + +> Я считаю, что было бы невозможно утверждать, что ALGOL 60 удовлетворяет <этому требованию>. +> Петер Наур, Европейская сторона последней фазы разработки ALGOL 60 (1978) [Naur78] + +ALGOL 60 не выглядит как язык, приближенный к математической нотации, но именно таким его планировали сделать по крайней мере некоторые из его авторов [Ruti67]. Уже в январе 59-го года Вуджер утверждал, что это не может быть сделано. Некоторые алголисты еще в 1978 году придерживались мнения, что математической нотации для порядка выполнения не существует вовсе [Naur78]. Разумеется, такая нотация или, точнее, нотация от которой произойдет такая нотация в ЯП, существует. Это форма записи множества и нотация для определения кусочно-заданных функций. Еще в 20-е годы в статьях [Acke28] [Neum23] можно было увидеть примеры нотации, + +``` +φ(a, 0) = 0, +φ(a, n + 1) = φ(a, n) + a +``` + +и + +``` +M(f(y); y ∈ Ξ, y < x) +``` + +напоминающей код на языках, уже имплементированных к тому 78-ому году, в котором алголисты писали, насколько это неосуществимо. +Происхождение от существующей математической нотации делает попытки проследить историю идей еще сложнее. Идея использовать нотацию Цермело-Френкеля (которую, похоже, изобрели не они) в ЯП гораздо более воспроизводима независимо, чем идея такой нотации. +Первоначально мы предполагали, что наличие каких-то сходных деталей синтаксиса может более-менее точно указать заимствование из одного языка в другой. Но форма записи множества стабилизировалась только в 60-е годы, незадолго до первого появления в ЯП, а до того существовала в виде бесчисленных вариаций, так что даже такой узнаваемый синтаксис как + +```python +f(x) for x in xs +``` + +мог бы появиться в двух языках независимо на основе нотации из книги Тарского 40-х годов или чего-то другого похожего. + +### Аксиоматизация + +Ожидаемо, что если не первым, то одним из первых языков, имеющих приближенный к математической нотации синтаксис стала система компьютерной алгебры SCRATCHPAD. Менее ожидаемо то, что среди систем компьютерной алгебры она была исключением. Правилом был синтаксис, приближенный к Алголу [Hulz83]. +И даже это редкое исключение было мало кому доступно, ведь SCRATCHPAD разрабатывался в уже знакомом нам с вами скрытом царстве функционального программирования: лаборатории IBM в Йорктаун Хайтс. SCRATCHPAD, в отличие от McG, не исчезнет. Вторая его версия станет сначала не особо успешным продуктом, а потом опенсорсом под названием Axiom, который существует и сегодня. Все это начнет происходить только в 80-е, а в 70-е SCRATCHPAD не использовался за пределами IBM [Hulz83]. Но мы уже рассказывали про некоторое движение идей между лабораторией IBM и Эдинбургом, а авторы и имплементаторы опубликовали несколько статей, так что система оказала какое-то влияние на первые ФЯ. +SCRATCHPAD позволял писать код вроде такого [Jenk71] + +``` +(p | n in (1,...,5)) + +((x:f) | x in float(rp), x > 0) +``` + +а позднее такого [Jenk74] [Jenk75] [Jenk79] + +``` +(p for n in (1,...,5)) + +((x:f) for x in float(rp) | x > 0) +``` + +Откуда взялся этот `for`? Авторы SCRATCHPAD пишут [Jenk74], что на дизайн этой нотации повлияли идеи [Earl73] более известного другой своей работой Джея Эрли (Jay Earley). + +``` +{F(A)} (FOR A ∈ S | P(A)) +``` + +Конструкция не совсем такая, как в SCRATCHPAD. Там `for` является разделителем, а у Эрли между конструкцией с `FOR`, определяющей `A` и операцией над этой переменной требуется другой разделитель. Но влияние узнаваемо. +Идею этой конструкции Эрли позаимствовал из раннего описания [Schw71] SETL, где это просто псевдокод, мало отличающийся от обычной математической нотации: + +``` +{f(x),x∈e|C(x)} +``` + +Эрли в своей статье не только ссылается на ранние описания SETL, но и утверждает, что SETL особенно сильно повлиял на его идеи. В отличии, видимо, от идей прочих авторов, кого он цитировал в этой статье. Например, Эдгара Кодда. +Часто можно увидеть, как SETL называют первым языком с такой нотацией. И авторы SCRATCHPAD даже ссылаются на этот язык как на один из оказавших влияние. Но ссылаются позже, в восьмидесятые [Jenk84] на описание 79-го года [Dewa79] в котором есть выражения такого вида: + +``` +{a : a in y | a>5} + +{[x**2,x] : x in {1..5}} +``` + +и конструкции для описания циклов вот такого: + +``` +(for i in {1,2..10} | even i) ... f(i) ... end; +``` + +Операция не с той стороны от `for`, с какой у Эрли, в SCRATCHPAD или в том языке, где такую конструкцию большинство впервые видит сегодня. Т.е. какие-то существенно отличающиеся от математической нотации детали не совпадают. Также в 70-е годы авторы SCRATCHPAD не используют название для этой конструкции из SETL - "set former". А использование в ЯП конструкции, похожей на форму записи множества - не похоже на идею, которая не может прийти независимо во множество голов. +Давайте посмотрим на то, как такие конструкции выглядели в SETL до того, как были имплементированы в SCRATCHPAD в начале 71-го [Jenk74]. В ноябре 70-го [Harr70] вот так: + +``` +SETC(MAPX(TL A, + PROC(X), + MAPX(TL B, PROC(Y), IF G(X,Y) THEN F(X,Y) ELSE UNDEF + END) + END)) +``` + +и планировалось, что будет выглядеть так: + +``` +CONSET(IF G(X,Y) THEN F(X,Y), X IN A, Y IN B) +``` + +А как выглядели ближе к появлению более нового вида с `for`? +Вот [Mull73] так: + +``` +≤X → A ↑ X GT. 2≥; +``` + +Ох. Да, это означает `[x | x <- a, x > 2]`, направление стрелки просто необъяснимо. +Думаем, что есть серьезные основания подозревать, что упоминание SETL - это просто обзор всей проделанной работы, а не обзор реально повлиявших на дизайн языков. Первое выдается/принимается за второе довольно часто. +В SCRATCHPAD, у Эрли и в SETL есть много идей о том, как должны выглядеть символы и ключевые слова для скобок, принадлежности к множеству и разных разделителей, но этим идеи, похоже и ограничиваются. Идей о деталях и удобствах которые являются само собой разумеющимися в такой нотации сегодня еще нет. Например, паттерн-матчинг слева от `in` не используется для отбора элементов множества или последовательности. Даже в SCRATCHPAD, в котором паттерн-матчинг есть и широко используется. +SCRATCHPAD позволял писать код вроде такого [Jenk71] + +``` +p<1> = 1 +p<2> = 1 + x +p = x*p - d*p, i in (3,4,...) +``` + +Тут `<` `>` у параметров - подсказка для рендерера двухмерного вывода, отображается как нижний индекс. +SCRATCHPAD позволял и намного больше - паттерном слева от `=` может быть выражение языка, записанное в конкретном, не абстрактном синтаксисе [Jenk79]. + +``` +df(u+v) = dfu + dfv +dflog(t) = 1/t +``` + +Вычислитель находил подходящее такому паттерну выражение языка и заменял его на выражение справа от `=`. Что могло бы быть отличной фичей для метаязыка вроде ML и существенно улучшить его качество как специализированного языка. Не смотря на обсуждаемые ранее заверения Милнера о том, что улучшить его уже невозможно. Для языка общего назначения - уже не такой хорошей фичей, с учетом её цены. Такие уравнения добавлялись в набор правил переписывателя по алгоритму Маркова. И, хотя имплементацию можно (но не легко) сделать довольно эффективной [Jenk76], языки общего назначения появляются обычно уже после того как придумают более эффективный способ имплементации чем такое переписывание. Как уже произошло с энергичной лямбдой после изобретения SECD и еще пары случаев, описание которых впереди. +Дело не только в проблеме для имплементатора, но и в удобстве. Обратите внимание на гард после третьего уравнение в определении `p` - он необходим потому, что каждого уравнения должно быть достаточно для матчинга. Да, со временем правила перестали применять по отдельности и стали строить из них всех единое дерево решений, но это только оптимизация. Пользователь системы должен определять их как самодостаточные. +Правила перезаписи для упрощения алгебраических выражений в других системах компьютерной алгебры обычно не походили на определения в SCRATCHPAD и, соответственно, не походили на определения функций в ФЯ: + +``` +MPRED(X):=IF (SIGNUM(X)=-1)THEN TRUE ELSE FALSE +DECLARE(M,MPRED) +TELLSIMP(COS(M),COS(-M)) +DECLARE(N,INTEGER) +TELLSIMP(COS(N*PI), (-1)**N) +``` + +Эти команды добавляют в упроститель системы компьютерной алгебры MACSYMA два правила. Гарды привязываются с помощью "динамических типов". И да, `if ... then true else false`. Реальный код из статьи [Fate71]. +И это еще с паттернами, которые похожи на деревья, которые они матчат. Более типичное описание правил было бы с условиями, предикатами и геттерами для внутреннего описания дерева на языке, на котором написана переписывающая по этим правилам система. +Поэтому трудно говорить о происхождении нотации объявления функций в ФЯ от правил перезаписи. Правила перезаписи бывают очень разного вида. И обычно серьезно отличающегося от ФЯ вида. Исключений не так и много, еще одну нотацию для описания правил перезаписи от которой синтаксис ФЯ мог бы произойти, как и от SCRATCHPAD, мы еще рассмотрим позднее. +В конце семидесятых началась разработка SCRATCHPAD 2, который должен был получить типы, модули, более традиционные (и эффективно имплементируемые) языковые конструкции и стать в достаточной степени языком общего назначения, чтоб имплементировать на нем его собственный компилятор [Jenk77]. Но в 70-е, даже для автора ФЯ, с соответствующими не самыми высокими требованиями к производительности и практичности решений, естественно заключить, что для языка общего назначения нужен хоть и не такой амбициозный, но зато более эффективно имплементируемый паттерн-матчинг. Например, как в статье Бурсталла [Burs69], синтаксис из которой - `cases:` также можно было использовать в SCRATCHPAD [Jenk74]. + +``` +d u = cases: u + x+y: d x + d y +``` + +Эта статья Бурсталла написана на основе доклада в Йорктаун Хайтс. Так что идеи двигались не только из IBM Research в Эдинбург, но и в обратном направлении, из Группы Экспериментального программирования в Йорктаун Хайтс. И, следовательно, в SCRATCHPAD. Если не непосредственно, то через работы Клиффорда Джонса [Jone78] [Jone78b], в неисполняемом языке спецификации которого такая конструкция тоже была. +Но в ФЯ Эдинбургской программы эта конструкция для паттерн-матчинга найдет применение только после другой, из языков гораздо менее требовательных к производительности, чем системы компьютерной алгебры и даже вовсе не предназначенных для выполнения. +Итак, ФЯ не выглядят _в точности_ как математическая нотация и как системы компьютерной алгебры. Они выглядят более или менее так: + +```haskell +df t (Plus u v) = df t u + df t v +df t (Log u) | t == u = 1/t +``` + +Так как _что_ они выглядят? + +### Индуктивное сопротивление + +Эпоха не предназначенных для исполнения языков началась в 70-е. В 60-х даже язык спецификации вроде ISWIM должен был исполняться. Потому, что а что еще с ним делать? В 70-е же, наконец, нашли что. Доказывать теоремы. +Почему про функциональные языки часто уверенно говорят, что свойства кода на них легко доказывать? Ведь свойства кода на этих (как и прочих) языках не особенно часто доказывают, не говоря уже о том, чтоб легко. Дело в том, что они происходят и от языков, которые буквально только для этого и разрабатывали. +И если язык не должен выполняться вовсе, то просто нет никаких пределов для тех степеней в которых он может быть неэффективным и высокоуровневым. Даже языки "исполняемой спецификации" вроде ISWIM или "педагогические языки" вроде PAL не могли себе позволить быть настолько неэффективными, как как языки, для которых эффективность исполнения вовсе не имеет смысла. Это сковывает воображение их авторов. +Все на что оказалась способна Обоекембриджская программа - это изобрести существенно урезанную Java 8, что может и впечатляюще для 60-х, но не поражает воображение уже в 70-е. Недостаточно амбициозно для следующих 700 непопулярных языков. Для того, чтоб изобрести то, что не попадет в мейнстрим так просто как обоекембриджские идеи эдинбуржцам нужно было, хотя бы на время, сбросить оковы исполняемости. +Разумеется, со временем находятся причины запускать код даже на совсем не предназначенном для исполнения языке. И впоследствии оказалось, что нужно не так уж много отступить назад от такого высокоуровневого языка, чтоб получить язык вполне исполняемый и даже быстрый. Но попасть в эту точку минуя область полной неисполняемости и неэффективности видимо было психологически сложно. + +#### Квадратный Лисп + +Одним из первых таких языков был Pure Lisp Бойера-Мура. Так называемый "pure lisp" уже был описан в мануале LISP 1.5 [McCa62], но это не один и тот же язык. И дело не в том, что Pure Lisp Бойера и Мура - это EDSL для POP-2 и имеет синтаксис его подмножества для определения списков. Это новый язык и даже новая разновидность языков. Pure Lisp МакКарти - довольно типичное явление 60-х, как чистое подмножество ISWIM или PAL. Как и чистое подмножество PAL оно полностью эфемерно не смотря на то, что LISP 1.5 и PAL имплементированы. Эти части языков имеют названия только для того, чтоб использовать их для построения фраз, вроде фразы Тернера "Pure Lisp никогда не существовал". (Не)соответствие подмножеству не может быть механически проверено даже в ограниченном смысле LCF/ML, в котором можно защитить от изменения ссылки, не объявив их как мутабельные. Pure Lisp Бойера и Мура - совсем другое дело. Чистота защищена надежно: никаких нарушающих её фич в языке и нет. + +##### Существует ли список из двух элементов? + +Даже Чистый Лисп Бойера-Мура и другой Чистый Лисп Бойера-Мура - это не один и тот же язык. Изобретение этого Лиспа происходило в два этапа. На первом, резолюционном, Бойер и Мур начали с доказателя-логического языка BAROQUE, который назван в честь разновидности шахмат Эббота "Барокко". Также они изобрели практичный способ имплементации логических языков. Правда, практичный в основном только на компьютере, который в Эдинбурге еще даже и не появился, и ни на каком другом, но это уже другая история. +Авторы языков того времени и Бойер с Муром в особенности не любят показывать в статьях, диссертациях и книгах как код выглядит на самом деле. Видимо потому, что считают его слишком страшным для печати. Может бумага все стерпит, но читатель стерпит не все. Когда увидит реальный код - будет сюрприз! Нам известно, как выглядел язык Бойера-Мура во второй фазе, так что можем предположить, что код на BAROQUE выглядел как-то так: + +``` +КАКАЯТОФУНКЦИЯPOP2("LEN1", [[+ [V [LENGTH [NIL]] 0]]]); +КАКАЯТОФУНКЦИЯPOP2("LEN2", [[+ [V [LENGTH [CONS X Y]] Z]] + [- [V [LENGTH Y] U]] + [- [V [ADD U 1] Z]]]); +``` + +но в диссертации Мура [Moor73] это выглядело так: + +``` +LEN1: ((+ (V (LENGTH (NIL)) 0))) +LEN2: ((+ (V (LENGTH (CONS X Y)) Z)) + (- (V (LENGTH Y) U)) + (- (V (ADD U 1 ) Z))). +``` + +и даже так: + +``` +LEN1: (LENGTH NIL) -> 0; +LEN2: (LENGTH (CONS X Y)) -> Z + WHERE + (LENGTH Y) -> U; + (ADD U 1) -> Z; + END; +``` + +Ага, вот они уравнения, вот откуда все пошло! Нет. Это нотация для правил перезаписи. Один из тех псевдокодов, который лисперы использовали в статьях для того, чтоб не отпугнуть читателя Лиспом. Использовалась в известной в Эдинбурге статье [McBr69] о расширении Лиспа. + +``` +RULE D +D1: (N X)->0 when (NUMBERP N) +D2: (X X)->1 +D3: ((+ U V)X)->(+(D U X)(D V X)) +D4: ((* U V)X)->(+(* U(D V X))(* V(D U X))) +D5: ((— U)X)->(-(D U X)) +``` + +Реальный код на Лиспе, разумеется, выглядел иначе: + +``` +DEFRULES(( +(+(DARG(A B) +(AP1((A B)(LIST("+)A B)) +(TP1((+ A B)(("+)B A))))) +(*(DARG(A B) +(AS1((A B)(LIST("*)A B)) +(TS1((* A B)(("*)B A)) +TS2(A(("*)1 A)) +TS3(A(("*)A 1))))) ... +``` + +Наиболее очевидное отличие этой нотации от уравнений с паттерн-матчингом в функциональных языках - каждое правило перезаписи имеет собственное имя. Не только вся группа таких правил - функция. Эти правила с индивидуальными именами можно увидеть и в ФЯ, но не как основной способ описания функций [GHC23]: + +```haskell +{-# RULES + "map/map" forall f g xs. map f (map g xs) = map (f . g) xs + "map/append" forall f xs ys. map f (xs ++ ys) = map f xs ++ map f ys + #-} +``` + +Бойер и Мур посчитали уравнения BAROQUE низкоуровневыми и "ассемблероподобными". Это только фундамент для построения по-настоящему высокоуровневого языка - Лиспа. + +``` +LENGTH: (LENGTH X) -> U + WHERE + (COND X + (ADD1 (LENGTH (CDR X))) + 0) -> U; + END; +``` + +Не беспокойтесь, у последователей Бойера и Мура в Эдинбурге конечно же будет противоположное представление о том, что более высокоуровнево. +Наконец то, ради чего все и затевалось. Поскольку это логический язык, можно вызвать функцию LENGTH "наоборот" + +``` +(LENGTH X) -> 2; +``` + +и доказать, что список из двух элементов действительно существует. Но это по большому счету и все, что удалось доказать. +Бойеру и Муру этого было мало, они хотели доказывать более интересные утверждения. Например, что длина конкатенации двух списков равна сумме длин этих списков. + +##### Индукционный переход + +Чтоб доказывать более интересные теоремы Бойер и Мур написали следующий доказатель, поддерживающий структурную индукцию. +В этот раз Чистый Лисп был встроен сразу в POP-2, а не сначала в логически язык. Пользователи доказателя, они же его авторы, могли определять рекурсивные функции такого вот вида [Moor18b]: + +``` +DEFINE +([MAPLIST + [LAMBDA [X Y] [COND X [CONS [APPLY Y [CAR X]] [MAPLIST [CDR X] Y]] NIL]]]); +``` + +и к осени 73-го написали пару сотен строк такого кода. Также написали сотню теорем такого вот вида: + +``` +COMMENT 'THEOREMS INVOLVING MAPLIST'; +[T 3 1]:: +[EQUAL [MAPLIST [APPEND A B] C] [APPEND [MAPLIST A C] [MAPLIST B C]]]; +[T 3 2]:: +[EQUAL [LENGTH [MAPLIST A B]] [LENGTH A]]; +[T 3 3]:: +[EQUAL [REVERSE [MAPLIST A B]] [MAPLIST [REVERSE A] B]]; +``` + +которые доказатель доказывал полностью автоматически (но не мог проверить завершимость, это должен был обеспечить пользователь) [Boye75]. Серьезный шаг вперед, по сравнению с доказательством существования списка из двух элементов! +Время доказательства средней такой теоремы было 8-10 секунд. Самые сложные, включающие `SORT` доказывались 40 - 150 сек. [Moor73] на ICL 4130 машине. +Третий язык в доказателе Бойера и Мура был пока что только псевдокодом в комментариях. Правила переписывания для упростителя были обычным POP-2-кодом. Такого EDSL, как для двух других языков, для правил перезаписи не было [Moor18]: + +``` +FUNCTION REWRITE TERM; +VARS TERM1 TERM2 TERM3; + +COMMENT 'IF TERM IS AN EQUALITY`; + +IF HD(TERM)="EQUAL" THEN +HD(TL(TERM))->TERM1; +HD(TL(TL(TERM)))->TERM2; + +COMMENT '(EQUAL KNOWN1 KNOWN2) => T OR NIL`; +IDENT(TERM1,TERM2) -> TERM3; +IF TERM3 = NIL THEN NIL; EXIT; +IF TERM3 THEN "T";EXIT; + +COMMENT '(EQUAL BOOL T) => BOOL`; +IF TERM1==1 AND BOOLEAN(TERM2)THEN TERM2 EXIT; +IF TERM2==1 AND BOOLEAN(TERM1) THEN TERM1 EXIT; + +COMMENT '(EQUAL (EQUAL A B) C) => + (COND (EQUAL A B) (EQUAL C T) (COND C NIL T))`; +IF SHD(TERM1) = "EQUAL" OR SHD(TERM2) = "EQUAL" AND (SWAP;1) + THEN + [% "COND", TERM1, + REWRITE([% "EQUAL", TERM2, "T" %]), + REWRITE([% "COND", TERM2, NIL, "T" %]) %] -> TERM; + GOTO COND; + CLOSE; +``` + +Понятно, что код, свойства которого проверял доказатель - это не тот код, который в то время писали даже и на первых ФЯ. Доказатель не мог доказывать даже свойства функции с хвостовой рекурсией вроде такой: + +```lisp +(REVERSE1 (LAMBDA (X Y)(COND X + (REVERSE1 (CDR X) + (CONS (CAR X) Y)) + Y))) +``` + +Раз уж свойства такой функции не доказывались, то и реального кода нет, так что мы воспользуемся случаем и сделаем её примером того, как Бойер и Мур оформляли код на своем Чистом Лиспе в своих статьях. +Да, свойства реального кода не проверить, но не лучше ли программисту и писать такой высокоуровневый код, предлагает Мур [Moor73]. +Что же делать потом с этим высокоуровневым кодом? К счастью, в Эдинбурге как раз существует еще один проект. Разрабатывается система для трансформации такого наивно-рекурсивного кода в циклы. Системы дополняют друг друга. Трансформации делают Чистый Лисп имплементируемым, а доказатель может доказывать равенства нужные для трансформаций. +Важно отметить, что про эту синергию пишут [Moor73] [Boye75] авторы доказателя Бойера-Мура, успешного проекта с большим будущим. А не только авторы проекта по трансформации кода, о котором никто ничего сейчас не знает, пытающиеся уцепиться за успешную вещь, набирающую ход. Правда, вероятно, что доказатель оказался успешным как раз потому, что такая сцепка вскоре оказалась ненужной. + +#### S-0 + +Не смотря на все заявления о том, как трансформационный проект и доказатель дополняют друг друга, не смотря на общего научного руководителя - Бурсталла, система для трансформации кода работала не с Чистым Лиспом Бойера-Мура, а другим языком, который появился раньше, чем второй Лисп Бойера-Мура, но не факт, что раньше, чем первый. Оба языка небольшие и транслировать один в другой было бы не особенно сложно даже в обсуждаемые времена, но это не было сделано и совместное использование - не более чем возможность, которая не была реализована. +У Милнера тоже был ЯП этой новой разновидности, не предназначенный для исполнения. Язык назывался L, корректность компилятора которого Милнер доказывал [Miln76]. Но, не смотря на расстояние 1 между их названиями, ML не произошел от L. По крайней мере переходные звенья между ними не сохранились. ML сформировался в относительно современном виде за пару лет. +Другое дело - S-0. Второй основной протоязык Эдинбургской программы имеет более долгую, инкрементальную и богатую названиями и описаниями разных фаз историю. И на протяжении большей части истории, которая разворачивалась в 70-е, язык не был даже функциональным. Поскольку нефункциональность может означать много чего, скажем точнее: был языком первого порядка. Язык в 70-е годы сменил три названия, но даже трех названий мало, для того чтоб назвать все существенно отличающиеся версии, так что мы будем по необходимости приписывать год к названию, хотя такая система именований его авторами не использовалась. +Как и у Милнера, у Бурсталла несколько соавторов, и первый из них - Джон Дарлингтон (John Darlington). Дарлингтон написал первую версию трансформационной программы работая над своей диссертацией. +В момент начала работы Дарлингтона Бойер и Мур еще ничего интересного не доказывали, и не известно было будут ли, идея программы появилась иначе. +Все началось с того, что Пэт Эмблер (A.P.Ambler) [Darl76], Поплстоун [Burs71] и Бурсталл [Burs71] [Darl76] написали набор процедур на POP-2 для манипуляции множествами. Библиотека из 43 (сорока трех) строк кода (в 1968) [Burs71] является редким примером функционального программирования тех лет. Одна из функций в библиотеке даже определена частичным применением функции к частично примененной функции: + +``` +VARS SUMSET; +LIT(% NIL,UNION(%NONOP=%) %)->SUMSET; +``` + +что примерно соответствует такому коду: + +```haskell +sumset = foldr (unionBy (==)) [] +``` + +Пользователи библиотеки столкнулись с проблемой, которую авторы функциональных библиотек такого типа пытаются решить и сегодня. Хотя эти функции можно использовать для написания других функций, сразу бросается в глаза, что можно было бы написать гораздо более эффективную программу, если манипулировать массивами или списками непосредственно. +Ну что же, значит вместо библиотеки нужен язык с абстрактными множествами и операциями над ними. И трансформационная система должна инлайнить тела этих операций и осуществлять слияние циклов и прочие необходимые оптимизации. +Манипуляции с абстрактными множествами важны для того, в каком направлении стал развиваться второй Эдинбургский язык, но не для Бойера с Муром. Более интересным для них было преобразование рекурсии в итерацию, что система также должна была делать. +Преобразованием рекурсии в циклы уже занимались в лаборатории IBM в Йорктаун Хайтс [Stro70], но без особых практических последствий. Дарлингтону с Бурсталлом не было известно ни о каких примерах использования таких преобразований в компиляторах, за исключением преобразования самых простых случаев рекурсии в BBN LISP. +Авторам идеи не хватило смелости делать трансформации автоматическими. Система трансформации программ Дарлингтона - это не оптимизирующий компилятор, а скорее что-то похожее на систему компьютерной алгебры: пользователь работает в REPL и переписывает программу в полуавтоматическом режиме, решая какие преобразования и где применить. +S-0 - это язык на котором пользователь пишет первоначальную наивную рекурсивную программу, работающую с абстрактными множествами [Darl72]. Этот код в полуавтоматическом режиме транслировался через ряд промежуточных языков. Сначала S-0 транслировался в S-1 - язык с циклами и переменными. Множества пока что оставались абстрактными, но на следующем этапе пользователь должен был выбрать конкретное представление, произведя трансформацию в B-0 или L-0. +В L-0 операции над множествами имплементированы как операции над иммутабельными списками. В B-0 - как операции над массивами. Код на B-0 - конечный результат, а код на L-0 можно было преобразовать еще раз в код на L-1, в котором операции над списками производятся деструктивно, на месте. Это переиспользование cons-ячеек Бурсталл и Дарлингтон называют "сборкой мусора времени компиляции" [Darl76]. +Система трансформирует в эффективный код важнейшие ФП функции `fact` и `fib`, с чем современные компиляторы ФЯ не справляются. Почему так? +Для трансформации в эффективный код системе нужно использовать свойства операций, такие как коммутативность и ассоциативность. Трансформации, которые преобразуют код в эквивалентный даже без учета таких свойств Дарлингтон и Бурсталл посчитали слишком слабыми для использования на практике. +И это, обычно, такие свойства, которые может доказать доказатель Бойера-Мура [Boye75] [Moor18b] : + +``` +DEFINE +([APPEND [LAMBDA [X Y] [COND X [CONS [CAR X] [APPEND [CDR X] Y]] Y]]]); + +[T 1 1]:: +[EQUAL [APPEND A [APPEND B C]] [APPEND [APPEND A B] C]]; +``` + +Именно это имеет в виду Мур [Moor73] когда пишет что не только трансформатор дополняет доказатель, но и доказатель дополняет трансформатор. +Вооруженный знанием о ассоциативности сложения целых чисел, трансформатор конвертирует функцию вычисления факториала в цикл. Наивный вариант ускоряется в 10 раз. +Наивная функция разворачивания списка при трансформации становится функцией, которая конкатенирует списки из одного элемента с результирующим, а не наоборот, так что ускорение еще больше, чем просто от преобразования в цикл - 30 раз. +Наконец наивная функция вычисления чисел Фибоначчи ускоряется в 100 раз на примере из статьи. В двух последних случаях улучшается асимптотика алгоритма, так что результаты могли бы быть лучше на машине, память которой вмещает более впечатляющие списки. Но к моменту появления таких машин оптимизировать эти функции перестали. +В современных компиляторах ФЯ преобразования, которым нужны для корректности свойства вроде ассоциативности сложения не делаются, но они делаются компиляторами C++. +В переписывателе Дарлингтона и Бурсталла трансляции этих однострочников производятся по командам пользователя системы, которые выполняются за десятки (в редких случаях - единицы) секунд. На той же машине, на которой разрабатывался доказатель Бойера и Мура - ICL 4130. +Дарлингтон защитил диссертацию [Darl72] в 72-ом, но первая версия системы готова только в январе 73-го [Darl76]. С 73-го года ведется разработка второй версии системы. +Если ML начали имплементировать уже на новой машине, то наработки Бурсталла и Дарлингтона, возможно, нужно было переносить на новую машину. Но нужно ли - зависит от того, что означало то, что "разработка ведется". Но если какой-то код уже писали с 73-го, а не только собирались писать, переезд на новую машину прошел довольно безболезненно. +Это вполне возможный вариант развития событий потому, что POP-10 для PDP-10 (удачного различения в дальнейшем тексте `D` и `O`) был написан еще в 1969 году Малькольмом Аткинсоном (Malcolm Atkinson) и Реем Данном (Ray Dunn) в Университете Ланкастера [Popp2002]. Это воспоминание Поплстоуна, правда не находит подтверждения в воспоминаниях Сломана [Slom89], который не упоминает Университет Ланкастера и утверждает, что разработал POP-10 Джулиан Дэвис (Julian Davies). POP-10 оставил после себя слишком мало следов, чтоб мы могли найти третий источник, который подтвердил бы правоту того или другого. Но мы, конечно, поверим нашему старому знакомому Поплстоуну (который скорее всего больше не появится в нашей истории), а не новому знакомому Сломану, появления которого еще впереди. +Заблуждение Сломана объясняется тем, Девис из Университета Западного Онтарио, Канада, по всей видимости занимался поддержкой POP-10 в это время [Davi76]. Эта имплементация POP-2 (с расширениями) разделяла код с коммерциализированной имплементацией, которой владела компания Мики Conversational Software Ltd., которая ничем не поможет пользователю POP-10 в случае чего, но может помешать. +Имплементация становилась все востребованнее потому, что PDP-10 получили и другие университеты, и теперь пытались использовать POP-2, ставший более-менее стандартным языком в Великобритании. Среди прочих, важный для нашей истории Имперский колледж Лондона (Imperial College London). Видимо, такой сомнительный статус имплементации, привел к тому что правительственная структура SRC выделила деньги на создание новой имплементации. В 76-м в Эдинбурге Роберт Рэй (Robert Rae) [Popp2002] [Slom89] и Аллан Рэмси (Allan Ramsay) [Slom89] написали еще одну имплементацию POP-2 для PDP-10 - WonderPOP (обычно WPOP), на которую перешел и Бурсталл. +Первая версия системы начинала с преобразования рекурсии в циклы [Darl76]. Бурсталл и Дарлингтон решили, что это было ошибкой. Нужно делать как можно больше преобразований с рекурсивной формой и только заканчивать трансформацией в циклы. Вторая версия [Darl75], работа над которой велась с 73-го года, использует этот подход. Работа с рекурсивным кодом проще и использует наработки из доказателя Бойера-Мура. +К июлю 1975 основа новой системы имплементирована [Darl76]. Новая система воспроизвела почти все преобразования из первой за исключением переписывания cons-ячеек. Это направление было заброшено и, по большому счету остается заброшенным и в наши времена. +Но вторая система [Darl75], в отличие от первой, не представляет собой некую полуавтоматическую имплементацию, преобразующую код на чистом рекурсивном языке в императивный код. Который можно запускать с помощью какой-нибудь имплементации POP-2 или похожего языка. В этот раз трансформируется только чистый рекурсивный код в чистый рекурсивный. Имплементировано только то, что авторам в тот момент интереснее всего. Авторы утверждают, что могут использовать свои наработки из первой системы для трансформации рекурсивного кода в циклы, но не используют. +Авторы используют наработки Мура [Moor75], который решал обратную задачу перехода от итеративного кода к рекурсивному, для работы с хвостовой рекурсией и аккумуляторами. Это сделало более интересную работу с рекурсивным кодом возможной. Это также сделало поддержку итерации в доказателе Бойера-Мура возможной и, соответственно, всю эту синергию первого трансформатора и их доказателя ненужной Бойеру с Муром. +Что еще нового из трансформаций? Как мы помним, Эдинбургская программа оказалась не особенно дружественна к продолжениям и выработала ряд ответов на вопрос "как же быть без продолжений?". Один из классических примеров, демонстрирующих полезность продолжений - это пример Хьюита [Hewi74]. Задача: проверить, что два бинарных дерева имеют одинаковую последовательность листьев. Естественное решение: одна функция сплющивает дерево в список, другая сравнивает два списка. Понятно, что в этом случае делается лишняя работа: даже если первые же элементы не совпали, оба дерева должны быть сплющены полностью. Продолжения позволяют сохранить модульность, обеспечив раннее завершение. Но это позволяет и вторая версия трансформатора программ [Darl75]. Трансформация соединяет эти две функции в одну, которая лишней работы не делает. +Как мы знаем, трансформационная система не имеет будущего. Пользователи последующих имплементаций ФЯ не будут выбирать где и как производить оптимизации вручную в интерактивном режиме. Что имеет будущее, так это язык. Как он выглядит? Сначала как ISWIM, т.е. по разному в каждой статье о нем. И даже по разному в одной и той же статье. +В диссертации Дарлингтона [Darl72] в псевдокоде слева от ветвей выбора везде подрисованы от руки "акколады", фигурные непарные скобки как в одном из вариантов нотации для задания кусочных функций. В более приближенном к реальному коде из примера REPL сессий в статье [Darl73] этим скобкам ничего не соответствует. Язык выглядит как ISWIM в статьях Ландина: + +```haskell +union(x,y)= +nullset(x)->y, +not nullset(x)->cosset(choose(x), + union(minus(choose(x),x),y)) +``` + +В самой поздней публикации [Darl76] о первой версии системы выглядит как ISWIM в статьях Бурсталла: + +``` +reverse(x) = if null(x) then nil + else concat(reverse(tl(x)), + cons(hd(x),nil)) +``` + +В статье 73-го года [Darl73] в некоторых примерах кода вместо `=` между именем функции и телом появляется обратная стрелка `<=` . Эта стрелка - одна из самых узнаваемых деталей языка. В следующие десять лет в статьях про вторую трансформационную систему [Darl75] и сам язык стрелка используется последовательно во всех примерах: + +``` +concat(x,y) <= if x=nil then y else + cons(car(x),concat(cdr(x),y)) fi +``` + +Но что такого особенного в этих языках? S-0 и Pure Lisp Бойера и Мура - просто урезанный ISWIM. Да, но на вид будущих ФЯ Эдинбургской программы повлияет не ISWIM-подобная часть ввода доказателя Бойера-Мура. +В статье [Darl75] представленной на конференции в апреле 75-го года Дарлингтон и Бурсталл пишут, что система работает пока что с традиционным ISWIM-подобным синтаксисом, но у них уже запланировано расширение, которое в основном и используется в статье. Другими словами, примеры в статье - это в основном псевдокод, который только станет реальным. И в этом псевдокоде есть ошибки. Но заявлено, что система работает, и все примеры трансформаций кроме одного имплементированы, просто для языка со старым синтаксисом. +Поскольку трансформатор программ требует спецификации операций и работает с абстрактными данными, только вопрос времени, когда эта исследовательская программа соединится с другой, занимающаяся спецификацией операций над абстрактными данными. И мы будем считать, что это произошло в 74-ом году, когда Бурсталл познакомился с вторым основным своим соавтором 70-х - Джозефом Гогеном (Joseph Goguen) [Burs2006]. + +### Ничего постояннее временного + +> И структуры данных, над которыми не производятся операции и программы, которые не производят операции над структурами данных не интересны. +> Джозеф Гоген, Некоторые принципы дизайна и теория OBJ-0, языка для выражения и исполнения алгебраических спецификаций программ. + +Через год после написания статьи о доказательстве свойств программ [Burs69], в которой вводится нотация для паттерн-матчинга в ФЯ, Бурсталл написал новую статью [Burs70] о доказательстве свойств программ, в которой вводит другую нотацию: + +``` +Sorts: values, states, expressions. +... + numeral ⊆ expressions + plus, minus, times, equal: expressions x expressions -> expressions + val: expressions × states -> values +... +Axioms: +... + numeral(m) => val(m,s) = numeralval(m) + val(plus(e,e'),s) = val(e,s) + val(e',s) + val(minus(e,e'),s) = val(e,s) - val(e',s) + val(times(e,e'),s) = val(e,s) × val(e',s) + val(e,s) = val(e',s) => val(equal(e,e'),s) = true + val(e,s) ≠ val(e',s) => val(equal(e,e'),s) = false +``` + +То есть, изобретает вторую из двух нотаций для паттерн-матчинга в ФЯ? Это уже похоже на объявления функций с помощью уравнений с паттерн-матчингом, если не считать необычный вид "гард". Да, это они слева перед `=>`. Но не торопитесь. Пока что никто не собирается ничего матчить и применять какие-то функции. Это описание спецификации, похожие декларации свойств были и в предыдущей статье [Burs69], но не такие сложные. Так что никаких интересных деталей вроде гард и групп уравнений, определяющих одну функцию там не найти: + +``` +(i) cons(car(x), cdr(x)) = x +(ii) car(cons(x, y)) = x +(iii) cdr(cons(x, y)) = y +``` + +Код, свойства которого эти уравнения описывают, там на псевдоисполняемом псевдокоде ISWIM. Из определения `cons(car(x), cdr(x)) = x` функцию так просто не сделать. +Бурсталл описывает более развитую нотацию из следующей статьи [Burs70] как "обычную", с некоторыми расширениями для краткости. Но если посмотреть на нотацию в работах, на которые ссылается Бурсталл, то видно, что обычной её можно назвать только при очень широком толковании слова "обычный". Бурсталл пишет, что в наибольшей степени эта его работа основана на работе Кордела Грина (Cordell Green) [Gree69]. Давайте посмотрим, как нотация выглядела там: + +``` +M5. (∀i,j,s,p,b)[test(p,s) = b ⊃ + f(select(p,b,i,j),s) = f(i,s)] +M6. (∀i,j,s,p,b)[test(p,s) ≠ b ⊃ + f(select(p,b,i,j),s) = f(j,s)] +``` + +Но это псевдокод для того, чтоб не пугать читателя Лиспом, реально язык описания свойств выглядит так: + +``` +MB5 (FA(I J S P B) (IF(EQ(TEST P S) B) + (EQ(F(SELECT P B I J) S) (F J S)))) +MB6 (FA(I J S P B) (IF(NEQ(TEST P S) B) + (EQ(F(SELECT P B I J) S) (F J S)))) +``` + +Эта нотация выглядит как обычная нотация для правил перезаписи, так что мы, видимо, нашли точку отделения предка синтаксиса для объявления функций в ФЯ от правил перезаписи. +Этот язык уравнений Бурсталла - язык первого порядка, чтоб доказательства было легче механизировать. И Бурсталл попробовал его механизировать с помощью доказателя теорем, основанного на методе резолюций, написанного Изобел Смит (Isobel Smith) и с помощью инструмента проверки доказательств Андерсона (D. B. Anderson). Опыт у него был тот же, что и у Милнера. Доказатель нельзя было применить ни к чему больше пары присваиваний, а инструмент проверки позволял проверить программу из пары десятков операторов, но ценой большого объема однообразного ручного труда. Как выглядел при этом код, точно соответствующий псевдокоду из статьи, мы не знаем, но судя по всему был "квадратным" Лиспом из списков POP-2, как позднее у Бойера с Муром. Язык описания спецификации Бурсталла опередил свое время, но через несколько лет ситуация изменилась. Бойер с Муром написали доказатель который работает быстро, а ручного труда не требует и с середины 70-х языки описания спецификаций вообще и абстрактных типов данных в частности стали серьезной исследовательской программой. +Разработка абстрактных типов данных, которой занимались наш знакомый по разработке композитных типов для PAL и CLU Циллес и важные герои этой главы Гоген и Гуттаг, продолжалась уже пару-тройку лет с начала 70-х. +Самые ранние описания алгебраических спецификаций появляются в 74-ом году у Циллеса [Zill74] + +``` +CREATE: -> set +INSERT: set x integer -> set +REMOVE: set x integer -> set +HAS: set x integer -> boolean + +1. INSERT(INSERT(s, i), j) ≡ if i = j then INSERT(s, i) + else INSERT(INSERT(s, j), i) +2. REMOVE(INSERT(s, i), j) ≡ if i = j then REMOVE(s, j) + else INSERT(REMOVE(s, j), i) +3. REMOVE(CREATE, j) ≡ CREATE +4. HAS(INSERT(s, i), j) ≡ if i = j then true + else HAS(s, j) +5. HAS(CREATE, j) ≡ false +``` + +Это "схемы" свойств. Для получения теоремы, выдвигается какая-то гипотеза о переменных в схеме: + +``` +(∀s, i, j)[i ≠ j => HAS(INSERT(s, i), j) = HAS(s, j)] +``` + +Что практически в точности соответствует нотации из [Gree69]. +Нотация описывает свойства CLU-кластера, скрывающего работу с изменяемым массивом + +``` +intset = cluster is create, insert, remove, has, ...; + rep = array of int; + create = oper() returns cvt; + ... +``` + +Это та половина работ Лисков и Циллеса, которую LCF/ML не получил (как и CLU). +Влияния Бурсталла пока не видно, но влияние или, может быть, переизобретение того же самого будет видно у других авторов этого направления. Во второй половине 70-х описатели свойств абстрактных типов данных алгебраического направления придут к общей структуре описания, отличающейся по большему счету деталями синтаксиса [Gutt78]: + +``` +type Stack[elementtype:Type] + +syntax + NEWSTACK -> Stack, + PUSH(Stack, elementtype) -> Stack, + POP(Stack) -> Stack U {UNDEFINED}, + TOP(Stack) -> elementtype U {UNDEFINED}, + ISNEW(Stack) -> Boolean. + +semantics + declare stk: Stack, elm: elementtype; + POP(NEWSTACK) = UNDEFINED, + POP(PUSH(stk, elm)) = stk, + TOP(NEWSTACK) = UNDEFINED, + TOP(PUSH(stk, elm)) = elm, + ISNEW(NEWSTACK) = TRUE, + ISNEW(PUSH(stk, elm)) = FALSE. +``` + +Описание типа, следующее за ним описание сигнатур функций этого типа, затем спецификация - набор алгебраических аксиом в виде уравнений. +Эта интерфейсная часть предполагала возможность разработки кода без наличия или знания имплементации, которая состояла из скрытого представления типа данных в памяти и имплементаций функций типа. + +``` +representation STAK(Array[Integer, elementtype], Integer) + -> Stack[elementtype], + +programs + declare arr: Array, t: Integer, elm: elementtype; + NEWSTACK = STAK(NEWARRAY, 0), + PUSH(STAK(arr, t), elm) + = STAK(ASSIGN(arr, t + 1, elm), t + 1), + POP(STAK(arr, t)) = IF t = 0 THEN STAK(arr, 0) + ELSE STAK(arr, t - 1), + TOP(STAK(arr, t)) = ACCESS(arr, t), + ISNEW(STAK(arr, t)) = (t = 0), + REPLACE(STAK(arr, t), elm) + = IF t = 0 THEN STAK(ASSIGN(arr, 1, elm), 1) + ELSE STAK(ASSIGN(arr, t, elm), t). +``` + +Описатели АТД часто представляют сигнатуру как (абстрактный) синтаксис встроенного языка [Gogu79], спецификацию - как денотационную семантику этого языка, а имплементацию - как его операционную семантику [Gogu79]. +Параметризация типов и в псевдокоде появилась не сразу, а c имплементацией дела обстояли еще хуже. Описатели АТД хотели накладывать ограничения на параметры для того, чтоб статически проверять какие операции над ними доступны. И просто не могли придумать как это сделать. В CLU, для которого АТД хотели имплементировать как можно скорее, просто временно перенесли эти проверки во время выполнения, в ML не сделали пока что ограниченный полиморфизм ни в каком виде, а многие имплементаторы языков для описания АТД не сделали параметризацию вообще. +Как мы помним, Лисков выкинула часть с аксиомами примерно в то время, когда Бойер и Мур научились их использовать для проверки имплементаций. Но, справедливости ради нужно заметить, что и десятилетия спустя такая проверка так и не стала мейнстримом. +В отличие от Лисков, большинство работающих над АТД больше интересовались интерфейсной частью, которую они считали в основном языконезависимой. Эти исследователи имплементировали ряд систем, состоящий из доказателей и языков исполняемой спецификации в сочетании с доказателями и без них. В качестве языков имплементации АТД, если до языков имплементации АТД вообще доходило дело, они выбирали какой-нибудь существующий язык не особенно похожий на то что описывало спецификацию. Например, PASCAL [Muss80a]. +Так что, по мере развития темы, представления АТД у разных авторов стали меняться чтоб лучше отражать их интересы и цели. +Что получилось с АТД после выкидывания аксиом мы уже выяснили в главе про ML, а теперь посмотрим что получилось у тех, кого больше интересовала спецификация, а не имплементация. Но что интересного это направление может дать для нас, интересующихся _имплементацией_ функциональных языков? + +#### Непосредственная имплементация + +Одну из важнейших систем описания абстрактных типов данных разрабатывали Гуттаг (John V. Guttag), Мюссер (David R. Musser) и другие в Институте Информационных наук Университета Южной Калифорнии в Лос-Анджелесе (USC Information Sciences Institute). +Описания свойств функций над АТД должны были проверяться доказателем, но для того, чтобы писать код независимо от имплементации одних статических проверок мало, утверждает Гуттаг [Gutt78]. Программист очевидно захочет исполнять и тестировать код. Для этого понадобится написать хотя-бы наивную имплементацию абстрактного типа данных. Но понадобится ли? +Представьте себе, такая имплементация - Гуттаг и др. называют её _непосредственной_ - у нас уже есть. Это секция с уравнениями, которая описывает семантику. +Посмотрим на определение АТД для стека на языке Гуттага и Мюссера [Muss80b]: + +``` +type Element; + +interface errElement : Element; + +end {Element}; + +type Stack; + +declare s : Stack; +declare e : Element; + +interface + newstack, + e push s, + pop(s), + errStack + : Stack; + +interface + top(s) + : Element; + +interface + isnew(s) + : Boolean; + +axiom + pop(newstack) = errStack, + pop(e push s) = s, + + top(newstack) = errElement, + top(e push s) = e, + + isnew(newstack) = true, + isnew(e push s) = false; + +end {Stack}; +``` + +Некоторые функции, такие как `newstack` и `push` не имеют соответствующих уравнений в секции аксиом. Результаты их применения только используются в уравнениях для таких функций как `pop`, `top` и `isnew`. +Первая категория функций - это конструкторы абстрактного синтаксического дерева языка, а вторая - программа, интерпретатор этого языка, работающий с AST. И в данном случае соответственно `[]`, `:`, `tail`, `head` и `null`. +_Непосредственные_ имплементации могут быть полезны не только для тестирования. В некоторых случаях - рискует предположить Гуттаг - они могут служить даже в качестве конечной имплементации. Сумасшедшая идея! Не факт, что эта идея впервые пришла в голову именно Гуттагу, но у него явно было больше желания её объяснять. +Понятно, что не всякую спецификацию, записанную как уравнения, можно вот так просто брать и выполнять. Интересно, что среди спецификаций, которые проверял доказатель Бойера-Мура [Boye75] практически не было исполняющихся. И исключения довольно скучные, вроде: + +```lisp +(EQUAL (DOUBLE A) (MULT 2 A)) +``` + +Единственный нетривиальный пример который в одном шаге от исполняемости это: + +```lisp +(GT (LENGTH (CONS A B)) (LENGTH B)) +``` + +Если б только Бойер с Муром были вынуждены описывать спецификацию как уравнения, а не неравенства, могло бы получится что-то исполняемое вроде: + +```lisp +(EQUAL (LENGTH (CONS A B)) (ADD 1 (LENGTH B))) +(EQUAL (LENGTH NIL) 0) +``` + +Но пример со стеком не был подобран Гуттагом и Мюссером специально из-за исполняемости спецификации, этот пример типичен для литературы об АТД еще с тех времен, когда спецификации не собирались исполнять [Zill75]. Но если не говорят, что собираются - не обязательно означает, что не думают. Так Циллес переходит от не настолько очевидно исполняемого примера в статье 74-го года [Zill74] к легко исполняемому примеру в статье 75-го [Zill75]: + +``` +Functionality: + CREATE: -> STACK + PUSH : STACK X INTEGER -> STACK + TOP : STACK -> INTEGER U INTEGERERROR + POP : STACK -> STACK U STACKERROR + +Axioms: + 1' TOP(PUSH(S,I)) = I + 2' TOP(CREATE) = INTEGERERROR + 3' POP(PUSH(S,I)) = S + 4' POP(CREATE) = STACKERROR +``` + +Может быть просто совпадением. +Итак, придется подбирать выполняющиеся уравнения. Но и тут Гуттаг видит плюсы. Программист, по его мнению, часто не готов писать спецификации. Так пусть он пишет наивные имплементации на высокоуровневом языке. С этим он скорее справится. +Какие уравнения выполняющиеся - зависит от имплементации их исполнителя. Существует широкий спектр возможностей: от сложных систем имплементирующих правила переписывания до различных имплементаций паттерн-матчинга, как в Прологе или как в современных ФЯ. +Гуттаг и др. начали с планов о более амбициозном исполнителе. В ноябре 76-го они уже обсуждают идею о том, что некоторые спецификации можно исполнять символической интерпретацией [Gutt76]. Но многих человеколет для амбициозной имплементации не понадобится! Как они считают. Основные усилия нужные для этого уже сделаны и существуют в системах компьютерной алгебры, например в SCRATCHPAD. Да, SCRATCHPAD нельзя использовать из-за его закрытости, но такая машинерия для переписывания выражений существует во всех системах компьютерной алгебры: в MACSYMA, в REDUCE, не только в тех, где языки с уравнениями, похожими на языки спецификации АТД. Один из соавторов Гуттага - Дэвид Мюссер уже работал над использованием системы компьютерной алгебры REDUCE для доказательства свойств программ [Muss74]. Так что Гуттаг с Мюссером планировали использовать REDUCE для имплементации исполнителя аксиом. +Но в мае 78-го, когда система под названием DTVS наконец имплементирована, никаких признаков использования REDUCE для исполнения спецификаций не видно. Остается только некое влияние идей из SCRATCHPAD. Со временем, языки для описания АТД сами повлияют на вторую версию SCRATCHPAD может быть даже и больше, чем SCRATCHPAD повлиял на них. +Имплементация исполнителя спецификаций достаточно простая, это даже не компилятор паттерн-матчинга в современном смысле, который пытается построить более-менее оптимальное дерево условий. Подсистема тем не менее называется "компилятор паттерн-матчинга" - PMC. Аксиомы транслируются в функции-конструкторы на Interlisp, конструирующие значения с еще более неэффективным представлением в памяти, чем у композитных типов МакКарти. Это лисповые списки в которых первый элемент - тег конструктора, а остальные элементы - его параметры. Также генерируются селекторы, обходящие эти списки и извлекающие из них тэги и прочие элементы. Теги проверяются гораздо чаще, чем нужно. +Решением проблемы этих лишних проверок может быть другая часть системы CEVAL (Conditional EVALuator). Это доказатель для проверки свойств, родственный доказателю Бойера-Мура. В доказателе есть переписыватель, который знает свойства условных выражений, а значит может использоваться как оптимизатор получившегося промежуточного кода. Переписыватель может использовать для оптимизаций и аксиомы из спецификации. В последующих ЯП, вроде GHC Haskell, конечно, исполняются одни уравнения, а используются оптимизатором - другие. +Гуттаг отмечает, что неэффективные представление и диспетчеризация - не проблема непосредственной имплементации вообще, а только конкретной имплементации. Гуттаг и др. думают о компиляции паттерн-матчинга в свитчи. А непосредственные имплементации могут иметь более эффективное представление в памяти, чем у композитных типов, планировавшихся для CPL и тем более чем в МакКартиевской системе с двухместными произведениями и суммами. +Давайте сравним эти представления для вот такого вот дерева: + +``` +node(leaf(1),2,leaf(3)) +``` + +значения, которые не конструируются этими двумя конструкторами мы покажем подписями, а не блоками на диаграмме чтоб убрать лишние детали, скрывающие представление конструкторов дерева. Но на практике это чаще всего тоже указатели на объекты в памяти. +В первой системе МакКарти, как в LCF/ML, где только пары и атомы, представление будет таким: + +``` + + ┌───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ + │ ├──►│*T*│ ├──►│ │ ├──►│ 2 │ ├──►│NIL│ 3 │ + └───┘ └───┴───┘ └─┬─┴───┘ └───┴───┘ └───┴───┘ + │ + ▼ + ┌───┬───┐ + │NIL│ 1 │ + └───┴───┘ + +``` + +В другой системе МакКарти, придуманной им позже и для другого языка, рассказ о котором впереди, а также в пропозале композитных типов для CPL [Stra67] с отдельными типами-суммами и типами-произведениями представление такое: + +``` + + ┌───┐ ┌───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┐ + │ ├──►│001│ ├──►│003│ │ 2 │ ├──►│002│ 3 │ + └───┘ └───┴───┘ └───┴─┬─┴───┴───┘ └───┴───┘ + │ + │ ┌───┬───┐ + └►│002│ 1 │ + └───┴───┘ + +``` + +Непосредственная имплементация может иметь такое представление: + +``` + + ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┐ + │ ├──►│001│ │ 2 │ ├──►│002│ 3 │ + └───┘ └───┴─┬─┴───┴───┘ └───┴───┘ + │ + │ ┌───┬───┐ + └►│002│ 1 │ + └───┴───┘ + +``` + +Упоминая его, Гуттаг ссылается на Хоара, который продвигал такое представление в памяти, к этому мы еще вернемся. +Но в реальности система Гуттага использовала представление в памяти, которое хуже, чем все эти варианты: + +``` + + ┌───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ + │ ├──►│001│ ├──►│ │ ├──►│ 2 │ ├──►│ │NIL│ + └───┘ └───┴───┘ └─┬─┴───┘ └───┴───┘ └─┬─┴───┘ + ┌───────────┘ ┌───────────┘ + ▼ ▼ + ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐ + │002│ ├──►│ 1 │NIL│ │002│ ├──►│ 3 │NIL│ + └───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘ + +``` + +Если используется какой-то уже готовый бэкенд, особенно лисповый, то система управления памятью может просто не поддерживать конструирование каких-то объектов кучи больше пар. К этому мы тоже еще вернемся. +В псевдокоде в статьях Гуттага использованы "схемы типов" т.е. параметризованные типы, но это не было имплементировано в 70-х и отсутствует в реальном коде из статьи Мюссера [Muss80b]. +В 1979-ом Мюссер работает над системой, которая теперь называется AFFIRM [Muss80a], уже без Гуттага. Использует систему для написания спецификации небольшой программы в 1KLOC. Осенью 79-го Мюссер ушел из института. В восьмидесятых Мюссер будет соавтором Степанова и будет работать над продвижением обобщенного программирования в мейнстрим. + +#### Сложный путь + +Авторы AFFIRM рассматривали систему исполнения спецификаций как вспомогательную по отношению к доказателю и не особенно старались имплементировать исполнитель эффективно и исполнять как можно больше возможных спецификаций. Но были и исследователи, для которых исполнение спецификации стало главной целью. Язык исполняемых спецификаций должен исполнять больше спецификаций и исполнять их быстрее. Они хотели сделать исполнение настолько эффективным, насколько это возможно, при условии, что не нужно будет слишком жертвовать способностью исполнять спецификации. + +##### OBJ + +По сложному пути пошел Гоген - автор языка OBJ и соавтор Бурсталла. Гоген работал над алгебраическим описанием семантики с 1972-го года [Gogu85], но дизайн языка, который предполагалось имплементировать начался только в 76-ом году [Gogu2000]. +Гоген работал в том же городе, что и Гуттаг с Мюссером - Лос-Анджелесе, но в Калифорнийском университете (University of California, Los Angeles). +OBJ - язык для написания и исполнения "абстрактных формальных спецификаций программ". Гоген отмечает, что его также можно рассматривать как довольно неэффективный, но весьма высокоуровневый язык программирования. Да, для программирования, а не для временного тестирования, пока не написана имплементация на Паскале. Гоген и Мюссер ссылаются на работы друг друга. Мюссер критикует OBJ за отсутствие доказателя [Muss80a], который не то чтобы обычная часть имплементации языка программирования, если не считать таковым любой тайпчекер, который в OBJ есть. Гоген критикует AFFIRM за то, что это неудобный язык программирования, которым AFFIRM и не должен был быть [Gogu82]. Справедливости ради, первая версия OBJ как язык и имплементация ЯП, на наш поверхностный взгляд, не выглядит лучше AFFIRM. +В OBJ АТД называется объектом и для его декларации используется ключевые слова `OBJECT` или `OBJ`, отсюда и название языка [Gogu79]. + +``` +OBJECT STACK-OF-INT +SORTS STACK / INT BOOL +OK-OPS + PUSH : INT STACK -> STACK + POP_ : STACK -> STACK + TOP_ : STACK -> INT + BOTTOM : -> STACK + EMPTY? : STACK -> BOOL +ERROR-OPS + UNDERFLOW : -> STACK + TOPL : -> INT +VARS + I : INT; + S : STACK +OK-SPECS + (POP PUSH(I,S) = S) + (TOP PUSH(I,S) = I) + (EMPTY? BOTTOM = T) + (EMPTY? PUSH(I,S) = F) +ERROR-SPECS + (TOP BOTTOM = TOPL) + (TOP BOTTOM = UNDERFLOW) +TCEJBO + +RUN PUSH(TOP POP PUSH(2,PUSH(1,BOTTOM)),POP POP PUSH(3,BOTTOM)) NUR +AS STACK: >>ERROR>> PUSH(1,UNDERFLOW) +``` + +Обратите внимание на объявления ошибок для каждого типа и разделение функций на работающие с ошибками и прочими - специфика языков спецификаций. Это идея Гогена о том, что исключения не должны просто населять любой тип как у Милнера в ML, например. +То, что "скобки" закрываются ключевым словом, написанным наоборот может показаться несуразным и ненормальным, но это довольно распространенная синтаксическая идея в 70-е, придуманная не Гогеном. +У уравнений бывают гарды ( = IF ) для имплементации которых в языке есть специальный хак. В OBJ-0 нет полиморфизма но для каждого "сорта" (типы в OBJ называются сортами) `S` автоматически определяется `IF_THEN_ELSE_ : BOOL,S,S -> S` и `_==_ : S,S -> BOOL`. В синтаксисе объявления условного оператора ничего специального нет, это доступная пользователю фича называемая "distributed fix". +Экспериментальная первая версия OBJ с зачаточными возможностями для объявления и исполнения спецификаций под названием OBJ-0 была имплементирована Джозефом Тардо (Joseph Tardo) и Гогеном в 1977-79 годах [Gogu2000] для IBM 360/91 на LISP 1.5. Также они начали имплементировать OBJ для DEC-10 на Rutgers/UCL LISP [Gogu79]. +OBJ должен исполнять больше уравнений, чем AFFIRM. В отличии от языка Гуттага и Мюссера OBJ-0 может исполнять программы, которые выглядят зацикливающимися. Например +``` +A * B = B * A +``` +Эвалуатор может запоминать состояния в которых он был и останавливаться чтоб не перейти в состояние в котором уже был. Но, конечно, и в нем есть ограничения по сравнению с чистой спецификацией из-за исполняемости: например, нельзя использовать справа от `=` переменные, которых нет слева от `=`. +Гоген более амбициозен чем Гуттаг и др. Для него производительность OBJ важна, нужно чтоб работало так быстро как только возможно в рамках желаемой Гогеном семантики, не слишком хорошо совмещающейся с быстротой. Но в 70-е эти амбиции не были реализованы. +Гоген ссылается на еще два языка, хоть и не для описания АТД, но для исполнения уравнений, и авторы которых старались добиться того, чтоб исполнять больше уравнений. Первый из них и, возможно, даже первый исполнитель групп уравнений с паттерн матчингом вообще назывался + +##### TEL + +Доказатель Бойера-Мура работал с тремя языками: Лисп-ISWIM-образными выражениями для описания кода, уравнениями для описания спецификаций и недоступными в первой версии для добавления пользователем правилами перезаписи. Но можно обойтись для всего этого только одним языком с уравнениями и паттерн-матчингом. Как в доказателе Леви (Giorgio Levi) и Сировича (Franco Sirovich) TEL [Levi75]. +Примерно в то же время эта идея о том, что все эти обоекембриджские конструкции и не нужны, если есть уравнения с ПМ - самая радикальная декембриджизация из всех - появится и у автора ФЯ, но это другая история. +TEL - это не язык описания спецификаций АТД. Так что то, что у Гуттага и др. называется непосредственной имплементацией в TEL, как и у Бойера с Муром называется символьной интерпретацией. В TEL 'функции', которые интерпретируются символьно, обозначены кавычками: + +```python +plus('zero',y)=y +plus('s'(x),y)='s'(plus(x,y)) +``` + +Это больше похоже на более современный подход, когда конструкторы отличаются от имен функций синтаксически, но отличается от подхода в языках для описания АТД и языке Бурсталла и Дарлингтона. Они в это время хотели, чтобы разницы было как можно меньше. Эта синтаксическая особенность сохранилась и в современном ФЯ с, пожалуй, наиболее прямым происхождением от языка Бурсталла и Дарлингтона, но не в большинстве других современных ФЯ. +Леви и Сирович работали в Пизе (Istituto di Elaborazione dell’Informazione), но эта их работа - часть расширенной Эдинбургской программы. Все их ссылки в статье на Бойера, Мура, Бурсталла и прочих эдинбуржцев. Из тех, кто во второй половине 70-х занимались символьным интерпретатором [Mart2008] TEL, наиболее известный исследователь, вероятно, Уго Монтанари (Ugo Montanari), разработавший вместе с Альберто Мартелли (Alberto Martelli) быстрый алгоритм унификации [Mart82] (издательство получило статью еще в сентябре 79-го). В своей статье Мартелли и Монтанари отмечают, что алгоритм унификации полезен не только для имплементации резолюционистских доказателей, но и для имплементации языков программирования. С уравнениями и паттерн-матчингом (ссылка на TEL, который действительно использует алгоритм унификации для паттерн-матчинга и язык Бурсталла и Дарлингтона [Darl77], который не использует) и без (ссылка на тайпчекер LCF/ML). Также они ссылаются на работы Уоррена, рассказ о которых впереди. Но такого разнообразия связей с Эдинбургом и ссылок на TEL нет в их более ранней работе по унификации [Mart76]. Такой связи с какими-то работами кроме работ над резолюционистскими доказателями нет и в другой работе того времени над эффективной унификацией [Pate76] не смотря даже на то, что один из соавторов работал в Йорктаун Хайтс. +Особого влияния работа этой итальянской четверки на Эдинбург не оказала, но вскоре из Пизы в Эдинбург приехал один из будущих главных героев нашей истории. + +##### Язык О'Доннела + +Еще одной работой над "непосредственными" имплементациями АТД, на которую ссылается Гоген, была работа Митчелла Уанда (Mitchell Wand), Индианский университет в Блумингтоне (Indiana University Bloomington) [Wand77] [Wand80] и ссылающегося на его работы Майкла О'Доннела (Michael J. O'Donnell), сначала Университет Пердью (Purdue University) в соседнем городке, но работа продолжалась и в других университетах. +Митчелл Уанд (ссылавшийся, кстати, на ту самую статью Бурсталла с уравнениями [Burs70]) вскоре переключится на работу над гораздо более известным языком программирования, рассказ о котором впереди. О'Доннел же упорно продолжал работать над языком с достаточно амбициозной системой переписывания по правилам. +Бойер и Мур не давали названий своим доказателям между Baroque и ACL2, называя их просто "доказатель" в разговорах друг с другом. В результате нам постоянно приходится упоминать их фамилии чтоб было понятно о каком же доказателе идет речь. Та же история и с языком О'Доннела, который нам придется называть языком О'Доннела. Сам О'Доннел называет его "языком программирования уравнений" (equational programming language) и так можно называть все языки, о которых рассказывает эта глава. +О'Доннел начал проект имплементации языка уравнений "без семантических компромиссов" в 1975 [O'Do87]. С самого начала О'Доннел столкнулся со скепсисом со стороны коллег. Они сомневались в возможной эффективности имплементации такого языка. Так что О'Доннел уделял эффективности особое внимание. Но только в той степени, какую позволял "бескомпромиссный" подход. +О'Доннел настаивает, что разделение на функции и конструкторы и следующее из этого упрощение паттерн-матчинга не приведет к существенному улучшению производительности [O'Do84]. Выдержала ли эта теория проверку? +В 1978-81 годах О'Доннел, Кристоф Хоффман (Christoph M. Hoffmann) и два студента (Giovanni Sacco, Paul Golick) работали над прототипами имплементации [O'Do84]. В 1979-80 написанный Джованни Сакко на Паскале интерпретатор (2700 строк транслятора в то, что будет интерпретироваться и 1200 строк рантайма) работает у О'Доннела на CDC 6600 и в университете Киля у Хоффмана. Исполняет он описания чистого Лиспа (не Бойера-Мура) и языка Lucid [O'Do82]. +Разворот списка этот интерпретатор выполняет в шесть раз медленнее компилятора Лиспа для CDC 6600. И это компилятор Лиспа не из лучших. Из его руководства пользователя [Gree75] можно узнать, что он производит код, работающий в 2-7 раз быстрее интерпретатора, да и используется не часто. +На этом этапе О'Доннел не знает как компилировать язык в принципе, хотя недавний опыт с одним Эдинбургским компилятором (рассказ о котором впереди) и выглядит заманчиво. И это итоги 70-х для данной исследовательской программы. Но, справедливости ради, и прочие имплементации языков с уравнениями и паттерн-матчингом обычно не отличаются хорошей производительностью в эти годы. +Со временем, в 80-е, О'Доннел и др. смогли компилировать язык более-менее эффективно, компилируя паттерны в автоматы и используя для редукции стек, с чего, как мы помним, и начинается практическая имплементация. +Насколько эффективно? Сравнимо с Franz Lisp на некоторых примерах, но и сам О'Доннел признает, что это имплементация не особенно эффективная. Но О'Доннел считает, что скорость определенно можно считать приемлемой, и он таки утер нос скептикам! Разница производительности с имплементациями того же времени с упрощенным паттерн-матчингом, правда, все-таки была значительной. +Исполнитель уравнений О'Доннела мог иметь и имел несколько фронтендов с разными синтаксисами. Один - похожий на математическую нотацию: + +```haskell +add(0,x) = x +add(s(y),x) = s(add(y,x)) +``` + +Почти как TEL, но никаких кавычек. +Другой вариант синтаксиса "лиспоподобный", но Лиспом О'Доннел называет M-LISP, псевдокод из 60-х, т.е. `foo[x;y]` вместо `foo(x,y)` + +``` +Symbols + + cons: 2; + nil: 0; + add: 2; + length: 1; + include integer_numerals, truth_values. + +For all a, x: + + length[()] = 0; + length[(x.a)] = add[length[a]; 1]; + + include addint. +``` + +Что именно означает "бескомпромиссность" семантики, которую продвигает О'Доннел? Её можно разделить на две части. Первая часть касается в основном использования функций слева от `=`. +О'Доннел противопоставляет свой подход имплементаторам, которые позволяют использовать слева от `=` только конструкторы [O'Do84], но позднее признает [O'Do87], что это ограничение позволяет писать практически все программы, которые программисты на практике хотят писать и писать такие программы легче. С другой стороны, "бескомпромиссная" семантика вводит ограничения, которые неудобны для программистов. Например, на практике перекрывающиеся паттерны удобно использовать, а писать код который решает проблему иначе - сложно. Также, многие считают удобным нарушение того, что О'Доннел называет левой линейностью. Левая линейность нарушена, когда в паттерне один байндинг встречается больше одного раза: + +``` +equal(x,x) = true +``` + +но это более редкое пожелание и не такое важное, как перекрывающиеся паттерны, справедливо считает О'Доннел. +О'Доннел приходит к таким неутешительным выводам, конечно, не первый, а один из последних. В конце 80-х. Но его рассуждения позволяют представить как рассуждало большинство имплементаторов языков с уравнениями в конце 70-х и начале 80-х. Зачем страдать со сложным матчингом, когда простой позволяет получить более удобный для использования язык? +Но если отказ разделять функции и конструкторы - тупиковая идея, не имеющая будущего в ФП, другой аспект бескомпромиссности О'Доннела разделяло больше имплементаторов ФЯ, хотя и меньшинство. Влияние на сложность имплементации этого второго набора идей может еще и большее, но и плюсы считались более важными. Но про это мы расскажем в следующей главе про третий Эдинбургский протоязык. + +#### Новый язык программирования + +Переход от использования нескольких промежуточных и целевых языков к трансформации кода из одного языка в код на этом же языке сделал название ненужным и года до 77 второй Эдинбургский язык как правило не называется никак. +Не позднее 77-го года язык Бурсталла и Дарлингтона начинают часто называть NPL, что означает "новый язык программирования". Да, название не очень удачное. Только вопрос времени, когда язык перестанет быть новым. Но не беспокойтесь, через пару-тройку лет он получил другое имя. +Как мы помним, в статье [Darl75] вводится новый синтаксис для языка, который еще не называется NPL в этой статье, но который мы будем называть NPL 75. Но есть оговорка - новый синтаксис пока не имплементирован. В более поздней версии статьи про вторую систему трансформации [Darl77] уже нет оговорок о том, что синтаксис только запланирована. Так что можно предположить, что к январю 1976 новый синтаксис был имплементирован. +И вы, конечно же, уже догадались, что этот новый синтаксис - уравнения с паттерн-матчингом. В 1975-ом году вербозный вариант ISWIM без первоклассных функций + +``` +f(x) <= if x=O or x=1 then 1 else + f(x-1)+f(x-2) fi +``` + +``` +concat(x,y) <= if x=nil then y else + cons(car(x),concat(cdr(x),y)) fi +``` + +превратился в язык, выглядящий как и прочие языки этой главы. Или же прочие языки этой главы выглядят как NPL [Darl75]: + +``` +f(O) <= 1 +f(1) <= 1 +f(x+2) <= f(x+1)+f(x) +``` + +``` +concat(nil,z) <= z +concat(cons(x,y),z) <= cons(x,concat(y,z)) +``` + +Можно использовать `x::X` вместо `cons(x,X)` и `[]` вместо `nil` и объявлять операторы: + +``` +nil.Y <= Y +(x::X).Y <= x::(x.Y) +``` + +у уравнений могут быть гарды: + +``` +member(x1, x::X) <= true if x1 = x + member(x1, X) otherwise +member(x1, []) <= false +``` + +c `if` как в OBJ, но не как в языке О'Доннела. В AFFIRM гарды вовсе отсутствуют. +NPL - язык первого порядка, что обычно и для языков описания спецификаций, так что когда конструктор АлгТД был переизобретен, он не стал первоклассным, как в тот раз, когда Бурсталл изобрел конструктор АлгТД впервые [Burs69]. +Зачем такой синтаксис в NPL? Авторы программы-трансформатора кода утверждают, что новый синтаксис удобнее для трансформации [Darl75]. Что Дарлингтон и Бурсталл внезапно открыли проработав над трансформацией ISWIM-подобного языка годы, но сразу после того, как Эдинбургская программа соприкоснулась с программой описателей АТД. +Да, еще в 69-ом году Бурсталл называет [Burs70] свою спецификацию языка интерпретатором, "написанным на исчислении предикатов как языке программирования". И в 72-ом году использовал для описания вычисляемых функций вместо обычного ISWIM-псевдокода псевдокод с уравнениями и ПМ: + +``` +rev: X* -> X* +rev(1)=1 +rev(xa)=rev(a)x for x ∈ X, a ∈ X* +``` + +``` +subst(s,a,a)=a +subst(s,a,b)=b if b ≠ a and b is an atom +subst(s,a,cons(t1,t2))=cons(subst(s,a,t1),subst(s,a,t2)). +``` + +А Рейнольдс в том же году писал [Reyn72], что алгебраические спецификации Бурсталла принципиально не отличаются от его подхода с определяющими интерпретаторами. Просто у Бурсталла интерпретатор не на ISWIM написан. +Но до того, как Бурсталл познакомился с Гогеном в 74-ом году никаких признаков работы над имплементацией этого неISWIMа нет. +Последняя статья про будущий NPL без упоминания уравнений с ПМ [Darl76] получена издательством осенью 74. Тогда же, осенью 74 Гоген делал доклад о своих идеях, которые привели к разработке OBJ [Gogu79], но о каких конкретно идеях мы не знаем. Сам OBJ Гоген разработал только в 76-ом [Gogu88]. Но это год, когда Гоген придумал первый способ работы с ошибками для OBJ, а не год, когда Гоген решил имплементировать язык уравнений. +Все герои этой главы постоянно ссылаются друг на друга. Помимо упомянутых уже ссылок, Гуттаг упоминает NPL как наиболее похожий на OBJ язык и критикует язык Гуттага как менее удобный по сравнению с NPL [Gogu79]. Для Гогена NPL вместе с OBJ и AFFIRM сначала один из трех важнейших языков описания спецификаций [Gogu81]. Но и позднее NPL под названием NPL и под следующим своим названием всегда упоминается как родственный OBJ исследовательский проект вместе с языком О'Доннела [Gogu85]. NPL под разными названиями также может упоминаться в обзорах языков описания спецификаций [Berz83] в 80-е, но не более поздних [Wirs95]. +Интересно, что упоминается героями этой главы только NPL-линейка, но не другие ФЯ с уравнениями и ПМ, которые вскоре становятся довольно нормальным явлением. О'Доннел в 84 [O'Do84] может ссылаться на работу Бурсталла об очередной версии NPL, которую он критикует за недостаточно выразительный паттерн-матчинг, но не на прочие языки с уравнениями, которые Бурсталл упоминает в этой статье. Еще позже, в 90-е О'Доннел откроет для себя эти языки и будет описывать как "языки с уравнениями" все ФЯ вообще. Но в конце 70-х и начале 80-х NPL-линейка явно особенная для описателей АТД и переписывателей по правилам, по сравнению с ФЯ, которые позаимствовали из NPL-линейки уравнения. Может быть так же, как в начале истории ФЯ, в ФЯ записывали все что только можно, так и в начале истории языков исполняемой спецификации записывали все, что имеет хоть какое-то отношение? +Никто из героев этой главы не пишет кто и у кого что позаимствовал, но и не пишет что сам изобрел. Самое близкое к этому - утверждение Дарлингтона о том, что уравнения с ПМ - это инновация NPL [Darl81]. +Мы не можем установить насколько независимо друг от друга герои этой главы изобрели исполнение уравнений с ПМ и как эта идея распространялась по их социальной сети. Можем только в очередной раз порадоваться, что не пишем историю идей и констатировать, что году в 75-ом появилось "сообщество" в котором принято делать языки с уравнениями и паттерн-матчингом, как до того в другом, но частично пересекающемся "сообществе" было принято делать ISWIM. +По меркам языков описания спецификаций, паттерн-матчинг в NPL не позволяет очень уж сложных вещей. Слева от `<=` только переменные и конструктор-функции [Darl75]. Но он пока что сложнее такого в современных ФЯ. Последовательность уравнений не имеет значения для ПМ. +Уравнения - не единственное, что связывает NPL с описателями АТД. Как мы помним, первый переписыватель программ Дарлингтона переписывал код, работающий с встроенными абстрактными множествами в код, оперирующий встроенными конкретными имплементациями. Для второго переписывателя была поставлена более амбициозная цель: обобщенный механизм переписывания кода, работающего с абстрактными типами в код, работающий с конкретными. +Для этого они использовали идею Хоара - функцию представления. Технически, для трансформации нужно две функции из абстрактных конструкторов в конкретные и обратно, но первоначально Дарлингтон и Бурсталл надеялись на то, что одна из этих функций будет автоматически генерироваться из другой. +Так что трансформация абстрактного типа + +``` +niltree ∈ labelled-trees +ltree: atoms × labelled-trees × labelled-trees + -> labelled-trees +``` + +в его _непосредственную_ имплементацию для гипотетического Лисп-бэкенда + +``` +nil ∈ binary-trees +atoms ∈ binary-trees +pair: binary-trees × binary-trees + -> binary-trees +``` + +поддерживается стандартными средствами переписывателя. Что не поддерживается переписывателем, так это такие декларации функций-конструкторов, также напоминающие о языках спецификации. В NPL 75 это псевдокод. +Определяем функцию представления + +``` +R: binary-trees -> labelled-trees +R(nil) <= niltree +R(pair(a,pair(p1,p2))) <= ltree(a,R(p1),R(p2)) +``` + +и система трансформирует такой вот (псевдо)код + +``` +twist: labelled-trees -> labelled-trees +twist(niltree) <= niltree +twist(ltree(a,t1,t2)) <= ltree(a,twist(t2),twist(t1)) +``` + +в такой + +``` +twistp: binary-trees -> binary-trees +twistp(nil) <= nil +twistp(pair(a,pair(p1,p2))) <= pair(a,pair(twistp(p2), + twistp(p1))) +``` + +более-менее автоматически. И для полуавтоматической трансформации доступны более впечатляющие конкретные имплементации, например неявное дерево в изменяемом массиве. +Сигнатуры вида `f: S -> T` также напоминающие сигнатуры в языках описания АТД - это тоже псевдокод, как в статьях Бурсталла и Берджа 60-х годов об ISWIM. +Но, как и бывшие сначала псевдокодом уравнения с МП, эти декларации и аннотации являются псевдокодом временно. +Определение того, что в NPL поддерживается, а что нет осложняется тем, что описывается обычно инструмент для этого языка - полуавтоматический переписыватель Дарлингтона, а не его имплементация - интерпретатор Бурсталла, написанный им, скорее всего, где-то между 75 и летом 77 [Darl81]. Что означает, что он имплементирован раньше, чем OBJ-0, AFFIRM и язык О'Доннела, но примерно одновременно или позже, чем TEL. Язык, поддерживаемый переписывателем и интерпретатором также, технически, не один и тот же язык. Бурсталл написал тайпчекер для интерпретатора раньше, чем синтаксис для аннотаций и объявлений типов стал поддерживаться трансформатором. И раньше, чем он стал использовать информацию о типах для переписывания кода. Также переписыватель может работать с неисполняемым кодом на NPL [Darl81]. Например таким, в котором слева от `<=` есть применения функций. Также, не то чтобы абстрактность конструкторов имела какой-то смысл без переписывателя, для интерпретатора есть только конструкторы с конкретной, непосредственной имплементацией. +NPL продолжает иметь более одного синтаксиса в одной статье и не всегда понятно, что из этого псевдокод специально для того, чтоб примеры выглядели красиво, а что разница в поддержке языка. Так ISWIM-образный синтаксис для конструкции и деконструкции туплов или является псевдокодом или не поддерживается трансформатором, потому что в примерах REPL-сессий вот такой код + +``` +exp where = +``` + +выглядит вот так + +``` +exp wherep maketupl([a, b]) == maketupl([i, j]) +``` + +а вместо условного оператора в стиле Бурсталл-ISWIM + +``` +if p then a else b +``` + +может быть "функция" + +``` +cond(p,a,b) +``` + +Еще одна разница между интерпретатором и трансформатором: правила перезаписи для трансформатора записываются в NPL синтаксисе уравнениями с ПМ, но им соответствуют вычисления, которые происходят не так, как вычисляется NPL-код интерпретатором. +В библиотеке для работы с множествами, с которой начался NPL были ФВП, которые в NPL объявить было нельзя (пока он еще назывался NPL), так что для работы с множествами в NPL есть специальный синтаксис + +``` +<: a + b : a in A & p(a), b in B :> + +exist x in X & p(x) : f(x) + +forall x in X & p(x) : g(x) +``` + +который не имеет каких-то синтаксических деталей, характерных для SCRATCHPAD или SETL, так что вполне может быть происходящим от математической нотации непосредственно. +Интерпретатор может вычислять такие выражения, но Дарлингтон отмечает, что они часто вычисляются неэффективно или вовсе не вычисляются и предназначены только для переписывателя. +Несмотря на наличие в NPL паттерн-матчинга, использовать его слева от `in` нельзя [Feat79], как и в SCRATCHPAD. +В 77-ом году Дарлингтон отправился работать в Имперский колледж Лондона (Imperial College London), но это не было концом переписывателя кода в Эдинбурге. Развитием переписывателя занялся другой человек. +Тем временем Бурсталл работал над тем, чтоб сделать из NPL что-то большее, чем язык для экспериментальной системы переписывания кода. И один из вариантов этого большего объясняет особое отношение описателей АТД к NPL. + +#### Все ясно + +Гоген был соавтором Бурсталла и их совместным творением стал язык описания спецификаций или, как его называют Бурсталл и Гоген, язык для структурного описания теорий Clear [Burs77]. +Бурсталл и Гоген считают, что антирезолюционисты продвигают процедурное представления знания вместо логического в числе прочего и потому, что наработан опыт структурирования программ, но нет такого опыта для структурирования теорий. И Бурсталл с Гогеном исправляют эту недоработку. Да, они ассоциируют языки с уравнениями с логическим программированием. И некоторые герои этой главы будут последовательны в этом. Гоген еще будет продвигать OBJ как более логическое программирование, чем Пролог, а О'Доннел напишет в 90-е обзор [Gabb98] логических языков, в котором функциональные языки будут разновидностью логических. +Clear - язык описания спецификаций, но не такой, как OBJ или AFFIRM, а такой, какими эти языки хотели сделать их авторы [Muss80b], но не смогли. По крайней мере в 70-е. Спецификации, которые описывает этот язык (Бурсталл и Гоген называют их теориями) могут быть параметризованы. И параметризованы более интересным образом чем Лисковские АТД в CLU и LCF/ML. +В Clear два языка. Один, в котором аналоги объектов OBJ и типов AFFIRM - это значения, теории-константы, которые могут быть объявлены локально `let T = ... in ...`, переданы в теории-процедуры, возвращены из них. Параметризованный стек будет теорией-процедурой: + +``` +p͟r͟o͟c͟ Stack (Value: Triv) = + i͟n͟d͟u͟c͟e͟ e͟n͟r͟i͟c͟h͟ Value + Bool b͟y͟ + s͟o͟r͟t͟s͟ stack + o͟p͟n͟s͟ nilstack: -> stack + push : value,stack -> stack + empty : stack -> bool + pop : stack -> stack + top : stack -> value + e͟r͟r͟o͟r͟o͟p͟n͟s͟ underflow: -> stack + undef : -> value + e͟q͟n͟s͟ empty(nilstack) = true + empty(push(v,s)) = false + pop(push(v,s)) = s + top(push(v,s)) = v + e͟r͟r͟o͟r͟e͟q͟n͟s͟ pop(empty) = underflow + top(empty) = undef + pop(underflow) = underflow + e͟n͟d͟e͟n͟ +``` + +И второй язык, на котором описаны уравнения - это в основном NPL в который добавлены декларации функций и конструктор-функций. По крайней мере NPL в его более приятном виде для публикаций с `where` и `` туплами, `if` гардами. Но с `=` вместо `<=`. +Эти языки не похожи друг на друга. Например функции в языке уравнений могут быть рекурсивными, а в языке теорий - нет. +Бурсталл и Гоген планировали начать со спецификации программ. Затем, естественно, перейти к исполняемой спецификации, что по их мнению не должно было стать проблемой, с учетом опыта имплементации NPL и OBJ. Так что те псевдокодовые декларации из статей про трансформатор кода должны были стать реальным кодом. Когда-нибудь. Но не в 70-е. Пока что это все еще псевдокод. В 1977 году Clear и не пытались еще имплементировать. +Псевдокодовость Clear 77 проявляется особенно очевидно в том, что в нем отсутствует конструкция, представляющая собой третий способ имплементации свободного от аннотаций типов кода. Мы уже сталкивались с двумя способами имплементировать псевдокодовые фантазии 60-х: отсутствие типов и вывод типов. Третье решение - внешние аннотации. Внешние аннотации - это не только сигнатура с типом функций отдельно от уравнения вроде + +``` +top : stack -> value +push : value,stack -> stack +``` + +отдельно от + +``` +top(push(v,s)) = v +``` + +Такие аннотации были и в псевдокоде 60-х, у Берджа и у Бурсталла. Такие аннотации имплементируются в сочетании с выводом типов и существуют в ФЯ и сейчас, считаются полезными для читаемости. Речь об аннотациях, которые нужно делать не для читаемости, а потому, что вывода типов нет. В этом случае нужны и внешние сигнатуры для `v` и `s`. И эти сигнатуры отсутствуют в Clear 77, но есть в тех языках, которые реально были имплементированы, а именно в OBJ-0 [Gogu79] + +``` +VARS V : VALUE; + S : STACK +``` + +в AFFIRM + +``` +declare v : Value; +declare s : Stack; +``` + +и наконец, но не в последнюю очередь в NPL. + +``` +var v : value +var s : stack +``` + +не в NPL 75 или 77, а в том, речь о котором еще впереди. Такой синтаксис появился даже в Clear, который был имплементирован, даже не смотря на то, что к тому времени вывод типов сделал его ненужным в современной ему версии NPL. +Clear был ближе к тому, что описатели АТД хотели получить, так что Clear позднее описывают [Sann94] [Wirs95] как первый такой язык, существенно повлиявший на остальные не смотря на то, что некоторые языки описания спецификаций были имплементированы раньше. Это, правда, может объясняться и тем, что наш будущий герой Саннелла имеет отношение к имплементации Clear. +Итак, теперь мы знаем все, что нужно чтобы понять загадочные комментарии Милнера [Miln82] о состоянии работ Бурсталла над паттерн-матчингом. Авторы ФЯ и наши великие предшественники писавшие историю ФП не особенно интересовались всем этим взаимодействием Бурсталла с языками описания спецификации. Для них уравнения с паттерн-матчингом просто появились из ниоткуда, и даже не один раз [SPJ87], а алгебраические типы данных каким-то чудом родились из нотации Ландина. Поэтому даже Гордон бессилен объяснить [Gord10] комментарий Милнера, смысл которого в свете наших открытий теперь очевиден. Уравнения с паттерн-матчингом и Clear связаны для Милнера напрямую и если Clear еще не готов, то и Милнеру использовать уравнения с паттерн-матчингом в LCF/ML рано. Сегодня это звучит так, что Милнер путает АТД и АлгТД, но в то время это звучало как то, что Милнер путает АТД и АТД. +Но, как и прочие такие заявления Милнера 82-го года, оно не совсем исторично. Когда уравнения только появились - в 75-ом году - основная работа над дизайном LCF/ML уже закончилась, и даже если бы Бурсталл и Гоген решили все Clear-вопросы в том же году когда они возникли, они бы опоздали повлиять на Милнера, к этому времени уже забросившего работу над LCF/ML. +Тем временем, пока Clear все продолжал оставаться неимплементированным, развитие NPL повернуло в другую сторону и он обзавелся другими конструкциями для декларации типов данных и функций, а затем и конструкциями для их группировки. +Но это разделение языка на два из Clear, которое многие читатели должны узнать, все эти сигнатуры и функции из одних теорий в другие в NPL еще будут, но только когда он будет называться ML, да еще и стандартным ML. Будучи одним из самых нестандартных ML из всех. +До этого еще далеко, а пока что, в 75-77 годах NPL сильнее всего сблизился с другим языком, который разрабатывался в то время в Эдинбурге. И этот проект и его основной участник представляют собой существенный сдвиг парадигмы в Эдинбурге. Если авторы языков с уравнениями соревнуются кто придумает больше требований к тому, что можно считать спецификацией и кто больше усложнит жизнь для имплементатора, то авторы этого языка на многое готовы закрыть глаза. Сойдет за спецификацию и это! И если авторы языков с уравнениями несмело говорят о применении своих языков для тестирования, прототипирования или как отправной точки для полуавтоматического преобразования в некий серьезный код, то сошедший за спецификацию код на этом новом языке - код реальных программ. Имплементатор этого языка заявляет открыто и смело: + +### Спецификация - это имплементация + +> Только в 1976 году, когда я работал в Имперском колледже в Лондоне, я наконец оценил гениальный, тонкий баланс, достигнутый в Prolog между довольно примитивным, но полезным средством проверки теорем и очень высокоуровневым языком программирования. +> Роберт Ковальски, Ранние годы логического программирования [Kowa88] + +> Основная идея называется "логическое программирование" и имеет интересную историю. Более 2300 лет назад Аристотель и его последователи... +> Дэвид Уоррен, Прикладная логика: её использование и имплементация как инструмента для программирования. [Warr78] + +Дэвид Уоррен (David H. D. Warren) уже поучаствовал в нашей истории, и мы расстались с ним в феврале 74-го, когда он привез из Марселя коробку с картами [Emde06] - второй интерпретатор Пролога, написанный Баттани (Battani), Мелони (Meloni) и Баццоли (Bazzoli) [Korn22] на Фортране в конце 73-го года. Интерпретатор был имплементирован вторым, но был первым, который использовал первый способ практической имплементации логических языков, придуманный в 1971 году Бойером и Муром [Moor18c]. Это на десятилетие позже, чем изобретенный Ландином первый способ практической имплементации ФЯ - SECD-машина. Если учесть это отставание и неудачный опыт Бойера и Мура - есть все основания усомниться, что из доказателя, который удалось применить только для доказательства существования списка из двух элементов, можно сделать рекордно производительную имплементацию декларативного ЯП, написанную на нем самом. Причем не только раньше, чем на не слишком-то предназначенных для исполнения языках, о которых мы рассказывали в этой главе, но и определенно предназначенных для исполнения ISWIMах, о которых шла речь до того. С таким-то настроем – конечно. У Уоррена был другой настрой. +Программирование и компьютеры отвратительны для Ковальского, он явно говорит об этом [Kowa88], как и Милнер [Miln2003]. Судя по поздним высказываниям Бурсталла - его отношение к программированию тоже плохое, хотя и сложнее [Burs92]. Важно, что среди авторов языков есть люди которые не любят программировать, ведь если б они любили - зачем бы им было придумывать что-то не похожее на то, что они в программировании не любят? Но важно, чтоб среди имплементаторов иногда попадались люди, которые не просто не хотят делать как раньше, а хотят делать что-то практичное и видят новую практику в новой теории. Насколько обоснованно - это уже другой вопрос. +Для истории ФП важно, что в конце 70-х появился человек, который стал для Милнера тем, кем Поплстоун был для Бурсталла, и Уоррен был для Ковальского. Но если Поплстоун и Бурсталл плодотворно сотрудничали какое-то время, а Ковальски, по крайней мере, не помешал Уоррену, то этому человеку с Милнером так не повезло. +Один из будущих героев нашей истории Сломан в своих воспоминаниях смеется над тем, что любимый язык Уоррена - ALGOL 68 [Slom89]. Ну, не всем же любить только языки для доказательства существования списков. Человек, который позднее написал первый компилятор ФЯ Эдинбургской программы тоже начал с хорошего отношения к ALGOL 68. +Какими же были последствия у такого настроя и бэкграунда Уоррена и его коллег? Да, уже Ван-Эмден использовал логический язык не для доказательства существования списков, а для их "быстрой" сортировки. Но от такого рода программ до компилятора, компилирующего самого себя большой путь. Который был пройден очень быстро. Осенью 74-го года Ван-Эмден был в Марселе и Колмероэ показал ему компилятор простого алголоподобного языка на Прологе [Emde06]. Это не то, что Колмероэ хотел писать на Прологе. Он занимался ИИ, пониманием естественного языка. Но как раз то, что было интересно Уоррену. Почти еще один год был потерян потому, что Ван-Эмден забыл об игрушечном компиляторе и показал его Уоррену только летом 75-го года. Перед тем, как уйти из Эдинбургского Университета. +Уоррен с энтузиазмом набросился на пример компилятора и вскоре стал присылать Ван-Эмдену все более сложные компиляторы Пролога на Прологе [Emde06]. Код первой версии, которая дошла до нас датирован 13 сентября 1975 [Prolog75]. Эта первая версия была размером всего в 902LOC и еще 80 строк комментариев, что сопоставимо с тем кодом, который был написан на ML пару лет спустя. Код на ML, правда, не был кодом компилятора. Но то, что компилятор ФЯ на ФЯ не помещается в 1KLOC - это проблема ФЯ. К 81-му году компилятор Уоррена вырастет до 6KLOC. Получается, что по нашему не очень требовательному критерию логическое программирование уже было, когда функционального программирования еще не было. +Компилятор Пролога на Прологе появился через 4 года после того, как была изобретена его практическая (что как обычно означает "с помощью стека") имплементация. Сравним это с успехами обоекембриджцев и ФП-эдинбуржцев. После изобретения SECD прошли 60-е, прошли 70-е, а компилятора ФЯ на ФЯ так и не появилось. Поразительно, что даже такой головокружительной скоростью прогресса логические программисты недовольны и рассуждают [Cohe88] о том, что же помешало все это сделать быстрее. Ну, наверное это логично: ничего не сделаешь быстро, если не будешь недоволен тем, как все медленно делается. Для нас недовольство медленным развитием Пролога не выглядит обоснованным. Ну, разве что, если считать его историю с Аристотеля, как в исторической справке в диссертации Уоррена, легко обошедшей историю SML начинающуюся всего-то с 1874. Коэн преувеличивает скорость развития Лиспа, когда сравнивает его с Прологом [Cohe88]. Например, компилировать лямбда-исчисление лисперы начали уже после того, как Уоррен написал компилятор Пролога. Но это уже другая история. +Недостаточная скорость и скромные успехи Обоекембриджской и Эдинбургской программ по имплементации ФЯ, по нашему мнению, наоборот, требуют объяснения. +Имплементаторы ФЯ с таким же настроем, как у Уоррена вскоре появятся, но одного настроя недостаточно. Недостаточно и для Пролога тоже, ведь эта имплементация Уоррена - тупиковая ветвь прологовских имплементаций, не дожила до наших дней в отличие от LCF/ML. Те свойства, которые сделали её успешной предопределили и её конец. Но рассказ о гибели целых программ и культур имплементации языков еще впереди. Сначала разберемся с тем, как они вообще появились. +Почему Уоррен хотел и мог имплементировать компилятор Пролога на Прологе? Уоррену нравилось многое из того, что прочим героям этой главы не нравилось совершенно. Ковальски, например, не любил Пролог. Не смотря даже на то, что написал статью на основе которой Пролог был создан [Kowa88]. Пролог - плохой доказатель теорем даже с точки зрения резолюциониста, не только по той причине по какой такие доказатели были заброшены Бойером и Муром. Пролог также плохой язык описания спецификации, за что его критиковали авторы языков спецификации вроде О'Доннела [O'Do84]. И Уоррен негодует, что Ковальскому не нравится Пролог [Kowa88]. Потому, что то, что не нравится Ковальскому и прочим - это то, что делает Пролог практичным ЯП [Warr77]. Возможно, "практичный" это не то, что обычно ассоциируется с Прологом, так что на этом мы остановимся подробнее. +Начнем с того, что делает Пролог практичным по сравнению с языками описания спецификации. Как мы помним, те авторы языков описания спецификаций, которые делали языки для исполнения в первую очередь, хотели исполнять как можно больше спецификаций, даже наивно написанных. И авторы Пролога с самого начала отказались от этого, не смотря на то, что Пролог предназначен в первую очередь именно для исполнения (как OBJ), а не для статического анализа (как AFFIRM). Программисту не следует писать наивные спецификации, он должен иметь в виду _процедурную семантику_ Пролога и писать код, который будет эффективно работать. +Еще Марсельская группа отказалась от проверки зацикливания во время выполнения [Cohe88], в отличие от Гогена, который хотел исполнять некоторый зацикливающийся код корректно [Gogu79]. +С точки зрения _декларативной семантики_ не должно быть важно, в какой последовательности написаны утверждения и цели, но это важно с точки зрения семантики процедурной. Эта очередность обхода исполнителем утверждений и целей, а также управление обходом с помощью `cut` - способ управления потоком контроля для программиста. Это, понятное дело, существенный недостаток для Ковальского [Kowa88], О'Доннела [O'Do84] и даже Бурсталла, судя по тому, какой паттерн-матчинг он изобрел. +Имея в виду _процедурную семантику_, программист может написать вычислительно эффективный код, который можно легко и быстро читать благодаря _декларативной семантике_. Это станет обычным компромиссным подходом для последующих практичных декларативных языков. +Пролог практичнее и "чистого Лиспа": логические переменные, которые могут быть недоопределены, позволяют делать то, что в Лиспе можно сделать только с помощью мутабельности. +Вся эта практичность не особенно помогла Марсельской имплементации быть производительной. Начнем с самой очевидной её проблемы - это интерпретатор. +Заслуга Дэвида Уоррена и примкнувшего к нему Луиса Перейры (Luis Moniz Pereira) в том, что они написали первый компилятор Пролога в машкод [Warr77]. Он же первый компилятор декларативного языка. Луис Перейра работал над компилятором в Эдинбурге в 75-ом году, после чего вернулся в Лиссабон и там до 78-го года продолжал разработку компилятора вместе с Фернандо Перейрой (Fernando Pereira) [Kowa88]. +Сначала они планировали компилировать в знакомый нам BCPL, но быстро поняли, что такой подход не позволит использовать особенности PDP-10, которые позволяют генерировать эффективный код [Warr78]. BCPL, не смотря на свою кажущуюся низкоуровневость, не подходит для компиляции через него языков, на BCPL не похожих. Эта проблема с компиляцией через какой-то язык будет серьезной проблемой для имплементаторов необычных языков программирования на протяжении всей последующей их истории. +Менее очевидная, но может быть даже более важная проблема Марсельской имплементации была в управлении памятью. Техника Бойера и Мура позволяет управлять всеми аллокациями при помощи стека, сборщик мусора не требуется. Это же хорошо, да? Нет, как и в ряде более поздних попыток сделать высокоуровневый язык без сборщика мусора, это приводит только к тому, что значительная часть памяти освобождается позже, чем нужно. +ИИ-применения Пролога, где на нем описывались "знания", которые извлекались из получившейся базы с помощью запросов, предполагали, что прологовские процедуры обычно возвращают ряд альтернативных результатов. Но если писать на Прологе более традиционные программы, которые сортируют списки или компилируют Пролог, то это совсем не так обычно. Значит, решил Уоррен, можно разделить стек на два: локальный и глобальный. Наиболее распространенная "традиционная" процедура, которая быстро завершается, может быстро освободить свои данные (каких большинство), аллоцированные на локальном стеке. А более редкая процедура, которая все не возвращает и не возвращает результат, вместо этого только вызывая продолжения, аллоцирует на стеке глобальном. Который обходит компактифицирующий сборщик мусора, обеспечивающий, что продолжает жить и жить только то, что нужно. +Но этого еще недостаточно. "Многоцелевые" процедуры, которые работают в обе стороны и с помощью которых Бойер с Муром доказали существование списка из двух элементов существенно затрудняют разделение данных на те, что идут в локальный стек и те, что идут в глобальный. Так что Уоррен добавил в язык аннотации, с помощью которых программист мог указать какие параметры процедур только принимают, какие только возвращают, а какие, как по умолчанию и полагается в Прологе делают и то и другое в зависимости от обстоятельств. + +``` +:-mode concat(+,+,-). +``` + +Итак, придумав, как получать выгоду от не использования основных фич Пролога, Уоррен радикально сократил потребление памяти по сравнению с марсельским интерпретатором. Компилятор Уоррена, скомпилированный самим собой требовал в десять раз меньше памяти, чем исполняемый марсельским интерпретатором, с помощью которого Уоррен осуществил бутстрап. +Пока что все это выглядит как изобретательное использование языка не для того и не так, как задумывали его авторы. Или, точнее, изобретательное неиспользование его основных фич. Но осталась одна главная фича, которой Уоррен не предлагает не пользоваться. Наоборот, предлагает пользоваться как можно больше: паттерн-матчинг. +Быть практичнее языков описания спецификаций и практичнее "чистого" Лиспа - не очень высокая планка. Но Пролог, по смелому утверждению Уоррена, практичнее и более важного языка. Пролог практичнее Лиспа. Не какого-то там "чистого", просто Лиспа. И причина, по которой он практичнее - паттерн-матчинг. +Если для авторов языков описания спецификаций паттерн-матчинг - это проблема, наличие которой надо обосновывать какими-то семантическими сложностями от которых авторы Пролога просто отмахнулись, то для Уоррена паттерн-матчинг - это решение. О'Доннел утверждает [O'Do87], что паттерн-матчинг не настолько сложный, как в его языке с уравнениями не оправдывает труда, потраченного на его имплементацию. Не стоит замедления исполнения по сравнению с кодом с селекторами. Разница с лисповым подходом с селекторами для О'Доннела не достаточна. Для Уоррена же разница принципиальна. По мнению Уоррена ПМ не создает проблемы для имплементатора, а решает их. Уоррен компилирует ПМ в такой эффективный код как свитчи и получает производительность лучше, чем у кода с селекторами. +Уоррен утверждает, что Пролог лучше Лиспа подходит для описания операций над структурированными данными. В Прологе есть не только `cons`, можно использовать конструкторы именованных структур с безымянными полями. То же самое, что конструкторы Бурсталла и непосредственные имплементации операций в языках описания спецификаций. Представление в памяти лучше составления всего из пар как в обычном Лиспе. И использование паттерн-матчинга для работы с этими конструкторами лучше Лиспового подхода с селекторами. Лучше и для программиста - код с паттерн-матчингом безопаснее постоянного использования `head` и `tail`. Лучше и для имплементатора - код с паттерн-матчингом позволяет на практике компилировать в машкод с меньшим числом проверок, чем код с селекторами в лисповом стиле. Может быть оптимизирующий компилятор, который трансформирует код непрактичного для того времени размера за непрактичное для тех лет время и сможет получить тот же результат. Но на практике этого не произойдет. К тому же, использование `cons` в Лиспе обязательно приводит к аллокации в куче и создании работы для сборщика мусора, а использование техники Бойера-Мура позволяет размещать большую часть Прологовых структур на стеке. Ценой дополнительной косвенности, но пока сборщики мусора делать как следует не умеют (об этом мы еще расскажем подробнее) и учитывая сильные стороны PDP-10 - это того стоит. +Уоррен осуществил на практике то, о чем авторы языков с уравнениями и ПМ даже не мечтали. Реальное приложение, компилятор на языке вроде тех, которые они посчитали недостаточно пригодными для своих существенно менее амбициозных целей. Вроде написания первоначальной наивной имплементации, которую затем поэтапно переписывается со значительным использованием ручного труда в реальный код. Или вовсе только для тестирования описаний спецификаций для реального кода. Компилятор написан на декларативном языке и код довольно простой и, можно сказать, наивный. Конечно, код компилятора выглядящий в 75-ом году так: + +``` ++LENGTH(*T.*A,*N1) + -LENGTH(*A,*N) + -PLUS(*N,1,*N1). ++LENGTH(NIL,0). +``` + +в 81-ом году, когда практичность Пролога достигла новых, невиданных до того уровней, выглядел уже так + +``` +length(L,_):- wd(count):=0,nolength(L),!,error(length(L)),fail. +length(_,N):- N is wd(count). + +nolength(X):- var(X),!. +nolength([X,..L]):-!,wd(count):=wd(count)+1,nolength(L). +nolength([]):-!,fail. +nolength(_). +``` + +Но и первая версия была достаточно практичной, чтоб компилятор компилировал компилятор. Без всяких сложных трансформаций. +Как эти неортодоксальные идеи и выдающиеся достижения Уоррена могли повлиять на функциональную половину протоэдинбургской программы и наоборот, насколько ФП могло повлиять на такой новый взгляд на Пролог? Мы уже выяснили, что один из первопроходцев такого стиля - Ван-Эмден вдохновлялся идеями Берджа и Левенворта о функциональном программировании. +Насколько вообще Ковальски, Ван-Эмден и Уоррен были связаны с бывшей группой экспериментального программирования? Организационно они были в другом департаменте - Вычислительной логики, бывшая Метаматематическая группа [Howe07] [Bund21]. МакКвин, прибывший в Эдинбург летом 75-го, не упоминает их, хотя упоминает, например, сотрудничество с Гогеном [MacQ15]. И Ковальски вспоминает, что они были довольно сильно изолированы от тех, кто уже не занимался резолюционизмом [Kowa88]. И Уоррен не цитирует работы группы экспериментального программирования новее чем POP-2 и резолюционные эксперименты Бойера и Мура. Но Ван Эмден и Ковальски пишут, что не позднее 74-го года обсуждали статью [Emde76] с Майклом Гордоном. Эта статья цитирует ту статью Бурсталла [Burs70], в которой используется ранняя версия нотации, похожей на уравнения с ПМ. +Мог бы Уоррен с такими-то взглядами написать не компилятор Пролога, а компилятор NPL75? Уравнения с паттерн-матчингом вполне похожи на то, что он хотел писать. +Такие конструкции, по всей видимости, просто опоздали произвести впечатление на Уоррена, как опоздали произвести впечатление на дизайнеров LCF/ML. Уоррен уже мог запускать код на Прологе в Эдинбурге начиная с весны 74-го, и маловероятно, что уравнения с ПМ имплементированы в NPL раньше лета 75-го. +Может быть это Пролог повлиял на NPL? Авторы NPL не пишут об этом, так что остается только смотреть на какие-то детали, которые скорее всего не будут придуманы независимо. И таких деталей нет. Сам Пролог в 74-75гг. выглядел не особенно похоже на языки с уравнениями [Colm96]: + +``` ++APPEND(NIL,*Y,*Y). ++APPEND(*A.*X,*Y,*A.*Z) + -APPEND(*X,*Y,*Z). +``` + +Обратите внимание на префиксы предикатов `+APPEND` `-APPEND` и специальный синтаксис для связанных переменных `(*A.*X,*Y,*A.*Z)`. Авторы языков с уравнениями аннотировали их с помощью отдельных конструкций, даже там где не было типов, как в языке О'Доннела. Может быть они повлияли на синтаксис параметров типов в LCF/ML? +Но Ковальски не позже 74-го года пользовался для объяснения логического программирования псевдокодом [Kowa74], несколько более похожем на языки с уравнениями. И существенно более похожим на будущий синтаксис Пролога, который назовут "Эдинбургским" [Korn22] + +``` +Append(nil,x,x) <- +Append(cons(x,y),z,cons(x,u)) <- Append(y,z,u) +``` + +Бросается в глаза отличие в регистре имен предикатов (вроде `Append`) и "функторов" (это имена конструкторов роде `cons`). Языки с уравнениями с ПМ обычно избегали такого различия в это время. Направление стрелки вполне логично в случае логических языков, но не так понятно в случае NPL. Могли ли стрелки `<-` вдохновить NPL-ные `<=`? Это влияние если и было, то не через Пролог, где позднее выбрали для представления этих стрелок совсем другие символы `:-`. Мы считаем, что такое влияние крайне маловероятно потому, что `<=` довольно обычная деталь псевдокода, который использовался для описания частичных функций в известных Бурсталлу статьях [Mann70] [Mann71] [Cadi72] [Vuil73] [Vuil74], лучше подходящих по времени к появлению такой стрелки в протоNPL в 73-ем году. Скорее всего такая стрелка происходит от `≃` в книге Клини [Klee52]. Функции-то частичные, нельзя просто так взять и написать `=`! +Вдохновил ли паттерн-матчинг Пролога паттерн-матчинг в NPL? Это тоже сомнительно, ПМ в Прологе отличается в деталях, а общие идеи вроде конструкторов в паттернах [Burs69], уравнений с ПМ [Burs70] [Burs72] и сама возможность имплементировать ПМ [McBr69] были известны Бурсталлу до появления Пролога. +Влияние Пролога на более поздние ФЯ с ПМ гораздо более вероятно. После появления диссертации Уоррена, его статей и доклада о ненужности Лиспа (слайды которой сохранились [Warr77b]), его труды заметили его эдинбургские коллеги, работающие над переписывателем. +Подход Уоррена, в котором программа состоит из спецификации и аннотаций, которые помогают имплементации эффективно его исполнять они называют интересным [Feat79] [Schw82], заслуживающим большего внимания. Работавшие над переписывателем Дарлингтона Альберто Петторосси (Alberto Pettorossi) и Джеральд Шварц (Jerald Schwarz) стали с энтузиазмом придумывать, какие аннотации для компилятора могли бы быть полезны в NPL. +Петторосси предложил [Pett78] аннотации для деструктивного изменения объектов. Например, `succ<1>(y)` означает, что результат `succ` т.е. `n+1` может быть записан в переменную `y` вместо `n`. Соответственно `0` в `minus<10>(x,y)` означает, что переписывать `y` не надо. +Шварц разработал более сложную и многоцелевую систему аннотаций [Schw82] (издательство получило статью еще осенью 77-го). Так он предложил описывать `concat` записывающий результат конкатенации поверх первого передаваемого списка: + +```haskell +concat-destructive(cons/label(nd)(x, list), m) <= + cons/use(nd)(x, concat-destructive(list, m)) +``` + +Даем имя `nd` конструктору с помощью аннотации `/label(l)` и указываем его как место аллокации для нового конструктора аннотацией `/use(l)`. Аннотации можно выносить из описания функций + +``` +declare concat-destructive(/destroy,) +``` + +Помимо новых конструкций для аннотаций Петторосси Шварц описывает и другие аннотации. Такие как `/copy` для устранения ненужного разделения и `/memo` для мемоизации параметров функций. Но рассказ об остальных придуманных Шварцем аннотациях лучше подойдет к теме следующей главы. +Все эти идеи не были имплементированы. Никто не написал для NPL такой компилятор, который Уоррен написал для Пролога. Работавшие над полуавтоматическим трансформатором кода продолжили и дальше работать над ним. Успехи Уоррена вскоре затмит другой компилятор. +Если ПМ в будущих ФЯ станет несколько больше похож на ПМ в Прологе, то про все остальное такого не скажешь. В 75-77 годах NPL и Пролог были так близки как они уже никогда не были после того: два нетипизированных языка первого порядка с паттерн-матчингом, работающим с не требующими объявления конструкторами. + +``` +concat(nil,L) <= L +concat(cons(X,L1),L2) <= cons(X,concat(L1,L2)) +``` + +``` +concat(nil,L,L). +concat(cons(X,L1),L2,cons(X,L3)) :- concat(L1,L2,L3). +``` + +В 77-ом году развитие NPL совершило серьезный поворот, который сделал его гораздо более сложным и большим языком, чем Пролог. И дальше он и происходящие от него языки становились только больше и сложнее. И это должно было быть проблемой для того, кто захотел бы написать компилятор NPL. +Но по крайней мере на один из языков уравнений с ПМ Пролог оказал более прямое влияние. Итальянцы, работавшие над TEL [Levi75], расширили его Прологовыми фичами и получили FPL (Functional plus Predicate Logic) [Levi82] + +``` +ndiv: NAT x NAT --> NAT x NAT +ndiv(in:x,y;out:0,x),lt(x,y)=true <-- +ndiv(in:x,y;out:s(q),r),lt(x,y)=false <-- + ndiv(in:z,y;out:q,r),minus(x,y)=z + +isfact: NAT x NAT --> BOOL +isfact(x,y) = false,ndiv(in:y,x;out:z,s(r)) <-- +isfact(x,y) = true, ndiv(in:y,x;out:z,0) <-- +``` + +И "почти доделали" имплементацию на Лиспе с использованием той же техники Бойера-Мура что и в марсельском интерпретаторе и имплементациях Уоррена. +Но создание таких языков - не те выводы которые нужно делать из результатов Уоррена. И какие были сделаны. Можно написать компилятор, который из спецификации и кое-каких аннотаций может производить достаточно эффективный код. Чтоб программист, вооружённый таким компилятором и пониманием того, как спецификация исполняется, мог писать реальные приложения. Например, такой компилятор. Более конкретное послание Уоррена к авторам и имплементаторам последующих языков программирования [Warr77]: паттерн-матчинг - это не какая-то экзотическая дополнительная фича - это предпочтительный способ работы с данными и для пользователя языка и для имплементатора. И даже если делаете не такой высокоуровневый язык как Пролог - сделайте хотя-бы паттерн-матчинг как в статье Хоара, который добавил паттерн-матчинг в алголоподобный язык. + +### Абстрактный синтаксис + +> Тип данных - это просто тупой язык программирования. +> Приписывается Биллу Госперу [Wand80] + +Энтони Хоар вместе с Эдсгером Дейкстрой и Оле-Йоханом Далем написал программную книгу "Структурное программирование". Хоар написал ту часть, что о типах данных [Hoar72]. Точнее скомпилировал. Книга вышла в 1972, но состояла в основном из написанных незадолго до того работ. Часть Хоара про типы данных основана на серии лекций, которые он читал в 1970. И "незадолго" тут может быть проблемой, потому что это похоже на первый случай в нашей истории, когда от совета Стрейчи сидеть на статье пока не передумаешь её публиковать могло бы быть больше пользы, чем вреда. Вскоре после выхода этой книги Хоар существенно изменил свои представления о том, как нужно делать типы данных. В результате его первоначальные недоработанные наработки были опубликованы в книге, существенно повлиявшей тех, кто сделали мейнстримное программирование таким, каким мы его сейчас знаем. Когда же Хоар все-таки доработал эти наработки - они были опубликованы как статья, которая влиятельной уже не была. С другой стороны, насколько была бы влиятельна книга, идеи из которой не будут выглядеть практичными следующие пару десятилетий? Дело в том, что доработанные наработки Хоара опередили свое время и, как мы уже убедились на примере CPL, ничего хорошего для наработок это не предвещает. +Как же работала первая версия типов данных Хоара и что с ней было не так? +В первой версии типы имеют "плоское" представление, по крайней мере пока могут. Пары из имени поля и его типа, когда соединяются `;` просто занимают место нужное для типов полей друг за другом. В этом случае имя не имеет представления во время исполнения и позиция поля определяется во время компиляции. Если же пары соединяются `,`, то имя становится тегом, который занимает место и проверяется во время выполнения и занимают место нужное для наибольшего из перечисляемых через эту запятую. Т.е. значение такого вот типа + +```ml +type demo = (foo:int; + (optional:(bar:int; + baz:int), + nothing); + quux:int + ). +``` + +сконструированное так `demo(1,optional(2,3),4)` имеет такое вот представление: + +``` + ┌───┬───┬───┬───┬───┐ + │ 1 │001│ 2 │ 3 │ 4 │ + └───┴───┴───┴───┴───┘ +``` + +а сконструированное так `foo(1,nothing,4)` такое: + +``` + ┌───┬───┬───┬───┬───┐ + │ 1 │002│ │ │ 4 │ + └───┴───┴───┴───┴───┘ +``` + +Размер сохранился, два слова просто не используются. +Определение типов похоже на систему Ландина с её бесконечным вложением. Но Хоар не сослался на Ландина, а сослался на систему МакКарти. О системе Ландина Хоар наверняка знал, потому, что они оба участвовали в создании важного для нашей истории языка, рассказ о котором еще впереди. Это ожидаемо практичный подход, вполне адекватный и сегодня, тем более в то суровое время. Но что же происходит, когда размер неизвестен? Первая система Хоара позволяет определить такой вот рекурсивный тип: + +```ocaml +type list = (null, cons:(head:int;tail:list)). +``` + +но представление значения такого типа больше не "плоское", поле `tail` содержит указатель. Что никак не обозначено синтаксисом или типами, и вот это уже неожиданно. Языки с таким "плоским" представлением типов обычно обозначают ссылку тем или иным способом. Почему же Хоар так не делает? Он не любит ссылки. Да, критика ссылок Хоаром во многом справедлива, но понятно как и почему большинство разработчиков ЯП исправят этот дизайн. Не так, как это сделал Хоар. +Следующая проблема в том, что Хоар не придумал хорошей конструкции для проверки создаваемых `,`-конструкцией тегов, хотя попытался это сделать, считая, что конструкция, позволяющая безопасно работать с типами данных важна и нужна. Получилось у него вот что: + +```ml +function reverse (l: list): list; + with l do + {null: reverse := l, + cons: reverse := cons(head, reverse(tail))} +``` + +И, наконец, еще одной проблемой было неудачное использование запятых. Тип данных`(bar:int;baz:int)` можно записать как `(bar,baz:int)` и тут запятая не означает то же, что тут `(bar:int,baz:int)`. Это кажется незначительной деталью, но позднее позволило сформулировать более важную идею. +Уже в октябре 73-го [Hoar89] Хоар написал отчет о новой системе типов данных, в которой все эти проблемы исправлены. Версия отчета 74-го года опубликована как статья [Hoar75] только в 75-ом году, но отчет имел хождение и до того. По крайней мере Уоррен на него ссылается [Warr77]. Для решения проблем Хоар использовал идеи Бурсталла [Burs69] и Кнута. +В январе 73-го более известный другими своими работами Дональд Кнут написал открытое письмо Хоару [Knut73] (и по одному письму остальным двум авторам книги "Структурное программирование", но эти письма не важны для нашей истории). В письме Кнут, помимо прочего, раскритиковал синтаксис для типов и предложил сделать его более похожим на BNF. На BNF как в описании ALGOL 60 [Back63], т.е. c `|`, а не как в статье Бэкуса [Back59] с ключевым словом `or`. Эта нотация имела разные варианты, как и произошедшая от нее нотация для описания типов. И если бы Хоар не посчитал нужным отметить, что эту идею подал ему Кнут, мы бы сочли, что это его собственная идея. Хоар по всей видимости был в шаге от нее потому, что у Кнута эта идея появилась, когда он смотрел на страницу в книге Хоара [Hoar72], на которой описывающий AST тип данных непосредственно соседствует с соответствующим описанием конкретного синтаксиса с помощью BNF. Среди описателей АТД были распространены идеи о том, что тип данных - это язык программирования [Wand80], что конструкторы типа данных - это абстрактный синтаксис [Gutt78]. Так что можно уверенно предположить что идея о том, что имеет смысл сделать нотацию для описания абстрактного синтаксиса похожей на нотацию для описания синтаксиса конкретного, носилась в воздухе. И могла быть переоткрыта независимо множество раз, но не проговаривалась настолько явно. +Более важным изменением было новое представление типов данных в памяти, которое и определило судьбу этого проекта Хоара. Почему Хоар не любил явные ссылки? Потому, что считал, что ссылка для типов данных - это то же, что `goto` для потока контроля. Низкоуровневая фича, которую программист должен использовать для воспроизведения более высокоуровневых фич, которые как раз ему и нужны для написания программ. И будет использовать плохо и с ошибками. Вместо `goto` в языке должен быть набор удобных конструкций для управления, например рекурсивные функции. Так что Хоар решил сделать такие типы данных, которые для явных ссылок будут тем же, чем рекурсивные функции для `goto`. +И если первая версия системы Хоара избегала ссылок до последнего, чтоб внезапно их добавить, то вторая система - это средство их массового создания по умолчанию. Хоар рассудил, что ссылки в основном нужны для создания деревьев в куче. Ну так вот вам конструкция для создания деревьев в куче. Причем иммутабельных деревьев, потому что циклы в таких структурах Хоар посчитал нежелательными. +Суммируем: вскоре после вполне практичного дизайна для низкоуровневых задач, который был бы похож на типы данных в языке Rust или анбокснутые суммы из Хаскеля, если бы не странное отношение к явным ссылкам, Хоар представил систему ссылочных иммутабельных типов данных со структурной проверкой равенства значений, требующих имплементацию со сборщиком мусора и много памяти. +Иммутабельность не такая и проблема, утверждает Хоар, который как раз ознакомился с работами Бурсталла. Т.е. в числе прочих и с системой трансформации [Darl73], которая может прозрачно для программиста переписывать значения на месте там, где это возможно. Хоар почему-то называет эту систему автоматической, хотя она в лучшем случае только _полуавтоматическая_. И говорит о возможностях системы как о возможностях оптимизатора компилятора, которым она не является. +Не смотря на этот оптимизм, Хоар сохраняет некоторые опасения того, что с оптимизациями все может получиться и не совсем хорошо, и что такие типы данных будет трудновато внедрить в мейнстриме 70-х годов, так что предусмотрел и мутабельную версию с ключевым словом `class` вместо `type`. +Хоару не нужно было убеждать авторов ФЯ принять самые тяжелые для "практиков" дизайн-решения, к которым авторы ФЯ и так уже пришли, хотя и по совершенно другим причинам. В ФЯ в это время все значения - неявные ссылки на объекты в куче из-за "динамической" типизации или параметрического полиморфизма. Эти неявные ссылки как раз в это время становятся иммутабельными из-за того, что непонятно как сочетать мутабельные с полиморфизмом. +Но эта статья нацелена не на авторов ФЯ, а на авторов языков вроде Паскаля. По отсутствию ПМ в которых понятно, чем все закончилось. Но не смотря на понятный конец этой истории, мы все равно к ней еще вернемся. +Что нужно было принять в этих типах данных авторам ФЯ эдинбургской программы, так это их вид, способ группировки конструкторов. Типы данных Хоара теперь выглядят как "традиционный" синтаксис для определения АлгТД, а точнее "традиционный" синтаксис выглядит как они: + +```haskell +type list = (null | cons(int, list)) +``` + +Хотя и не без синтаксических странностей, которых нет в более современных версиях. Так `data Or = Left Foo | Right Foo` в системе Хоара можно записать `type or = (left,right(foo))`. +"Традиционный" там в кавычках потому, что в свете наших изысканий традиционность такого синтаксиса, по сравнению с перечислением сигнатур конструкторов, довольно сомнительна. Но это несомненно самый популярный синтаксис, который господствует в языках Эдинбургской программы и в наши дни, хотя и немного потеснен перечислением сигнатур конструкторов, возвращающихся начиная с 90-х. +Трудно сказать наверняка, что это было первое и последнее изобретение АлгТД в таком виде, но оно определенно довольно раннее. Пока остается загадкой, почему с 73-го года и до года 78-го или 79-го авторы ФЯ продолжали использовать для декларации типов перечисление сигнатур конструкторов или, как в LCF/ML вовсе требовать имплементировать конструктор вручную с помощью конструкторов элементарных типов и их комбинаторов, пока вдруг первые из них не решили, что BNF-образный вариант это то, что нужно. +Наконец, для обхода деревьев определенных таким образом Хоар теперь предлагает использовать конструкцию, позаимствованную у Бурсталла, которую авторы языков Эдинбургской программы не хотели имплементировать более десятилетия: + +```ml +function reverse (l: list): list; + reverse := cases l of + (null -> l | + cons(h,t) -> cons(h, reverse(t))); +``` + +Если точнее, ухудшенную конструкцию Бурсталла, но это только наиболее вероятная из возможных интерпретаций. +Дело в том, что в статье [Burs69], описывающей первые конструкции Бурсталла для паттерн-матчинга, этим конструкциям уделяется не так много внимания. Ведь статья не про них, а про доказательства свойств программ. `cases`-выражение вводится только как нотация, которая делает эти доказательства читаемее. Более того, значительная часть места, которое осталось паттерн-матчингу съедено описанием отвергнутых неудачных вариантов. И хотя Бурсталл обсуждает вложение паттернов, описывая первые неудачные варианты, когда доходит до примеров с `cases`-конструкциями, вложение уже не использует. Посчитал ли он, что в `cases` вложение не нужно или оно просто не понадобилось? Примеров, в которых вложение не используется, хотя было бы полезно он не приводит. +Дарлингтон использует `cases`-конструкцию в псевдокоде, которым описывает трансформации в своей диссертации [Darl72]. И в его псевдокоде вложенные паттерны тоже не встречаются. Но вполне возможно, что тоже по причине ненужности для конкретных примеров. +С другой стороны, в разработанном в IBM неисполняемом языке спецификации META-IV [Jone78] (да, это каламбур, меты с другими номерами не было), который по всей видимости также позаимствовал конструкцию `cases` у Бурсталла, вложенные паттерны использовались [Jone78b]. +И если в случае псевдокода Бурсталла могут быть какие-то сомнения о том, запрещает ли он вложение паттернов, то в случае псевдокода Хоара все ясно: он приводит BNF для своих конструкций. Вложение паттернов невозможно. С учетом того, что у Хоара (как и у Бурсталла) последовательность ветвей значения не имеет, его `cases` ближе к `case` в Core, чем в SML и Haskell. Но вне зависимости от того, как Хоар трактовал неопределенность в описаниях Бурсталла, идея о вложенности паттернов не только в `let` должна была быть ему знакома. По крайней мере по статье [McBr69] МакБрайда (не того) и др. о матчере, на который Хоар ссылается как на имплементацию `cases` Бурсталла. Нужно отметить, что это не имплементация `cases` Бурсталла потому, что матчит не конструкторы АлгТД, а списки списков и атомов. Т.е. это как если бы представление, в которое компилировались паттерны в системе Гуттага [Gutt78] было на пользовательском уровне. +Итак, Хоар должен был уже быть знаком с идеей вложенных паттернов, так что он сознательно выбрал, что их не должно быть. Можно предположить, что это из-за его требований к работе `cases`-конструкции. Хоар хотел, чтоб можно было объявлять разные типы с одинаковыми именами конструкторов. Также хотел проверять полноту матчинга. Ну и конечно хотел, чтоб `cases` можно было скомпилировать в эффективный код. Возможно, он считал что все это будет сложно имплементировать в случае разрешения вложенных паттернов. +Но, как мы уже отмечали выше, принять эту конструкцию в каком бы то ни было виде авторы языков Эдинбургской программы пока не готовы. Но Бурсталл готов принять нотацию для объявления типов данных. + +### Конец экспериментального программирования + +В 1977 NPL получил тайпчекер, имя и программный доклад на конференции [Burs77b]. Статью, которая не отсканирована. Все, что нам остается это судить о ней по тем материалам, которые её цитируют. Действительно ли типизированный NPL в какой-то момент выглядел как пример + +``` +FUNCTION sum : NATLIST |-> NATURAL + sum(nil) = 0 + sum(u::l) = u + sum(l) +``` + +из диссертации [Vase85] или просто переделан до неузнаваемости автором, чтоб выглядеть лучше/хуже/иначе? (Что совершенно реальная вещь которую в то время делали). Действительно ли конструкторы одного типа и уравнения одной функции в NPL77 не составляли единую конструкцию и могли добавляться инкрементально, как пишут в статье [Dugg96]? Неизвестно. МакКвин, спустя десятилетия вспоминает [MacQ15] добавляемые конструкторы, но нам трудно доверять этим утверждениям. У нас есть основания сомневаться, что он хорошо помнит эту часть истории NPL. +Эта версия NPL, от которой осталось меньше следов, чем от всех прочих версий NPL, быстро сменилась следующей версией. Как это обычно и бывало с версиями NPL. Последняя версия NPL с названием "NPL" (назовем её NPL79) была первой на которой написали какой-то код кроме однострочников. Этот код писался для того, чтоб испытать переписыватель программ на чем-то кроме однострочников. Чем занимался в 77-79 годах Мартин Фезер, основной разработчик переписывателя в Эдинбурге после ухода Дарлингтона. Мартин Фезер поместил в приложении к своей диссертации [Feat79] описание NPL79 и это первое сохранившееся описание языка NPL-линейки. Интересно, что эта версия NPL, которая документирована лучше, чем любая из предыдущих версий и на которой написано больше кода, чем на всех предыдущих вместе взятых не оставила никаких следов в памяти МакКвина. +Мы мало что знаем про указание и объявление типов в NPL77 потому, что в NPL79 они по какой-то неизвестной причине были полностью переделаны. CLEAR все ещё не готов, так что они снова выглядят не так как в CLEAR. Переписыватель, судя по всему, никогда не поддерживал синтаксис типов как в NPL77, что заставляет задуматься, были ли они вообще имплементированы, или были только псевдокодом в той неотсканированной статье. И был ли их синтаксис в NPL79 придуман именно таким из-за вечного отставания поддержки языка в переписывателе по сравнению с интерпретатором Бурсталла. Иначе зачем коду выглядеть вот так? + +``` ++++ length(list alfa) <= num +VAR H : alfa VAR T : list alfa +--- length(nil) <= 0 +--- length(H::T) <= succ length(T) +``` + +Может это для того, чтоб легко вырезать только то, что поддерживает переписыватель простым препроцессором? Обратите внимание на соглашения о регистре имен переменных как в Прологе и также на то, что каждое имя теперь нужно объявлять с указанием типа, как полагается в большинстве языков описания спецификаций из этой главы. Вывода типов-то нет. Но такие объявления - общие для всех функций, они не составляют одну конструкцию с уравнениями. Как и сигнатуры функций, которые могут быть сгруппированы отдельно, как в Хаскеле. +То, что типизированный NPL оброс ключевыми словами и закорючками - более-менее ожидаемо. ISWIM в стиле Бурсталла имел синтаксис потяжелее Ландинского уже в 60-е, и с годами ситуация становилась только хуже. Возможно из-за влияния на Бурсталла языков спецификации вообще или Гогена в частности. Поплстоун уже работал над ЯП вместе с Бурсталлом и не мог остановить его, как в той истории с синтаксисом паттерн-матчинга. Да и большая часть синтаксиса, придуманного Поплстоуном, особой легкостью не отличалась. +Все происходящие от NPL79 языки уже никогда не смогли оправиться от этой чудовищной метаморфозы. Во всех есть какие-то ключевые слова перед декларацией функции и какие-то символы между уравнениями. К счастью, существуют языки, произошедшие от NPL75 и сохранившие легковесный синтаксис уравнений из него и языков спецификации. +NPL79 - первый NPL с привычными объявлениями алгебраических типов данных. + +``` +INF 4 :: +DATA list(alfa) <= nil ++ alfa::list(alfa) +``` + +Они уже не перечисления сигнатур конструкторов как в языках описания спецификаций. Имена конструкторов все еще могут выглядеть как имена функций. Все как предлагал Хоар. Если не считать `|` символа. И, что более важно, параметризованности. Как в таком случае отличать имена конструкторов типов от имен параметров типов? Имена параметров типов надо объявлять как и имена всех прочих параметров `VAR omega : type`. `alfa`, `beta`, `gamma`, `delta` и `epsilon` уже объявлены. +Ключевое слово `data` работает не так как в Хаскеле, взаимно рекурсивные типы данных объявляются так: + +``` +DATA option1 <= none1 ++ some1(option2); + option2 <= none2 ++ some2(option1) +``` + +Такое потрясающее средство для объявления типов используется без особой фантазии. Стандартные типы это булевы значения, списки, множества, которые на самом деле списки: + +``` +DATA set(alfa) <= nilset ++ consset(alfa, set alfa) +``` + +и натуральные числа, которые, совершенно верно, списки: + +``` +DEF +/// define numbers, addition, multiplication and factorial + +PRE 20 succ +DATA num <= 0 ++ succ num + +VAR N,M : num + ++++ num + num <= num +--- 0 + N <= N +--- succ M + N <= succ(M + N) + +INF 6 * ++++ num * num <= num +--- 0 * N <= 0 +--- succ N * N <= N + M*N + ++++ factorial(num) <= num +--- factorial(O) <= 1 +--- factorial(succ N) <= succ N * factorial(N) +END + +VAL factorial(3) END /// evaluates to 6 +``` + +На этом языке было написано несколько программ. Не особенно больших, но и не однострочников. И, разумеется, работающий над трансформатором Мартин Фезер обнаружил, что трансформатор Дарлингтона требует слишком много ручного труда и слишком много памяти для работы с чем-то, что больше однострочников. Фезер добавил больше автоматизации и сделал возможным загрузку только части кода трансформируемой программы в оперативную память. В результате почти весь код Дарлингтона был переписан и новому трансформатору дали имя ZAP. Пока трансформатор был один - он, как обычно, был безымянным. +Настало время проверить, что практичнее: подход Уоррена или полуавтоматический трансформационный подход, над которым Бурсталл с Дарлингтоном проработали все 70-е. И проверка показала, что полуавтоматический подход не очень-то практичен. Даже после всех улучшений в системе Фезера, он обнаружил, что с самой большой из тех программ, которые он пробовал трансформировать, работать очень тяжело. Все еще слишком много ручного труда. Размер этой самой большой программы? 600LOC (шесть сотен) и 152 строк комментариев. Значит подход Уоррена победил? Не в битве за сердца Фезера с Дарлингтоном, которые пока что не оставили надежду. +Пока Фезер испытывал свой с трудом трансформирующий трансформатор на последнем NPL, называвшемся NPL, Бурсталл и МакКвин готовили новую версию, с новым названием. Фезер упоминает эти работы и утверждает, что в новой версии будут исправлены два основных недостатка NPL. Первый недостаток - отсутствие абстракции данных. CLEAR не успел стать такой системой для NPL, успеют ли его доделать для того, чтоб он стал такой системой для нового языка? Второй недостаток - отсутствие функций высшего порядка [Feat79]. Да, новый NPL должен наконец-то стать функциональным языком. Весь этот затянувшийся рассказ про языки первого порядка в истории ФЯ был не зря. +В конце 1979-го года работающие над этим новым языком члены бывшей Группы Экспериментального Программирования перешли в Департамент Компьютерных Наук в Кингс Билдингс. Туда, где работали Милнер с Гордоном. На этом история Группы Экспериментального Программирования и закончилась. +С ней закончилась и история NPL как более-менее обособленного проекта. И конец этой истории стал началом истории NPL как компонента, который авторы языков Эдинбургской программы смешивают с ISWIM в разных пропорциях, получая узнаваемый стиль, присущий только продуктам Эдинбургской программы. Позаимствованный у Обоекембриджской ветви CPL/ISWIM стал прародителем многих, в том числе и мейнстримных языков и не смотря на неустанную декембриджизацию все еще узнаваем в них. Другое дело - NPL. Это компонент, определяющий всю самобытность языка Эдинбургской программы. За пределами которой все эти уравнения с паттерн-матчингом можно найти только в том, что и языком программирования обычно не считается. Но могли не считаться такими и языки Эдинбургской программы. Пока что их имплементаторы не предприняли особых усилий, чтоб на этих химерах, объединяющих в себе два языка исполняемой спецификации, можно было писать какие-то программы. Если авторы и имплементаторы Пролога практичнее вас, то довольно безопасно предположить, что вам следует уделить практичности побольше внимания. +Закончилась и предыстория алгебраических типов данных. Наши великие предшественники обычно перескакивают от нотации Ландина сразу к современным АлгТД. И не без причины. Этот совсем нетривиальный переход оставил не так уж много следов, которые не так-то легко датировать и у которых не так просто определить авторство. Мы полагаем, что между этими событиями были: +* Изобретение конструкторов, которые можно разбирать в `case`-выражениях. Но конструкторы сначала первоклассные потому, что изобретены для функционального языка. Также, каждый конструктор конструирует значение отдельного типа и в один тип их нужно объединять специальным комбинатором типов как у МакКарти. +* Изобретение исполняемых уравнений с паттерн-матчингом, которые изображают запись свойств в алгебраических спецификациях. Конструкторы теперь абстрактные функции в АТД и больше не являются первоклассными потому, что и все прочие функции в языках алгебраической спецификации первоклассными не являются. +* Идея о том, что АТД должны вводить пространство имен для своих членов как,например, у Лисков. Но многие не принимают эту идею. +* Идея о том, что у Абстрактных Типов Данных может быть сколько угодно конструкторов. Логично, ведь у АТД может быть сколько угодно функций. Специальные комбинаторы типов больше не нужны. +* Не особенно успешная борьба за то, чтоб между конструкторами и прочими функциями АТД было как можно меньше разницы, следы которой до сих пор остаются. +* Идея о том, что АТД можно считать языком, а его конструкторы считать его абстрактным синтаксисом. Прочие функции - это его семантика, разница между конструкторами и прочими функциями вполне естественна. +* Идея о том, что удобно придать нотации для определения абстрактного синтаксиса вид как у нотации для конкретного синтаксиса. + +Готово! Первая версия современных АлгТД, которые далее будут эволюционировать в основном в сторону все большего стирания следов борьбы за сходство конструкторов и прочих функций. + +------------- + +NPL начинает очередную трансформацию, которая сделает его не просто ФЯ, а первым сформировавшимся языком Эдинбургской программы. Но перед тем, как рассказать об этом, нам нужно отправиться в соседний с Эдинбургом город, в котором появился третий прародитель функциональных языков. + +Две диссертации +----------- + +### От экспериментального программирования к исследовательскому + +Вернемся в конец 60-х годов к трудам Кристофера Стрейчи, который в Оксфорде возглавляет экспериментальную группу по программированию и является научным руководителем двух важных героев нашей истории. + +#### Переписывание графов + +Один из них нам уже знаком - это Кристофер Вадсворт, один из изобретателей продолжений и авторов LCF/ML. Как мы помним, изобретение продолжений для Вадсворта было только отступлением [Reyn93] от работы над его диссертацией. Так что же, была основная работа более важной, чем отступление? Вероятно. +Нам не удалось с ней ознакомиться. Дело в том, что диссертация Вадсворта [Wads71] - один из тех документов, которые не были отсканированы. Но это один из самых цитируемых источников, который не был отсканирован. +Потому, что Вадсворт в том же ряду, что Ландин и Бойер с Муром. Один из тех, кто придумали практические способы имплементировать целые разновидности ЯП. Или, с другой точки зрения, придумал как имплементировать корректно [Vuil73] то, что до него придумывали как имплементировать не совсем корректно. +Когда мы писали о том, как Ландин придумал способ имплементировать лямбда-исчисление мы не сделали важной оговорки о том, что SECD не может вычислить целые классы вполне корректных выражений. Причина - аппликативный порядок редукции. +Но это не очень волновало многих имплементаторов ФЯ. Они справедливо посчитали, что ЛИ-выражений, которые вычисляются, в сочетании с некоторыми расширениями будет вполне достаточно для того, чтоб писать программы. Тем более, что практической альтернативы все равно еще не было. Был только вызов по имени, постоянно перевычисляющий одно и то же. Но такое решение приняли не все. Так, непрактичность вызова по имени не помешала авторам ALGOL 60 добавить его в язык и даже как способ передачи параметров по-умолчанию. Конечно, нужно учитывать, что в значительной степени это - результат борьбы за и против того, чтоб сделать из Алгола ФЯ (к которой мы еще вернемся). ALGOL 60 - это линия фронта между фракциями, которые и десятилетия спустя обожают рассказывать как же были неправы их оппоненты. Так что никого не должно удивлять то, что такое решение определенно не стало популярным. В языках, происходящих от ALGOL 60 от него отказывались. +Вадсворт придумал [Turn19] как решить, по крайней мере в принципе, проблему перевычисления одного и того же. Нужно работать не с деревьями, как SECD, а редуцировать графы. Значения с которыми работает переписыватель представляются как ссылки, которые указывают на один и тот же объект в куче. А значит ссылаются и на результат, получаемый после вычисления этого объекта, когда этот результат впервые понадобится. Вадсворт также ввел одно из менее популярных названий для этой идеи - вызов по необходимости (call-by-need). +Фактически происходящие от Алгола языки тоже заменили передачу по имени на ссылки и мутабельность но не таким скрытым от программиста способом. +Разумеется, для замены вычисления на его результат нужно чтоб результат вычисления всякий раз был одним и тем же. Так что явная мутабельность долго будет в таких языках нерешенной проблемой. В гораздо большей степени проблемой и в гораздо меньшей степени решенной, чем нерешенные проблемы сочетания мутабельности с параметрическим полиморфизмом из позапрошлой главы. Зато решение первой проблемы в ленивых ФЯ решит в них и вторую. +Временная необходимость отсутствия императивных фич в языке для нормальной работы вызова по требованию была еще одним препятствием. Но, как мы выяснили в прошлой главе, языки без императивных фич как раз стали появляться вскоре после защиты диссертации Вадсворта, хотя и не в следствии её. И авторы этих языков, не очень-то предназначенных для исполнения спецификаций, оценят принципиальность нормальной редукции. Так О'Доннел считает её важным требованием для принципиальной имплементации языка с уравнениями [O'Do87]. +Ландин и Бойер с Муром не ограничились только изобретением практического способа имплементации и попытались, в основном безуспешно, воплотить свои идеи в жизнь и написать практические имплементации. В отличие от них Вадсворт не стал пытаться и, защитив диссертацию, оставил это направление навсегда. + +#### PAL или пропал + +Другой герой нашей истории, наоборот, не защитил диссертацию и писал и писал код таких имплементаций c 70-х годов и до настоящего времени. По крайней мере писал еще в 2020-ом году. +Дэвид Тёрнер (David Turner) работал в Оксфорде над диссертацией под руководством Стрейчи с 69-го года [Turn19], но был в другой "партии" соискателей и не был знаком с Вадсвортом. В отличие от работы Вадсворта, результатом работы Тернера должна была стать практическая имплементация, но не идей Вадсворта, а идей Ландина. +Джо Стой (Joseph E. Stoy) привез [Turn19] из МТИ ленты [Turn12] с интерпретатором PAL и Стрейчи посоветовал Тернеру написать эффективную имплементацию PAL. Что Тернер три года безуспешно пытался сделать. Тернер уверенно называет продолжения основной причиной своей неудачи. О какой эффективной имплементации языка может идти речь, если в нем эквивалентная продолжениям фича? Тернер упоминает [Turn19] об одной из конкретных проблем продолжений: слишком много объектов задерживается в куче. Да, это та проблема, которую Уоррен решил пять лет спустя в своем компиляторе Пролога. +Так что взгляд Тернера на продолжения, который по всей видимости был достаточно типичный для протоэдинбургской программы, не следует воспринимать некритично. К счастью, это один из тех вопросов, ответ на который вполне возможно найти. Выяснить когда эффективная имплементация ФЯ вообще стала возможной и почему - одна из основных целей нашей работы. Тернер - автор многих важных имплементаций ФЯ и осталось достаточно свидетельств об их эффективности чтоб установить его личный вклад в эту неудачу. И, конечно, были и другие попытки имплементировать язык с продолжениями и многие их результаты тоже известны. Все это будет рассмотрено более подробно в соответствующих главах. +В октябре 72-го Тернер покинул Оксфорд без степени и отправился читать лекции в Сент-Эндрюс [Turn12], город в 80км от Эдинбурга. + +### Статический динамический язык + +> На особую оригинальность SASL не претендует - более того, целью проекта было сделать нотацию как можно более "стандартной". +> Дэвид Тёрнер, Руководство по SASL + +В Сент-Эндрюсе Тернер использовал для преподавания псевдокод, который был примерным чисто-функциональным подмножеством ISWIM. К удивлению Тернера, его коллега Тони Дейви (Tony Davie) за выходные имплементировал этот псевдокод на Лиспе. Раз уж язык был имплементирован, вспоминает Тернер, ему пришлось дать имя: SASL (St Andrews Static Language) [Turn12]. "Статический" тут означает "иммутабельный". +В это время Тернер не был знаком с LISP 1.5 [Turn19], но вскоре вынужден был познакомится. Потому, что курс ФП до него использовал Лисп. И когда Тернер познакомился с Лиспом, он обнаружил, что лямбды работают не так как надо [Turn12]. К истории о неправильных лямбдах в Лиспе мы еще вернемся. + +#### SASL 74 + +Разумеется, Тернер решил использовать для преподавания SASL. Но, как и первая имплементация PAL на Лиспе, первая имплементация SASL на Лиспе работала недостаточно хорошо. Поэтому во время пасхальных каникул 1973 Тернер имплементировал SASL заново, на том же языке, что имплементировали PAL - BCPL, для младшей модели той же линейки машин, на которой имплементировали McG - IBM 360/44. В первой версии этой имплементации, представляющей собой компилятор в SECD байт-код и SECD интерпретатор, было только немного больше чем 300 строк кода [Turn12]. Конечно, это воспоминание Тернера, озвученное сорок лет спустя. Существует более близкое к описываемому времени свидетельство о том, что только в интерпретаторе SECD-байт-кода на BCPL было 350 стейтментов [Somm77]. Стейтменты, конечно, не строки, и это по-видимому больше, чем 300 строк на все, не в одном только интерпретаторе. Но все еще очень мало. +"SASL был не очень большим языком" - говорит Тернер. И Тернер предпринимал усилия для того, чтоб сделать язык еще меньше. Поэкспериментировав с некоторыми деталями синтаксиса `let`, он отказался от аннотаций рекурсии. Это вероятнее всего произошло в 75-ом году. +Хотя Тернер рассказывает, что начал работу над SASL в 72-ом году по крайней мере с 82-го года [Turn82], самые старые ссылки на неотсканированные репорты с описанием SASL и его имплементации датируются 75-ым годом. Составить представление об их содержимом можно по отсканированным и выложенным в интернет диссертациям, которые писали об имплементации SASL [Somm77] или о программах на SASL [Well76]. В этих диссертациях есть не только ссылки на неотсканированные документы, но и код на SASL, его стандартная прелюдия (да, называется как в Хаскеле) и описания SASL. Включая и BNF. +Первый мануал по SASL вышел в отчете в январе 75-го. SASL был функциональным языком, так что мы можем, наконец-то, после долгого перерыва на нефункциональные языки с уравнениями, вернуться к нашему традиционному примеру: + +``` +_REC MAP F X +_BE X=() -> (); F(_HD X),MAP F(_TL X) +_IN _LET Y = 1 +_IN MAP (_LAMBDA X. X + Y) (1,2,3,) +``` + +SASL - название для многих языков. Бывает, что имеющих мало общего между собой, так что мы будем называть разные версии обычным способом, добавлением года. Сам Тернер такие наименования не использует. Этот SASL из январского отчета мы назовем SASL 74. Почему 74? + +#### SASL 75 + +Уже 16 сентября 75-го вышла ревизия мануала. Из SASL убрали `rec` и получили SASL 75. Код `map` из стандартной библиотеки этой версии дошел до нас [Well76]: + +``` +_LET MAP F X || 'MAPS' F ALONG THE LIST X +_BE X=() -> (); F(_HD X),MAP F(_TL X) +``` + +Эти `be` и `||` комментарии - из синтаксиса BCPL. +Итак, в отличие от библиотеки PAL, в библиотеке SASL были ФВП. И некоторые из них даже написаны с применением ФВП и частичного применения функций! Но не c применением `foldr` (`Lit`) Стрейчи, не смотря на то, что он был научруком Тернера. Возможно, что в 70-е `Lit` был интересен Бурсталлу с Гордоном больше, чем самому Стрейчи. Набор ФВП для работы со списками не очень богатый и повлияли на него, по всей видимости, только статьи Ландина о SECD и ISWIM, но не Стрейчи и Берджа. Но в библиотеке есть набор комбинаторов из книги Карри [Curr58]. Тернер узнал о комбинаторах из лекций Даны Скотта в Оксфорде в 1969 [Turn19]. +Если говорить о подмножествах реально имплементированных языков, то SASL 75 скорее подмножество не PAL, с имплементацией которого Тернер столько страдал, а McG, который Тернер, правда, не упоминает. Есть `let` но нет `where`, функции объявляются каррированными, синтаксис для списков вместо синтаксиса для туплов, строки как списки символов. Последнее Тернер считает [Turn19] собственным изобретением и одной из важных инноваций SASL, но нет, уже было [Burg71] у Берджа и Левенворта в McG. В SASL 75 была лямбда, как в PAL, но с другим ключевым словом. Правда, в отсутствии лямбды в McG мы не полностью уверены. + +#### Функционального программирования нет в меньшей степени, чем обычно + +На первый взгляд, SASL 74/75 выделяется из обсуждаемых нами раньше ISWIM-ов только урезанием всех императивных фич. И такое урезание тоже не является чем-то необычным в это время. Это скорее правило для нового поколения не очень-то предназначенных для исполнения исполняемых языков спецификации из прошлой главы. Следующие версии SASL интереснее, но и SASL 75 выделяется из ряда ФЯ, которые мы обсуждали до сих пор. На SASL 75 была написана программа под названием IDEA, которая на момент написания была больше чем любая другая сохранившаяся программа на протоФЯ. Но не беспокойтесь, функционального программирования все еще нет, размер этой рекордной программы только 1565LOC. Да, это почти как самая крупная программа на LCF/ML и самая крупная программа на NPL вместе взятые. Но размер кода не особенно впечатляет. И это даже не компилятор, как в случае Пролога. Но эта программа, как и компилятор, работает с деревьями. Небольшими деревьями. Программа дифференцирует и интегрирует [Well76]. +Автор-первопроходец делится с нами опытом разработки таких гигантских программ на ФЯ. Опыт не особенно приятный. Да, есть и плюсы. Автор считает, что код на SASL лучше читается, чем код на Лиспе и меньше чем у аналогичной программы на Лиспе. Функциональность программы на Лиспе больше, так что сравнение не совсем честное. Также, автор сравнивает размеры этих программ в разных единицах. 1600 карт против 70 страниц. Ну, карты это просто строки. В распечатке дошедшей до нас программы на одной странице 44 строки. Получается, что размер программы на Лиспе в два раза больше. Неплохо, но зависит от того какая разница в функциональности. +Автор считает, что скорость работы программы удовлетворительная. У пользователя может сложиться другое мнение потому, что интеграция "в большинстве случаев требует менее пяти минут". Скоростью работы компилятора в байт-код автор программы уже не так доволен. Имплементация Тернера явно не предназначена для того, чтоб писать программы в 1KLOC. Раздельной компиляции нет. Нужна ли она в для программ в 1KLOC? В 76-ом году еще как. Интерпретатор Тернера компилирует в байт-код при каждой загрузке и весь этот процесс занимает три минуты. Программирование получается не очень интерактивным. Также, программа не умещается в обычный пользовательский лимит в системе разделения времени. То есть разрабатывать и даже использовать программу обычный пользователь не может, ему нужно получать разрешение на увеличение лимитов. По этой причине даже сделана специальная версия, которая только дифференцирует и может быть использована обычным пользователем. +Автор IDEA также недоволен отсутствием в SASL императивных фич, а именно процедуры для вывода. Интерпретатор выводит только результаты функций. + +#### Воплощение ISWIM в реальность завершено + +Нужно отметить, что небольшой размер SASL может быть обманчив. Да, в нем нет двух десятков разновидностей `let` как в LCF/ML. Но было несколько инноваций, которые делали имплементацию не такой уж простой. Репорт об имплементации SASL 74/75 вышел в марте 75-го и тоже не отсканирован. Так что об этих инновациях остается судить только по существенно более поздним воспоминаниям Тернера [Turn19] и некоторым следам в коде на SASL. Тернер вспоминает, что в SASL была впервые имплементирована фича ISWIM-псевдокода, о которой мы уже рассказывали в главе про ML - вложенный паттерн-матчинг в `let`. Вложенный паттерн-матчинг в SASL еще бесполезнее, чем в LCF/ML (в котором он появился не намного позднее) из-за отсутствия средств для обработки его неуспешности. Но для этого Тернер вскоре найдет более современное решение. +В примерах SASL 74 [Somm77] и BNF, описывающем SASL 75 [Well76] вложенные паттерны есть. Но есть несколько поводов заподозрить, что фича была в языке не с самого начала. А может быть была в языке но не в имплементации. Помимо заявляемого Тернером малого размера имплементации, в которой не так много строк даже для недо-компилятора такого недо-ПМ, вызывает вопросы наличие ключевых слов для для `head` (`_HD`) и `tail` (`_TL`). Можно бы было ожидать, что автор, который старается сделать язык поменьше, воспользовался бы случаем и не делал бы явно дублирующие ПМ конструкции. Еще один довод в пользу того, что вложение паттернов появилось не сразу - оно практически не используется в дошедшем до нас коде [Well76]. Но автор этого кода пишет, что имеет небольшой опыт программирования. +Еще одна возможная недоделка или баг. В реальном коде на SASL лямбду записывали `_LAMBDA (X,). X + Y` хотя BNF-описание позволяет `_LAMBDA X. X + Y`. +Но не стоит уделять этим вопросам много внимания. Такие фичи SASL как `let` с паттерн-матчингом и лямбды в этой истории с нами не надолго. +Тернер утверждает [Turn12], что эта имплементация SASL осуществляла оптимизацию хвостового вызова. Что подтверждается хвосторекурсивной имплементацией конкатенации списков (но не `map`) в стандартной библиотеке [Well76]. + +``` +_LET SHUNT X Y +_BE X=() -> Y; SHUNT(_TL X) (_HD X,Y) +_NEW REVERSE X _BE SHUNT X() +_LET APPEND X Y || CONCATENATE THE LISTS X AND Y +_BE SHUNT(REVERSE X) Y +``` + +Едва ли вы захотите так имплементировать конкатенацию, если нет оптимизации хвостового самовызова. + +#### Имплементации настоящие и будущие + +К обсуждаемому времени помимо имплементаций на LISP 1.5 и BCPL в Сент-Эндрюсе написали интерпретаторы SECD-кода на микрокоде компьютера, который университет собирался, но так и не смог купить и на низкоуровневом языке, который должен был компилироваться в разные микрокоды [Somm77]. Это не последние имплементации, написанные в этом университете, но еще больше имплементаций появится после того, как о SASL узнают за его пределами. +Летом 1975-го Тернер выступил с докладом о SASL в Суонси на межуниверситетском коллоквиуме. С этого времени другие университеты стали интересоваться SASL. Распространение SASL познакомит с ФП будущих важных героев нашей истории, например Леннарта Августссона. Но распространением интерпретаторов, написанных в Сент-Эндрюсе все не ограничилось. Удачные идеи Тернера о языковом дизайне, не такие удачные работы Тернера как имплементатора и не такой уж большой размер языка привели к тому, что SASL стал в конце 70-х и 80-х ФЯ с самым большим числом имплементаций. Не стоит, конечно, ожидать, что все эти имплементации "SASL" могли исполнять один и то же код. Как не исполняют один и тот же код все имплементации языков, называющихся "ML", например. Также, не все эти диалекты "SASL"-ов будут называться SASL. Имплементаторы ФЯ так привыкнут имплементировать языки Тернера, что возникнут некоторые проблемы, когда окажется, что Тернер не очень-то этим доволен. +Нужно только отметить, что такое распространение получил не SASL, о котором мы только что рассказали, а SASL, о котором нам только предстоит рассказать. Уже в следующем, 1976-ом году все эти наработки были забыты, смыты бурным потоком идей. "SASL" стали называть совсем другой язык. + +### Проблема одинаковых кромок + +Мы уже вскользь упоминали эту проблему в предыдущей главе. Нужно проверить, что два дерева имеют одинаковую последовательность листьев. Сделать это нужно с помощью двух "функций". Одна получает "последовательность" листьев. Другая сравнивает две "последовательности". Требуется написать эти функции так, чтоб сравнение могло завершаться неуспешно без материализации всех последовательностей листьев и без полного обхода деревьев. +Так уж вышло, что эту проблему использовали для обоснования нужности ленивости. Разумеется, для решения этой проблемы не нужна ленивость. С ней справится простой итератор, никакого ненужного перевычисления не будет. Но эта проблема традиционно использовалась для обоснования нужности существенно более мощных конструкций, чем нужно для её решения. Мы не видим ничего плохого в простых примерах для демонстрации нужности чего-то. Но если это и в самом деле нужно. +С другой стороны, до этого самым близким к обоснованию нужности ленивости производительностью было упоминание в статье Жана Вюийемена (Jean Vuillemin) [Vuil73] о том что вот такая функция + +``` +Ble(X,Y) <= IF X = 0 THEN 1 ELSE Ble(X-1,Ble(X-Y,Y)) +``` + +работает быстрее при нормальном порядке редукции, чем при аппликативном. Можно порадоваться, что речь хотя-бы зашла о сколько-нибудь реальной задаче. + +#### `same-fringe ?` `yes)))))))))))]` + +Хьюит [Hewi74] использовал её для того чтоб обосновать нужность продолжений, а точнее построенных на них абстракций: обменивающихся сообщениями акторов и обобщения стримов Ландина. Все просто, пишете акторы, принимаете и шлете сообщения: + +``` +[same-fringe ? <= + (=> + [=tree1 =tree2] + (start loop + [(streamer tree1)(streamer tree2)] + (=>> [=s1 =s2] + (next s1 + (then-to : + (=>> + (#stream (first := x1)(rest := c1)) + (next s2 + (then-to : + (=>> + (#stream + (first := x2) + (rest := c2)) + (rules x1 + (=> x2 + (restart loop + [c1 c2])) + (else + (#no))))) + (else-to : + (=>> (#exhausted) + (#no))))) + (=>> (#exhausted) + (next s2 + (then-to : + (=>> (#stream) + (#no))) + (else-to : + (=>> (#exhausted) + (next s2 + (then-to : + (=>> (#stream) + (#no))) + (else-to : + (=>> (#exhausted) + (#yes)))))))))))] + +[streamer <= + (=>[=the-tree] + (=> + (#next(else-to := the-complaint-dept)) + (internal-streamer + the-tree + the-tree + (=> + (#next) + (% the-complaint-dept (#exhausted)%)))))] + +[internal-streamer <= + (=> + [ + =the-node + =the-customer + = the-alternate-supplier] + (rules the-node + (=> (terminal) + (%the-customer + (#stream + (first : the-node) + (rest : the-alternate-supplier))%)) + (else + (internal-streamer + (left the-node) + the-customer + (=> + (#next (else-to := the-complaint-dept)) + (internal-streamer + (right the-node) + the-alternate-supplier + the-complaint-dept))))))] +``` + +Что, не очень просто? Это похоже на то, что используют серьезные программисты в мейнстриме и сегодня. Разумеется, не с таким синтаксисом. Но наша история про будущих функциональных программистов, для которых, как ни странно, это выглядит не особенно хорошо. Конечно, все это может выглядеть лучше, как выглядят стримы о которых писали Ландин и Бердж. Но все это не совсем то, что нужно. Будущие функциональные программисты, например, хотели работать с рекурсивными структурами с помощью рекурсии. Почему? Во-первых потому, что они обычно этого хотели, но во-вторых потому, что Хьюит заявил, что все эти рекурсивные подходы несовместимы с модульностью и эффективностью. Списки антимодульны. Что толку от комбинирования комбинаторов, если их комбинация строит все эти ненужные списки и обходит деревья, которые не нужно обходить, хотя уже давно понятно, что последовательности листьев деревьев не равны? +На следующий, 1975-й год Бурсталл с Дарлингтоном "решили" проблему полуавтоматической трансформацией наивных рекурсивных функций в одну. Но, как мы знаем, такие трансформации хорошо не заработали. Что же заработало? +Не прошло еще и года после этого и на радикальное заявление Хьюита нашелся ответ у нашего старого знакомого Джеймса Морриса. Разумеется, для иллюстрации своего очередного независимого изобретения он использовал + +#### ГИПЕР-ЧИСТЫЙ ЛИСП + +а не PAL, как и полагается одному из авторов и имплементаторов PAL. Работа Питера Хендерсона (Peter Henderson) и Джеймса Морриса [Hend76] была представлена на конференции в январе 76-го года. Отличие "гипер-чистого" Лиспа от просто "чистого" Лиспа МакКарти в том, что в "гипер-чистом" есть работающие лямбды. Авторы, по всей видимости, придумали наиболее распространенное название для фичи - "ленивость". +Авторы ссылаются на диссертацию Вадсворта и статью Вюийемена [Vuil73]. +Первый пример использования ленивого языка - взятие головы от хвоста бесконечного списка. + +``` +integers[i] = cons[i;integers[i+1]] + +car[cdr[integers[0]]] +``` + +Что должно работать, а не бесконечно вычислять бесконечный список. Невероятно! +Второй пример интереснее, это решение проблемы кромок [Hewi74] без продолжений. + +``` +EqLeaves[x;y] = EqList[Flatten[x];Flatten[y]] + +Flatten[x] = if atom[x] then cons[x;NIL] + else Append[Flatten[car[x]]; + Flatten[cdr[x]]] + +Append[x;y] = if null[x] then y + else cons[car[x];Append[cdr[x];y]] + +EqList[x;y] = if null[x] then null[y] else + if null[y] then false else + if eq[car[x];car[y]] + then EqList[cdr[x];cdr[y]] + else false +``` + +Да, код выглядит в точности, до последней детали как "немодульный" рекурсивный код на строгом языке, но работает как надо. Шах и мат, Хьюит! +Ну, вернее должен работать, если его имплементировать. Это псевдокод. Хендерсон и Моррис, в отличие от Хьюита, пока еще из тех лисперов, которые считают, что вместо того, чтоб показывать как выглядит Лисп на самом деле нужно приводить примеры на M-LISP псевдокоде. Но со временем таких будет все меньше и меньше. +Что-то похожее в конце концов имплементировал О'Доннел, так что может быть нам следовало называть его язык Гипер-Чистый Лисп? +Наконец, есть еще и третий пример. + +``` +primeswrt[x;l] = if car[l] mod x=O then primeswrt[x;cdr[l]] + else cons[car[l];primeswrt[x;cdr[l]]] +primes[l] = cons[car[l];primes[primeswrt[car[l];cdr[l]]]] + +primes[integers[2]] +``` + +То самое квадратичное нерешето неэратосфена, которое демонстрируется в момент написания этого текста на сайте https://www.haskell.org/ как пример кода на хаскеле. Конечно у Хендерсона и Морриса он не в такой краткой форме. Это ставший классическим пример с вычислением простых чисел. Разумеется, существует более современный его вариант, который демонстрирует ленивость лучше. Хорошая демонстрация нужности ленивости была так возможна, но возможность упущена. Как мы видим здесь и видели в истории с ФВП, придумывание небольших понятных примеров, иллюстрирующих использование новых языковых фич, дается не легко! +Независимо от кого Моррис изобретал на этот раз? В том же январе 76-го, когда был сделан доклад про Гипер-Чистый Лисп и Ленивый Вычислитель, Дэниел Фридман (Daniel P. Friedman) и Дэвид Уайз (David S. Wise) выпустили отчет о том, что `cons` не следует вычислять свои аргументы. Под названием "CONS не следует вычислять свои аргументы" [Frie76]. В минимальном ленивом Лиспе Фридмана и Уайза больше ничего менять для поддержки ленивости не требуется, потому что функция применяется к списку параметров. И список ленивый потому, что `cons` не вычисляет свои аргументы. Разумеется, если писать реалистичную имплементацию, то одним `cons` все не ограничится. Фридман и Уайз написали даже прототип имплементации на полторы страницы. +На основе этого отчета был сделан доклад [Frie76b] на международном коллоквиуме по автоматам, языкам и программированию в Эдинбургском Университете в июле 76-го. Эта версия содержит уже ссылку на Хендерсона и Морриса, а так же на книгу, изданную в 1975, в которой способ имплементации ленивого ФЯ описан раньше, чем это сделали Хендерсон, Моррис, Фридман и Уайз. Но ссылка сделана из-за описания стримов. Возможно, что описания имплементации ленивого ФЯ в ней ссылающиеся вовсе не заметили. Ну или мы не заметили какую-то их реакцию на это. +Эта книга, которую читали практически все авторы протоэдинбургских языков, вдохновила Тернера на то, на что не вдохновляла никого из остальных. + +### Прокрастинирующая машина + +появляется в уже известной нам книге Берджа [Burg75] словно из ниоткуда. Ближе к концу главы о SECD Бердж вдруг пишет о том, что вот, кстати, можно и так вычислять выражения. Книга Берджа обычно рассказывает о том, о чем у Берджа уже были статьи. Но не в этом случае. Бердж много писал про стримы [Burg75b], генераторы в SCRATCHPAD сделаны на основе стримов Берджа [Jenk74], описанных в неотсканированном отчете Берджа 73-го года под названием "Еще более структурное программирование". Позднее Бердж пишет про материализующиеся в список стримы для Scratchpad II [Burg89] наподобие динамических списков POP-2. Все это можно использовать для решения проблемы кромок, но не для опровержения заявлений об антимодульности списков. Но как-то так вышло, что в свободное от всех этих занятий время Бердж решил и проблему антимодульности списков и описал в книге. Раньше чем вышли статьи про ленивый вычислитель и откладывающий вычисления `cons`. Ну, Берджу не впервой опережать свое время. +Бердж обычно испытывал свои идеи с помощью McG. Имплементировал ли Бердж прокрастинирующую машину? Существовал ли уже ленивый ISWIM до SASL? Неизвестно. +Статьи Хендерсона, Морриса, Фридмана и Уайза привлекли больше внимания, но часть книги Берджа про прокрастинирующую машину привлекла внимание наиболее важное для нашей истории. Внимание Тернера. + +#### SASL 76 + +Моррис, Фридман и другие не имели ничего против продолжений. Моррис - один из их изобретателей и оба они с Фридманом из тех, кто работал с продолжениями как средством имплементации ФЯ. Но, как мы помним, у функциональной части Эдинбургской программы отношение к ним неважное, а хуже всего - у Тернера, имевшего с ними негативный опыт. Ничего удивительного, что Тернер ухватился за их замену. Да, он смотрит на ленивость именно как на замену продолжений. Тернер утверждает, что ленивые списки заменяют корутины (спасибо за отличный пример, который так хорошо помогает понять для чего нужны корутины, Хьюит), а метод, который через десяток лет Вадлер назовет "список успехов", заменяет бэктрекинг [Turn19]. Про ленивость Тернер мог бы узнать на годы раньше, если бы больше интересовался тем, над чем работают его коллеги. Но похоже, что отсутствие такого интереса довольно типично для героев нашей истории. +Хотя книга Берджа, вдохновившая Тернера, вышла в 75-ом году, Тернер написал первую имплементацию ленивого по-умолчанию языка в 76-ом году, в котором уже были сделаны все прочие основные доклады о ленивости, когда идея уже широко обсуждается. Сложно назвать имплементацию серьезной, но серьезнее, чем прототип Фридмана и Уайза на Лиспе. Как имплементации PAL и SASL на BCPL были серьезнее соответствующих прототипов на Лиспе. Такого уровня серьёзности никто кроме Тернера не достигал еще долгое время. +Итак, первая более-менее работающая имплементация ленивого ФЯ появилась в конце 76-го. Так что ленивость опоздала ко времени дизайна LCF/ML еще больше паттерн-матчинга и да, она тоже перечислена [Miln82] Милнером в его антиисторичном списке нерешенных вопросов ML дизайна, как будто было что решать. +Отчет с руководством по SASL 76, как мы будем его называть, вышел в декабре 76-го. У этого руководства три редакции авторства Тернера, которые мы будем называть SASL 76, SASL 79 и SASL 83. Но были отсканированы и сейчас доступны только руководство по SASL 83 [Turn83] и версия руководства по SASL 76 - руководство [Abra81] по SASL, имплементированному не Тернером. В руководстве по SASL 83 не очень подробно, но задокументирована история изменений SASL между версиями 76 и 79 и между 79 и 83. Но не между 75 и 76 и более ранними. SASL, о котором мы рассказывали до сих пор, там вовсе не упоминается. +Тернер пишет [Abra81] (в более поздних версиях руководства перестает), что SASL не претендует на новизну. Эпиграф к главе про строгий SASL взят из руководства по SASL 76. И то, что в нем говорится справедливо для SASL 75, но не для SASL 76. +SASL 76 называется в комментариях [Coro83] имплементации "Новый SASL (нестрогий, с группами уравнений и функциональной композицией)". "С функциональной композицией" тут означает, что этот оператор является встроенным, а не библиотечной функцией. Как и все прочие, например `++` для конкатенации списков и `:` для их конструирования. Конкатенация списков и в прошлом SASL была единственной оптимизированной библиотечной функцией, а тут стала языковой фичей. +Да, с группами уравнений. Будто бы одной смены порядка вычисления было мало, Тернер решил еще и позаимствовать некоторые фичи NPL 75. Первым начав таким образом процесс объединения наработок Эдинбургской программы в одном языке. И этот процесс назывался бы не так, как мы его назвали в предисловии, если б Тернер не завершил его одним из последних. +Тернер был знаком с Дарлингтоном и ездил в Эдинбург пообщаться с ним несколько раз [Turn19]. Как мы помним, Сент-Эндрюс не так и далеко. Раз уж Тернер был знаком и общался с Дарлингтоном, то в своих воспоминаниях он присваивает авторство большинства идей из NPL Дарлингтону. Например уравнения с ПМ, с заимствования которых Тернер и начал. И, как мы выяснили в прошлой главе, Тернер скорее всего заблуждается, автор уравнений с ПМ не Дарлингтон. Происхождение синтаксиса SASL 75 от NPL времен работы над ним Дарлингтона сохранило для Эдинбургской программы легковесный синтаксис уравнений, в самом NPL и в произошедших позднее от него языках синтаксис уравнений перестал быть легковесным. +Если бы Тернер не заявлял прямо, что позаимствовал уравнения с паттерн-матчингом в NPL об этом было бы нелегко догадаться. Уравнения - возможно, но паттерн-матчинг? Паттерн-матчинг в SASL 76 не такой как в NPL и не такой, какой делали в языках с уравнениями. SASL ленивый, что соответствует одному из требований О'Доннела к "принципиальному подходу". С остальными требованиями О'Доннела все хуже. Функции не только нельзя применять слева от `=`, к этому мы уже привыкли. Но нет и объявляемой пользователем специальной их разновидности на этот случай - конструкторов. Мало того, паттерны перекрываются, их порядок имеет значение и они еще и нелинейные: + +``` +member () a = false +member (a : x) a = true || обратите внимание на повторяющееся `a` +member (a : x) b = member x b +``` + +Другими словами, паттерн-матчинг в SASL 76 больше напоминает ПМ в Прологе, чем ПМ в NPL 75 и прочих языках с уравнениями. Тернер не рассказывает про влияние Пролога, но если перекрытие паттернов - это то, к чему вполне можно просто прийти независимо, совмещая удобство с простотой имплементации, то воспроизведение нелинейности уже сомнительно. Потому, что не делает имплементацию проще. +Тернер, как и Уоррен считает, что ПМ принципиально лучше подхода с селекторами как в Лиспе. +Но из-за отсутствия пользовательских конструкторов ПМ получился и не как в Прологе. На матчинг не конструкторов а только списков, как в языках вроде PLANNER [Hewi09] и в матчере МакБрайда [McBr69], он похож еще меньше. +Нельзя также сказать, что матчинг в SASL 76 это шаг в сторону такого ПМ, каким он обычно бывает в современных ФЯ. Перекрытие паттернов больше похоже на обычный ПМ в ФЯ сегодня, но отсутствие пользовательских конструкторов и гардов - шаг назад по сравнению с NPL. +Не хватает не только фич NPL связанных с матчингом, нет и пользовательских операторов. SASL 76 меньше NPL, даже NPL того же года. +Но для нашего традиционного примера хватит и встроенных конструкторов списка `:` и `()`: + +```haskell +def +map f () = () +map f (a : x) = f a : map f x + +map f (1,2,3) where + f x = x + y + y = 1 +``` + +В стандартной библиотеке есть `map`, но все еще нет никаких разновидностей `fold` и `filter`. +Одна за другой определены как уравнения почти одинаковые функции вроде `length`, `sum`, `product`, `or`. Почти нет переиспользования кода с помощью комбинирования комбинаторов. Это и общая бедность библиотеки ФВП (их стало только меньше потому, что из неё исчезли комбинаторы вроде `I` и `K`) плохо совмещается с чтением книги Берджа. Но может это и понятно, когда столько нового сразу. Тернеру просто хотелось писать уравнения с паттерн-матчингом. +Но следы влияния книги Берджа заметны в другом. Например, тот самый "квиксорт": + +```haskell +sort () = () +sort (a : x) = sort m ++ a : sort n + where m, n = split a x () () +split a () m n = m, n +split a (b : x) m n = b < a -> split a x (b : m) n + split a x m (b : n) +``` + +видимо, старейший дошедший до нас вариант на функциональном языке (не на псевдокоде Берджа и не на Прологе). +Тернер не приводит решение все равно не требующей ленивости проблемы кромок. Вычислитель простых чисел в руководстве Тернера покороче, но не лучше по сути: + +``` +primes = sieve (from 2) +sieve (p:x) = p:sieve (filter p x) +filter p (a:x) = a rem p = 0 -> filter p x + a : filter p x +``` + +Зато есть вполне приличный минимальный демонстратор ленивости, который применяется и сегодня: + +```haskell +fib = 1 : 1 : map sum (zip (fib, tl fib)) +``` + +Если NPL, получив уравнения с паттерн-матчингом, в основном сохранил свое старое ISWIM-образное подмножество, то SASL был существенно переработан. Поскольку Тернер хотел сохранить язык небольшим, он не только добавил новые фичи, но и убрал из языка их старые аналоги. +Помните, как обоекембриджцы писали, что если есть `where` - то лямбда не нужна? Похоже, что нашелся первый автор языка, которого они убедили. Лямбд в SASL больше нет. Но нет и обычного для ISWIM `let`/`where` дуализма. Добавив `where`, Тернер убрал `let`. Странно, что остался тернарный оператор, хотя Тернер должен был видеть замену для него в NPL - гарды. Но не будем забегать вперед. +Как видите, новый язык имеет с SASL 75 не так много общего. Но Тернер снова назвал язык "SASL". Может быть для того, чтоб университеты, которые, по его словам, заинтересовались SASL в 75-ом году нашли не старый заброшенный язык, а новый и живой. Тернер больше не называл два существенно отличающихся языка одним и тем же именем. Следующие его новые языки не будут называться SASL, хотя отличаются от SASL 76 меньше, чем он отличается от SASL 75. +Новая имплементация SASL с помощью "прокрастинирующей" SECD написана на C для UNIX [Abra81], после Тернера над ней работал Уильям Кэмпбелл (William Campbell) [Abra81] [Coro83]. Описание вышло как неотсканированный отчет в июле 79-го. Значительная часть этой работы была проделана в Университете Сент-Эндрюса уже без участия Тернера, который перешел работать в Университет Кента (University of Kent) в январе 77-го [Turn12]. +Интерпретатор переписывали на BCPL для MTS в Университете Британской Колумбии [Abra81] и в Сент-Эндрюсе на разработанный там язык S-algol для экспериментов с эмулятором параллельной машины. Переписан он был из-за того что с кодом на C было тяжело экспериментировать [Coro83]. Код на S-algol дошел до нас и в нем меньше 3K строк. Размер кода на C был скорее всего примерно таким же, языки отличаются непринципиально. +Эта реконструкция событий не очень надежна. Почему она вообще важна? Она может определить наследие Тернера как имплементатора. Насколько ему удалось сделать ленивые ФЯ практичнее, а не только навести на мысль о том, как это сделать. К этой проблеме мы вернемся позднее. + +#### Lazy NPL + +В коде на ленивых языках из приводимых нами до сих пор примеров не хватает чего-то важного. Чего же не хватает этим рекурсивным уравнениям с ПМ? Конечно же аннотаций. +Мы уже упоминали написанную в 77-ом но изданную намного позднее работу Джеральда Шварца [Schw82] об аннотациях для NPL, которые предоставляют компилятору информацию, нужную для того, чтоб компилировать уравнения с ПМ в эффективный код. Но основная часть статьи лучше подходит к этой главе, чем к предыдущей. Потому что NPL, который Шварц расширяет аннотациями - ленивый по-умолчанию NPL. +В своей трансформационной системе для NPL Фезер рассматривает вместе с прочими "проблему телеграммы". "Проблема телеграммы" была сформулирована Питером Хендерсоном (да, одним из авторов статьи про ленивый вычислитель) и Сноудоном как упражнение по структурному программированию. В упражнении нужно разбирать поток символов неизвестной длины в поток телеграмм, для каждой из которых определяется число подлежащих оплате слов и наличие слов длиннее заданного лимита. Фезер решал эту проблему слиянием функций, как Бурсталл с Дарлингтоном решали проблему кромок. Шварц решает проблему так же, как решали проблему кромок Хендерсон с Моррисом - с помощью ленивости. Но не только. +Внимание на проблему телеграммы обратил Рейнольдс, который посчитал, что она идеально подходит для демонстрации ленивости. Он, по словам Шварца, был удивлен, что одной ленивости по-умолчанию для наилучшего решения таких задач недостаточно. "Может показаться, что вызов по необходимости всегда лучше, чем вызов по значению" - пишет Шварц. Но использование стека для уменьшения аллокаций в куче и избегание других оверхедов ленивости делают вызов по значению предпочтительнее в некоторых случаях. +Так что Шварц предлагает аннотировать способ передачи параметров. Передача параметра по значению аннотируется `f(t/value)`. Шварц рекомендует аннотировать так аккумуляторы. +Паттерн-матчинг также может быть разной степени ленивости. Если у Тернера все паттерны "неопровержимые" по-умолчанию, то в ленивом NPL - нет. Там как в Хаскеле требуется аннотация. Аналог хаскельного `where (~a,~b) =` выглядит у Шварца так: `where/lazy =`. +Шварц рассматривает проблему аналогичную известной хаскельной проблеме вычисления среднего значения списка чисел, которое материализует весь список. И для решения придумал аннотацию `/opportunity`, а также еще несколько идей, которым мало что соответствует сегодня. Вроде `/almost-tail-recursive` и `/constructor`. Шварц отмечает, что считает свой синтаксис аннотаций неудачным и одной из основных проблем своей работы. +Все это Шварц изобрел рисуя диаграммы. Потому, что ленивый NPL, судя по всему, не был имплементирован. Ленивость появилась в NPL совсем в другом виде. + +### Ни тот, ни этот: имена запретны. + +С января 1977 Тернер работает в Университете Кента. В том же году и Дарлингтон уехал из Эдинбурга, так что контакт Тернера с Эдинбургской программой временно оборвался. Тернер еще не закончил адаптировать идеи, которые узнал от Дарлингтона, но на время отвлекся от этого занятия. +В первые два семестра у Тернера было не очень много преподавательской работы, так что он решил воплотить в жизнь идею, над которой думал уже не один год [Turn12]. +В это время для имплементации передачи функций в функции, хотя-бы и только вниз по стеку, используются структуры в памяти - окружения. И уже существует широкий спектр подходов для имплементации окружений. +От наивного подхода первых интерпретаторов Лиспа со списками пар. Функция `pairlis` создает окружение, параллельно соединяя два списка: список имен аргументов и список значений, к которым применена функция, и присоединяя получившийся список пар к начальному. Функция `assoc` - предок современных функций вроде `Data.List.lookup` - работает с такими окружениями. Обходит в куче такой односвязный список пар в поисках нужного имени и возвращает первый найденный результат, который таким образом затеняет все добавленные до него [McCa62]. +И до подхода из имплементаций ALGOL 60 со статической цепью и дисплеем. Статическая цепь - это список участков стека в порядке лексического вложения, а дисплей - это массив ссылок на эти участки [Rand64]. +Имплементации SECD пока что ближе к первому представлению и улучшить их вполне можно. +Но все это давалось имплементаторам тяжело, с большим числом ошибок и небольших и концептуальных. Из-за которых видимость часто работала не так как нужно для функционального или даже хоть какого-нибудь программирования. + +#### S K + +Что если разом избавиться от всего этого? Ведь именованные переменные не нужны. Еще в ревущие двадцатые годы логики придумали, как переписывать выражения так, чтоб никаких переменных не осталось. Тернер решил испытать такой радикальный подход: заменить окружения набором констант-комбинаторов [Turn79]. +Трансляция лямбд в комбинаторы, например `S` и `K`, известна функциональным программистам в это время. Она есть как в книге Карри [Curr58], так и в книге Берджа [Burg75], хотя и не как пример способа имплементации ФЯ. Простейший алгоритм - только несколько строк кода. +Тернер называет множество плюсов такого подхода. Для исполнения получающегося в результате трансляции комбинаторного кода нужна простая машина. И эта простая машина делает довольно много. В отличие от SECD машины, она переписывает исполняемый код и работает как оптимизатор. Тернер имплементировал свертывание констант для имплементаций SASL [Turn12], но комбинаторный интерпретатор заменяет все константные выражения на их результаты при первом использовании [Turn79]. И не только константные, ведь оптимизация происходит во время выполнения. Индерекшены, которые оставляют после себя ленивые вычисления, "излечиваются" интерпретатором также автоматически. Это просто применение `I`. Сборщик мусора может копировать меньше исполняя при копировании код программы. Первый вызов функции приводит к тому, что её тело инлайнится. Абстракция бесплатна. И эта бесплатность абстракции почти бесплатна и для имплементатора. Какие-то сотни строк кода против десятков тысяч во всяких сверхсложных трансформирующих код компиляторах, делающих то же для лямбд. +И раз абстракция бесплатна, Тернер приступает к переписыванию библиотечных функций так, как завещал Бердж [Burg71] [Burg72] и ссылается на его книгу [Burg75]. Объясняет то, что не делал этого раньше тем, что в SECD имплементации у такого подхода будет слишком большой оверхэд. +Тернер описывает правую свертку, впервые с современным названием `foldr` + +``` +def foldr op a = f + where + f x = + x = nil -> a; + op (hd x) (f (tl x)) +``` + +и переписывает то, что раньше записывал как наборы рекурсивных уравнений + +``` +def sum = foldr plus 0 +def product = foldr times 1 +def all = foldr and true +def some = foldr or false +``` + +(В реальном коде на SASL76 нужен только один `def` на файл, не понятно, почему Тернер пишет так в статье [Turn79]. То, что он пишет код в статье, в основном, без использования новых синтаксических фич SASL - более понятно, но об этом позже.) +Программист может смело добавлять бесплатные слои абстракции не только потому, что решена проблема производительности. Проблема генерации кода для того, что не используется тоже решена. Ведь оптимизируется только то, что вызывается во время исполнения. Никакой генерации массы специализированного кода, который никогда не будет вызван. +Думаем, сложно переоценить, насколько уютен для имплементатора ФЯ этот локальный оптимум, в котором Тернер впервые оказался в 1977-ом году. Вернувшись туда несколько позже, Тернер так уже и не выбрался из него, в поисках чего-то получше. Не смотря на то, что продолжал имплементировать ФЯ еще долгие годы. Только один раз он использовал другой, еще более минимальный подход для совсем уж неамбициозной имплементации. + +#### S B + +Но если преобразование из ЛИ в комбинаторы - такой хороший способ имплементации ФЯ, то почему сегодня его можно встретить разве что как плагин для `lambdabot` или утилиту `pointfree`? Разумеется, не все так просто. +Даже если оптимизируется только то, что исполняется, результаты оптимизации могут быть слишком велики и может понадобиться заменить эти результаты на первоначальный неоптимизированный код в рантайме, во время сборки мусора, например. +Трансформация кода приводит к тому, что сложно восстановить из какого кода произошли те комбинаторы, в которых обнаружилась ошибка в рантайме. И как выглядел бы стек без этих трансформаций. Тернер пытается все это сделать, но считает, что скоро это будет не так и нужно. Почему? Тернер хочет добавить в SASL проверку типов. Уже 77-ой год, LCF/ML работает, проблема типизации ФЯ в принципе решена. Сейчас Тернер в своих воспоминаниях говорит, что у проверки тегов во время выполнения могут быть плюсы [Turn12] [Turn19], но в 77-ом он считает, что "почти все" программы которые выдают ошибки в рантайме после добавления проверки типов будут выдавать ошибки компиляции [Turn79]. Компилируется - работает! +Но, наверное, после того, как так удачно избавился от страданий с окружениями, сложно заставить себя снова начать страдать с ними уже ради тайпчекера. Так что типы в SASL не появятся, и вообще появятся в языках Тернера только ближе к середине 80-х годов. +Но даже с той самой трехстрочной трансляцией лямбд в комбинаторы не все так просто. Еще Тернеру и его предшественникам понадобилось решить ряд проблем. Если ограничиваться минимальным набором комбинаторов вроде `S` и `K`, то комбинаторный код будет намного больше, чем лямбды и быстро расти в зависимости от размера лямбд. Проблема проявляет себя даже на крошечных примерах, в результате ей занимались еще логики. И книга Карри [Curr58] содержит одно из решений, которое использовал Тернер. Нужно добавить два комбинатора `B` и `C`, они же `(.)` и `flip`. Версии комбинатора `S`, в которых один из параметров принимает константу. И четыре правила оптимизации, которые преобразуют специальные случаи применения `S`. Оптимизацию можно совместить с преобразованием из лямбд, так что плохой код огромного размера не существует на промежуточных этапах. Имплементация становится сложнее, но увеличивается только на несколько строк. И качество кода становится существенно лучше. +Еще один комбинатор, который нужно добавить - `Y` и имплементировать его так, чтоб создавалась циклическая ссылка. Имплементация через `S` и `K` и правила редукции как в учебнике приводят к плохой производительности. Если имплементировать всю рекурсию как `Y`, не создающий циклической ссылки, то можно управлять памятью только с помощью счетчиков ссылок, сборщик мусора не нужен. Но Тернер считает, что это только повредит производительности. + +#### S' S + +Наработок из книги Карри все еще недостаточно, чтобы получать такой компактный код, как у SECD. В реальном коде множество связанных переменных и по мере того, как Тернер удаляет их одну за одной, комбинаторный код растет: + +```haskell +-- убираем первую и из +S a1 b1 +-- получаем +S(B S a2)b2 +-- избавляемся от следующей +S(B S(B(B S)a3))b3 +-- и еще одной +S(B S(B(B S)(B(B(B S))a4)))b4 +``` + +Растет "как минимум квадратично". +Тернер решил проблему добавив еще комбинаторов и правил переписывания в оптимизатор [Turn79b]. +Если добавить параметр к комбинатору `S x y z = x z (y z)` , то получаем комбинатор `S' a x y z = a(x z)(y z)`, который позволяет записывать предыдущий пример так: + +``` +S a1 b1 +S' S a2 b2 +S'(S' S) a3 b3 +S'(S'(S' S)) a4 b4 +``` +Разумеется, аналогичные версии нужны и для `B` и `C`. Предложенные Тернером `B'` комбинатор и соответствующее правило оптимизации на самом деле неправильные и делают комбинаторный код только хуже [SPJ87]. Но это будет обнаружено позже и исправлено не Тернером. +Уэлч (P. H. Welch) обратил внимание Тернера на то, что алгоритм для получения компактного кода уже придуман Абдали, одним из последних независимых изобретателей продолжений [Reyn93]. + +#### B(n,m) + +Камаль Абдали (Syed Kamal Abdali) в своей диссертации [Abda74] 74-го года и статье [Abda76] того же года, которая была опубликована только в 76-ом году, рассматривает другое решение проблемы, с которой столкнулся Тернер. +Абдали не удаляет связанные переменные из терма последовательно, одну за одной. Он делает это одним шагом. И добавляет не три комбинатора, а неограниченное количество. Точнее, три правила для генерации неограниченного количества комбинаторов `K(n)`, `I(n,m)` и `B(n,m)`. Можно создавать и создавать такие все более крупные специализированные комбинаторы, пока у вас есть для них место. А что останется без специализаций - будет работать не с бинарными деревьями применений комбинаторов к комбинаторам, а с массивами. С понятными последствиями для компактности представления, производительности и распараллеливаемости. Абдали, правда, занимался формальной семантикой ЯП, а не их имплементацией, так что никаких компиляторов и интерпретаторов не написал. Ускорять и параллелизовать было нечего. +Как и в случае Тернера, внимание Абдали также обратили на то, что уже проделана серьезная работа по решению проблем, которыми он занимался. В случае Абдали это сделал лично Хаскель Карри. Карри указал ему на то, что публиковал собственный алгоритм который работает с несколькими переменными одновременно в 33-ем году. В книгу 58-го этот алгоритм просто не попал. Вероятно потому, что для логиков эта проблема была не так актуальна, как для программистов. Абдали все равно опубликовал алгоритм потому, что его версия проще. Какие же оправдания были у Тернера? +Один из имплементаторов ФЯ и будущих героев нашей истории Стюарт Рэй (Stuart Charles Wray) позднее считал, что подход Абдали позволил бы генерировать более компактный и быстрый код, чем подход Тернера [Wray86]. И даже сам Тернер был согласен с утверждениями о компактности [Turn79b]. Но необходимость в большом количестве комбинаторов и необходимость в сложно устроенных комбинаторах для тех случаев, которые не покрываются специализациями Тернер считал проблемой. Слишком сложной будет машина, которая исполняет комбинаторный код. Почему он считал это важным - отдельная история. Но когда эта история более-менее закончилась, и Тернер и другие имплементаторы ФЯ будут делать эти сложные комбинаторы уже другими способами. Идеи Абдали для имплементации ФЯ так и не используют [Wray86]. Это не тот путь, по которому пошли реальные имплементаторы ФЯ, которые хотели улучшить Тернеровский комбинаторный подход. + +### SASL 79 + +[Turn79] + +Тернер сравнил имплементацию строгого SASL с помощью SECD, имплементацию ленивого SASL с помощью ленивой SECD и новую имплементацию SASL с помощью SK-машины на нескольких сотнях строк бенчмарков. +Бенчмарков? Да, мы не забыли упомянуть о такого рода исследованиях для PAL, LCF/ML и NPL. Их просто не было. Тернер один из немногих и из первых имплементаторов Эдинбургской программы, который этим занялся и даже что-то опубликовал. +Благодаря Тернеровским `S' B' C'` комбинаторный код в памяти примерно в два раза меньше SECD-кода. Со сравнением производительности этого кода получилось уже не так хорошо. +Тернер пишет, что не может сравнивать скорость имплементаций непосредственно. Имплементации написаны на разных языках, для разных ОС и машин. В статье о комбинаторном интерпретаторе [Turn79] Тернер не пишет на каких языках и для каких ОС и машин. Про энергичный SECD SASL мы знаем, что он написан на BCPL [Turn12]. Но есть свидетельства, что обе имплементации ленивых SASLов на C для UNIX. И на прокрастинирующей SECD [Abra81] и комбинаторная [Turn83] [Bund84]. Поэтому странно, что Тернер сравнивает их между собой так же, как сравнивает их со строгим SASL. +Комбинаторной машине нужно больше шагов редукции, чем строгому SECD но Тернер считает, шаг комбинаторной занимает меньше времени. Поэтому Тернер применяет тот же способ оценки, какой применяют разработчики GHC и сегодня: сравнивает аллокации. На микробенчмарках в которых мало применений функций SK-машина аллоцирует раза в два больше, чем SECD, а на тех, в которых в основном только функции и применяются наоборот - SECD аллоцирует в два раза больше. Сравнение со строгой SECD Тернеру интереснее и он в итоге заявляет, что переписывание графов сочетает "безопасность нормального порядка" с "эффективностью аппликативного порядка" +[Turn79]. Под безопасностью тут понимается, что нормальный порядок доредуцирует до нормальной формы, а аппликативный - как повезет. +Сравнению с прокрастинирующей машиной уделяется меньше внимания, но отмечается, что если эффективность аппликативного порядка если и не достигается SK-машиной во всех случаях, то уж точно достигается лучше, чем прокрастинирующей SECD-машиной, которая аллоцирует в десять раз больше, чем строгая. +Но если первая имплементация ленивого SASL настолько хуже, почему Харви Абрамсон портировал именно её? Годы спустя Саймон Пейтон Джонс [SPJ82] имплементировал упрощенные версии всех трех машин на одном языке (BCPL). Специально для того, чтоб произвести замеры размеров кода и аллокаций в куче и на стеке. И разница между машинами оказалась гораздо меньше. Но нам интереснее сравнение реальных имплементаций, которое запланировано в отдельной главе. +Почему годы спустя? Потому, что статьи Тернера про улучшенный алгоритм трансляции из лямбд в комбинаторы и про имплементацию SASL с помощью комбинаторов были получены издательством соответственно в октябре и декабре 1977-го, а опубликованы только в 1979-ом году. В августе того же 1979-го года вышла ревизия руководства по SASL 76 для того, что Тернер называет ""комбинаторная" версия", а мы будем называть SASL 79. Имплементация изменилась существенно, но изменения в языке незначительны - добавлены только числа с плавающей точкой. +Если в 1976 более-менее законченный вид принял язык, и прочие версии были только редакциями, то в 1979 это произошло с библиотекой, версия библиотеки 83-го года, будет только редакцией. +Правда, эта версия 79-го года до нас не дошла, только отредактированная в июле 84-го [Turn83]. Так что, хотя большая часть изменений должны были быть сделаны до августа 79, какие-то из них появились на годы позже. +В стандартной прелюдии SASL появляется `filter` с современным названием: + +``` +filter f () = () +filter f (a:x) = f a -> a:filter f x; filter f x +``` + +А также `foldr`, который написан как и в статье [Turn79] с вложенной функцией, у которой на один аргумент меньше, но с использованием группы уравнений: + +``` +foldr op r + = f + WHERE + f () = r + f (a:x) = op a(f x) +``` + +И некоторые функции написаны или переписаны как частичные применения `foldr`: + +``` +some = foldr or FALSE +``` + +Но некоторые, как `map`, так и остались рекурсивными уравнениями. +Конечно, не только `foldr` используется для имплементации библиотечных функций. Функция `member`, которую мы использовали выше для демонстрации паттерн-матчинга в SASL 76, теперь написана так, что ПМ уже не продемонстрируешь: + +``` +member x a = some(map(eq a)x) +``` + +Появляется и `foldl` с современным названием, но не современного вида. + +``` +foldl op r () = r +foldl op r (a:x) = foldl op(op a r)x +``` + +Не тот порядок аргументов у `op` что сейчас. Как у Берджа [Burg72], чтоб можно было писать `reverse` так: + +``` +reverse = foldl cons () +``` + +Но число и порядок аргументов уже не как у Берджа. +Функция `length` имплементирована как нехвосторекурсивные уравнения, а не с помощью `foldl`. Но `foldl` ленивый, так что может это не так и странно. +`I` и `K` снова в библиотеке. +На SASL 79 заканчиваются интересные результаты Тернера как имплементатора, но основные его результаты как разработчика ФЯ еще впереди. + +### Рекурсивный Калькулятор и Декембриджизация. + +> Я пытался изобрести функциональный эквивалент языка BASIC +> Дэвид Тёрнер [Turn19] + +Раз уж Тернер теперь работает в университете Кента, начав разрабатывать новый язык, он снова назвал его по месту работы - KRC (Kent Recursive Calculator). Но это последний язык Тернера, который он назвал по месту работы. + +#### Прибавляем + +Как мы помним, остается еще много того, что Тернер должен был увидеть в NPL 75 и может позаимствовать оттуда. Чем Тернер и занялся. +Одним новым заимствованием из NPL стали гарды, выглядевшие в NPL 75 так: + +``` +f x <= r1 if p + <= r2 otherwise +``` + +Тернер сделал синтаксис легче + +``` +f x = r1 , p + = r2 +``` + +Это, по всей видимости, рекорд легкости синтаксиса гардов. +И Тернер использовал эту очень легковесную синтаксически фичу для того, чтоб писать больше кода. Почему? +Возможно, Тернеру было несколько некомфортно делать все эти большие шаги в сторону современного вида уравнений с ПМ. И современный их вид - это не то, что имело широкое признание в узких кругах тех, кто вообще знал про паттерн-матчинг в 70-х. Может быть, Уоррен посчитал такие шаги разумными, но мало кто еще. И нет особых свидетельств того, что Тернер с Уорреном знали о том, как они могли бы быть друг с другом согласны. +К счастью, Тернер не стал запрещать перекрытие паттернов или делать гарды обязательными. Он стал дописывать [Turn81] гарды в некоторые примеры, которые он использовал для демонстрации краткости кода. + +``` +A 0 n = n + 1 +A m 0 = A (m-1) i, m>0 +A m n = A (m-1) (A m (n-1)), m>0&n>0 +``` + +И явно отмечать, что эти гарды дописаны для того, чтоб уравнения можно было переставить в другом порядке. То есть стал писать как на SCRATCHPAD. +В ФП этот подход, конечно, не прижился. На что Пролог мог повлиять более чем одним способом. Так что, когда мы сегодня видим один из бесчисленных примеров функции Фибоначчи, то в нем нет гарда в третьем уравнении, а у Тернера какое-то время был: + +``` +fib 1 = 1 +fib 2 = 1 +fib n = fib (n-1) + fib (n-2), n>2 +``` + +Но Тернер вскоре перестал писать эти необязательные гарды. Тем более, что сделать с их помощью переставляемыми уравнения с матчингом чего-то кроме чисел уже совсем не так легко. +Паттерн-матчинг в KRC приблизился к современному виду ближе, чем в каком-то другом ЯП 70-х. Если, конечно, забыть о том, что нет пользовательских конструкторов. Ну, по крайней мере максимально приблизился в деталях, если не в главном. +Другим новым заимствованием из NPL были конструкторы множеств, выглядевшие в NPL так: + +``` +<: f(x) : x in S & p(x) :> +``` + +Тернер называет их ЦФ-выражениями (ZF expressions). Их синтаксис Тернер тоже сделал легче: + +``` +{ f x | x <- s ; p x } +``` + +или + +``` +{ f x ; x <- s ; p x } +``` + +Тернер, судя по коду примеров [KRC81], начал с `;`, потом перешел на `|`, а затем обратно на `;`, когда обнаружил конфликты парсинга с операцией "или". Но поддержка `|` в языке осталась. +ЦФ-выражения работают со списками, а не с множествами, имплементированными как списки, как в NPL. Т.е. дубликаты элементов не удаляются крайне неэффективным способом. Но для конструирования таких множеств крайне неэффективным способом есть функция `mkset`. Раз уж работа идет со списками, можно имплементировать стандартные списочные функции с помощью новой фичи, что и сделано [KRC81]: + +``` +filter f x = {a|a<-x;f a} +``` + +Но функция `map` в прелюдии имплементирована не так. Кстати, обратите внимание, что больше никаких `DEF`, даже и одного на целый файл. +Первоначально, `filter` можно было записать проще: + +``` +filter f x = {a<-x;f a} +``` + +Просто повторять `a` перед `|` было не нужно, раз уж генератор единственный и к `a` ничего не применяется. Но это упрощение для особых случаев было убрано из языка, хотя использующий его код остался в примерах [KRC81]. +После этого изменения ЦФ-выражения стали, на первый взгляд, предельно близки к современным лист компрехеншонс. Если не считать мелких синтаксических деталей вроде `;` и того, что скобки не совпадают со скобками для списков, которые в KRC квадратные, как в LCF/ML и NPL, а не как в SASL 76/79. Но это только на первый взгляд. +Тернер не слышал о SETL до 80-х годов. Тернер уверен, что и Дарлингтон не слышал, и потому считает, что конструкторы множеств в NPL изобретены Дарлингтоном независимо [Turn19]. Но Дарлингтон не изобрел как сделать так, чтоб они хорошо работали. Вероятно, это можно посчитать доводом в пользу того, что Дарлингтон изобрел их независимо и от авторов SCRATCHPAD. +Обычный конструктор множества - это как проблема кромок. И если Дарлингтон пытался решать и то и другое трансформацией и решение не заработало, то Тернер решил, как решал Моррис - с помощью ленивых списков. +Но Тернер прилагает больше усилий, чтоб починить протокомпрехеншоны Дарлингтона, чем прилагают более поздние их имплементаторы: + +``` +krc> take 4 {[a,b]|a <- ["True","False"];b <- [1..]}? +[["True",1],["False",1],["True",2],["False",2]] +``` + +Результаты генераторов чередуются. В отличие от Хаскеля: + +``` +ghci> take 4 [(a,b)|a <- [True,False],b <- [1..]] +[(True,1),(True,2),(True,3),(True,4)] +``` + +`False` никогда не появится. Как авторы языков спецификации пытались сделать больше неработающего сегодня кода с паттерн-матчингом работающим, так и Тернер пытался сделать больше неработающего сегодня кода с лист-компрехеншонами работающим. +Обратите, также, внимание на современную нотацию для получения списков чисел в KRC. `[a,b..c]` тоже работает. +Но паттерн-матчинг слева от `<-` не работает, как и в SCRATCHPAD и в NPL. `i,j <- xs` означает не ПМ, а то же, что `i <- xs; j <- xs`. В SETL же к этому времени можно было хотя бы разбирать туплы: `{y : [x,y] in xs | x /= 0}` [Dewa79]. +Как раз в то время, когда Тернер исправил конструкторы множеств, сделав из них ЦФ-выражения, из новой версии NPL их убрали. Тернер считает, что убрали потому, что со строгими списками они бесполезны. Но это говорит только о том, что после потери контакта с Эдинбургской программой Тернер не особенно внимательно следил за NPL. Конструкторы множеств появились в NPL потому, что там не было ФВП. И исчезли скорее всего потому, что ФВП там появились. +Но если в NPL конструкторы множеств были потому, что там не было ФВП, то зачем они в KRC, в котором ФВП есть? + +#### Отнимаем + +Как и в случае с SASL 76, если Тернер что-то добавляет, то считает нужным что-то и отнять. +Или передумать и не отнимать. Раз есть гарды - нелинейные паттерны не так и нужны и больше не используются: + +``` +assoc ([a,b]:x) a' = b, a = a' + = assoc x a' +``` + +Бывший пользователь нелинейных паттернов, функция `member`, обошлась и без гард. + +``` +member [] a = "FALSE" +member (a:x) b = a = b | member x b +``` + +Наверное, нелинейные паттерны не попали в KRC? Нет, они там есть. Почему же они не используются? Трудно сказать. Может быть их сначала не было, может быть Тернер только собирался их убрать. По той или иной причине, код написан так, как будто их нет. +Разумеется, другие удаления фич вполне состоялись. Еще как! +KRC - одно из самых смелых и радикальных высказываний Тернера как разработчика языков программирования. NPL 75 и SASL 76, получив уравнения с паттерн-матчингом, сохранили и какие-то конструкции-выражения, позаимствованные у обоекембриджцев. Так и не стали языками уравнений в чистом виде. Мы писали в прошлой главе про языки Эдинбургской программы как смешение ISWIM и NPL. Но такое описание портит то, что NPL сам по себе смешение ISWIM с языком уравнений. +Тернер, победив в SASL 76 `let` и `lambda`, не остановился на достигнутом. В KRC нет тернарного условного оператора. Понятно, что он не нужен - есть же гарды. Это довольно логично. +Что действительно впечатляет, так это то, что в KRC нет выражения `where`. Да, ни `let`, ни `where`, ни лямбд. Не все так ужасно, как может показаться на первый взгляд: функции объявляются каррированными и их частичное применение делает ФП более-менее возможным, но, как, например, в POP-2 не особенно удобным. + +``` +map f [] = [] +map f (a:x) = f a:map f x + +f y x = x + y + +{map (g y) [1..3]|y <- [1]} 1? +``` + +Все труды имплементатора, которые нужны для поддержки лямбд, `let` и прочего нужны и для имплементации вот этого вот. Вложение областей видимости возможно, просто должно выглядеть ужасно. Потому, что не должно быть двух фич, которые делают одно и то же (если только это не нелинейные паттерны). +Так что ЦФ-выражения нужны в KRC не намного меньше, чем в NPL. + +``` +{x + y|y <- [1]; x <- [1..3]} +``` + +Разумеется, `let` нет и в ЦФ-нотации. +KRC легко сочетает красоту одного из самых легких ФП-синтаксисов с неожиданным уродством, появляющимся, когда захочется воспользоваться возможностями, поддержка которых для ФЯ, казалось бы, сама собой разумеется. Понятно, даже и Тернеру, что декембриджизация зашла слишком далеко и куда-то не туда. +И Тернер сделал шаг назад, добавил ЦФ-нотацию в SASL, но не перенес в SASL более радикальные идеи из KRC. +Позднее Тернер все-таки придумал, как сделать язык с вложенными функциями в стиле уравнений, а не ISWIM. Не знаете ни одного современного языка в котором ушли от лямбд и `let`? Да, это направление ухода от синтаксиса выражений к синтаксису уравнений было тупиковым. Но движение в тупик не было напрасным, ведь в этом тупике Тернер нашел свое ключевое синтаксическое изобретение, определившее вид современных ФЯ. Но это уже совсем другая история. + +#### Что если даже SK-машина - это слишком сложно? + +KRC имплементирован не с помощью SK-машины, а способом, который обычно предшествует каким-то "машинам" - переписыванием абстрактного синтаксиса в памяти [Hugh83]. В данном случае не дерева, а графа, так что имплементация не самая медленная из возможных, но одна из самых медленных. Преобразования при компиляции в SK-код и его оптимизации простые. Но не проще, чем их, преобразований, отсутствие. +Равнодушие к производительности, видимо, может мотивировать высокоуровневую имплементацию стандартной прелюдии не хуже чем "бесплатность" абстракции. По крайней мере функции в ней часто определены с помощью ФВП, таких как `foldr`, которая называется просто `fold`: + +``` +fold op s [] = s +fold op s (a:x) = op a (fold op s x) +``` + +Функции `foldl` нет. Возможно, она не нужна, если не заботиться о производительности. Хотя сомнительно, что кто-то измерял производительность `foldl` в прелюдии ленивого SASL. Потому, что это ленивый `foldl`. +Тернер имплементировал KRC на BCPL для EMAS ОС на ICL 2960 с ноября 79 по октябрь 81-го [KRC2016]. Эта имплементация использовалась в Университете Кента для преподавания с 80-го до 86-го года и в Оксфорде в начале 80-х [Turn16]. +Но почему вдруг такая простая имплементация и снова на BCPL, на котором Тернер ничего не имплементировал начиная с SASL 76. И почему для преподавания не использовался SASL 79, который сам появился как язык для преподавания? Об этих причинах Тернер ничего не рассказывает в своих воспоминаниях. Так что нам остается только разобраться, что это за EMAS. Поскольку у EMAS не особенно много пользователей, история операционной системы EMAS описывает и историю её использования в Университете Кента. +Университет Кента владел мэйнфреймом ICL 2960 c 1976 по 86. Это была младшая машина линейки со специальной версией ОС VME, которая нормально не работала. Падала чаще раза в день. К тому же, производитель компьютера решил еще и урезать ее функциональность в 79-ом году. В результате, в Университете Кента решили перейти на разрабатываемую в Эдинбурге ОС для этой линейки - EMAS (Edinburgh Multi Access System) [Eager]. Что и было сделано в декабре 79-го [Eage22]. В отличие от VME, для EMAS существовал компилятор языка, на котором Тернер умел писать код - BCPL. +Так что, когда заработал компьютер, который использовали для преподавания, Тернер проскочил в образовавшееся окно возможностей со своим быстро и просто имплементированным "функциональным Бейсиком". В 85-ом году, не задолго до перехода с ICL 2960 на машину, на которой можно было использовать UNIX и C, KRC был переписан как SK-интерпретатор на C Саймоном Крофтом (Simon Croft) [Turn16]. Все сходится! +Имплементация KRC на BCPL Тернера - первая его имплементация, код которой дошел до нас [KRC81]. В 2016 Тернер портировал её на C [KRC2016]. Эта имплементация компилируется и работает в момент написания этого текста и использовалась для того, чтоб подтвердить или опровергнуть некоторые наши гипотезы о KRC. Но использовать её для этого нужно с осторожностью. Например, Тернер зачем-то поменял при портировании индексацию списков. В KRC 81-го года она начинается с единицы, а в KRC 2016 - с нуля. Нельзя полностью исключить, что он и нелинейные паттерны имплементировал в 2016-ом году. + +### Все ещё нет? + +К концу 70-х Тернер создал уже целое семейство ленивых ФЯ с несколькими имплементациями и несколькими пользователями. Что же с функциональным программированием, появилось ли оно? +В 83-ем году более известный другими своими работами Саймон Пейтон Джонс решил, что ему известно очень мало программ даже "среднего размера" на функциональных языках. А таких программ на имплементированном Тернером ленивом SASL не известно вовсе. Так что Саймон Пейтон Джонс написал [SPJ85] на SASL генератор парсеров в 835 LOC. Для сравнения, его программа той же функциональности на BCPL имела размер в 1501 LOC. +И с годами, по-видимому, такие программы не стали доступны и известны даже тем, кому они должны были быть интересны. Потому, что даже в 87-ом году обсуждаемый генератор парсеров - самая большая программа в наборе бенчмарков [Hart88] для очередной имплементации SASL. Получается, что не смотря на намного большую распространенность и время жизни, не известно о существовании открытых программ на KRC и SASL 76-83 даже такого размера, как IDEA на SASL 75. +Ну, хотя-бы Тернер наконец защитил диссертацию о комбинаторной имплементации SASL, которая напечатана в 81-ом году и тоже не отсканирована, как и диссертация Вадсворта, с рассказа о которой мы начали эту главу. + +------------------------------- + +Хотя SASL и KRC - самые успешные и важные для нашей истории ленивые языки на рубеже 70-х и 80-х, они не единственные ленивые языки этого времени. Упоминаемые нами в прошлой главе, язык уравнений О'Доннела и гибрид TEL с Прологом под названием FPL [Levi82] также ленивые языки. Но разработчики ленивых языков в это время совсем не так хорошо связаны как разработчики языков спецификации, не смотря на существенное пересечение этих групп. Так, О'Доннел даже в 1984 году пишет [O'Do84], что не знает никаких ленивых языков, кроме своего языка уравнений. Не знает даже о языке SASL, на который ссылается Бурсталл в статье, на которую О'Доннел ссылается сам. +Очередное подтверждение того, что стоит с осторожностью делать выводы о том, прочел ли ссылающийся на статью эту статью. И с еще большей осторожностью к выводам о том, что сославшийся на статью ознакомился с тем, на что ссылаются в этой статье. +Но что это за статья Бурсталла, в которой ссылаются на SASL? +Сейчас, когда мы закончили рассказывать историю всех составных частей для сборки функционального языка Эдинбургской программы, как он был определен в предисловии, пришло время рассказать о первой попытке собрать такой язык, а вместе с этим и подвести итоги 70-х годов. + +Надежда умирает последней +-------------------- + +> A very high-level language such as HOPE pays penalties of inefficiency because it is remote from the machine level. It could be thought of as a specification language in which the specifications are 'walkable' (if not 'runnable') +> Р. М. Бурсталл, Д. Б. МакКвин, Д. Т. Саннелла. HOPE: экспериментальный аппликативный язык [Burs80] + +Первым к сборке ФЯ из подготовленных трудами Эдинбургской программы деталей приступил Дэвид Тернер еще в 1976. Но эта его попытка затянулась почти на десятилетие, так что лучше мы начнем с того, кто первый закончил такую попытку. А это сделал Бурсталл с двумя новыми соавторами. +В 1979-ом году Бурсталл сделал очередной программный доклад на конференции [Burs79]. Доклад назывался "Аппликативное программирование" и текст этого программного доклада не сохранился так же, как и текст предыдущего. Но сохранилась аннотация. В докладе Бурсталл рассказал о преимуществах и недостатках аппликативного программирования, которое "также называется непроцедурным или функциональным", происходит от чистого Лиспа и описано в книге Берджа [Burg75]. Бурсталл записал в это функциональное программирование Пролог и все логическое программирование вообще, а также анонсировал новый аппликативный язык - HOPE, с которым он в данный момент экспериментирует. +Язык назван в честь адреса группы экспериментального программирования, располагавшейся на Хоуп Парк Сквер (Hope Park Square) [Ryde82] [Ryde2002]. Здание группы экспериментального программирования располагалось рядом с парком и Хоуп - фамилия организатора осушения земли, на которой парк был устроен. Но HOPE последовательно записывается заглавными буквами. Возможно, что это еще и акроним? Мы не видели расшифровок в статьях того времени и в более поздних воспоминаниях авторов, но есть работа по истории ЯП [Pigo95], ссылающаяся на недоступные в электронном виде источники, в которой расшифровка есть. HOP означает "Higher Order Parameters", а `E`, в таком случае, видимо, означает Extension? Звучит правдоподобно для названия ФВП расширения NPL. +Описания первых двух версий HOPE опубликованы как отчет Эдинбургского университета 80-го года и его редакция от февраля 81-го. Но эти отчеты недоступны, так что о развитии от HOPE 80 до HOPE 81 мы будем как обычно судить по статьям [Burs80] [Burs80b] и описаниям в приложениях к диссертациям [Ryde82] [Sann82]. +Бурсталл работал над HOPE с двумя соавторами. Один был имплементатором языка, а другой - одним из первых пользователей. + +### Дэвид МакКвин + +Третий основной соавтор Бурсталла 70-х годов - Дэвид МакКвин (David MacQueen) - работал в Эдинбурге с мая 75-го года [MacQ15]. МакКвин - один из самых важных авторов и имплементаторов функциональных языков. И уж точно самый важный из тех, про которых (на момент написания этого текста) нет статьи в Википедии. +МакКвин закончил Стэнфордский университет в 68-ом, защитил диссертацию в МТИ в 72-ом. Работал научным сотрудником (Research Fellow) в Университете Эдинбурга 1975-79 [MacQueen]. +Сначала МакКвин поработал над Эдинбургской имплементацией POP-2 для PDP-10 - WPOP [Slom89]. Но года с 78-го [MacQ15] или даже с 77-го [MacQ20] он стал работать вместе с Бурсталлом над HOPE, новой версией NPL [Feat79], следующей после той, которую мы тут называем NPL 79. Или, может быть, существующей с ней параллельно. +Дело в том, что NPL 79 начисто отсутствует в воспоминаниях МакКвина. И это самая неподходящая версия NPL чтоб вот так пропасть из памяти. Потому, что она оставила больше всего следов. Многие версии NPL не особенно запоминающиеся, но на NPL 79 написан какой-то код. И версия важная потому, что первая, в которой появляются АлгТД более-менее современного вида. Выпадение такого важного достижения из истории, конечно, не может остаться для неё без последствий. Поэтому, если у нас HOPE - первая завершившаяся попытка собрать ФЯ Эдинбургской программы из деталей отработанных в протоэдинбургских протоязыках, то в исторических работах МакКвина [MacQ15] [MacQ20] HOPE - это язык, впервые испытавший одну из важнейших таких деталей. +МакКвин помнит про один из последних NPL-ей (NPL 75 или NPL 77?), в которых еще были отдельные конструкторы, вместо привычного синтаксиса для объявления АлгТД. И рассказывает теперь, что "закрытые" АлгТД с BNF-образным синтаксисом появились впервые именно в HOPE [MacQ15] [MacQ20]. Возможно, что работа над NPL 79 и HOPE велась параллельно. NPL 79 был версией NPL, которую не забросили так быстро, как все прочие NPL до него. И писали на ней какой-то код только из-за отставания языка, поддерживаемого переписывателем, от языка, поддерживаемого интерпретатором. Дополнительный довод в пользу такого разветвления - то, что NPL 79 это последний интерпретатор Бурсталла. Интерпретатор следующей версии пишет в основном МакКвин. Если это разветвление NPL-линейки на две параллельные вообще было, оно было только репетицией гораздо более важного и длительного разделения NPL-линейки в 80-е. +Но никакого параллельного развития NPL и HOPE могло и не быть, и это просто история, которая должна научить нас с большей осторожностью полагаться на воспоминания о событиях, произошедших несколько десятилетий назад. Совсем не полагаться на которые мы, к сожалению, не можем. + +### Какие проблемы решали авторы HOPE? + +Мартин Фезер в своей диссертации [Feat79] перечислял недостатки NPL. Основными недостатками он посчитал отсутствие средств для абстракции данных и отсутствие ФВП. Также, Фезер писал, что для более полного воспроизведения функциональности первого переписывателя Дарлингтона и Бурсталла в NPL нужно добавить мутабельность. Если не для непосредственного использования программистом, то как примитивы, которые добавляются в процессе трансформации. Фезер знает о работах Бурсталла и МакКвина над первыми двумя недостатками: чтоб получить HOPE они добавляют в NPL абстракцию данных и ФВП. +Дэвид Тернер рассказывает [Turn19] о бесполезности конструкторов множеств в NPL, которые конструируют строгие списки из строгих списков. Тернер не единственный, кто критиковал строгие списки как контрол-структуру для связывания функций. Одним из таких критиков был в описываемое время и наш старый знакомый Рейнольдс [Schw82]. Авторы HOPE, судя по всему, знакомы с этой критикой и сами критиковали NPL за большинство этих недостатков [Burs80b]. +С какими языками из тех, в которых какие-то из этих проблем решены, они знакомы? Авторы HOPE ссылаются на ISWIM, Лисп, Пролог, ML, SASL, OBJ, SCRATCHPAD, SETL, "язык Берджа", т.е. псевдокод из книг Берджа, а не McG [Burs80]. Не беспокойтесь насчет некоторых особо радикальных инноваций из этих языков, они в HOPE не попали. Но, к сожалению, авторы HOPE не позаимствовали кое-что из того, что не помешало бы позаимствовать. Конечно, следует учитывать, что в статьях упоминается то, о чем узнали к моменту написания статьи, а не ко времени дизайна языка. +Авторы HOPE заявляют, что их цель - создать простой но мощный язык, который способствует написанию понятных и легко преобразуемых программ, с хорошими шансами избежать ошибок при их написании. По замыслу авторов, HOPE обладает мощностью Лиспа без его сложностей. Что авторы считали простым, но мощным? У них был набор идей на этот счет, и HOPE - эксперимент для их проверки. + +#### ФВП + +HOPE - первый ФЯ в NPL линейке. Наконец, мы можем написать наш традиционный пример: + +``` +dec map : (alpha -> beta) # list(alpha) -> list(beta) +--- map(_, nil) <= nil +--- map(f, h::t) <= f(h) :: map(f, t) + +map ((lambda x => x + y), [1, 2, 3]) where y == 1 +``` + +Функции высшего порядка - одна из главных составляющих "простого, но мощного языка" с точки зрения авторов HOPE. В качестве примеров таких функций авторы приводят `map`, который в HOPE называется `*` и `foldl`, который называется `**`. Эта функция произносится как `reduce` и будет иметь такое название в библиотеках будущий версий HOPE. Один из популярных сегодня вариантов названия левой свертки, который авторы HOPE заимствуют, по их словам, из APL. Обе функции, по заявлениям авторов [Sann82], в стандартной библиотеке и только пара представителей группы функций, которые широко используются в HOPE коде для того, чтоб сократить использование явной рекурсии настолько, насколько возможно. Исходный код стандартной библиотеки не дошел до нас, а дошедшего кода на HOPE недостаточно чтоб подтвердить такие заявления или опровергнуть их. + +``` +module list_iterators + pubconst *, ** + + typevar alpha, beta + + dec * : (alpha->beta)#list alpha -> list beta + dec ** : (alpha#beta->beta)#(list alpha#beta) + -> beta + + infix *, ** : 6 + + --- f * nil <= nil + --- f * (a::al) <= (f a)::(f * al) + + --- g ** (nil,b) <= b + --- g ** (a::al,b) <= g ** (al,g(a,b)) + +end +``` + +Интересно, что это один из ранних случаев, когда про правую свертку даже не вспоминают, не то что не рассматривают её как основной вариант свертки. Последовательность аргументов сворачивающей функции как у Берджа. +В HOPE не попали конструкторы множеств из NPL. Авторы HOPE не то чтобы решили, что они совсем не нужны. Они даже писали, что собираются их имплементировать [Burs80]. Просто пока не дошли руки. Не нужны срочно. И мы полагаем что потому, что в отличие от NPL, в HOPE есть ФВП. +Тернер считает [Turn19], что авторы HOPE от конструкторов множеств отказались из-за их бесполезности при работе со строгими списками. Проблема гипотезы Тернера в том, что списки в HOPE ленивые. +Авторы HOPE считают, что ленивые списки - это основная полезная контрол-структура, которую дает ленивость и больше ничего и не нужно. Мечты Шварца [Schw82] о ленивом NPL пока что, в основном остались мечтами. В HOPE добавили только один ленивый конструктор `lcons`, который конструирует ленивые списки, тип которых тот же, что и у строгих. И паттерн `x :: xs` матчит не только энергичный `::`, но и `lcons(x,xs)`. +Ленивый `map`: + +``` +typevar alpha, beta +dec <*> : (alpha->beta)#list alpha -> list beta +infix <*> : 6 + +--- f <*> nil <= nil +--- f <*> (a::al) <= lcons(f(a),(f <*> al)) +``` + +Полиморфизм, функции высшего порядка и ленивые списки позволяют писать настолько обобщенный код, насколько возможно, считают авторы HOPE. Использовать готовые комбинаторы вроде map и fold проще, чем циклы и рекурсию. +Авторы не посчитали, что в ФЯ лямбды не нужны, так что лямбды появились, но не как в LCF/ML, а более похожие на NPL. Т.е. с несколькими (если нужно) кейсами, не каррированные, с тяжелым синтаксисом + +``` +lambda true,p => p + | false,p => false +``` + +С этого, по видимому, начинается история двух видов лямбд в функциональных языках: один вид лямбд с каррингом, второй - без карринга, зато с несколькими кейсами ПМ, если нужно. В некоторых языках будут только первая разновидность, как в Haskell 98. В некоторых - только вторая, как в Standard ML, а в некоторых обе разновидности как различные конструкции, как в OCaml и в GHC Haskell после добавления `\case` [GHC23]. +Интересно, что разделители между кейсами в лямбдах HOPE не такие, как между уравнениями. В лямбдах тот разделитель кейсов и уравнений, который со временем станет самым популярным. В тех языках, которые произошли от NPL 79 с такими специальными разделителями, а не от NPL 75, в котором специальных разделителей для таких случаев нет, используются те же разделители строк, что обычно. +И, если посмотреть на код на HOPE, начинаешь подумывать, что немного борьбы с лямбдами бы не помешало. +`let` и `where` в HOPE это не конструкции для объявления функций как в ISWIM и LCF/ML, а конструкции только для матчинга как `where` в NPL. +Нельзя писать: + +``` +let function(x) <= ... +``` + +По крайней мере так не пишут в дошедшем до нас коде. Пишут так: + +``` +let function == (lambda x => ...) +``` + +не смотря на то, что матчинг конструктора в `let` + +``` +cons(x,y) == ... +``` + +и декларация функции + +``` +--- func(x,y) <= ... +``` + +отличаются синтаксически. +Нет легкого синтаксиса для объявления каррированных функций как в LCF/ML и SASL. Остается только использовать лямбды: + +``` +typevar alpha,beta,tau +dec compose : (alpha->beta)#(beta->tau) + -> (alpha->tau) +--- compose(f,g) <= lambda x => f(g(x)) +``` + +Вся эта тяжесть не от того, что на HOPE не писали кода в котором много использования первоклассных функций. Наоборот, на HOPE написали код в котором такого использования больше, чем в чем бы то ни было до того. Пример [Ryde82]: + +``` +dec monadic_signature : Signature(Tag alpha) -> + M_Signature(Set(Tag alpha),Set_Mor(Tag alpha)) +--- monadic_signature(Opns,mor(_,arity,_),Sorts) <= + let C & cat(_,_,id,_) == cat_of_sets in + let omap == ! object part of functor + (lambda S => ! S is a set of variables + let indexed_set_of_terms == + ! set of terms indexed on operations + (lambda rho => + let string(l1) == arity(rho) in + (lambda l => string(rho::l)) + * lists(length(l1)-1)(S)) * Opns in + let set_of_terms == + ! either pinked variables or terms + ! of depth one + (pink*S) U total_union(indexed_set_of_terms) in + set_of_terms) in + let mmap == ! morphism part of functor + (lambda mor(s,f,t) => + let f1 == (lambda + pink(s) => pink(f(s)) + | string(rho::l) => + string(rho::(f*l))) in + mor(omap(s),f1,omap(t))) in + let Sigma == functor(omap,mmap) in + let sigma == ! the natural transformation + nat_transform(I(C), + (lambda S => + mor(S, + (lambda x => pink(x)), + omap(S))), + Sigma) in +( Sigma, sigma ) +``` + +И двое из трех программистов писавших этот код - Бурсталл и Саннелла - авторы HOPE. Но не имплементаторы. Так что, либо им хотелось вот это все писать, либо к тому времени, когда возникли какие-то пожелания по итогам первого опыта использования, основные имплементаторы уже над этой имплементацией не работали, либо имплементаторов HOPE - МакКвина и Леви - не так-то просто было заставить что-то имплементировать. +Но можно. Упоминания и примеры использования некоторых фич отсутствуют в материалах, опубликованных в 80-ом году [Burs80] [Burs80b]. Но присутствуют в материалах 81-82гг. [Sann82] [Ryde82]. Можно предположить, что отсутствуют не просто потому, что их не посчитали важным упомянуть, а потому, что они появились в HOPE не сразу. Были добавлены из-за того, что код, который писали первые пользователи HOPE, состоял в основном из ручного перекладывания и распаковывания словарей: + +```haskell +let C & cat(_,_,id,_) == cat_of_sets in ... +``` + +Первая фича - паттерны, матчащие все что угодно. В LCF/ML они записывались `()` [Gord79], но в HOPE имеют современный вид: `_`. В Прологе такие паттерны современного вида появились, по видимому [Warr78], раньше. +Вторая фича - `@`-паттерны, они же `as`-паттерны. В HOPE - "многоуровневые паттерны" с `&` вместо современного `@`. Это больше похоже на серьезную инновацию. +HOPE часто называют чисто-функциональным языком (Бурсталл называет такие языки "аппликативными"). Оператор присваивания в нем отсутствует. Авторы считают, что это существенное упрощение языка, одна из основных идей, которую они хотели проверить. Отсутствует в HOPE и ввод-вывод. Второе это больше недоработка, чем принципиальное решение. Ленивыми списки сделали, в числе прочего и для того, чтоб организовать ввод-вывод сохранив чистофункциональность языка. +Как же HOPE обходится без всего этого? Пожелания Фезера о добавлении мутабельности не были воплощены в жизнь? Ну, не совсем. +HOPE обходится без всего этого по той же причине, по которой Гордон посчитал, что мутабельные ссылки в LCF/ML не нужны [Gord79]. Да, как в LCF/ML можно имплементировать функции на императивном языке. В случае HOPE этот язык - POP-2. Вот такое вот упрощение языка отсутствием присваивания. В HOPE есть стандартные функции с побочными эффектами, например выводящие текст в терминал и генерирующие новые имена, разные при каждом вызове. Саннелла пишет, что технически все это делает HOPE неаппликативным языком [Sann82]. Так что этот эксперимент с ссылочной прозрачностью пока что не увенчался успехом. +Поскольку HOPE не транслируется в POP-2, как LCF/ML транслируется в LISP, интероп обходится не так дешево, как в LCF/ML и, по видимому, все библиотечные функции HOPE не написаны на POP-2, в отличие от LCF/ML, в котором почти все функции стандартной библиотеки написаны на Лиспе. Впрочем, код стандартной библиотеки HOPE до нас не дошел. +Не то чтобы на этом закончился период не очень приспособленных для исполнения чистых языков исполняемой спецификации, но начался период, когда в эти языки уже добавляют лазейки для создания эффектов из практических соображений. Но, пока что, не придумали работающей системы управления эффектами. Начался период языков номинально чисто-функциональных, но на практике не так и отличающихся от LCF/ML, который несколько ограничил изменяемость всего как в PAL, но не более того. + +#### Вывод типов + +В главе об LCF/ML мы рассказывали о том, что Милнер считал, что если ФЯ типизировать, то нужны и полиморфизм и вывод типов. И Тернер и авторы HOPE с этим согласны. Пока Тернер собирался писать тайпчекер для SASL, МакКвин, руководствуясь советами Милнера и Гордона, имплементировал его для HOPE, по-видимому, второго языка с выводом типов. Авторы HOPE, правда, не отвергают аннотации типов так бескомпромиссно как Милнер. +Аннотировать типы каждого имени, как требовалось в NPL, уже не нужно. Общий для языков с уравнениями способ получать код, похожий на псевдокод 60-х, просто задвигая аннотации типов от него подальше, на этом уходит из употребления в Эдинбургской программе. Но аннотировать типы некоторых имен все еще нужно. +Обязательные аннотации типов топлевел-функций в HOPE сохраняются, и они не связаны с мутабельностью, как в случае LCF/ML. Эти аннотации нужны для решения проблем с тем, с чем Милнер вообще решил не связываться - перегрузкой. +Перегрузка досталась HOPE из NPL 79 [Feat79] где она, по-видимому, работала (но не в трансформаторе программ) и, в условиях необходимости аннотировать типы всего, не имела очевидных отрицательных последствий. Интересно, что перегрузка появилась не для арифметики. В NPL были только натуральные числа Пеано. В HOPE, как и в LCF/ML, не было числовых типов кроме одного для целых чисел. В это время единственным "Эдинбургским" ФЯ не только с целыми, а еще и с числами с плавающей точкой был SASL 79 и он был "динамически типизирован". Так что, статически разрешаемая перегрузка оператора `*` в Эдинбургских ФЯ появилась для `map` раньше, чем для умножения чисел с плавающей точкой. +Перегрузка имени по типу в HOPE разрешается с помощью алгоритма Вальца (Waltz), разработанного для компьютерного зрения [MacQ20]. Это звучит как неожиданное применение, но только потому, что в компьютерном зрении начали решать задачи удовлетворения ограничений раньше [Burg90]. Вальц сокращает пространство поиска исключая такие метки для вершин графа, которые несовместимы с соседними вершинами. Это лучше переиспользует проделанную уже работу по сравнению с "биениями бэктрекинга", перевычисляющего то, что можно бы и не перевычислять. +Необходимость держать в памяти и обходить граф и заставляет ограничить его размер, введя обязательные аннотации типов не только для перегруженных функций, а вообще всех функций на топлевеле. Но программистов так просто не перехитрить! По дошедшим до нас отрывкам кода на HOPE видно, что они пишут гигантские, по сравнению с прочими ФЯ упоминавшимися в этой истории, топлевельные функции со множеством локальных, которые объявлять может и не очень удобно, как мы выяснили в предыдущем параграфе, но для которых хотя-бы не требуется аннотировать типы. Пролог и KRC явно более успешно сопротивлялись написанию на них гигантских функций/процедур. +Так или иначе, но авторы HOPE посчитали имплементацию перегрузки сложной, а работу алгоритма все равно недостаточно быстрой. В результате, их отношение к перегрузке стало гораздо более скептическим, но не до такой степени, как у авторов LCF/ML и его более непосредственных наследников. В языках непосредственно происходящих от HOPE перегрузка в той или иной степени сохранится. +Естественно, поскольку статическая проверка типа для POP-2 в это время - нерешенная задача, если вы пишете функцию на POP-2, которая не соответствует своей сигнатуре на HOPE - программа просто ведет себя неопределенно [Sann82]. +Как уже бывало с предыдущими версиями NPL, в мелочах изменилось все, но не обязательно стало лучше. +Сигнатуры функций, выглядевшие в NPL 79 так [Feat79]: + +``` ++++ append(list A, list A) <= list A +``` + +В HOPE выглядят как в языках описания спецификаций, ну или в "типизированном" ISWIM-псевдокоде [Burs80]: + +``` +dec append : list A # list A -> list A +``` + +В NPL 79 сигнатуры типов функций похожи на определяющие функции уравнения, но вид туплов и списков отличается от вида их типов и в NPL. Так что, может это идея и родственная более поздним о том, что конструкторы и конструкторы типов должны выглядеть одинаково, но другая. +Кстати, и конструкторы и конструкторы типов туплов изменились между NPL и HOPE. Но и в списках и в туплах элементы разделяются запятыми, в отличие от LCF/ML. Позднее МакКвин объяснял [MacQ14] разделитель элементов списков `;` в ML тем, что парсинг методом Пратта осложняет использование "многоцелевых" разделителей. Но сам-то МакКвин, имплементируя парсер HOPE тем же методом, не поленился, справился! + +#### Типы данных + +Еще один важный компонент "простого, но мощного языка" - алгебраические типы данных. Авторы HOPE считают, что для пользователя языка должно быть легко определять и использовать свои типы данных. Чтоб он не поленился использовать тип `age` вместо типа `integer` и избежать ошибок. И легко - это АлгТД и паттерн матчинг, а не энкодинг через примитивные типы и использование с помощью всяких предикатов и геттеров как в LCF/ML. Паттерн-матчинг проверяется на полноту покрытия - избегаем еще больше ошибок. Все это вместе делает HOPE первым языком, про который говорили "компилируется - работает". Авторы пишут, что обнаружили, что довольно просто писать программы, работающие правильно при первом запуске. +В HOPE типы данных объявляются в стиле BNF/Хоара, почти как в NPL 79. Почти, потому что, разумеется, не обошлось без обычного для NPL-серии изменения пары деталей. Вместо `<=` теперь `==`, а вместо `;` в объявлениях взаимно рекурсивных типов ключевое слово `with` [Burs80] [Sann82]. + +``` +typevar alpha +data list alpha == nil ++ alpha :: list alpha +``` + +Паттерн-матчинг может и похож на современный на первый взгляд, особенно с добавлением таких стандартных сейчас фич как `_`-паттерны и `@`-паттерны. Но все еще работает как в NPL, а не как у Тернера или в Прологе. Порядок паттернов все еще не имеет значения. +В NPL 79 алгебраические типы использовались и для того, чтоб дать название какому-нибудь композитному типу [Feat79]: + +``` +DATA instream <= in(list list char) +DATA word <= wo (list alphanumeric) +DATA telegram <= te(list word) +DATA statistics <= st(num, truval) +DATA message <= me(telegram, statistics) +``` + +В HOPE появились и синонимы для типов как `deftype` в LCF/ML, но не совсем: + +``` +type Right_Obj_Comma_Mor(o1 ,m1) == Comma_Mor(o1,m1,o1,m1,Num,Num) +``` + +в отличие от LCF/ML, синонимы в HOPE параметризованные как и АлгТД. +Не понятно, как это согласуется с идеями авторов HOPE, излагаемыми выше, о том, что структуры данных нужно оборачивать конструкторами разных типов, чтоб их не перепутать. Видимо, удобство им и тут дороже всяких принципиальных подходов и негибких идей. + +#### Абстракция данных + +NPL не относился к языкам описания абстрактных типов данных непосредственно. В нем отсутствовала конструкция для группировки и сокрытия функций и конструкторов. Так что, для участников исследовательской программы, занимающейся АТД, NPL - это демонстрация того, как можно имплементировать исполнение их едва исполняющихся спецификаций. В отличие от него, HOPE - это язык описания абстрактных типов данных без всяких натяжек. +В HOPE появились простые непараметризованные модули, с помощью которых можно скрывать некоторые функции и конструкторы. Это довольно обычное явление для языков спецификации. Как мы помним, многие разработчики языков с уравнениями, описывающими АТД, хотели параметризовать эти АТД, но не смогли или не успели это сделать в 70-е. Модули - одно из названий обычных в этих языках конструкций для объединения деклараций функций и/или типов. Примечательно только то, что в HOPE конструкция впервые в ФЯ под современным названием. +Та ранняя разновидность этих конструкций, которая попала в CLU и LCF/ML менее типична, чем та, что попала в HOPE. Она построена вокруг типа, а такой подход, вскоре после отделения этой ветки от программы исследования АТД, сочли непрактичным и позволили группировать функции многих типов и без обязательных конструкторов [Lisk93]. Неудобно и нет смысла группировать конструкторы значений типа и прочие функции этого типа с помощью одной конструкции. И HOPE использует две разные языковые конструкции для этого. Еще одна из множества инноваций, которые опоздали в LCF/ML совсем чуть-чуть. +Интересно, что синтаксис модулей в HOPE не выглядят как синтаксис теорий из Clear, объединение с которым планировалось, но так и не состоялось. По непонятной причине модули выглядят почти как в языке программирования MODULA [Wirt76], на который авторы не ссылаются. Именно MODULA, а не намного более известный язык MODULA-2. В меньшей степени похожи, но все еще похожи они на синтаксис модулей в языке описания спецификаций SPECIAL [Robi76], на который авторы HOPE ссылаются [Sann82]. +Модули HOPE напоминают модули Хаскеля своими отдельными, как и аннотации типов, аннотациями экспортов. + +``` +module ordered_trees + pubtype otree + pubconst empty, insert, flatten + ... +end +``` + +но в списке импортов перечисляются модули, а не функции. Механизма для импорта только части публичных функций и типов модуля, по видимому, нет. + +``` +module tree_sort + pubconst sort + uses ordered_trees, list_iterators + ... +end +``` + +Разработка и имплементация Clear существенно продвинулась с 77-го года, но ожидаемое сближение с HOPE пока не состоялось. Это не мешает авторам этих языков продолжать утверждать что на самом-то деле они довольно похожи, как они утверждали во времена Clear 77 и NPL 75. Почему они это делают? Потому, что на это сходство опирается предлагаемый авторами Clear метод его использования. +Предполагается записывать сначала абстрактную спецификацию и затем инкрементально конкретизировать её, пока спецификация не станет исполняемой, то есть программой [Sann82]. Это соответствует идеям Вирта и Дейкстры, но противоположно направлению в котором предлагал двигаться Гуттаг [Gutt78]. Который считал, что программисту написать программу проще, чем спецификацию. Чтоб спецификация на Clear стала исполняемой, её нужно сделать во-первых "анархической". Саннелла называет "анархической" спецификацию в которой нет аксиом (уравнений) для конструкторов типов данных. Во-вторых, у всех уравнений нужно сделать простые левые части. Так что речь идет об исполняемости как у AFFIRM, не как в OBJ или у О'Доннела. Строгое разделение на функции, которые нельзя использовать слева от `=` и конструкторы, которые можно. +Не подумайте только, что "исполняемая спецификация" так просто исполнится. Какой-то экстракции в HOPE из Clear нет. Бурсталл и Гоген планируют сделать полуавтоматический переписыватель спецификаций на Clear в будущем. Но пока что нужно переписать код вручную и на практике это не так легко. +Саннелла пишет, что некоторые Clear спецификации - это просто HOPE программы, записанные "немного отличающейся нотацией" [Sann82]. В этом утверждении есть доля правды. Но только доля. Clear похож на HOPE в достаточной степени для того, чтоб Саннелла использовал в имплементации Clear модифицированный парсер и тайпчекер, написанные МакКвином для имплементации HOPE. С другой стороны видно, что модифицировано в парсере не так мало: + +``` +proc Reverse(X:Triv) = + enrich List(X) by + opns reverse : list -> list + eqns reverse(nil) = nil + reverse(a::l) = append(reverse(l),a::nil) enden +``` + +Еще сильнее отличается синтаксис деклараций типов данных. В Clear, как и принято в языках описания спецификаций, они декларируются как прочие функции. В итоге надо прийти к простым конструкторам, но можно начинать не с них [Ryde82]. + +``` +proc List(X : Triv) = + enrich X by + data sorts list + opns nil : list + (_ :: _) : element,list -> list + end + +theory Triv: + constant Triv = sorts element + end + +constant Bool_Lists = List(Bool[element is bool]) end +``` + +Сравните с компактными BNF-образными декларациями АлгТД в HOPE. +Но главное отличие не во множестве мелких деталей из-за которых при переписывании из Clear в HOPE и наоборот потребовалось бы править практически каждую строку кода. Это были бы довольно простые механические правки. Главное отличие в параметризации. Вернее, в параметризациях. +Обратите внимание на тип `reverse`. У типа списка `list` нет параметра. Зато параметр есть у модуля `List(X)` в котором список объявлен. В Clear нет параметрического полиморфизма как в HOPE. Есть параметризованные модули. Которые придется использовать для написания обобщенного кода. +Но параметризованных модулей нет в HOPE. Что толку от постепенной трансформации спецификации в программу, если программу придется потом переписывать в принципиально другом стиле? +Саннелла допускает, что параметрический полиморфизм в Clear может появиться, но главные ожидаемые изменения - это параметризованные модули для HOPE. Бурсталл и МакКвин обсуждали Clear-образные параметризованные модули для HOPE в 78-80-гг, но так ничего и не имплементировали. Для HOPE все закончилось пропозалом параметризованных модулей, который не отсканирован и не выложен в интернет. МакКвин сделал по нему доклад на конференции в 81-ом году. +Интересно, что язык модулей для HOPE старается выглядеть другим языком даже в мелочах. Так аннотации типов для параметризованных модулей не отдельные декларации как в HOPE, а аннотации на месте, как в LCF/ML. Также, есть легкий способ объявлять каррированные параметризованные модули, в отличие от легкого способа объявлять каррированные функции: + +``` +structure Sorting(L : LIST)(P : POSET) : SORTING(P,L) ... end +``` + +или даже + +``` +structure Sorting : SORTING(P0,L0) ... end +``` + +Декларации типов функций отправляются в интерфейсы (сигнатуры) модулей. В интерфейсе (сигнатуре) модуля можно записать АлгТД [MacQ20]: + +``` +interface LIST (BOOL) + data list a == nil ++ cons (a, list a) + dec null : list a −> bool +end +``` + +а в имплементации потом указать, что пользуетесь _непосредственной_ имплементацией: + +``` +structure List : LIST (Bool) + data-rep list is free + −−− null (nil) <= true + −−− null (cons(x, l)) <= false +end +``` + +Один из последних приветов из эпохи, когда АлгТД еще был абстракцией, для которой, конечно же, нужно писать серьезную, низкоуровневую имплементацию. Но иногда или временно можно согласится и на такую несерьезную вещь как ссылка на один из объектов кучи с тегом и другими ссылками. Из эпохи, когда АТД было нормально перепутать с АТД. + +### Что делать в 80-е годы + +Довольны ли авторы HOPE тем, что у них получилось? Обладает ли язык мощностью Лиспа без его сложностей? Чем HOPE точно обладает, так это знакомым видом ФЯ. На первый взгляд. При более внимательном рассмотрении видны странности и отличия. Например, нечувствительный к порядку уравнений паттерн-матчинг и перегрузка, требующая обязательных аннотаций. Но большая часть важных деталей уже собрана в единое целое, на рабочем столе осталась только пара не понадобившихся деталей. Если бы мы писали историю идей, то на этом бы предыстория ФЯ завершилась и началась история. Но мы пишем историю имплементаций, и тут до окончания предыстории еще далеко. +Понятно, что HOPE не похож на современные ФЯ в деталях потому, что ни одна из версий HOPE не дожила до наших дней достаточно хорошо сохранившись, чтоб детали HOPE стали деталями современных ФЯ. +И это довольно ожидаемый результат для очередного варианта NPL. Как обычно, через пару лет авторы NPL бросят очередной NPL и сделают новый, поменяв детали и название, чтобы потом поменять еще раз и еще, пока n-ый вариант NPL, называющийся не NPL, не станет современным ФЯ. +Но не все так просто, пока большая часть авторов NPL/HOPE всем этим занимались, другая группа разработчиков и имплементаторов ФЯ поддерживала HOPE примерно в одном и том же виде, сохраняя все узнаваемые детали и странности. Пока не перестала. В отличие от предыдущих эфемерных NPL-ей, каждому из которых уже через пару лет на смену приходил следующий, HOPE просуществовал гораздо дольше. Разделение NPL-линейки, консервация одной из ветвей и её последующая гибель произошли благодаря автору Фортрана и популярности Пролога в Японии. Разочарование автора Фортрана в "фортранах" и популярность Пролога в Японии запустили череду странных решений и невероятных последствий, которая повлияла как на то, что HOPE не был таким же короткоживущим, как предыдущие версии NPL, так и на то, что HOPE недостаточно долгоживущая версия, чтоб дожить до наших дней. И повлияла не только на HOPE. Но это уже другая история. +А пока что у авторов HOPE много идей о том, как HOPE переделать. Авторы HOPE недовольны перегрузкой. Она замедляет компиляцию и её сложно имплементировать. К тому-же, ad-hoc полиморфизм хочется сделать менее ad-hoc: нужно работать над еще одной проблемой, с которой Милнер решил вовсе не связываться - ограниченным полиморфизмом. Авторы HOPE планируют описывать, какие операции можно применять к значениям типов-параметров `otree(alpha[<])` или использовать именованные группы ограничений, как теории в Clear. +Судя по всему, такого проработанного плана как для параметризации модулей, не было. Только общие идеи. Считали, что нужно смотреть на то, как это сделано в CLU. Тем более, что там в 76-79гг. уже придумали как проверять ограничения во время компиляции и в 80-ом эффективно (по крайней мере, по мнению авторов CLU) имплементировали такую разновидность перегрузки [Lisk93]. +Если сначала авторы HOPE были не уверены, хотят ли они только один вид параметризации с ограничениями, то со временем решили, что нужно два вида, отдельно для языка "уравнений", отдельно для языка модулей. По крайней мере, в следующем их языке так и было сделано. +Но многое в HOPE еще рано переделывать. Надо, для начала, доделать. Многие планы авторов пока что не реализованы. Главные проблемы HOPE связаны с имплементацией. + +### Что было и что не было сделано + +МакКвин имплементировал HOPE на POP-2 для PDP-10 как компилятор в инструкции для стековой машины, имплементированной как интерпретатор. Да, интерпретатор тоже на POP-2 [Burs80]. Код компилятора не сохранился, но пишут, что его размер был примерно 7 тыс. строк [Moor82]. МакКвин написал парсер методом Пратта, как Ньюи написал парсер LCF/ML [MacQ14] [MacQ15]. Написал тайпчекер, руководствуясь советами Милнера и Гордона. +МакКвин пишет, что первая имплементация HOPE была сделана в 79-80гг. [MacQ22]. Есть основания предположить, что основная работа по имплементации была проделана до 80-го года. МакКвин пишет [MacQueen], что работал в Эдинбурге до 79-го года. В статье 80-го года все авторы HOPE включая и МакКвина записаны в Департамент Компьютерных Наук Эдинбургского Университета, куда Бурсталл и др. перешли работать из бывшей Группы Экспериментального Программирования в конце 79-го. Но связываться с МакКвином уже предлагают через Институте Информационных наук Университета Южной Калифорнии в Лос-Анджелесе, где Гуттаг и Мюссер делали AFFIRM. На странице МакКвина в LinkedIn [MacQueen] в момент написания этого текста это место работы не указано, между окончанием работы в Эдинбурге в 79-ом и началом работы в Лабораториях Белла в 81-ом просто пропуск. С окончанием его работы в бывшей Группе Экспериментального Программирования его работа над имплементацией ФЯ не закончилась. Даже во время написания этого текста он работает над имплементацией очередного ФЯ на GitHub. Но основная его работа над первой имплементацией HOPE, по-видимому, продолжалась до ухода из Эдинбургского Университета. Судя по ссылкам в диссертации Райдхерда [Ryde82], отчет с описанием HOPE мог выйти еще в 79-ом году, но не вышел. Так что большая часть работы была проделана в одном департаменте, а отчет вышел в другом, когда бывшие участники бывшей Группы Экспериментального Программирования уже работали в Департаменте Компьютерных Наук. И советы по имплементации проверки типов МакКвин получал от Милнера и Гордона еще не работая с ними в одном департаменте. +Другой разработчик HOPE, упоминающийся в первой статье [Burs80] - Майкл Леви (Michael Robert Levy), защитивший диссертацию в Университете Уотерлу в 78-ом. Что именно он имплементировал, правда, авторы HOPE не пишут. Бурсталл и/или Саннелла умели писать на POP-2. И от них могло потребоваться что-то имплементировать, если принять, что МакКвин закончил работать в Эдинбурге в 79-ом, а Леви пребывал в Эдинбурге временно. Но об их вкладе нет даже таких расплывчатых упоминаний, как о работе Леви. +Авторы HOPE собирались сделать, но не сделали оптимизирующую компиляцию паттерн-матчинга, исключающую лишние проверки. Хотя у МакКвина уже были какие-то идеи о том, как это можно было сделать [MacQ22]. Не имплементировали параметризованные модули, потоковый ввод-вывод, конструкторы множеств. Не написали интерпретатор на чем-нибудь побыстрее POP-2. Эдинбургская система для трансформации программ не была обновлена для поддержки кода на HOPE. +В отличие от имплементации LCF/ML, дожившей почти что до наших дней, первая имплементация HOPE вскоре была заброшена. Практически сразу же после того как закончить работу над ней, МакКвин уже приступил к написанию новой имплементации HOPE на другом языке. + +### Можно сказать, что компилируется. Можно сказать, что работает. + +#### Дональд Саннелла + +Другой соавтор Бурсталла, работавшим над HOPE, был первым пользователем этого языка. Дональд Саннелла (Donald Sannella) получил степень бакалавра от Йельского университета в 77-ом и степень магистра от университета Калифорнии в Беркли. Чтоб защитить диссертацию в 82-ом году в Эдинбургском Университете [Sann14] он имплементировал CLEAR. Дважды. +Еще в 77-ом году Бурсталл стал писать код на NPL чтоб разобраться с новым для него понятием теории категорий - копределом [Burs80b]. Этот код мог быть даже больше, чем самая большая дошедшая до нас программа на NPL 79, но это не очень высокая планка. Конечно, без ФВП с теорией категорий особенно не поразбираешься, так что код был со временем переписан Бурсталлом, Райдхердом (David Eric Rydeheard) и Саннеллой на HOPE. Райдхерд и Саннелла развивали код, пока он не вырос до примерно тысячи строк [Ryde82] и стал набором ТК-абстракций, который был использован Саннеллой для имплементации CLEAR [Sann82]. Существенная часть этого кода дошла до нас как примеры в диссертации Райдхерда [Ryde82]. Код с перекладыванием лямбд в/из АлгТД выше - как раз оттуда. Райдхерд называет эту тысячу строк "большой программой". +Имплементация CLEAR с помощью этого ТК-инструментария, правда, столкнулась со сложностями. Абстрактный, высокоуровневый код работал слишком медленно. Пара примеров спецификаций по 8 строк каждая требовали 2 и 4 минуты работы [Sann82]. Так что Саннелла написал менее абстрактный код. Для того чтоб выяснить, насколько быстрее результат можно получить, если не писать на ФЯ в ФП-стиле. И для того чтобы получить "программу, которой можно пользоваться". Разница получилась существенной. Те спецификации, который использующий ТК-абстракции код обрабатывал за полчаса, теперь обрабатывались в 1000 раз быстрее [Ryde82]. Те, что требовали минут пять - заработали только в 100 раз быстрее [Sann82]. Более серьезные примеры заставляли абстрактный код удерживать достаточно памяти, чтоб большая часть времени тратилась на сохранение страниц на диск и чтение с диска. +Ну, нельзя сказать, что писать в ФП стиле на ФЯ так просто даже и сегодня. К сожалению, Тернер не воспользовался возможностью продемонстрировать заявленную им "бесплатность" ФВП в SASL 79, переписав и запустив эту "большую" тысячестрочную программу. +В результате трудов Саннеллы, на HOPE написана часть имплементации CLEAR размером в 1700 строк [Sann82]. Парсер и тайпчекер на POP-2 на основе кода имплементации HOPE, написанного МакКвином. Этот код используется из кода на HOPE с помощью машинерии для вызова функций на POP-2. Весьма вероятно, что это одна из добавленных позднее фич и, вместе с `@` и `_` паттернами определяет разницу между HOPE 80 и HOPE 81. Потому, что не упоминается в ранних материалах по HOPE. Но установить это точно по доступным в электронном виде материалам нельзя. +Как продемонстрировал Уоррен, если не писать на Прологе в стиле логического программирования - можно добиться неплохой производительности. К сожалению, не писать на ФЯ в ФП стиле все еще недостаточно для хорошей производительности. Саннелла отмечает, что работа кода на POP-2 составляет какие-то единицы процентов по сравнению с кодом на HOPE. Не смотря на то, что код на POP-2 это, например, тайпчекер с разрешением перегрузки, который имплементаторы и пользователи HOPE считают медленной, проблемной частью имплементации. +Самая крупная спецификация, которую проверяет CLEAR - спецификация вывода типов из статьи Милнера [Miln78]. 270 строк на CLEAR, проверяется 15 минут. +Да, с производительностью все плохо, но Саннелла очень доволен HOPE как языком программирования. Считает, что писать код на HOPE легче, чем на всех остальных известных Саннелле языков, называет это "величайшим триумфом HOPE". Список известных ему языков не приводится. Саннелла считает, что принцип "компилируется - работает" подтверждается на практике. Ленивость Саннелле при имплементации CLEAR не пригодилась. +Раз уж работа над CLEAR теперь велась в Департаменте Компьютерных Наук, где работали Милнер с Гордоном и где разработали LCF/ML, Саннелла написал экспорт CLEAR теорий в PPλ для работы с ними в LCF. При этом Саннелла приобрел опыт программирования на LCF/ML и сравнил его с HOPE. Языки он посчитал очень похожими. Фича ML, которой ему не хватало в HOPE - исключения [Sann82]. Очередной тревожный звоночек, не предвещающий ничего хорошего для тех, кто почему-то ждет, что следующий язык от авторов HOPE будет чисто функциональным. + +#### Часть компилятора для части языка + +Как мы помним, наш старый знакомый, соавтор NPL Джон Дарлингтон в 77-ом году перешел из Эдинбургского Университета работать в Имперский колледж Лондона. +И летом 82-го года в Имперском колледже Лондона написан компилятор подмножества HOPE на HOPE [Moor82]. Или, правильнее сказать, был написан _из_ Имперского колледжа Лондона. Ранее там интересовались новой имплементацией POP-2, и можно было бы предположить, что в Лондоне есть все что нужно для использования и развития HOPE, но нет. Интерпретатор HOPE, который используется для написания компилятора HOPE на HOPE, запускают на эдинбургском компьютере. По крайней мере до осени 82-го года. Возможно, эдинбургскую имплементацию HOPE не так просто использовать не на том компьютере, на котором её разрабатывают. Возможно, в Лондоне так и не появилась машина, на которой работал POP-2. Или она уже не работала в 82-ом году. Что обычно и происходило с этими машинами в это время. Но это другая история. +Лондонский компилятор разделяет код - написанный вручную парсер рекурсивным спуском - с системой трансформации программ. Ну разумеется, Дарлингтон пишет систему трансформации программ, теперь программ на HOPE. +Мур утверждает, что, насколько ему известно, других компиляторов в "аппликативном" стиле еще не написали. Видимо он, в отличие от Бурсталла [Burs79], не считает Пролог аппликативным языком. Надо заметить, что "аппликативность" условная. В HOPE нет ввода-вывода, так что для сохранения сгенерированного кода в файл вызывается функция на POP-2. +Компилятор - определенно исследовательский проект, он задуман продемонстрировать, что на таком языке как HOPE можно писать значительные программы. Удалось ли это продемонстрировать? +В начале нашей работы мы определили HOPE как первый язык, соответствующий нашему определению ФЯ. И определили компилятор ФЯ, написанный на ФЯ как ключевой момент в истории имплементаций ФЯ. Имели ли мы в виду этот компилятор подмножества HOPE на HOPE? Нет, по двум причинам. +Насколько значительная это программа? Три тысячи строк. Авторы считают такую программу "большой". Напомним, что компилятор Уоррена в это время больше 6KLOC. Эти три тысячи строк на HOPE неплохо выглядят по сравнению с кодом, написанным на эдинбургских прото-ФЯ в 70-е. Но главным образом потому, что код продолжали писать даже когда было уже понятно, что в использовании имплементаций ФЯ 70-х годов для разработки "значительных программ" нет особого смысла. +Три тысячи строк кода - это подозрительно мало для компилятора даже подмножества HOPE. И кода так мало не из-за выразительности и краткости функционального языка. Кода мало потому, что компилятор делает не так много работы, как можно ожидать от компилятора в код обычной машины. Потому, что он транслирует один высокоуровневый язык в другой высокоуровневый язык - Compiler Target Language [Moor82]. Который не так и отличается от HOPE, поддерживая все те же конструкторы алгебраических типов и правила перезаписи [Reev86]. Такой вот код на HOPE + +``` +data tree(alpha) == tip(alpha) ++ node(tree(alpha) # alpha # tree(alpha)) +dec size : tree(alpha) -> NUM +--- size(tip(i)) <= 1 +--- size(node(t1, i, t2)) <= plus (1, plus(size(t1), size(t2))) +``` + +соответствует такому на CTL: + +``` +CONSTRUCTOR tip($1), node ($1, $2, $3) +REWRITEABLE size($1) +SEQ_DO + SNAPSHOT_PACKET($1: OPERATOR, OPERAND()) + SEQ_ALT + IS_CONSTRUCTOR($1) + ALT + $1 IS tip($T1) + REWRITE_$PBP(!1) + $1 IS node($T1, _, $T2) + SEQ_DO + GENERATE_PACKET(&1: size($T1)) + GENERATE_PACKET(&2: size($T2)) + GENERATE PACKET(&3: plus(&1, &2)) + REWRITE_$PBP(plus(!1, &3)) + END_SEQ_DO + END_ALT + TRUE + DO + UPDATE_PACKET($1: + PENDING DEMANDS = --1, + SIGNAL_SET = ++ (CONTROL_FLOW($PBP, + WHEN_CONSTRUCTOR) + ) + ) + RESTORE_$PBP(PENDING_SIGNALS = 1) + END_DO + END_SEQ_ALT +END_SEQ_DO +``` + +Предполагается, что в будущем CTL будет исполняться компьютером - параллельным переписывателем графов, а пока что исполняется его эмулятором, написанном на Паскале. +Более серьезная проблема - компилятор работает слишком медленно для того, чтоб его можно было использовать. Программа из двадцати строк кода компилируется за 5-10 минут, из них полторы минуты работает код на HOPE, полторы минуты - сборщик мусора, а все остальное время страницы памяти читаются с диска и пишутся на диск. +Опыт Мура с ленивостью противоположен опыту Саннеллы. Ленивость критична для того, чтоб компилятор вообще работал. Компиляция происходит в пять проходов: лексер, парсер, проверка типов и два прохода кодогенератора. Стадии компиляции имплементированы как 5 функций и первоначально обменивались полностью материализованными в памяти структурами, которые занимали слишком много памяти в случае "нетривиальных" программ, так что функции стали принимать и возвращать ленивые списки. Сделать это изменение было несложно, что по мнению Мура должно продемонстрировать плюсы ФП. +Мур, как и Саннелла, доволен самим языком - писать код на ФЯ легко и приятно! В Лондоне не унывают из-за медленной работы. Все-таки компилятор - исследовательский проект, производительность не главная цель. Для начала, они надеются имплементировать более эффективный эмулятор, который позволил бы бутстрап и достаточно быстро работающий для практического использования компилятор HOPE на HOPE. И у них есть планы как решить проблемы с производительностью. +Но об этом позже. Сначала попробуем оценить эти проблемы с производительностью. Компиляция со скоростью 3 строки в минуту выглядит плохо, но что выглядело бы хорошо в эти годы? Насколько HOPE и прочие ФЯ 70-х медленнее прочих высокоуровневых языков? + +### Реабилитация прокрастинирующей машины + +В 80-ом году авторы HOPE пишут [Burs80], что интерпретатор этого языка исполняет программы в 9 раз медленнее, чем интерпретатор Лиспа и 23 раза медленнее, чем работает код, сгенерированный компилятором Лиспа. В 82-ом Саннелла пишет [Sann82], что тот же интерпретатор HOPE в 3 раза медленнее интерпретатора и в 50 раз медленнее компилятора Лиспа. Имплементация Лиспа в обоих случаях одна и та же, но не указано одна и та же версия этой имплементации или нет. Что за программы они сравнивают - авторы HOPE не пишут. Производительность LCF/ML на той же машине вовсе не известна. И это довольно нормально для этого времени, авторы и пользователи имплементаций почти всех языков, о которых мы рассказывали в этой истории, не уделяют бенчмаркам особого внимания. Почти всех. Исключение? Уоррен. + +#### Пролог + +> И все равно неожиданно, что Лисп не быстрее Пролога в несколько раз. +> Дэвид Уоррен, Прикладная логика: её использование и имплементация как инструмента для программирования. [Warr78] + +В своей диссертации [Warr78] Уоррен сравнивает производительность кода своего компилятора Пролога (Prolog-10) и своего интерпретатора Пролога (Prolog-10I) с Марсельским интерпретатором (Prolog M) и компиляторами Лиспа и POP-2 на нескольких микробенчмарках. + +| | nreverse | qsort | d times10 | d div10 | d log10 | d ops8 | palin25 | dbquery | +| :--------- | :------: | :------: | :-------: | :------: | :------: | :------: | :------: | :------: | +| Prolog-10 | 1.55 | 1.71 | **1.00** | **1.00** | **1.00** | **1.00** | 2.03 | **1.00** | +| Lisp | **1.00** | **1.00** | 1.74 | 2.62 | 1.14 | 1.31 | **1.00** | | +| Pop-2 | 5.87 | 3.06 | 3.73 | 5.41 | 4.46 | 2.34 | | 1.62 | +| Prolog M | 33.4 | 29.0 | 28.8 | 30.8 | 32.1 | 27.3 | 36.0 | 53.9 | +| Prolog-10I | 33.5 | 30.7 | 25.4 | 28.7 | 25.6 | 28.4 | 30.5 | 48.0 | + +``` +Prolog-10 +░▒▓▓ +Lisp +▒▓░▒▓ +Pop-2 +░░░▒▒▓▓░░░▒▒▓▓ +Prolog M +░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +Prolog-10I +░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +``` + +И график только для компиляторов: + +``` +Prolog-10 +░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓░░░▒▒▒▒▓▓▓▓▓▓▓ +Lisp +░░░▒▒▒▒▒▒▓▓▓▓▓▓▓▓░░░░░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓ +Pop-2 +░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +``` + +Здесь и далее результаты бенчмарков безразмерные. Это отношения времени работы ко времени работы самого быстрого варианта. Соответственно, чем меньше - тем лучше. +`nreverse` - это наивный разворот списка наивной конкатенацией. `qsort` - _тот самый_ "квиксорт". `d times10`, `d div10`, `d log10`, `d ops8` - процедура для символьного вычисления производных применяется к разным выражениям. `palin25` - пример для сравнения логических переменных с мутабельными ячейками. И `dbquery` это запрос для "базы данных" определенной как прологовые предикаты против попарного сравнения элементов в массивах POP-2, чему такой запрос примерно соответствует. Большинство бенчмарков не совсем уж однострочники, а маленькие программы примерно на страницу текста на самом многословном языке. И это обычно не Пролог. +Интересно, что сравнимая с компилируемым Лиспом производительность достижима для аппликативного языка. Но не для POP-2. Имплементация POP-2 определенно оставляет желать лучшего. Особенно, если посмотреть на код. Когда такой вот код + +```prolog +:-mode qsort(+,-,+). +:-mode partition(+,+,-,-). + +qsort([X,..L],R,R0) :- + partition(L,X,L1,L2), + qsort(L2,R1,R0), + qsort(L1,R,[X,..R1]). +qsort([],R,R). + +partition([X,..L],Y,[X,..L1],L2) :- X =< Y, !, + partition(L,Y,L1,L2). +partition([X,..L],Y,L1,[X,..L2]) :- + partition(L,Y,L1,L2). +partition([],_,[],[]). +``` + +работает в два раз быстрее, чем такой: + +``` +FUNCTION QSORT LIST; +VARS Y Z Q QQV QQW QQS; + 0; +L2:IF NULL(LIST) OR NULL(TL(LIST)) THEN GOTO SPLIT CLOSE; + NIL->QQS; NIL->Y; NIL->Z; + HD(LIST)->QQW; +L1:HD(LIST)->QQV; TL(LIST)->LIST; + IF QQW>QQV THEN QQV::QQS->QQS + ELSEIF QQWZ + ELSE QQV::Y->Y + CLOSE; + IF NULL(LIST) THEN Z;Y;1; QQS->LIST; GOTO L2 ELSE GOTO L1 CLOSE; +SPLIT: ->Q; IF Q=0 THEN LIST EXIT + ->Y; + IF Q=1 THEN ->Z; LIST<>Y;2; Z->LIST; GOTO L2 CLOSE; + Y<>LIST->LIST; + GOTO SPLIT +END; +``` + +это выглядит не очень хорошо для POP-2. +Эта имплементация Лиспа (более старая версия той, с которой сравнивали авторы HOPE) не самая лучшая, но из более-менее производительных. Поэтому подозрительно, что Уоррен вот так взял и догнал компилятор Лиспа. Результат двадцатилетней работы над компиляцией Лиспа. Насколько хорошо вообще можно скомпилировать Лисп? В этом сравнении скоростей просто нет никаких языков, про которые мы можем уверенно сказать, что они хорошо компилируются и демонстрируют как быстро можно было бы работать на этой машине в принципе. +Довольно безопасно предположить, что бенчмарки подобраны так, чтоб компилятор Уоррена выглядел лучше. Так, измерения для Лиспа и POP-2 включают время сборщика мусора. И сборщик мусора Уорреновской имплементации не работал при исполнении всех этих программ вообще. Конечно, Уоррен поступил бы более добросовестно, если бы он включил в набор бенчмарков и такие, которые потребовали бы работы сборщика мусора. Но это существенное преимущество имплементации, когда можно сделать столько всего, аллоцируя на стеке. +Разумеется, один лиспер - Клаудио Гутьеррес (Claudio Gutierrez) - попытался Уоррена разоблачить [Guti82]. Гутьеррес не улучшал бенчмарки Уоррена, написанные на Лиспе. Он написал собственные на Прологе. +Гутьеррес прошелся по всем пунктам, которые мы рассматривали в главе о причинах хорошей производительности Пролога Уоррена. Использовал списки вместо конструкторов, сделав представление в памяти структур данных не лучше, а хуже, чем у Лиспа и добавив лишней работы для ПМ. Не стал аннотировать параметры, не дав компилятору Уоррена более рационально аллоцировать и генерировать более быстрый код, оптимизировать хвостовую рекурсию. +Это, конечно, не все что можно было сделать. Гутьеррес использовал прологовскую базу данных в качестве мутабельных ссылок, а она слишком медленная для такого использования. Но это еще не все, компилятор Уоррена не компилирует предикаты, которые добавляются в базу данных во время исполнения, так что Гутьеррес таким использованием еще и обеспечил вызовы интерпретатора в цикле. Он, также, не ставил каты (`!`), которые помогают оптимизировать хвостовые вызовы. +И в результате всего этого лиспер мог бы просто уничтожить Пролог, если бы не стал измерять, в основном, скорость вывода текста. Так возможность была упущена. Не вышло даже отставания в десять раз. Гутьеррес мог бы заподозрить, что что-то не так из-за небольшой разницы между производительностью скомпилированного и интерпретируемого Лиспа, но нет. +В итоге, после ответа [O'Ke83] из Эдинбурга, в котором все это было исправлено, компилятор Уоррена стал выглядеть еще лучше, чем после первоначальных бенчмарков Уоррена. +Такая богатая культура написания и критики бенчмарков напоминает современную. Основное отличие от которой - ожидание ответов годами. Потому, что дискуссия идет в публикациях. + +#### Эпилог + +К сожалению, функциональной части Эдинбургской программы эта культура была чужда, и оставалась чуждой большую часть 80-х. +К счастью, мы все-таки можем составить какое-то представление о производительности других упоминавшихся в этой истории имплементаций. Замеры были сделаны на других машинах, на еще более микро микробенчмарках, чем у Уоррена. Но все-таки были сделаны. + +| | fib 20 | primes 300 | 7queens | 8queens | insort 100 | tak | +| :------------ | :------: | :--------: | :------: | :------: | :--------: | :------: | +| LCF/ML (LISP) | 100 | 145 | 425 | | 18.8 | 172 | +| SASL (LSECD) | 67.4 | 100 | 425 | | 15.0 | | +| Miranda (SKI) | 157 | 61.5 | | 422 | | | +| LISP int. | 45.7 | 39.0 | 120 | | 8.00 | 42.2 | +| LISP comp. | 2.39 | 5.50 | 13.0 | 36.2 | **1.00** | 1.67 | +| C | **1.00** | **1.00** | **1.00** | **1.00** | | **1.00** | +| Pascal | 2.00 | | 2.50 | 2.92 | | 1.44 | + +``` +LCF/ML (LISP) +░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +SASL (LSECD) +░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +Miranda (SKI) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +LISP int. +░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓ +LISP comp. +░▒▓ +C +▒ +``` + +Таблица собрана из двух статей [Augu84] [Augu89]. Не беспокойтесь о поздних датах публикации этих статей. 80-е не были так бедны достижениями имплементаторов как 70-е. +`fib 20` - это вычисление 20-го числа Фибоначчи наивным рекурсивным способом. `primes 300` - вычисление простых чисел меньше 300 нерешетом неэратосфена из руководства SASL [Abra81]. `8queens` - задача расстановки восьми ферзей. `7queens` - то же, но ферзей семь потому, что расставлять 8 слишком долго. `insort 100` десять сортировок вставками списка из ста элементов. `tak` - это любимый бенчмарк лисперов - функция Такеучи, измеряет скорость рекурсивного вызова, как и `fib`, но работает быстрее в ленивом языке, чем в энергичном. + +##### LCF/ML (LISP) + +Форк первой имплементации LCF/ML. Тоже транслирует в Лисп, который интерпретируется, но в другой Лисп, не тот, в который компилировали Милнер и др. и с которым сравнивали Уоррен и авторы HOPE. Этот Лисп похуже, но несущественно. Точнее сказать сложно, на одной машине эти Лиспы не работали. + +##### SASL (LSECD) + +Это Сент-Эндрюсовская имплементация SASL c помощью "прокрастинирующей" SECD, которую начал писать Тернер, а потом дорабатывал Кэмпбелл. И он, по видимому, отлично справился с доработками. Потому, что интерпретатор работает гораздо быстрее, чем можно было ожидать после чтения статьи Тернера. В своей статье о комбинаторной интерпретации [Turn79] Тернер писал, что не мог сравнить их на одной машине, но ленивая SECD имплементация аллоцирует в 5-20 раз больше, чем комбинаторная и в 10 раз больше, чем строгая SECD. С предполагаемыми серьезными последствиями для производительности. Но вот прокрастинирующая SECD работает на одном компьютере с комбинаторным интерпретатором, а существенного отставания не видно. Или комбинаторный интерпретатор вовсе не является прорывным изобретением, которое ускорило интерпретацию ленивых языков на порядок и выровняло их скорость с интерпретаторами строгих языков, или те, кто измеряли скорость просто перепутали имплементации SASL. Это вполне возможно, статьи не про скорость имплементаций SASL. Они в этом сравнении только для того, чтоб имплементация авторов статьи хорошо выглядела. Но особых оснований считать, что они что-то перепутали нет. Кроме того, их университет отсутствует в списке пользователей комбинаторного SASL [Turn19]. Да, и некоторых авторов имплементаций ФЯ в эти годы есть списки пользователей этих имплементаций. Легко умещающиеся на одну страницу. А те, у кого такого списка нет - обычно являются единственными пользователями собственной имплементации. + +##### Miranda (SKI) + +Комбинаторные интерпретаторы в этом соревновании представляет не SASL, а более поздний интерпретатор Тернера. Который должен быть быстрее того, который Тернер сравнивал с SECD-машинами в своей статье. Например потому, что в нем исправлены проблемы с неправильным `B'`. Установить это точно нам не удалось, замеры производительности комбинаторного SASL мы не нашли, но это довольно правдоподобно. +Может быть Тернер просто плохой имплементатор? Это проверить можно. Вот результаты комбинаторной имплементации KRC Саймона Крофта [Thom90], которая пришла на смену Тернеровскому интерпретатору KRC в Университете Кента: + +| | fib 20 | primes 300 | +| :------ | :------: | :--------: | +| KRC | 79.3 | 23.8 | +| C (VMS) | **1.00** | **1.00** | + +Измерения сделаны на машине из той же линейки, что и машина на которой запускали бенчмарки SASL. Модель более дешевая, в полтора раза медленнее. И ОС отличается и компилятор C, по всей видимости, хуже. Не смотря на все эти отличия видно, что и SKI-интерпретатор, написанный не Тернером все равно не демонстрирует на порядок более высокой скорости, чем ленивая SECD. + +##### LISP + +`LISP int.` - это интерпретатор Лиспа, в который транслируется измеряемый тут LCF/ML. Можно было бы ожидать, что разница с LCF/ML будет поменьше. +`LISP comp.` - это компилятор того же самого Лиспа. Теперь его можно сравнить с языками, производительность которых понятнее: Си и Паскалем. И, хотя скомпилированный Лисп существенно быстрее того, что смогли сделать герои этой истории до сих пор, проблемы видны. И, судя по результатам бенчмарков, чем сильнее программа отличается от однострочника, тем хуже результат. + +#### Потерянное десятилетие + +В 60-е годы исследователи Обоекембриджской программы попытались создать практичную имплементацию ФЯ. Эта попытка завершилась частичным успехом - созданием непрактичной имплементации ФЯ. С тех пор прошло еще одно десятилетие, но практичной имплементации ФЯ как не было - так и нет. Исследователи Эдинбургской программы не ставили перед собой грандиозных целей своих предшественников. Они гораздо лучше представляли себе, что такое "ФЯ". И, может быть поэтому, делали языки для скриптования и обучения программированию. Языки кое-как исполняемой спецификации и вовсе неисполняемые. +За десятилетие от PAL до HOPE изобретены, хотя-бы в первом приближении, все недостающие элементы, составляющие современный ФЯ. Но бурное десятилетие истории идей неприятно контрастирует с десятилетием стагнации истории имплементаций. +Конечно, производительность PAL неизвестна, но нет оснований считать, что производительность этой SECD-имплементации может существенно отличаться от производительности SECD-имплементации в конце семидесятых. +Новинка 70-х - SK-машина - не дает, на практике, никакого прогресса. +И даже использовать наработки других исследовательских программ не удалось. Трансляция ФЯ в Лисп почему-то производит Лисп, который исполняется медленнее чем написанный вручную как раз настолько чтоб и на этом направлении никаких изменений к лучшему не было. +Единственная идея, которая улучшила производительность получающихся программ - это вызов кода на более практичных, но менее функциональных языках 60-х. +Итоги 70-х вызывают массу вопросов. Почему не появилось компиляторов ФЯ в нативный код? Были ли для этого какие-то объективные препятствия или ФЯ просто не так повезло с имплементаторами, как Прологу? Может важно то, что Пролог - язык первого порядка? Почему тогда для Лиспа есть компиляторы не хуже? Почему код на Лиспе, в который транслируется ML, исполняется настолько медленнее? Почему он не компилируется, а интерпретируется? Почему типизированный язык транслируется в Лисп, а не в какой-нибудь типизированный язык со сборщиком мусора? Почему LCF и через десять лет после него HOPE разрабатываются на компьютерах из одной линейки? Что не так с развитием аппаратного обеспечения? Почему на том компьютере, на котором запускают бенчмарки в 83-ем году нет POP-2, HOPE, компилятора Пролога Уоррена и даже того же самого Лиспа, с которым Уоррен сравнивал свой нативный компилятор и в который транслировали оригинальный LCF/ML? +Пришло время разобраться с этими загадками. И в следующей главе мы начнем с компиляции ФЯ с помощью Лиспа. + + +Lambda the Ultimate Misunderstanding +==================================== + +Эдинбургская исследовательская программа объединяет не особенно значительное число исследователей и потому вполне естественно ожидать, что они будут использовать множество наработок других исследовательских программ. Которые, так уж вышло, полезны и для имплементации функциональных языков Эдинбургской программы. Итак, что они могут использовать для того, чтоб создать компилятор ФЯ? +В 70-е вывод типов имплементируют только в Эдинбурге. И имплементаторам ЯП все равно нужно имплементировать вывод типов самим - проверка типов делается до каких-то трансформаций. +Над паттерн-матчингом работает больше исследователей, но эти паттерн-матчинги, в основном, делают больше и медленнее, чем авторам ФЯ нужно. Как в языке О'Доннела, например. +Есть, правда, компилятор Уоррена. Нельзя ли компилировать ФЯ в Пролог? Авторы первых компиляторов ФЯ не стали этого делать, но разработчики Пролога об этом думали. Уоррен сформулировал [Warr81] свои идеи об этом в конфронтационном ключе. Заявил о производности и следующей из этого ненужности лямбды. А процесс трансформации лямбд в предикаты формально не описал. Ван Эмден и другие позднее представили эти идеи как технику трансляции ФЯ в Пролог [Emde90], но даже первая статья Уоррена вышла слишком поздно. К этому времени первые имплементаторы компиляторов ФЯ уже определились с теми способами, какими они будут ФЯ имплементировать. +Наиболее очевидная часть функционального языка Эдинбургской программы, которую можно позаимствовать и использовать в готовом виде - это, собственно, сам функциональный язык в более широком смысле - язык с первоклассными функциями. Язык без паттерн-матчинга и вывода полиморфных типов, в который будет транслироваться язык, в котором они есть. +POP-2 - один из таких языков, но, как мы видели, обладает не особенно хорошей производительностью и, по всей видимости, страдает от недостатка труда, вложенного в его совершенствование. Для того, чтоб строить на фундаменте, уже наработанном исследователями и имплементаторами, воспользоваться успехами более раннего и богатого направления, есть более подходящий кандидат - LISP. +Первый компилятор Лиспа начали писать еще в 1957-ом году Роберт Брайтон (Robert Brayton) и помогавший ему Дэвид Парк. Да, тот самый Девид Парк, который после этого будет так успешно имплементировать CPL. Брайтон и Парк писали компилятор LISP 1 на ассемблере. Параллельно Клайм Мэлинг (Klim Maling) писал компилятор Лиспа на Лиспе. Но проект написания компилятора на Лиспе был заброшен из-за очевидного для лисперов того времени превосходства в производительности будущего компилятора на ассемблере. Но в 1960-ом Брайтон ушел из МТИ и без него разобраться в компиляторе на ассемблере и развивать его лисперы уже не смогли [Blai70]. Какие выводы сделали Лисперы из этого опыта, который МаКарти кратко описывает как "неудачный" [McCa78]? Ну, не то чтобы у такого подхода было много работающих на практике альтернатив в то время. +Но для написания на ассемблере компилятора альтернатива все-таки была. Не позднее 1962, более известные другой своей работой, Тимоти Харт (Timothy P. Hart) и Майкл Левин (Michael Levin) написали компилятор LISP 1.5, который считается первым компилятором, скомпилировавшим самого себя [Hart62] [McCa62]. Этот компилятор Лиспа на Лиспе лисперы развивать смогли. +Компилятор Харта и Левина производит код, работающий в 40 раз быстрее интерпретатора [Hart62]. Или в от 10 до 100 раз, в зависимости от программы [McCa62]. В зависимости от каких именно свойств программы? Это мы еще рассмотрим подробнее. Бутстрап занимает всего 5 минут, при том, что большую часть времени большая часть компилятора еще интерпретируется. +И если уже в 62-ом году можно было компилировать язык с первоклассными функциями, то почему же не было компилятора Эдинбургского ФЯ даже уже в 1980? Лисп - это же ФЯ с первоклассными функциями, да? +Так же думал и Дэвид Тернер, когда в 72-ом году начал изучать Лисп. Но он быстро заметил что код делает не то, что он ожидал бы от языка, в котором есть лямбда [Turn12]. Тернер вчитался в документацию [McCa62] и понял, что... + +## LAMBDA не конструирует функцию. + +Проблему впервые обнаружил Джеймс Слагл (James R. Slagle), работая с первой имплементацией Лиспа. МакКарти, в своей истории Лиспа даже приводит псевдокод, соответствующий коду Слагла, при отладке которого лисперы впервые поняли, что что-то работает не так надо: + +``` +testr[x, p, f, u] <- if p[x] then f[x] else if atom[x] then u[] else + testr[cdr[x], p, f, λ:testr[car[x], p, f, u]]. +``` + +Да, разумеется, как обычно, это псевдокод, соответствующий коду. Не публиковать же в статье все эти скобки. +Слагл думал, что написал что-то вроде такого: + +```haskell +data Lisp = Atom String | Cons Lisp Lisp deriving (Show, Eq) + +testr x p f u | p x = f x +testr (Atom _) p f u = u () +testr (Cons h t) p f u = testr t p f $ \() -> testr h p f u +``` + +но на самом деле получилось у него что-то вроде этого: + +```haskell +testr x p f u | p x = f x +testr h@(Atom _) p f u = u h -- <== +testr (Cons h t) p f u = testr t p f $ \h -> testr h p f u +``` + +МакКарти первоначально считал, что это баг в имплементации интерпретатора, и первый имплементатор Лиспа Стив Рассел (Stephen Russell) его быстро исправит. Но оказалось, что интерпретатор делает все, как и задумывал МакКарти. Баг был в идее МакКарти. Конструкция `LAMBDA`, придуманная МакКарти, не имплементирует лямбду из ЛИ, в литературу по которому МакКарти не вникал, а просто позаимствовал название [McCa78]. Придуманная Джоном МакКарти (John McCarthy) конструкция - это средство метапрограммирования, создает описание кода функции, которую интерпретатор может выполнить, используя окружение в месте вызова интерпретатора. В описании кода функции есть свободная переменная с именем `x`, интерпретатор идет по списку пар и ищет в нем первый ключ `"x"`. И находит пару, которая в коде Слагла соответствует параметру функции. Это динамическая видимость. Для функционального программирования же нужна лексическая видимость. Нужно чтоб лямбда конструировала замыкание на окружение в месте вычисления лямбда-выражения. Интерпретатор искал не в том списке пар. Но другого у него и нет. +Рассел, более известный другой своей работой, нашел ошибку и, не без помощи Патрика Фишера (Patrick Fischer), придумал как ее исправить [McCa78]. Код на Хаскеле выше работает потому, что замыкания образуют стек для обхода дерева. Так что Рассел добавил в Лисп замыкания. + +``` +eval[(FUNCTION, f); b] = (FUNARG, f, b) + +apply[(FUNARG, f, b); x; a] = apply[f; x; b] +``` + +Удивительно, что описание конструирования и использования замыкания выглядят как уравнения с паттерн-матчингом, в отличие от обычных описаний на псевдолиспе. +Исправления ошибки, однако, не произошло. Ошибка была классифицирована как фича, а исправление как другая фича, которую можно и не использовать. Видимость по умолчанию не поменялась и конструирование замыканий сделано явным. Не настолько явным, как в POP-2. Ручная работа, которую Поплстоун не хотел автоматизировать, делается в первых Лиспах автоматически. Так что аргумент Поплстоуна для объяснения лисперских решений неприменим. +"Исправление" в Лиспах было сделано не позднее 1960-го [McCa60b], так что прошло не очень много времени для формирования традиции и для написания кода, работоспособность которого нужно сохранять. +Трудно объяснить динамическую видимость по умолчанию и производительностью. Как мы увидим в дальнейшем, лисперов очень беспокоила "глубина" окружения. Они хотели сделать представление окружения в памяти настолько плоским, насколько возможно. И если в наивной имплементации статической видимости она определяется глубиной вложения блоков, то в наивной имплементации динамической возможно придется ползти через ассоциативный список размером со стек вызовов, чтоб из глубин рекурсии прочесть значение переменной определенной до первого вызова [Bake78]. +Казалось бы, не важно, что конструировать замыкания нужно явно, если этот код на Лиспе все равно генерируется. Но это явное конструирование замыканий только одно из проявлений равнодушного отношения лисперов тех времен к функциональному программированию. Другим, более важным для компиляции ФЯ трансляцией в Лисп, было то, что наработки по компиляции Лиспа и улучшению производительности обычно не распространяются на эти замыкания. +В 1964-ом году началась разработка нового компилятора Лиспа для нового компьютера. Новый компьютер - той самой линейки, компьютер из которой станет не таким уж и новым новым компьютером в Эдинбурге через десять лет. А от нового компилятора произойдут и важная для нашей истории имплементация Лиспа в МТИ - MacLisp - и та, с которой Милнер познакомится в Стенфорде и будет использовать для имплементации LCF и ML в Эдинбурге - Stanford Lisp. +В 1966-ом году имплементатор этого нового компилятора Ричард Гринблатт (Richard D. Greenblatt) решал ту же проблему окружений, имплементированных как поиск имен в списках пар, что Тернер решал десятилетием позже. И решил, заменив основной способ имплементации окружений с ассоциативного списка на стек [Whit77]. И, более того, даже не такой как в ALGOL 60, в котором есть статическая цепь и дисплеи для имплементации передачи функций в функции. Предполагается, что вы пользуетесь функциями в новом Лиспе даже не как процедурами в ALGOL, а только как в BCPL. +В это время Лисп уже разделился на диалекты. Но имплементаторы BBN-Lisp, начала второго из двух основных семейств Лиспов, Дэниел Бобров (Daniel G. Bobrow) и Дэниел Мерфи (Daniel L. Murphy) позаимствовали Гринблаттовскую идею о имплементации окружения с помощью стека. При этом ошибочно [Weiz68] считая, что такое решение полностью равномощно ассоциативному списку [Bobr67]. +Лисперы придумали несколько способов имплементации окружения. Медленных и неправильных, побыстрее и тоже неправильных. И неправильных по-разному, в зависимости от того, интерпретируется код или скомпилирован. Придумали даже правильный но медленный способ. И программист на Лиспе мог и даже должен был постоянно выбирать вручную какие из этих изобретений использовать. Вот только как имплементировать окружения и правильно и быстро пока что придумать не удалось. Так закончилась первая неудачная попытка сделать Лисп функциональным языком. + +## И теперь у вас две FUNARG-проблемы + +> Компилятор старается имитировать действия интерпретатора настолько точно, насколько это возможно, чтобы интерпретируемый и компилируемый код вели себя одинаково. Примечательно, что для Лисп-систем такое свойство является скорее исключением, чем нормой. +> Джулиан Паджет, Три необыкновенных Лиспа. [Padg88] + +Второе дыхание FUNARG-проблемология получила после контакта лисперов с Ландином и Берджем. + +### MacLisp + +Одна из основных работ по теме этого периода - статья Мозеса [Mose70]. В ней Мозес вспоминает про работу Ландина в МТИ в 1967 и его ограниченное влияние на лисперов. Основное внимание уделяется описанию разных способов имплементации окружений в Лиспе и их развесистости в памяти. Костыльно-грабельный ландшафт, который Мозес обозревает вместе со своим читателем, выглядит мрачно. Вывод, однако, Мозес делает неожиданный. Он не считает проблемой все эти разновидности быстрых неработающих и медленных работающих связываний переменных, которых в других языках, "к сожалению" нет. Тем более, лисперам особо и не надо возвращать никакие замыкания. +Лисперам, который представляет главный пользователь MacLisp Мозес может и не надо. +Это, однако, не все лисперы МТИ. Есть еще разработчики высокоуровневых языков поверх Лиспа. Им надо самим имплементировать свои работающие окружения как структуры в куче [Stee96]. В MacLisp такие фичи для них не добавляют. Это обосновывается идеей, которая в наше время ассоциируется совсем не с Лиспом: не нужно платить за то, чем не пользуешься. +И если разработчикам высокоуровневых языков, разрабатывающихся в МТИ как правильная альтернатива резолюционизму, помощи от имплементаторов MacLisp ждать не стоит, нужно надеяться только на себя, то ради кого разработчики MacLisp стараются? Кто не будет платить, потому что не использует высокоуровневые фичи? Мы еще рассмотрим работу этих более важных пользователей MacLisp подробнее. Эти работы будут действительно полезны для функционального программирования, хотя не самым непосредственным образом и это не то, чего планировали достичь авторы этих работ. +Только в 75-ом году в MacLisp добавили фичу `PROGV` [Whit77], более-менее похожую на ручное замыкание из POP-2. Но насколько эффективно ее можно было бы использовать для имплементации ФЯ - неизвестно. Потому, что в Stanford Lisp - версии LISP 1.6, отколовшейся от МТИ-компилятора вместе с МакКарти, отколовшимся от МТИ, ничего такого не появилось. Если кто вдруг захочет возвращать функции - ему остается только интерпретатор, и поиски имен переменных в ассоциативном списке. +Именно эту версию Лиспа использовали в Эдинбурге и это объясняет то, что код на LCF/ML 70-х не компилировался, а интерпретировался. И объясняет производительность этого интерпретатора по сравнению с интерпретатором Лиспа. +Таким образом, едва ли можно говорить о преимуществе Пролога как языка первого порядка для имплементатора компилятора. Имплементация Лиспа, с которой он сравнивается более-менее компилирует только подмножество Лиспа первого порядка. +Итак, влияние Ландина и ISWIM/PAL в МИТ оказалось ограниченным и флагманская имплементация Лиспа продолжила неколебимо следовать прежним антифункциональным курсом. К счастью, такой недружественный к функциональному программированию Лисп уже не единственный Лисп. Появились уже имплементации, у создателей которых было другое мнение о нужности возвращения функций из функций. + +### Interlisp + +Весной 1968 более известный другой своей работой Джозеф Вейценбаум (Joseph Weizenbaum) написал одну из первых статей [Weiz68], объясняющих FUNARG-проблему. В ней он упомянул и заблуждения [Bobr66] имплементаторов BBN-LISP о том, что они могут решить FUNARG-проблему с помощью стека как в Алголе. Это может и лучше, чем стек как в MacLisp, но все еще недостаточно. +Упомянутые имплементаторы не стали упорствовать в своих заблуждениях, открыли для себя возвращение функций из функций и ссылаются на статью Берджа [Burg71] про это, продолжили работать над решением проблемы. И в 1970 году уже знакомый нам Дэниел Бобров с пришедшей на смену Мерфи Элис Хартли (Alice K. Hartley), придумали [Teit2008] более подходящий для этого нелинейный, древовидный стек. Стек, фрагменты которого не освобождаются, пока на них кто-то продолжает ссылаться, что определяется с помощью счетчика ссылок или даже сборщика мусора. Скорее всего с помощью сборщика мусора. Потому, что с помощью одного только счетчика ссылок не удалось сделать все что хотелось бы. Заодно, сборщик мусора может и компактифицировать слишком развесистый стек. +Казалось бы, лисперы не должны быть недовольны использованием сборщика мусора, но тут не все так просто и к этому мы еще вернемся. +Статья об этом стеке [Bobr73] была отправлена в издательство в 72-ом и напечатана в 73-ем. Новым соавтором Боброва стал Бен Уэджбрейт (Ben Wegbreit), который до того занимался имплементацией бэктрекинга. Хартли не упоминается в статье, но указана в анонсе [Teit78] новой фичи как имплементатор. +Этот новый стек Боброва, Хартли и Уэджбрейта обычно называется спагетти-стеком, но это название не единственное и не используется в первой статье [Bobr73]. Но такое название самое популярное в среде лисперов. Не смотря на то, что в отличие от многих других (вроде кактус-стека) оно происходит от очень плохой визуальной метафоры. Спагетти не разветвляются, что очень сложно не заметить. Возможно, что название дано критиком и спагетти в нем означают только нежелательную запутанность, как в спагетти-коде. +Изобретатели спагетти-стека ожидали, что если использовать язык со спагетти-стеком как язык со стеком обычным, то работать все будет не намного медленнее [Stee96]. Но этот способ неуплаты за неиспользуемое лисперы МТИ все равно посчитали нежелательным, и решили ничего такого не делать [Whit79]. +Если же эти возможности использовать для возвращения функций, бэктрекинга или корутин, то уже не все так хорошо. Исследователи, работающие в МТИ над высокоуровневыми языками на базе Лиспа, раскритиковали [Stee77m] спагетти-стек за ряд недостатков. Корутины и возвращаемые из функций функции удерживают больше стека, чем нужно. Это также не особенно быстрый способ имплементировать рекурсию. Но все равно имплементировали что-то похожее своими силами в МакЛисповой куче [Stee96]. +Между изобретением спагетти-стека и его использованием для имплементации Лиспа прошло немало времени [Teit74]. Элис Хартли закончила имплементацию в бывшем BBN-LISP (который к этому времени уже назывался Interlisp) в 1975-году [Teit78]. +Разница в поддержке функционального программирования имплементацией - не единственное и не главное отличие между двумя основными ветвями развития Лиспов. MacLisp был более традиционным средством разработки, интерпретатором и компилятором работающими с текстом, написанным в текстовом редакторе. Основные усилия разработчиков были направлены на качество генерируемого кода (если вы, конечно, не генерируете его из кода функционального). У разработчиков Interlisp были другие приоритеты. Программирование на Interlisp предполагало то, что сегодня ассоциируется с совсем другим языком, разрабатывающимся в это время по соседству. А именно манипулирование супом из объектов в памяти. Сохранение и загрузку образов этого супа из нечеловекочитаемых файлов. В добавок к этому, манипулирование должно было происходить с помощью текстовых команд, которые среда исправляет так, как считает нужным. Весь фокус имплементаторов был на инструментарии для этого. + +### LISP/370 + +Статьи Берджа оказали некоторое влияние на имплементацию Interlisp, но главного успеха в продвижении ФП в среде лисперов Бердж добился в другом месте. К сожалению, в не самом важном месте для развития Лиспа. Разумеется, этим местом была уже известная нам лаборатория IBM в Йорктаун Хайтс. +Ван Эмден вспоминает [Emde06], что в Йорктаун Хайтс Лисп влачил периферийное, а иногда даже подпольное существование. Использовался он для имплементации уже знакомой нам системы компьютерной алгебры SCRATCHPAD. Но у Фреда Блэра (Fred W. Blair) были более амбициозные планы, касающиеся Лиспа. МакКарти сделал Лисп на 85% правильно, объяснял Ван Эмдену Блэр, осталось исправить оставшиеся 15, добавить в Лисп первоклассные функции. Об этих планах Блэр рассказывал, по видимому, в 71-ом году, когда Ван Эмден посещал лабораторию IBM. И годы после этого планы все оставались планами. И Ван Эмден считает в своих воспоминаниях само собой разумеющимся, что проект Блэра вскоре помер. Этого, однако, не произошло. +Разработка Lisp/370 по настоящему началась только в 74-ом году [Padg88]. Встречаются ссылки на отчет Блэра 76-го года, но до нас дошла только версия 79-го [Blai79], описывающая раннюю версию LISP 1.8+0.3i. Да, номер версии - комплексное число. +Блэр, сам один из авторов обзоров разнообразных лисповых переменных и областей видимости [Blai70], и относившийся к этому разнообразию хуже Мозеса, все-таки разнообразие по большому счету сохранил. Не полностью, отличия в работе областей видимости в одном и том же коде между компилятором и интерпретатором он посчитал нежелательными. Также Блэр поменял умолчания. Лямбда по умолчанию создает лексические замыкание. Имплементация основана [Padg88] на спагетти-стеке Боброва и его развитии Стилом [Stee77m]. Блэр ссылается на работы обоекембриджской программы и работы Милнера и Гордона по описанию семантики Лиспа. Описывает семантику своей имплементации с помощью SECD-машины. Описывает данные с помощью нотации Ландина. +Сначала Lisp/370 был внутренней разработкой для разработки внутренней разработки, но со временем стал продуктом, за который нужно было платить IBM $1500 в месяц. После двух лет можно было перестать платить, так что Lisp/370 обошелся бы своему пользователю не больше чем какие-то $36000 [Whit77] ($182000 в 2023-ем году). Это был если и не первый компилятор ФЯ в широком смысле, то по всей видимости первый, который продавался. Сложнее сказать, покупался ли. Но это серьезно помешало бы использовать его как бэкенд для компилятора эдинбургского ФЯ. Какой-нибудь MacLisp и прочие университетские разработки того времени разрабатывались на государственные деньги и были общественным достоянием. Более серьезной проблемой было то, что Lisp/370 работал на машинах, которых не было у большинства лисперов и у имплементаторов эдинбургских ФЯ. Почему так получилось мы разберемся позже. И портировать этот Лисп на те машины, которые у них были, было бы сложно из-за плохой портируемости имплементации, написанной в значительной степени на ассемблере. Да и IBM было не особенно интересно это делать. +Не смотря на такую изолированность этого Лиспа от истории ФЯ, он на нее все-таки повлиял. Над этим ФП-лиспом поработали Джон Уайт (Jon L. White) и Ричард Гэбриел (Richard P. Gabriel) [Whit77] [Stee96] и этот опыт позднее сделал их сторонниками функционализации Лиспа. +Как мы уже отмечали, Коэн необоснованно строг к изобретателям и имплементаторам Пролога. Их переход от идеи до компилятора рекордно быстрый. Лисперы же смогли наконец скомпилировать лямбду только почти через двадцать лет. Конечно, справедливости ради нужно сказать, что они не очень-то и хотели. Успеют ли имплементаторы эдинбургских ФЯ скомпилировать лямбду хотя-бы за это же время? В конце 70-х время у них еще есть! + +------------------------ + +Более-менее поддерживающие функциональное программирование варианты Лиспа опоздали к началу разработки LCF/ML. Но, до того, как некоторые проблемы стали очевиднее и до того, как появились средства получше была пара лет, окно возможностей для того, чтоб скомпилировать ML или HOPE например в Interlisp. Это не было сделано. Имплементаторы эдинбургских ФЯ для своих попыток имплементации ФЯ с помощью Лиспа выбирали антифункциональное MacLisp-семейство. Может быть это просто случайность, вопрос моды, и если бы Милнер поработал в Калифорнии на пару лет позже, то он привез бы в Эдинбург оттуда моду на Interlisp. И LCF/ML был бы компилятором с самого начала. Да, современный Лисп происходит из MacLisp семейства, а Interlisp умер вскоре после того, как ФЯ стали компилировать через Лисп. Но можно ли было предвидеть это в 70-е? Может это какой-то эффект отбора и мы знаем о функциональном программировании потому, что Лисп был выбран правильно, а про AFFIRM транслирующийся в Interlisp, например, не слышали? Насколько важным для истории ФП было выбрать правильный Лисп? Рано или поздно мы это выясним. +Не смотря на то, что началась с прибытия Ландина в МТИ, вторая волна функционализации Лиспа породила имплементации, которые были еще новыми и даже экспериментальными десятилетие спустя, в конце 70-х. Как раз вовремя, чтоб их затмили результаты волны третьей. + +## Лишняя деталь + +> Иногда здравомыслящему дизайнеру удается проанализировать накопившийся набор идей, отбросить менее важные и получить новый, небольшой и чистый дизайн. +> Это не наш случай. Мы на самом деле пытались создать нечто сложное, но обнаружили, что случайно получили нечто, отвечающее всем нашим целям, но гораздо более простое, чем мы собирались сделать. +> Джеральд Сассман, Гай Стил [Stee98] + + +Итак, вторая волна функционализации Лиспа бессильно разбилась о неприступную крепость МТИ, никак не поколебав уверенности тамошних лисперов в том, что ФП им ненужно. Но в то же самое время функциональное программирование уже завоевывало их изнутри. После того, как они сами переизобрели его, отсекая все лишнее от своих экспериментальных антирезолюционистских противологических языков. +Как мы еще помним, в 1970 гости Эдинбурга из МТИ, одним из которых был Джеральд Сассман (Gerald Jay Sussman), убеждали не связываться с резолюционизмом. Что же они предлагали вместо этого? Много чего, но ничего работающего. +В 1969 Карл Хьюит спроектировал "крайне амбициозный" язык Planner с паттерн матчингом и бэктрекингом. Этот язык так никогда не имплементировали полностью, но спроектировали и имплементировали язык для его имплементации - MDL. MDL хотя бы использовался для того, чтоб имплементировать ранние прототипы CLU [Lisk93] и игры Zork. Не такие амбициозные проекты как Planner. Почему бы не транслировать Planner в Лисп? Из-за отсутствия в нем в то время структур данных кроме пар и динамической видимости. +В 1971 Сассман с коллегами имплементировал подмножество Planner под названием Micro-Planner. Правда, имплементировали они его неправильно. Micro-Planner не использовал для матчинга алгоритм унификации. Просто как-то матчил. И формального описания языка не было, и трудно было сказать как матчить надо было. Но Сассман считал, что в ряде сложных случаев он матчил не так. Как Сассман впоследствии рассказывал новому важному герою нашей истории Гаю Стилу (Guy Lewis Steele, Jr.), первой корректной имплементацией Micro-Planner был Пролог. Эти проблемы с практичной альтернативой резолюционизма, однако, не поколебали уверенности МТИ в превосходстве практичности над резолюционизмом. Практичность решили еще усилить. +В 1972, не в силах преодолеть ограничения Micro-Planner, Сассман с коллегами решили, по крайней мере, преодолеть бэктрекинг. Бэктрекинг - рассуждает Сассман - это просто переусложненный способ выразить обычный перебор с помощью вложенных циклов. Вот пусть пользователь языка и пишет вложенные циклы. Так ему будет понятнее, что он делает и нужно ли ему это делать. Так появился следующий антипролог МТИ - Conniver. Этот язык был достаточно простым, чтоб имплементировать его на основе MacLisp. Но не без собственного, похожего на Interlisp-идеи стека в куче. +Дальше эти более явные средства перебора стали совершенствовать. И мы уже знакомы с вершиной этой эволюции средств перебора. С восстания против них началась история ленивых ФЯ. Это акторы, решающие проблему кромок в языке Planner-73, позднее названном PLASMA (PLAnner-like System Modeled on Actors) [Stee98]. Да, это все пока выглядит как история идущая куда-то не туда, в сторону от истории ФП. Не беспокойтесь, все еще можно исправить. Остался еще один шаг, последний антипролог МТИ. +Создания этого антипролога не произошло бы, если б Сассман не был должен читать курс, который заставлял его думать о лямбда исчислении, а Стил не пытался бы понять акторы [Stee75]. Стил учился в МТИ и совмещал учебу с работой, поддержкой MacLisp. Как разработчик MacLisp, он интересовался конструкциями, которые разработчики антипрологов организуют в куче. Чтоб сделать более-менее нормальную видимость, ведь MacLisp им с этим не помогает. PLASMA была актуальным антипрологом в это время и Стил привык к тому, как работают акторы, но не то чтобы понял. Потому, что не мог никому объяснить как они работают. И лучший способ что-то понимать - это, конечно же, написать игрушечный интерпретатор. К чему Сассман со Стилом и приступили. Почему они писали один интерпретатор и для лямбд и для акторов? Акторы требовали видимости как в Алголе, ну или как в лямбда-исчислении. +Первоначально интерпретатор был с вызовами по имени, как и полагается для интерпретатора ЛИ. Но Стил с Сассманом столкнулись с проблемой: итерация громоздила в памяти кучи санков, пропорциональных числу итераций. Решение было найдено, но слишком поздно. Только после того, как интерпретатор переделали на вызов по значению, а ЛИ расширили условным оператором. Энергичным этот язык так и остался. А вот чего в нем не осталось, так это акторов. Потому, что в ходе имплементации Стил с Сассманом обнаружили, что лямбды и акторы имплементированы одинаково. Поэтому они решили, что акторы - то же самое, что и замыкания. Так что Стил с Сассманом просто выкинули все наработки многолетней борьбы с Прологом, кроме одной - статической видимости. Лучше бы оставили еще одну, но об этом позже. +Так замкнулся круг борьбы с резолюционизмом: от Лиспа борцы пришли к Лиспу, но только с работающими лямбдами. ООП как в SmallTalk было серьезной заявкой на абсолютный антипролог, спору нет. Но ничто не сделает Пролог настолько ненужным, как функциональное программирование. +Язык назвали в духе предыдущих - Schemer. Но в ОС на которой Schemer разрабатывался было ограничение на длину имен в 6 символов. В это ограничение не влезали ни Planner ни Conniver. Но только используемое для имени команды урезанное название Schemer было настоящим словом, а не сочетанием согласных вроде `PLNR`. Так что язык стали называть SCHEME. Перове описание языка вышло как отчет [Stee75] в конце 1975. +Акторы оказались не нужны в Схеме из-за того, что это просто функции, которые ничего не возвращают, а продолжают вызывать другие функции [Stee98]. И не то чтобы Стил собирался что-то возвращать из остальных. Он не собирался останавливаться на наивном интерпретаторе, а хотел компилировать функции в производительный код [Stee77]. Было бы неплохо, если б и циклы оказались ненужными. + +## Сказки Венского леса + +> Я в каком-то смысле обескуражен. Во многих смыслах, я бы сказал. +> Эдсгер Дейкстра + +> Нет-нет! <..> Нет-нет-нет! <..> Что! Что? Что? +> Адриан Ван Вейнгаарден + +Первый способ компилировать функции в производительный код был изобретен Адрианом Ван Вейнгаарденом, автором Алголов и научруком Дейкстры и Ван Эмдена. И в сентябре 1964 Ван Вейнгаарден представил [Wijn66] свои идеи на рабочей конференции IFIP в Бадене близ Вены. Представил, правда, как способ транслировать производительный код в функции, что вызвало не самую хорошую реакцию у аудитории. +В докладе он неформально описал трансляцию из алголоподобного языка в меньший язык, в котором остаются только конструкции, семантику которых, по мнению Ван Вейнгаардена, будет легче описать. Процедуры он считает конструкцией, которую описывать легко. Именно процедуры, даже функции преобразовывает в процедуры добавлением параметра. А `goto` и метки описывать сложно, так что он и их преобразовывает в вызовы процедур и их декларации. +Интересно, что этот доклад слушал Стрейчи, который позднее будет вместе с Вадсвортом несколько лет ломать голову над тем, как транслировать `goto` в лямбды. Пока они не переизобрели то, о чем Стрейчи должен был бы узнать из этого доклада. О CPS-преобразовании. Там же присутствовал МакКарти, который тоже не смог позднее помочь Локвуду Моррису переизобрести продолжения. На конференции присутствовал и Ландин. Но Ландин не подал виду, что Локвуд Моррис изобрел что-то уже известное Ландину, когда Моррис докладывал о своих результатах в Лондонском колледже королевы Марии. +Эту полную непроницаемость аудитории для идей Ван Вейнгаардена Рейнольдс в своей работе [Reyn93] называет главной загадкой истории продолжений. Рейнольдс не надеется, что загадка будет когда-то разгадана со всей определенностью, хотя можно и строить предположения. Ну что же, давайте строить предположения. Для строительства предположений есть материал: сохранился не только доклад, но и стенограмма его обсуждения. И, как точно подметил Рейнольдс, "последовавшая за докладом дискуссия выявила глубокие философские различия между ван Вейнгаарденом и остальными исследователями". +Вейнгаарден, судя по всему, потерял Стрейчи, когда сказал, что транслирует функции в процедуры. Когда началось обсуждение, Стрейчи заявил, что Вейнгаарден "убрал из языка все, про что люди думают, что это важно". И "оставил то, что все считают неважным". Стрейчи недоволен, что Вейнгаарден заменяет функции на процедуры, он бы хотел чтоб было наоборот, потому что функции - более изученные математические объекты. Вейнгаарден отвечает, что это вопрос "вкуса", ему процедуры понятнее. Возможно, Стрейчи и не слушал остальной доклад потому, что не сделал никаких комментариев по более важной для нашей истории его части. +Примерно та же история и с МакКарти, он в основном дискутирует что более фундаментально - числа или строки. +Комментарий Самельсона из той же серии, но ближе к важной части доклада. Самельсон спрашивает, почему `goto` заменены процедурами, а не наоборот. `goto` выглядит для него более фундаментальной конструкцией. Вейнгаарден отвечает, что не знает, как описать семантику `goto`. +Дейкстра спрашивает, доказал ли Вейнгаарден корректность трансформации. Вейнгаарден отвечает что нет, но выглядит так, что должно работать. Из других вопросов понятно, что Дейкстра почему-то решил, что трансформация заменяет вызовы функций на `goto` а не наоборот. В результате трансформации остаются только `goto`. В принципе, не самое плохое направление мысли для имплементатора ЯП, но не в этом случае. Вейнгаарден уверяет, что нет, наоборот, никаких `goto` не остается. +Доклад слушают имплементаторы языков и их, конечно, интересуют вопросы имплементации. Но нельзя сказать, что они сами сделали важные для имплементатора выводы из доклада. Видимо узнать в чем-то молоток, когда нужно забивать гвозди - сложнее, чем видеть везде гвозди, когда в руках молоток. +Горн спрашивает, будет ли трансляция осуществляться какой-то программой, видимо, как часть процесса компиляции. Вейнгаарден отвечает, что это разделение на два языка с производными конструкциями и без делается в основном для упрощения спецификации языка. Далее Горн сомневается в том, что такие преобразования сохранят смысл программы, но видимо не совсем понимает, что делает Вейнгаарден и Вейнгаарден не совсем понимает в чем заключаются его претензии. +МакИлрой говорит, что ему в принципе нравится выделение минимального языка. Особенно с точки зрения имплементатора языка. Но ему не нравится, что `goto` заменен на процедуры. И, значит, нужно тратить память, чтоб "поддерживать всю историю вычисления". Вейнгаарден отвечает, что МакИлрой должно быть имеет в виду конкретную имплементацию процедур. Но после преобразования процедуры никогда не возвращают, только вызывают следующую перед своим окончанием. А значит можно использовать имплементацию, которая ничего не делает для того, чтоб можно было возвращаться из процедуры. А в этом, по мнению Вейнгаардена и есть вся сложность имплементации процедур. Процедуры, нужные после трансформации проще. Они то же самое, что и `goto`. +Таким образом, Вейнгаарден явно проговаривает главную, самую важную для имплементатора идею в обсуждении, хотя сам доклад сфокусирован на другом. Ничего слушателям самим и не нужно было додумывать. +На этом стенограмма обсуждения заканчивается. Реакция МакИлроя и прочих имплементаторов на переформулированную идею либо не сохранилась, либо ее и не было. Последнее вполне возможно. Рейнольдс, когда работал над историей продолжений, запрашивал комментарий у МакИлроя. Дуглас МакИлрой (Douglas McIlroy) в письме Рейнольдсу [Reyn93] вспоминает, что так и не увидел никаких практических примеров, только трюк для демонстрации того, что и так известно. +Но идеи Вейнгаардена не были просто проигнорированы. Результат доклада был гораздо хуже. +МакИлрой вспоминает [Reyn93], как на следующий день во время утреннего перерыва Дейкстра записывал на салфетке свои идеи структурных команд для выхода из цикла. Он не сделал из доклада Вейнгаардена вывод о том, что `goto` можно использовать для имплементации функций. Он сделал вывод, что `goto` не нужен. И он не собирался заменять `goto` эффективно имплементированными функциями, он собирался заменять `goto` конструкциями, которые для имплементации функций использовать в общем случае нельзя. В последующие годы Дейкстра сформулировал мем, носители которого будут уничтожать любую возможность использовать какой-то язык как целевой для компиляции ФЯ с помощью CPS, даже не зная о существовании продолжений и давно найдя для этого другие обоснования. Война с продолжениями идет так успешно, что даже низкоуровневые VM вроде LLVM и WebAssembly сделаны непригодными для эффективной компиляции в них ФЯ. +Как и компиляция с помощью CPS-трансформации, противофункциональная идея Дейкстры берет все возможное от того факта, что процедура и `goto` - это одно и то же. Если бы формулировка была "procedures considered harmful", знакомящиеся с ней могли бы засомневаться: так ли плохи процедуры на самом деле? Но процедура выступает в своей наиболее неприглядной форме - `goto`. И сомнения в оправданности борьбы возникают гораздо реже. Поскольку ни Дейкстра, ни его последователи особо не интересовались и не интересуются тем, с чем они на самом деле борются, полноценной поддержки ФП взамен исключаемой обычно не добавляется. План Дейкстры не существует, но действует. +Ну, по крайней мере, как мы помним, Хоар придумал АлгТД современного вида как борьбу с голыми указателями, по его замыслу, аналогичную борьбе с `goto`. Не самый прямой и даже объяснимый эффект, но определенно положительный. + +## Пожалуйста, возвращайтесь + +Было ли изобретение Вейнгаардена жертвой самого недопонятого из всех недопониманий этой главы? Или у имплементаторов ЯП были более серьезные основания отмахнуться от него как от простого курьеза, ничего не дающего на практике? Но что плохого в эффективных функциях Вейнгаардена? Что тут не любить? +Начнем с того, что алгоритм Вейнгаардена содержит ошибку [Reyn93]. Что справедливо подозревают его оппоненты во время обсуждения, хотя и не могут на эти ошибки указать. Но это проблема не особенно значительная. В первой половине 71-го года Джеймс Моррис, конечно же изобрел продолжения независимо, в этот раз независимо и от другого Морриса, который в свою очередь изобрел их независимо от него. Моррис описал CPS-трансформацию подробнее и исправил ошибку обработки вложенных блоков. Также он не транслирует функции в процедуры - столкновение со Стрейчи вполне можно было избежать. Но Джеймс Моррис, в отличие от многих других изобретателей продолжений, был разоблачен. Это все уже изобретено, в публикации отказано. Опубликовано было только письмо в редакцию [Morr72], в котором Моррис кратко излагает собственную идею, для реализации которой он и переоткрыл CPS-преобразование. Также он называет все это бесполезным на практике. И это, судя по всему, нормальная реакция имплементатора того времени на "быстрые" функции Вейнгаардена. То, что по замыслу Вейнгаардена делает их быстрыми, также делает их крайне непривлекательными и даже и бесполезными с точки зрения имплементатора ЯП. +Моррис придумал CPS-преобразования во время экспериментов по энкодингу структур данных в лямбда-исчислении. И применил для обхода ограничений языков на возвращение значений из функций. ALGOL 60 не может возвращать из функций многое из того, что функции могут принимать через параметры. Можно передать функции несколько параметров, но нельзя вернуть несколько результатов. Нельзя вернуть массив или функцию, но и то и другое можно передавать в алголовскую функцию. И CPS-преобразование для Морриса - это трюк, который позволяет преобразовать псевдокод на псевдоалголе, который все это возвращает, в "работающий" код на ALGOL 60, который все это только принимает. Разумеется, возвращение всего этого в Алголе запрещено не просто так. Моррис это понимает, но про CPS-преобразование в это время пишут и то, что, скажем так, не способствует серьезному отношению имплементатора ЯП к этой идее. В качестве примера рассмотрим работу очередного независимого изобретателя продолжений. +В январе 1972 Майкл Фишер (Michael J. Fischer) сделал доклад [Fisc93] на конференции. Фишер впервые [Reyn93] доказал, что CPS-преобразование сохраняет смысл преобразуемого кода. То, чем интересовался Дейкстра во время обсуждения доклада Вейнгаардена за почти восемь лет то этого. Но крайне маловероятно, что новый доклад понравился бы Дейкстре. Для чего Фишер применил CPS-преобразование? Есть две стратегии управления памятью, рассказывает Фишер. В одном случае память удерживается пока нужна, что определяется сборщиком мусора. В другом случае память удерживается до возврата из функции. С помощью стека. Не все корректные лямбда-выражения можно корректно вычислить при использовании второго метода управления памятью. Он освобождает память раньше времени. Но мощность обоих методов одинаковая, утверждает Фишер. Ну, это явно неверное утверждение. Но погодите, Фишер технически прав, и эта его правота полностью бесполезна на практике. Следите за руками: Фишер может преобразовать любое лямбда-выражение вот так и после этого, при его вычислении с помощью стека никогда не окажется, что что-то нужное для вычисления освобождено. Да, потому, что вообще ничего никогда не освобождается. CPS-преобразование, функции никогда ничего не возвращают. Это звучит как шутка, но нет. Все серьезно. Одна из целей этой работы показать, что управления памятью как в MacLisp "достаточно" для "всего". И дальше таких идей от лисперов будет только больше. +Разумеется, имплементатор ЯП этого времени не хочет, чтоб функции никогда ничего не возвращали. Он не так давно, можно сказать, только что изобрел способ автоматического управления памятью, который намного быстрее сборки мусора и хочет использовать его побольше. Да, функция - это важнейшее средство управления памятью [Dijk60] [Stee77]. "Улучшение" функций ценой потери такого важного средства для него неприемлемо. +Правда, Моррис пишет, что трюк бесполезен только для существующих имплементаций. Может быть можно как определить что нужно освободить перед хвостовым вызовом, предполагает Моррис. Смогут ли Сассман и Стил решить эту проблему? + +## Удивительные приключения кроликов + +Итак, популяризация ФП в среде лисперов по настоящему началась, когда Джеральд Сассман и Гай Стил переоткрыли ФП заново. Под влиянием того же, под влиянием чего это сделали Обоекембриджцы: ALGOL 60 и лямбда-исчисления. Но, в этот раз ФП изобрели, отрезая лишнее от объектно-ориентированного антипролога. Наконец-то на ФП обратили внимание в самом мейнстриме Лиспа, а не в каком-то ответвлении. +На протяжении десятилетий одни лисперы объясняли другим лисперам, что такое FUNARG-проблема и как её (не) решать. Объясняли что функции, с которыми можно обращаться как с объектами - не то же самое, что представления функций, с которыми можно обращаться как с объектами. Но настоящий золотой век FUNARG-проблемологии начался со статей "Lambda The Ultimate". +В серии публикаций Сассман и Стил описывают Схему - "full-funarg" диалект Лиспа с ключевыми фичами для ФП в самом широком толковании: лексической видимостью, замыканиями для решения FUNARG-проблемы, оптимизацией хвостовых вызовов. +В статьях третьей волны обсуждения FUNARG-проблем [Stee75] [Stee77] [Bake78] смелее критикуется динамическая видимость и отличия в работе скомпилированного кода от кода, исполняющегося в интерпретаторе. И критика не ограничивается тем, что без лексической видимости и первоклассных функции язык не будет иметь близкую к лямбда-исчислению семантику. Динамическая видимость критикуется с позиций производительности. +Лисперы защищали динамическую видимость из-за того, что считали, что лексическая видимость не может быть имплементирована так же эффективно. Сассман и Стил утверждают, что это заблуждение. Лексическая видимость может быть имплементирована эффективнее, чем динамическая. И поддержка опциональной динамической видимости замедляет работу имплементаций окружений вроде спагетти-стека. К тому же, лексическая видимость и оптимизация хвостового вызова не независимы, динамическая видимость не позволяет оптимизировать хвостовой вызов [Stee77]. И Сассман со Стилом считают, что им есть что продемонстрировать для подтверждения их убеждений. +CPS-трансформация, преобразующая все вызовы в хвостовые и их имплементация как переход к метке - центральная идея, используемая Стилом для имплементации ФЯ. Первый компилятор Схемы под названием CHEAPY, написанный им, делает практически только это. Это пруф-оф-концепт, половина - код из отчета [Stee76] ноября 76-го года "LAMBDA: The ultimate declarative", другая половина - простой транслятор без оптимизаций в другой язык. +CHEAPY показал, что компилировать ФЯ таким образом можно, и Стил приступил к более амбициозному проекту. Для защиты своей магистерской диссертации, он не позднее мая 77-го написал первую версию второго, гораздо более сложного компилятора Схемы под названием RABBIT. Научруком был Джеральд Сассман. На этом развитие компилятора не закончилось, он был изменен для поддержки второй версии Схемы, а его оптимизатор был полностью переписан. Эта версия RABBIT описана в отчете [Stee78], вышедшем в мае 1978. +Да, у этого компилятора, уже есть оптимизатор. Но это все еще прототип компилятора, точнее даже прототип части компилятора. Стил не писал кодогенератор и рантайм, использовав как бэкенд компилятор MacLisp. Схема транслировалась в очень небольшое и низкоуровневое подмножество Лиспа. Разумеется, использовать лисповые функции и объявления переменных нельзя, они работают медленно и неправильно. Точнее нельзя использовать функции как функции. Лисповая функция используется как "модуль". Функции Схемы становятся метками и командами перехода к ним `GO` внутри Лисповой процедуры `PROG`. В MacLisp был `goto`. Но в стандартном и лучше всего имплементированном на PDP-10 системном языке BLISS, аналоге BCPL, `goto` не было [Stee76b]. До автора этого языка к этому времени уже дошли идеи Дейкстры. Ну, разработчики MacLisp особое внимание уделяли качеству генерируемого кода, так что это не самый плохой выбор для бэкенда. И можно, наверное, надеяться, что у флагманской имплементации языка, который использует сборку мусора уже двадцать лет, будет неплохой сборщик мусора. Мы к этому еще вернемся. +Для компилятора планировался фронтенд, транслирующий какой-нибудь алголоподобный язык в Схему. К этому времени описатели семантики ЯП наработали много идей по представлению разных, в том числе и императивных конструкций в ЛИ. И Сассман и Стил считают серьезным преимуществом корректной имплементации ЛИ то, что все это можно использовать для имплементации такого фронтенда. Тем более, что такой энкодинг императивных конструкций не приводит к неприемлемому росту размера кода. Поскольку имплементация функций эффективная - поддержка циклов в ядре языка не нужна. Получающиеся в результате трансляции цикла лямбды будут скомпилированы в тот же `goto`. Транслятор из алголоподобного языка не был сделан, но подход опробован трансляцией Схемы в её подмножество, которое используется как промежуточный язык. Сассман и Стил сделали обзор этих трансляций в отчете "Lambda: The Ultimate Imperative" [Stee76b], вышедшем в марте 76-го года. +И главная, наиболее интересная для истории ФЯ часть RABBIT - трансформации этого промежуточного языка, расширенного лямбда-исчисления. Ну или двух языков: до и после CPS-преобразования. Все эти подмножества Схем никак статически не проверяются и не разделяются все равно. +Небольшой набор преобразований транслирует это расширенное ЛИ в императивный код. Как утверждает Стил, такой код, который ожидается от традиционного компилятора. Большое количество традиционных техник оптимизации - частные случаи небольшого количества оптимизаций, имплементированных в RABBIT. Это не случайность, а правильный выбор базиса. CPS-преобразование разрешает важные проблемы компиляции естественным образом: промежуточные значения становятся явными аргументами, также становится явным и порядок вычислений. +Компилятор как бы примиряет подходы Бурсталла и Уоррена. Компиляция не требует ручного управления, и тем не менее размах трансформации должен был производить впечатление в свое время. Трансформационная часть пайплайна - десять проходов: + +* Три прохода предварительного анализа. Поиск мутаций, сайд эффектов и тривиальных выражений - того, что можно оставить вычислить MacLISP. +* Оптимизации, которые данные этих анализов используют. +* CPS-преобразование. +* Четыре прохода анализа окружений и замыканий. +* Генерация кода на малом подмножестве MacLISP. + +Лексическая видимость означает, что получить доступ к переменным окружения может только или код в этом замыкании, или код, получивший их через параметр. Так что представление окружения не должно быть какого-то стандартного формата. Это абстрактные данные, с которыми работают только через интерфейс "вызов функции". Внутренне представление может быть выбрано наиболее подходящим для каждого случая. И RABBIT выбирает разместить окружение в регистрах, на стеке или в куче. +Да, осуществлены мечты Морриса о том, чтоб имплементация размещала и освобождала память с помощью стека корректно, даже если функции ничего никогда не возвращают. Если бы кто-нибудь написал транслятор ALGOL 60 в Схему, то RABBIT генерировал бы код для ALGOL 60, который все окружения размещает в регистрах или на стеке, как и полагается компилятору Алгола. Ничего не понадобилось бы размещать в куче и обходить сборщиком мусора. Для имплементации с помощью RABBIT языка, который можно имплементировать без сборщика мусора, сборщик мусора и не понадобится. Не смотря на все эти лямбды в промежуточном коде. Быстрые функции не заставляют на самом деле жертвовать управлением памятью с помощью стека. +Те окружения, которые все же надо размещать в куче из-за возвращения функций из функций, например, не представляют ничего интересного. Стил упоминает, что они могли бы быть имплементированы с помощью недавно добавленных в MacLisp непрерывных структур в памяти вроде массивов и рекордов. Структур не являющихся парами, которыми обычно был ограничен Лисп. Это было сделано в PLASMA, но это не сделано в RABBIT, окружения в памяти представлены как списки. Разумеется в них не нужно искать имена. Это же лексическая видимость, все положения переменных в списках известны статически. +RABBIT и CHEAPY в основном написаны на Схеме и транслируют сами себя в MacLisp. CHEAPY - меньше 10 страниц кода (примерно 700 строк, если судить по распечатке кода RABBIT), "мог бы быть написан" за день работы. Мог бы, но едва ли Стилу дали бы поработать над ним целый день. +Окончательная версия RABBIT от 15 мая 1978 это 3290 LOC [RABBIT]. Ну, по сравнению с функциональными Эдинбургскими достижениями - неплохо. На первую версию RABBIT для магистерской диссертации ушел один человеко-месяц. На переписывание оптимизатора и получение той версии, которая описывается в отчете - еще два. Но эти два человеко-месяца работы были проделаны в течение восьми календарных месяцев. Каждую неделю Сассман тратил на разработку компилятора не более десяти часов. Потому, что его машинное время было ограничено и он мог запустить разрабатываемый им компилятор только один или два раза за ночь. +Надо полагать, что именно так и прочим героям этой истории удавалось за годы написать только единицы тысяч строк кода. +У читателя может возникнуть вполне закономерный вопрос: почему RABBIT не поучаствовал в ФП-бенчмарках прошлой главы? Дело в том, что нам мало что известно о производительности генерируемого им кода. В отличие от Уоррена, который с удовольствием сравнивал свой компилятор с другими, Стил сравнивает RABBIT только с интерпретатором Схемы и с самим собой. И то сравнению особого внимания не уделяется. Неизвестно на каком коде сравнивается. Надеемся, что на более-менее серьезном по меркам этого времени - самом RABBIT. +С самим собой - в смысле с оптимизатором и без. Компиляция с оптимизацией занимает в два раза больше времени. Без учета времени в сборщике мусора - в 1.5 раз. Для работы RABBIT нужен примерно один мегабайт памяти. +Но оптимизированный код быстрее только в 1.2 раза. Оказывается, компилировать Схему мешает ряд проблем. Мешает отсутствие типов. Мешает то, что нельзя рассчитывать на ссылки без нуллов. Мешает то, что в Схеме все мутабельное как в PAL. Значительная часть анализа перед оптимизатором нужна для того, чтоб выявить какие объявления просто объявления констант как `let` в ML. Сассман и Стил жалеют [Stee98], что не позаимствовали из Хьюитовской PLASMA мутабельную ячейку как специальный объект. И даже те оптимизации, которые удается сделать, основаны на шатком фундаменте. Они корректны только при правильном употреблении `CATCH`. +Ну, возможно при компиляции с помощью RABBIT какого-нибудь ФЯ Эдинбургской программы можно смягчить часть проблем. Но ФЯ Эдинбургской программы не компилировали с помощью RABBIT. Может потому, что, как пишет Стил, трансляция в MacLisp затрудняет практическое использование RABBIT? Должны быть более серьезные трудности. Разработчики Эдинбургских ФЯ определенно знали о нем и часто ссылались на описание. RABBIT быстро и полностью затмил для них компилятор Уоррена. +С интерпретатором RABBIT расправляется, но не очень легко. Оптимизированный код в 30 раз быстрее интерпретатора. Выглядит не очень хорошо. И нет никаких сравнений с MacLisp или с какими-нибудь "традиционными компиляторами". Заявлено, что RABBIT должен генерировать "сравнимый" код, но сравнения нет. Не очень хороший признак. +И в 80-е годы, когда бенчмарки стали популярнее, никто не сравнивал RABBIT с другими имплементациями ФЯ или "традиционными компиляторами". Так же как и компиляторы POP-2 и компилятор Уоррена, этот кролик не смог спастись и найти новый дом. Что же случилось? +И, кстати, требования к памяти в один мегабайт это много? + +У меня нет памяти, а я должен аллоцировать +========================================== + +> В 60-е и 70-е один человек или небольшая команда могли спроектировать и имплементировать язык с достаточной производительностью для того, чтобы быть полезным. +> Г. Стил, Р. Гэбриел, Эволюция Лиспа [Stee96]. + +> И тут наши игры стали оборачиваться серьезными последствиями, учитывая стоимость памяти в [три с половиной миллиона долларов]. +> Дж. Мозес [Mose08]. + + +Давайте выясним, много ли памяти требовал RABBIT в 1978. Единица компиляции RABBIT - это функция. Для того, чтоб скомпилировать самую большую функцию своего собственного исходного кода RABBIT только-только хватало 256K слов на PDP-10. Слово на PDP-10 - это 36 бит. Поэтому "примерно" мегабайт. +И да, 256K это все адресное пространство для этого компьютера с 18бит указателями. Так что рекомендуемая Стилом память является также и максимально доступной на этой машине. Рекомендуемая память - не результат того, что кто-то попробовал разные размеры и решил что начиная с мегабайта все начинает сносно работать. Это размер функций в исходниках ограничивали так, чтоб они умещались в памяти и компилятор хоть как-то работал. +Но это еще не все, RABBIT занимает больше одного адресного пространства. Его исполняющийся код находится при этом в отдельном адресном пространстве и на компьютере примерно два мегабайта памяти. Но насколько полно RABBIT использует второй мегабайт мы не знаем. +Для имплементации, в которой почти все - указатель на набор указателей в куче конечно важнее, сколько в эту память умещается указателей. В каждом слове может быть два указателя по 18 бит. Так что на более современной машине с 32-бит указателями структуры, которые RABBIT строит в куче заняли бы скорее два мегабайта. +Стил пишет, что не очень-то и старался компилятор оптимизировать и может можно написать его так, чтоб он требовал меньше памяти. Но как мы увидим дальше, последующие компиляторы ФЯ требовали только больше и больше памяти. RABBIT работает на PDP-10 с примерно двумя мегабайтами физической памяти. И на 32-х битной машине это будет скорее 3Мб. Забегая вперед, первый компилятор ML для компиляции другой имплементации ML требует по крайней мере 4Мб на 32бит машине. +Итак, компилятор ФЯ - слишком тяжелая программа для массовых персональных компьютеров вплоть до середины 90-х, если не дольше. Так что возможности отдельного индивидуума в 70-е сильно преувеличены Стилом и Гэбриелем. Посмотрим, какие немассовые компьютеры были доступны пользователям и имплементаторам ФЯ, которых поддерживали могущественные организации. + +Титаномахия +----------- + +> Я спросил, может ли Atlas управлять автомобилем у одного из разработчиков. Тот ответил, что Atlas "может делать все что угодно". +> Поплстоун, Р. Дж. Ранняя разработка POP [Popplestone] + +Первыми машинами, для которых пытались написать компилятор ФЯ были Atlas [Lavi12] в Лондоне и прототип Atlas 2, называемый Titan в Кембридже. Как мы помним, для этих машин писали компиляторы CPL. Группа Экспериментального Программирования имела какое-то время доступ к Atlas в Манчестере по телефону. До того, как Эдинбургский университет получил свой компьютер [Edinburgh]. К тому же компьютеру в Манчестере имел ранее доступ и Поплстоун, но тогда, когда еще не знал как его можно использовать [Popplestone]. Но можно сказать, что и Стрейчи с Парком не знали как Atlas можно использовать, а как - нельзя. Интересно, что скорость Atlas была сопоставима со скоростью той машины, на которой компилятор ФЯ впервые более-менее заработал. В 1962, когда заработал первый серийный Atlas, это означало претензии на самый быстрый компьютер в мире. +Манчестер и Кембридж участвовали в разработке машин и не покупали их, а Лондон заплатил за свою машину два миллиона фунтов. Что примерно соответствует 56 миллионов долларов 2023-го. Специально для этого компьютера было построено двухэтажное здание и его работа означала множество других расходов, так что более полное представление о том, насколько дорого обошлось бы его использование можно составить по рыночной цене на машинное время. Один час Atlas продавался за 750 фунтов - 21 тысячу долларов 2023-го года. +Разумеется, шанс воспользоваться такой дорогой машиной не очень часто выпадал имплементаторам ФЯ, но нельзя сказать, что в этот раз он был упущен. Дело в том, что с памятью все обстояло совсем не так хорошо, как с количеством операций в секунду. В 48бит слово Atlas умещалось два 24бит указателя. Так что адресного пространства хватило бы первым компиляторам ФЯ с запасом. Вот только такое адресное пространство Atlas и Titan никогда не понадобилось. В 1966 - год завершения работы над CPL - у Atlas в Лондоне было 32K слов физической памяти и еще 96K слов свопа на четырех барабанах. У более бюджетного Titan в Кембридже не было виртуальной памяти вообще, но со временем его память была увеличена с 32K до 64K и потом и до 128K слов, до того, как Titan был окончательно выключен в октябре 73-го. Хуже того, имплементация CPL началась еще до получения TITAN на EDSAC 2 [Rich13] с 16K слов памяти [Wilk92]. +Так что можно довольно уверенно заключить, что амбициозные версии CPL, мечты о ML-ях за десятилетие до ML, не могли быть имплементированы на этих машинах. Замена не сумевшего написать компилятор CPL Стрейчи на не сумевшего написать компилятор Лиспа Парка не могла ничего изменить. Как не могла бы изменить и замена Парка на кого-нибудь сумевшего написать какой-нибудь компилятор. Даже знавшие гораздо больше об имплементации ФЯ разработчики компиляторов 70-х и 80-х не смогли уместить компилятор ФЯ, компилирующий компилятор ФЯ в память на 256 тысяч указателей. Тем более на 64 тысячи. +Но решительное заключение о реальности менее амбициозных CPL-ей делать пока рано. Мы выяснили, что имплементаторы CPL не могли сделать то, что хотели. Но имплементация CPL не продвинулась достаточно далеко, чтоб определить, что они сделать могли. К счастью, как раз такой язык: почти CPL, почти амбициозный и почти ФЯ был почти имплементирован. И его историю, удобно проводящую нижнюю границу пространства возможностей для имплементаторов ФЯ, мы еще рассмотрим подробнее. Но не слишком подробно. + +75K слов достаточно каждому +----------------------- + +В 1974 [Emde06] Эдинбург получил компьютер из той же линейки, что и компьютеры на которых RABBIT работал в МТИ: PDP-10, он же DECsystem-10 с KI10 ЦП, 256K слов памяти и 512K слов на диске для свопа [Gord79] c ОС TOPS-10. И дело даже не в в два раза меньшей физической памяти. Три эдинбургские группы имплементаторов ЯП были одними из многих обычных пользователей этой машины, не сконфигурированной для исполнения больших программ. Каждый процесс на ней получал не больше 75K физической памяти, все остальное отправлялось на диск. +Из этих 75K LCF вместе с интерпретатором ML занимал 60K [Gord79]. Имплементация POP-2 занимала 19K [Feat79], интерпретатор NPL 11K [Feat79] - 12K [Darl81] +система трансформации Дарлингтона 6K [Darl81], система Фезера 36K [Feat79]. Интерпретатор HOPE, видимо, вместе с POP-2 66K [Sann82] - 70K [Burs80]. +Интересна разница в том как заявляются требования. Если Стил вообще не особенно заботится о размере кода, только о том, сколько памяти нужно на деревья в куче, которыми этот код манипулирует, то обычный эдинбургский пользователь PDP-10 рад, что хотя-бы код влез в память, а уж какой-нибудь однострочник он худо-бедно обработает. Компиляторы ФЯ он не компилирует. По крайней мере до 80-х. В восьмидесятые Clear требует минимум 110K слов памяти из которых 66K интерпретаторы POP-2 и HOPE [Sann82]. Но это другое время и другая машина, лимиты для пользователей могли изменить. +В такой тяжелой ситуации интерпретатор - способ оптимизировать использование памяти. По сравнению с компилятором вроде RABBIT. Но что же тогда с компилятором Уоррена? Для бутстрапа ограничение могло бы быть проблемой. Для исполняемого Марсельским интерпретатором компилятора Уоррена 75K слов едва хватает, сам Марсельский интерпретатор занимает 25K из них. +После бутстрапа ситуация улучшилась. Код и рантайм компилятора Уоррена 25K слов, а интерпретатора 14K [Warr78]. Компилятор Уоррена генерирует достаточно компактный код. Но главное - компилятор Уоррена может использовать аннотации чтоб меньше аллоцировать и память необходимая для компиляции редко превышает 5K слов [Warr77] [Warr78], в десять раз меньше, чем в случае Марсельского интерпретатора. Но почему такая разница с требованиями RABBIT? +Компилятор Уоррена компилирует одно правило за раз, а не целую процедуру, как компиляторы ФЯ. Другими словами, всегда компилирует однострочники. Много однострочников, но не слишком много, потому что в памяти еще должна умещаться таблица глобальных символов. Такая небольшая единица компиляции может быть в разы или даже десятки раз меньше, чем функция - единица компиляции для компилятора Стила. Кроме того, даже с этими однострочниками компилятор Уоррена не производит каких-то амбициозных трансформаций. Команды виртуальной машины примерно соответствуют конструкциям в коде: + +``` +clause2: uskeld(O,cons) qsort(cons( + uvarl(O,global,0) X, + uvarl(1,local,0) L), + uvar(1,local,1) R, + uvar(2,local,2) RO + init(1,2) + localinit(3,5) + neck(5,2) ):- + call( partition) partition( + local(O) L, + global(O) X, + local(3) L1, + local(4) L2), + call(qsort) qsort( + local(4) L2, + global(1) R1, + local(2) RO), + call(qsort) qsort( + local(3) L1, + local(2) R, + label1 cons(X,R1)) + foot(3) + +label1: fn(cons) + var(O) + var(1) + +clause3: uatom(O,nil) qsort(nil, + uvar(1,local,0) R, + uref(2,local,0) R, + neckfoot(0,3) ). +``` + +И команды - просто макросы, генерирующие по паре строк на ассемблере. +В результате компилятор компилирует себя примерно за 5 минут. Эти системные требования и скорость Уоррен называет "приемлемыми", но не особенно доволен. Можно было бы и лучше сделать! Утверждает, что производительность сравнима с макроассемблером, поставляемым с PDP-10. +Можно предположить, что для какого-нибудь нетипизированного языка уравнений, вроде KRC, Уоррен мог бы написать компилятор с похожими системными требованиями. Но компилятор полноразмерного Эдинбургского ФЯ таким нетребовательным не сделать. +Но вернемся к ограничениям памяти для пользователя. Эдинбург вовсе не был каким-то особенно несчастным местом. Существование Stanford LISP объясняется тем, что почти всем желающим использовать MACLISP-образный Лисп нужна имплементация, требующая меньше памяти чем MACLISP, пусть даже и хуже. "Очень немногие" места могли себе позволить использовать компилятор MACLISP, требовавший от 65K слов [Whit77]. МТИ использовал на PDP-10 собственную ОС ITS, лучше приспособленную для запуска больших программ. С 1973 MACLISP мог работать на TOPS-10, но не особенно хорошо из-за особенностей работы с памятью в стандартной ОС PDP-10. Стенфорд смог начать использовать его только в 1976-ом году. +Теперь ясно, что место разработки RABBIT - очень особенное место, где довольно обычный мэйнфрейм используют необычным образом, как рабочую станцию [Chio2001]. Там работают над средой для использования программ требующих много памяти. Программ вроде компиляторов ФЯ. Давайте выясним, что им удалось сделать и как это можно использовать для имплементации ФЯ Эдинбургской программы. + +Полдень, 1972 год. +------------------ + +> Финансируй людей, а не проекты! +> Джозеф Ликлайдер + +> Ликлайдер давал нам деньги одним большим шматком. +> Марвин Мински [Crev93] + +Лаборатория ИИ МТИ больше известна неработающими роботами, неработающими программами для работы с естественными языками и неработающим распознаванием изображений [Crev93]. Конечно, не сразу стало понятно, что все это не будет работать. +С 58-го года ИИ группа МТИ, основанная МакКарти и Марвином Мински (Marvin Lee Minsky), занималась разработкой и имплементацией Лиспа. Также МакКарти имел отношение к разработке системы разделения времени - крайне успешной идее, сделавшей работу за компьютером более-менее интерактивной. Что, как мы помним, вдохновило Мики на создание группы экспериментального программирования, имело такое решающее значение для Поплстоуна и произвело такое сильное впечатление на Ван Эмдена. Все ждали новых успехов и летом 63-го ARPA (Advanced Research Projects Agency) стартовала проект MAC грантом в $2,220,000 (22 миллиона 300тыс. долларов в 2023). Это было только началом, долгие годы после этого проект MAC получал три миллиона каждый год (т.е. 21-30 миллионов долларов в 2023). Ну, может быть не все ждали успехов. МакКарти ушел из МТИ перед тем, как все это счастье на них свалилось [Crev93]. +Идея Ликлайдера (Joseph Carl Robnett Licklider) заключалась в выращивании кадров. Деньги ARPA выделяются не на конкретные проекты, а на лабораторию, в которой делают что хотят. Предполагалось, что если то чем они занимаются не вырастет во что-то полезное, то хотя бы те, кто работал в лаборатории смогут делать что-то полезное. +Так что Мински хотел чтоб студенты работали над разными проблемами, а не развивали одно и то же направление. Поэтому, когда один из тысячи цветов начал расцветать, понадобились специальные усилия для того, что Мински позволил это [Mose08], чтоб проект не повторил судьбу слишком высоких колосьев из рассказа о Фрасибуле. Так что этот цветок расцвел, и только один этот цветок. Остальным, кто хотел заниматься другими работающими - как выяснилось позднее - вещами не удалось перехитрить Мински и он их успешно разгромил. +Для чего из перечисленных работ над пониманием естественного языка, распознавания образов и роботами может понадобиться запускать требующие много памяти программы на Лиспе? Да, в мини-MAC в Эдинбурге занимающиеся роботами и распознаванием образов писали на сравнимом с Лиспом языке POP-2. Но их принуждало это делать устройство эдинбургской системы разделения времени. Их коллег из МТИ принудить использовать Лисп было сложнее. Занимавшийся в МТИ компьютерным зрением Бертольд Хорн (Berthold Horn) вспоминает [Crev93], что в 60-е Лисп имел игрушечную имплементацию и только в 70-е стал полезным инструментом. Так что остается только работа с естественными языками. Но это пользователи антипрологов Хьюита и Сассмана. И мы уже знаем, что это не самые важные пользователи. У имплементаторов MACLISP есть пользователи поважнее и нет времени имплементировать пожелания антипрологистов. Но что в ИИ Лаборатории важнее ИИ? Ну, как гласит расхожее среди разработчиков неработающего ИИ выражение, которое обычно приписывается МакКарти: когда что-то начинает работать, никто не хочет больше называть это ИИ. +Эта заработавшая программа, сегодня уже не считающаяся ИИ по причине своей работоспособности, занималась трансформацией деревьев, построенных в куче из объектов и ссылок на них. Поэтому ее системные требования оказались похожи на системные требования первых компиляторов ФЯ. Так что человеко-годы труда лисперов, которые создавали доступную среду в которой она могла работать, создали заодно такую среду и для первых компиляторов ФЯ. И история этой программы поможет нам обозначить пространство возможностей для первых компиляторов ФЯ. Понять, на каких платформах мог бы работать компилятор ФЯ, насколько они были редкими и как дорого бы обошлись. Эта программа - система компьютерной алгебры MACSYMA (project MAC’s SYmbolic MAnipulator). + +MACSYMA — суровая хозяйка +------------------------- + +> Тогда я еще не знал, что моя и другие группы потратят следующие два десятилетия на улучшение скорости работы и расхода памяти Лиспа. +> Дж. Мозес, Macsyma: личная история [Mose08]. + +> Шли годы и вопрос все задавался: "Почём Лисп?" +> Дж. Уайт, Лисп: программа - это данные: историческая перспектива на MACLISP [Whit77] + + +Главным проектом в группе/Лаборатории ИИ МТИ, как и в Йорктаун Хайтс, была система компьютерной алгебры на Лиспе. Все началось с программы символьного интегрирования SAINT (Symbolic Automatic INTegrator) Джеймса Слагла, того самого, который обнаружил, что видимость переменных работает как-то неправильно. Но продолжилась программой SIN того самого Джоэла Мозеса (Joel Moses) [Mose08], который писал [Mose70], что правильно работающая видимость не так и нужна. +Поскольку Слагл защитил диссертацию по символьному интегратору в 61-ом году, Мински считал [Mose08], что еще одна диссертация на эту тему не нужна. И в 63-ем году не дал Мозесу работать над улучшением интегратора. Мозес стал работать над другой темой. Но, к счастью, в 65-ом году выяснилось, что и эту проблему уже кто-то решил. Так что Мински разрешил таки Мозесу работать над усовершенствованным интегратором, что он и делал до 67-го года. +Еще одной развилкой было решение Мозеса использовать Лисп. Сначала он хотел использовать ассемблер, но Слагл писал на Лиспе и Мозесу Лисп показался приятным языком. Программа-то все равно исследовательская. Что может пойти не так? Мозес использовал и уже готовый код на Лиспе, но трудно сказать, повлияло ли это на решение использовать Лисп. +Разработчики SCRATCHPAD в Йорктаун Хайтс посчитали хорошей идеей писать такую систему на Лиспе в числе прочего и из-за решения Мозеса. И использовали код его интегратора. Вот только обстоятельства в МТИ изменились и имплементаторы MACSYMA стали не особенно уверены, что идея хорошая. Что же случилось? То, что еще долго не будет угрожать разработчикам SCRATCHPAD. У MACSYMA появились пользователи. +Узнав о планах разработки систем компьютерной алгебры, Уильям Мартин (William Martin) объединил ряд мелких проектов в МТИ, прячущихся ниже радара Мински, в систему компьютерной алгебры. Вскоре, в июле 1969 разработку этой системы поддержало ARPA, решившее все-таки поддержать проект, а не людей в этот раз. Что привело к практически полному подчинению ИИ Лаборатории этому проекту. Уже с 68-го года изменения в MACLISP были в основном для нужд MACSYMA [Mose74] [Stee82]. В 71-ом году Мартин покинул проект и Мозес стал его руководителем на всю оставшуюся историю MACSYMA в МТИ. В 1972 разработчики MACSYMA стали фактическими хозяевами MACLISP [Stee96]. Опасения Мински вырастить слишком большой проект полностью оправдались. +Если разработчики MACSYMA были не вполне довольны Лиспом, то Лисперы МТИ были недовольны тем, как Лисп используется для имплементации MACSYMA. Мозес не хотел делать ИИ как полагалось его делать в МТИ - использовать какой-то обобщенный инструментарий для поиска, вроде разрабатывающихся в МТИ антипрологов. Мозес хотел делать очень специализированные имплементации, использовать поиск как можно меньше [Mose08]. Даже "практичный" антипрологовый поиск все еще непрактичен для его предполагаемого пользователя. +Лисперы позднее в своих воспоминаниях [Stee96] будут уверять, что работа над Лиспом в МТИ была не только ради MACSYMA, но в 70-е они вынуждены были уверять в обратном. Даже в описании RABBIT [Stee78] Стил упоминает обсуждения полезности своей работы для MACSYMA. Поэтому Мозес так уверенно объясняет, что лисперам нужно [Mose70], а в воспоминаниях лисперов о событиях вокруг MACSYMA часто заметна _горечь_. +Раз Лисп был не самым подходящим языком для разработки MACSYMA - то теперь лисперам нужно было его сделать подходящим. Разработчикам MACSYMA нужна была быстрая арифметика и массивы. В это время это было не особенно совместимо с динамическим языком в котором все значения - ссылки на объекты в куче. Такие же проблемы, как мы помним, были и у POP-2, но у его пользователей в Эдинбурге были не те ресурсы, чтоб что-то с этим сделать. Но в МТИ ситуация была другой. +Пользуясь новой поддержкой ARPA разработчики MACSYMA наняли программиста Джеффри Голдена (Jeffrey P. Golden), который занялся улучшением компилятора MACLISP. Прототип первого компилятора Лиспа, который специализирует код с аннотациями, работающий с числами назывался LISCOM [Gold70]. Этот компилятор произошел от того самого компилятора Гринблатта и Нельсона, которые заменили поиски имен в списках на стек. Работу над LISCOM начал более известный другими своими работами Уитфилд Диффи, но его вскоре сменил Голден, и Диффи отправился в Стенфорд [Stee96]. Там он работал над версией MACLISP если и не для бедных, то не для таких богатых как в МТИ и учил Робина Милнера писать на Лиспе. Голден же довел LISCOM до более-менее рабочего состояния в 71-ом году, на момент написания отчета [Gold70] 70-го года работал только первый из двух проходов, собирающий информацию для кодогенерации. После прототипа пришло время рабочего альтернативного компилятора NCOMPLR для MACLISP. Голден, Уайт и Эрик Розен (Eric Rosen) развивали его с 1972-го параллельно с основным, более быстрым компилятором COMPLR без этой оптимизации [Moon74]. COMPLR также произошел от компилятора Гринблатта-Нельсона и был полностью заменен на NCOMPLR только в 1975 [MACLISP]. И если в 73-ем арифметика хорошо компилировалась и можно уже было смело переходить от написания хэллоуворлдов к вычислению факториалов, то поддержка массивов чисел появилась в NCOMPLR только к лету 75-го [MACLISP]. Поддержка объектов кучи со многими полями - в 76-ом году [Stee82]. +Непрерывные объекты в памяти, в которых больше пары полей и быстро работающие массивы ссылок в сочетании с быстрой арифметикой для вычисления индексов полезны для эффективной имплементации окружений в ФЯ. Или, по крайней мере _могут_ быть полезны. Стил не успел воспользоваться ими для имплементации RABBIT, только написал, что надо бы ими воспользоваться. Также, быстрая арифметика пригодится для того, чтоб хорошо выглядеть в основном бенчмарке для ФЯ 80-х - вычислении чисел Фибоначчи. +Насколько наработки авторов MACLISP по специализации полиморфного кода были полезны для имплементаторов ФЯ, у которых схожие проблемы если не из-за динамической типизации, то из-за параметрического полиморфизма на универсальном представлении? Мы разберемся с этим подробнее, когда будем писать о таких решениях для ФЯ. По крайней мере, MACLISP продемонстрировал возможность. Насколько убедительной была демонстрация, и насколько обоснованным было это убеждение - другой вопрос. +Насколько быстро работал скомпилированный NCOMPLR код? Гэбриел и Стил вспоминают о нем как об образце хорошей производительности для имплементаторов лиспа на годы [Stee96]. Но это может больше говорить об успехах имплементации Лиспа в эти годы. Неплохо было бы сравнить с какими-то имплементациями языков, у которых точно должна быть приличная производительность. И с этим не все хорошо. Мозес заявляет [Mose74], что тесты показали: код, генерируемый NCOMPLR лучше, чем генерируемый DEC PDP-10 FORTRAN. Утверждения Стила скромнее: скорость только "соперничающая" с FORTRAN [Stee77b]. Проблема в том, что такие заявления ссылаются на один единственный микробенчмарк, опубликованный Ричардом Фейтманом (Richard Fateman) в 73-ем году [Fate73]. Микробенчмарк представлял из себя вычисление кубических корней тысячи чисел с плавающей точкой. Массивы не используются - в 73-ем оптимизаций для массивов еще не было. Версия на фортране справлялась за 2.22 сек, версия на Лиспе с GOTO за 1.81 сек, версия с рекурсией за 2.20 сек. Пример на языке MACSYMA работает с той же скоростью, что и на MACLISP. Оцените его высокоуровневость: + +``` +MAIN():=(FOR I : 1.0 STEP 1.0 THRU 1000.0 DO CRT(I))$ + +CRT(X):=BLOCK([TEST,VAL], + DECIARE([TEST,VAL,X],FLOAT), + VAL:X, +A, TEST:(VAL^3-X)/(3.O*VAL^2), + VAL:VAL-TEST, + IF (ABS(TEST/VAL) > 1.0E-7) THEN GO (A), + RETURN(VAL))$ +``` + +На этот микробенчмарк - и только на него - десятки ссылок. Даже в описании оптимизации арифметики в MACLISP [Stee77b] нет никаких измерений, заявления о хорошей производительности ссылаются на все тот же микробенчмарк. Более интересные замеры производительности будут в 80-е, но таких известных сравнений с Фортраном нет, так что ссылаться продолжили и в 80-е и в 90-е. Но уже с комментариями, что сравнимую с Фортраном производительность больше никогда не удавалось воспроизвести [Stee82] [Stee96]. +Нам не приходилось видеть, чтоб лисперы допускали то, что никакого превосходства над Фортраном никогда и не было, просто Фейтман что-то не то померил. Стил и Гэбриел пишут два десятилетия спустя, что эта славная победа LISP над FORTRAN просто заставила разработчиков компилятора Фортрана совершенствовать его до тех пор, пока компилятору Лиспа не стало трудно с ним соревноваться. Никакими ссылками история этого эпического противостояния, правда, не подтверждена. +Много усилий тратилось на не настолько интересные для имплементаторов ФЯ вещи вроде длинной арифметики. Длинная арифметика была встроена в рантайм и написана на ассемблере, в MACLISP нет средств использования кода на низкоуровневом языке кроме встраивания в рантайм. Отсюда, видимо, происходит длинная арифметика из коробки в Haskell. +Многие требования авторов MACSYMA не нуждались в какой-то исследовательской работе и происходили просто из той самой игрушечности Лиспа 60-х. Даже флагманская имплементация Лиспа такая как MACLISP была просто не готова к размерам практически полезных программ. Скомпилированная часть MACSYMA загружалась в интерпретатор около часа. Но это можно было исправить: оптимизации сократили это время до каких-то пары минут [Whit78]. +Но одних только этих усилий по улучшению MACLISP было недостаточно. Для MACSYMA сначала удвоили память в собственном PDP-10 Лаборатории ИИ, впервые получив эту необычную систему, у которой больше памяти чем в максимальной конфигурации, продаваемой DEC. Теперь код программы мог быть загружен в память и для программы все еще была доступна память размером с адресное пространство. В конце 60-х это обошлось в $400,000 (три с половиной миллиона долларов 2023-го). Через три года, в 1972 разработчики MACSYMA получили еще один PDP-10 такой конфигурации. А в 74-ом году еще один PDP-10 новой модели с более быстрым процессором, появившемся к этому времени. В 1975 году свежая версия центрального процессора PDP-10 KL-10 с 2Мб памяти стоила примерно как один тот первый апгрейд памяти - $638,000 ($3.6M в долларах 2023) [Full76]. +Пользователи MACSYMA не покупали собственные машины этой экзотической конфигурации, а работали с системой через ARPANET с мая 72-го [Mose74]. И по заверениям [Mose08] разработчиков MACSYMA, в 72-ом году узел, на котором она работала, был одним из самых популярных в ARPANET. За первые два года системой пользовались сотни людей. Половина пользователей были из МТИ [Mose74], другая половина была пользователями, которые решили судьбу Лиспа, а также много других, часто довольно неожиданных судеб. Но об этом позже. +MACSYMA продолжала расти, с 73-го года она загружалась только частями и её разработчики ожидали, что её ядро перестанет помещаться в адресное пространство по крайней мере с 75-го года [Mose08]. Так что возникает вопрос: если ИИ Лаборатории МТИ хватало средств на недешевые модификации мэйнфреймов 60-х, как же так получилось, что и в 78-ом году RABBIT все еще запускают на них? Почему MACSYMA десять лет кое-как работает на этих машинах вместо того, чтоб начать хорошо работать на более новых компьютерах без ограничений вроде адресного пространства в 256K слов? И что случилось с RABBIT вскоре после его создания? А заодно с POP-2 и компилятором Уоррена? Почему все это отсутствует в первых ФП-бенчмарках 80-х? Во время разработки RABBIT ИИ Лаборатория МТИ - это явно не только особое место, оно еще и проходит особенный период своей истории. +Дело в том, что мы, описывая Эдинбургский период истории ФЯ, не упомянули одного важного обстоятельства. Все это время история ФЯ разворачивается в постапокалиптическом сеттинге. Апокалипсиса было даже два, просто совпавших по времени, но не связанных причинно. Один был быстрым и сокрушительным ударом, второй - медленным угасанием, растянувшимся на десятилетие. + +Ветра зимы +---------- + +> Что, вы не слышали о Лайтхилле? О том, кто хочет с нами расправиться? +> Мартен ван Эмден, Вспоминая историю с Лайтхиллом [Emde19]. + +> ДРЮ МАКДЕРМОТ: И он меня спрашивает: "ядерная зима" - это когда урезают финансирование ядерного оружия? +> (НИКТО НЕ СМЕЕТСЯ) +> Дрю МакДермот и др., Темные века ИИ: панельная дискуссия на AAAI-84 [McDe84] + + +Ван Эмден впервые услышал фамилию "Лайтхилл" в ноябре 72-го. Вскоре после того, как начал работать в Отделе Машинного Интеллекта [Emde19]. Но события, которые привели к нападению Лайтхилла на машинный интеллект начались гораздо раньше. Организатор мини-MAC Мики начал воспроизведение большого MAC в Эдинбурге с разработки системы разделения времени, а продолжил разработкой ИИ. Для этого он уговорил работать в Эдинбурге Ричарда Грегори (Richard Langton Gregory) и Кристофера Лонге-Хиггинса (Christopher Longuet-Higgins). Группа Экспериментального Программирования стала частью новой структуры - Отдела Машинного Интеллекта. Но отношения между основателями отдела не задались. Гегори покинул Эдинбург уже в 1970 [Howe07], так что сложные отношения продолжались в основном между Мики и Лонге-Хиггинсом. Основной причиной конфликтов, по воспоминаниям Ван Эмдена, было желание каждого из них спихнуть административные обязанности на другого. Но существовали и разногласия о машинном и не только разуме [Emde19]. Со временем эти трения привлекли внимание тех, кто выделял на них средства - SRC (Science Research Council). В рамках выяснения, что же происходит в Эдинбурге с разработкой машинного разума, прикладному математику Джеймсу Лайтхиллу (Michael James Lighthill), не имеющему вроде бы никакого отношения к ИИ, поручили написать обзор успехов этой области [Howe07]. И он написал [Ligh72], что успехов особых нет, да и есть основания считать, что и не будет. +Ван Эмден, правда, сомневается, что Лайтхилл не имел совсем уж никакого отношения к ИИ. Особенно после того, как узнал из интервью Фримена Дайсона (Freeman Dyson) какие хорошие друзья они с Лайтхиллом и Лонге-Хиггинсом еще со школы [Emde19]. +В докладе Лайтхилл пишет, что не все, что делается под зонтиком "ИИ" одинаково плохо. Но подозрительно, что как-то так получается, что то, над чем работает Мики (роботы) - из самого худшего, а Лонге-Хиггинс (моделирование ЦНС) - из самого лучшего. Похоже, правда, что в результате пострадали все машинисты разума, и не только в Великобритании. +Проблему того, что кое-что в ИИ все же работает Лайтхилл решает просто: выписыванием всего работающего из ИИ. Более-менее работающее имеет смысл вывести из под зонтика "ИИ" и интегрировать с родственными областями и областями применимости. И не то чтобы авторам всего работающего требовалась такая подсказка. Если от ассоциации с ИИ больше нет пользы - такая идея носится в воздухе. Например, компьютерная алгебра и доказатели дистанцировались от ИИ. Понятен и увод Пролога Уорреном от марсельской работы с естественным языком (очень плохо по мнению Лайтхилла) к парсерам и компиляторам (достаточно успешно). +Языки с автоматическим управлением памятью - имеют право на существование, разрешает Лайтхилл. И Эдинбургские ФЯ появились достаточно поздно, чтоб легко дистанцироваться от ИИ. Не смотря на то, что Группа Экспериментального Программирования была частью Департамента Машинного Интеллекта и статьи Бурсталла про ПМ и уравнения выходили в сборнике "Машинный Интеллект". Так что Эдинбургские ФЯ появились в главном Британском ИИ-центре, но их авторы предпочитали упоминать ИИ примерно так же часто, как упоминали его мы в этой истории. До начала этой главы. Для Пролога дистанцироваться от ИИ было несколько сложнее. А для Лиспа - практически невозможно. К сожалению, не все видели между этими языками какую-то принципиальную разницу. +Только в проекте MAC не понадобилось дистанцироваться от ИИ Лаборатории потому, что Мински отделился со своей ИИ лабораторией от проекта MAC сам, в 1970. Рассчитывая, что так лаборатория получит больше ресурсов [Chio2001]. Но, скорее всего, просчитался. +Но даже если согласиться с Ван Эмденом, что Лайтхилл предвзят - эта предвзятость выражается в основном в том, что он не критикует то, что можно было и покритиковать. Проблема в том, что дела с имплементацией ИИ в это время действительно идут неважно. И ИИ-деятелям трудно что-то противопоставить главному возражению Лайтхилла. Всем этим придуманным ими перебирателям хоть бектрекингом, хоть циклами, хоть корутинами, надо целую вечность перебирать в случае хоть сколько-нибудь реальной задачи. И уменьшить это пространство перебора они могут только с помощью специфических знаний предметной области, так что от универсального подхода и самого смысла объединять это все в какой-то "ИИ" мало что остается. +Существует ответ МакКарти Лайтхиллу в формате рецензии на его отчет. МакКарти пишет, что проблема с большим пространством поиска была очевидна разработчикам ИИ с самого начала. И только некоторые из них короткое время слишком оптимистично смотрели на то, насколько эвристики "общего назначения" тут могут помочь [McCa74]. Мы же тут сделаем предположение, что Лайтхилл не пытался научить разработчиков ИИ чему-то новому, а писал для тех, кто платил разработчикам ИИ деньги. И вот для них эта проблема могла быть не такой очевидной. +Интересно, что МакКарти сохраняет оптимизм, пишет, что Лайтхилл "промахнулся", безуспешно попытался остановить разработку ИИ. И зачем-то рассказывает, что проиграет в споре 250 фунтов, если до 78-го года компьютерная программа не обыграет в шахматы международного мастера, с которым он поспорил [McCa74]. Этот спор МакКарти, конечно, проиграет. Но ему и прочим ИИ-исследователям предстоят потери гораздо большие и существенно раньше. +Лайтхилл определенно не промахнулся, выстрелив в британскую школу ИИ. Но, возможно, что первая ИИ-зима в США началась и не с событий в Эдинбурге. Существуют и другие версии начала, например с недовольства результатами работ по распознаванию речи в Университете Карнеги — Меллона [Crev93]. +В 74-ом году ARPA урезало финансирование основным ИИ-лабораториям США, в том числе и финансирование ИИ Лаборатории МТИ и разработки MACSYMA. Но пользователи MACSYMA хотели продолжать ей пользоваться. И пользователи MACSYMA могли себе это позволить. Так что следующие восемь лет все финансирование разработки MACSYMA, MACLISP и будущих, более важных для истории ФЯ имплементаций MACLISP-образных диалектов Лиспа поступало от MACSYMA-консорциума. Который состоял из Министерства Энергетики (DoE, до 77-го года Управление по энергетическим исследованиям и разработкам), NASA, ВМФ США и Schlumberger. Большая часть средств шла на исправление багов и новые фичи системы компьютерной алгебры, но так же и на улучшение компилятора [Mose08]. Теперь, чтоб получать деньги надо было делать не неработающих роботов, а работающую MACSYMA! +Но жизнь найдет путь, так что тот же Стил получал от ВМФ США деньги на RABBIT при том, что его отношение к MACSYMA ограничивается благодарностью человеку, с которым Стил обсуждал, как можно применить RABBIT на пользу MACSYMA (никак). Все стало делаться во имя MACSYMA и именем её. Не беспокойтесь, в 80-е МТИ и выходцы из него отомстят за все эти унижения. И если и не добьются того, чтоб MACSYMA нигде не работала, то по крайней мере сделают её тем, чем она является сегодня. Но, к счастью, только после того, как многое полезное для работы компиляторов ФЯ будет уже сделано. +SRC урезал финансирование ИИ еще раньше. Правда, Эдинбург пострадал меньше, чем другие, более слабые центры ИИ Великобритании. Наступившая ИИ-зима определенно не несла ничего хорошего для Мики [Emde19] и роботов. Но возможно, что это даже пошло на пользу развития ФП. Так Бурсталл занялся более важными для ФЯ делами, вместо того, чтоб досыпать роботу детали игрушечной машины, как на видео [BBC73]. +Можно предположить, что ослабление Мики могло повлиять на то, что мини-MAC стал использовать построенную вокруг PDP-10 экосистему, родственную той, что использовалась в большом MAC. На первый взгляд, это хорошо для использования наработок многих других ИИ-лабораторий для имплементации ФЯ. Но, как будто мало одной ИИ-зимы, назревает еще одна катастрофа. Построенная вокруг PDP-10 экосистема обречена. + +Core War +-------- + +Большинство значительных ИИ-лабораторий 60-х повторяли ИИ Лабораторию МТИ. Поэтому, чтобы разобраться как погибли их наработки, нужно понять причины ряда решений в МТИ 60-х. +Как вышло, что МТИ стал использовать мэйнфреймы линейки PDP-6/10, а не существенно более популярные мэйнфреймы IBM, тем более, что разработка Лиспа началась на них? Одной из причин была задержка релиза новой линейки IBM [Chio2001]. Главной причиной - отношения между МТИ и IBM испортились из-за патентного спора [Stee96] [Chio2001]. МТИ претендовал на изобретение ферритовой памяти и требовал IBM платить по два цента за каждый бит. При этом в 1965 производство ферритовой памяти обходилось IBM в 1-3 цента за бит в зависимости от скорости её работы. И IBM так не хотел платить, что разрабатывал несколько новых видов памяти для замены ферритовой. Ни один из них не был практичным или не успевал стать практичным достаточно быстро. Но Джей Райт Форрестер (Jay Wright Forrester), которому IBM демонстрировал свои разработки, этого не знал. И МТИ, ошибочно считая, что IBM вот-вот сделает их патент ненужным, в феврале 64-го согласился на разовый платеж в 13 миллионов долларов (129 миллионов в 2023). На тот момент это была рекордная сумма на урегулирование патентного спора, но не испугавшись неработающих изобретений, МТИ мог бы получить намного больше [Emer91]. Почувствовав себя обманутым, МТИ решил не покупать новые машины IBM. И совсем не очевидно, что МТИ действительно причинил какой-то заметный вред IBM, а не просто отморозил уши назло им. По крайней мере уши разработчиков MACSYMA, которым на машине IBM нужно бы было её умещать не в `2^18`, а в `2^24` адресное пространство [Emer91]. +Почему не IBM понятно. А почему именно выбрали именно DEC PDP-10? DEC основали выходцы из МТИ, где они были еще и участниками клуба железнодорожного моделизма. Поезда управлялись с помощью компьютера, так что МакКарти и Мински использовали клуб для того, чтоб искать программистов для ИИ Лаборатории. Так они нашли Гринблатта, например. На основателей DEC в МТИ пока еще не обиделись. Пока еще. +Другая причина скорее всего была той же, что и до того причина выбора IBM. IBM подарил МТИ компьютер 704 в 1957 году, а DEC компьютер PDP-1 в 1960. И так же, как МТИ покупал после этого более новые машины IBM, так же и ИИ Лаборатория приобрела PDP-6, а затем и его более новую, надежную и популярную версию PDP-10 [Chio2001]. +Еще одной причиной было то, что PDP-6/10, по мнению лисперов, хорошо подходил для имплементации Лиспа. Из-за инструкций для работы со стеком и того, что `cons`- ячейка умещалась в одно слово. Правда, зато практически полезная программа на Лиспе не помещалась в его адресное пространство. Так что все-таки мог бы подходить и лучше. +Лисперы утверждают даже, что разработчики PDP-6 прислушивались к пожеланиям лисперов [Stee96]. Что не так очевидно. Например, у Atlas тоже два указателя помещаются в одно слово. И делать из него "Лисп-машину" точно не собирались. А когда лисперы действительно спроектировали компьютер для Лиспа - у него с PDP-6 было мало общего. Лучше бы лисперы повлияли на адресное пространство, если могли. Все таки, когда выпускаешь машину у которой физическая память размером с адресное пространство, должна возникать какая-то тревога, что система не выдержит проверки временем. +Разумеется, пользователи PDP-10 ждали новой версии, поддерживающей большее адресное пространство. Между релизами PDP-6 и PDP-10 прошло два года. Но ни через два, ни через четыре, ни даже через восемь лет эта долгожданная версия все не появлялась. В чем же проблема? Как мы уже упоминали выше, мэйнфреймы DEC не были популярны. DEC была ведущим производителем другой разновидности компьютеров - миникомпьютеров. Так что в DEC решили сконцентрировать усилия на успешных линейках машин. Но, надо полагать, если донести такие решения до клиентов и потенциальных клиентов - они могут воздержаться от апгрейда или покупки PDP-10. Поэтому решение было донесено только в 1983-ем году. Конечно, просто отмалчиваться десять лет трудно. Мозес вспоминает [Mose08], что в 1976 на встрече с представителями Лаборатории ИИ МТИ вице-президент DEC Гордон Белл (Gordon Bell) обещал новую, более дешевую версию PDP-10 в 1978. Это обещание не было исполнено. Очередной удар в спину МТИ от производителя железа. +DEC продолжал продавать апгрейды и новые системы и даже, в конце концов, расширение адресного пространства. Но для его использования нужно было писать весь рантайм и кодогенератор заново, как для новой платформы, так что это мало кого привлекало. Версию Stanford LISP [Stee96] с такой поддержкой - Rutgers ELISP сделали в 1980, и Стенфорд даже купил апгрейд в 1983. В год окончания производства системы. Да, только в 1983-ем году DEC официально сообщил о том, что продолжения линейки PDP-6/10 не будет. +И наработки ИИ Лабораторий, которые её использовали нельзя было просто так взять и портировать. Огромные по тем временам лисповые рантаймы написаны на ассемблере [Stee96] [Mose08]. И не только лисповые. +Уже в момент покупки PDP-10 Эдинбургским университетом у платформы не было будущего. Так что не только RABBIT и MACLISP, а и POP-2 обречен, первая имплементация HOPE обречена. Но хуже всего ситуация складывалась для компилятора Уоррена. Два указателя в слове не оказалось особо критичным для имплементации Лиспа, но эта и другие особенности PDP-10 были критичны для того чтоб сама идея Бойера и Мура об имплементации Пролога, которую использовала имплементация Уоррена, была практичной. И целый способ имплементации Пролога - разделение структуры - практически перестал использоваться и сменился на новый стандарт де-факто: копирование структуры [Li96] [Korn22]. +Линейка PDP-6/10 оказалась для Лиспа, POP-2, Пролога смертельной ловушкой, из которой что-то все-таки спаслось, но от которой вся эта ИИ-культура МТИ, Стенфорда и Эдинбурга так уже и не оправилась. +В каком-то смысле, более бедные лаборатории лучше подготовились к восьмидесятым годам. Так Сент-Эндрюс в середине 70-х не смог заменить свой мэйнфрейм IBM на другой, новый мэйнфрейм. Хотя рассчитывали на это, даже писали интерпретатор SASL на микрокоде этого пока еще не купленного мэйнфрейма [Somm77]. Вместо мэйнфрейма они получили миникомпьютер из линейки, развитие которой DEC решил приоритизировать. Адресное пространство в 64K ограничивало их не намного сильнее, чем обычные ограничения одного пользователя на PDP-10. И это-то адресное пространство DEC намеревался увеличить в ближайшее время. +Но компиляторы Лиспа и Пролога хотя-бы работали на PDP-10. А что делать тем, кто хочет разрабатывать более требовательные к ресурсам компиляторы ФЯ? Что будут делать авторы ФЯ, правда, мало кому интересно. Гораздо важнее - что делать разработчикам MACSYMA? Она, как мы помним, едва помещается в адресное пространство в 75-ом году. +У лисперов МТИ было три основных плана бегства с PDP-10. Один хуже другого. + + +Eight Megabytes And Constantly Swapping +--------------------------------------- + +> Программа приобрела репутацию чудовищно сложной, запутанной до такой степени, что ни один человек не утверждал, что понимает ее. И ни один человек не мог модифицировать ее не боясь повредить без возможности исправить. +> Бернард Гринберг + +Поскольку проблема с памятью была осознана еще до начала ИИ-зимы первые направления бегства были намечены еще как экспансия. Правда, не в область популярных систем, а в область больших бюджетов. +Лаборатория ИИ была только частью проекта MAC. В другой части продолжалась разработка систем разделения времени. Лаборатория ИИ разработала собственную ОС потому, что эта работа их коллег по проекту MAC продвигалась не очень быстро, да еще и по мнению ИИ Лаборатории куда-то не туда [Chio2001]. С 64-го года проект MAC был вместе с General Electric и Bell Labs разработчиком системы MULTICS для больших компьютеров с виртуальной памятью. И в начале семидесятых такая машина появилась в МТИ - Honeywell 6180. Версия 6080 с аппаратной защитой памяти, один из первых образцов [Honeywell72]. "Honeywell" потому, что эта компания подобрала проект заброшенный General Electric. +Осуществить перенос MACSYMA на MULTICS хотел еще Мартин, но разработку MACLISP для MULTICS начал только в конце 71-го года Дэвид Рид (David P. Reed). В 1973-74 годах работы велись под руководством Дэвида Муна (David Moon). MACLISP на MULTICS заработал в 73-ем году [Whit77]. Имплементация не могла быть портирована, за исключением кода компилятора, написанного на Лиспе. Но даже часть этого кода была воспроизведена потому, что никто уже не понимал как он устроен и работает [Gree96]. Ни сам язык MACLISP, ни устройство имплементации не было документировано. Но для воспроизведения нужно было все это прояснить. Результатом этой программной археологии стал мануал MACLISP [Moon74], который лисперы называют "Moonual" [Stee96]. Разобраться в устройстве (N)COMPLR было еще сложнее. В 78-ом году LCP, компилятор для MULTICS, был в полтора раза короче. Часть трюков были просто не нужны на MULTICS. Например потому, что там размер указателя был не 18бит и мог вмещать целые числа полезного размера. Но значительное количество трюков просто посчитали практически неизвлекаемыми из кода NCOMPLR и утраченными. Сопровождающие код компилятора для MULTICS, как и Стил в описании RABBIT рекомендовали [Gree96] читать о том, как надо делать оптимизирующие компиляторы не в коде NCOMPLR, а в книге [Wulf73] Вульфа об имплементации BLISS-11. +Не смотря на то, что портировать MACSYMA удалось [Whit77], это направление бегства привело в тупик. Машина была "непопулярна" [Fate81] [Stee96], производитель гордился двумя сотнями заказов и способностью поставить 25 машин за квартал. Это для всей линейки из которой Macsyma работала только на топовой модели [Honeywell72]. Система была непопулярна по сравнению с мэйнфреймами IBM, но и PDP-10 не был популярным. Более важным было то, что система была не особенно популярна среди пользователей Macsyma, которых почему-то не привлекала перспектива смены платформы на ту, которая на десятичный порядок дороже. И с 74-го года к их мнению надо было прислушиваться еще более чутко. +Honeywell 6180 была одной из первых машин, на которой бы мог работать компилятор ФЯ. Если бы в 73-ем году лисперы из проекта MAC умели и хотели делать компиляторы ФЯ. Машина, на которой будущий первый компилятор ML смог бы как-то компилировать, например, другой компилятор ML (4Мб памяти, 8Мб быстрого хранилища для свопа) обошлась в какие-то семь миллионов долларов (51.5 миллион долларов 2023) [Honeywell72]. И мы знаем, что мог бы, потому что на машине этой серии работал один из первых компиляторов ФЯ. Но было это в 80-е годы, когда было много гораздо более доступных альтернатив. Лаборатория, в которой это произошло, получила машину в 1979 [MULTICS1]. Эх, почему имплементаторам ФЯ дали поработать на компьютерах за 50 миллионов в 60-х, а не в 70-х? В 70-х от этого могла бы быть какая-то польза. Для имплементаторов ФЯ, конечно. +Но MACLISP на MULTICS мог бы и не дожить до того момента, когда LCP скомпилировал компилятор ФЯ, если бы на Лиспе не написали вторую программу с пользователями. +После того как спасение MACSYMA с помощью MACLISP на MULTICS не нашло понимания у её пользователей, имплементация Лиспа влачила жалкое, полузаброшенное существование. Его ведущие разработчики в МТИ вроде Муна оставили его ради другого проекта. Разработкой теперь занимался Бернард Гринберг (Bernard S. Greenberg), который больше пытался расшифровать и описать NCOMPLR, чтоб перенести из него еще что-нибудь в LCP [Gree96]. Honeywell не сопровождал его, никто не распространял никакие программы написанные на Лиспе для MULTICS. +Но в 78-ом году Гринберг и Брюс Эдвардс (Bruce Edwards) начали писать на нем EMACS, который стал за пару лет популярным у пользователей MULTICS. EMACS стал продуктом Honeywell, поставляемым вместе с ОС и компания вынуждена была поддерживать разработку имплементации Лиспа [Gree96b]. + + +Лисп-машина останавливается +--------------------------- + +Второе направление было еще более амбициозным и еще менее уместным в наступающие тяжелые времена. Лидером направления был имплементатор MacLisp Ричард Гринблатт. Позднее к нему присоединились еще один имплементатор MacLisp Дэвид Мун, и многие другие лисперы Лаборатории ИИ МТИ, в том числе более известный другими своими работами Ричард Столлман (Richard Stallman). +Лисп-машины становились все более популярным проектом в МТИ и оттягивали все больше исследователей и программистов с других направлений [Stee96]. Написание эффективной имплементации Лиспа на обычном железе произвело на лисперов тяжелое впечатление. И опыт с имплементацией и реимплементацией MACLISP совсем не внушило лисперам уверенность в способности делать это снова и снова. У большинства отняло всякое желание это когда-нибудь делать. Какого же желания после этого оставалось хоть отбавляй? Правильно, разрабатывать собственное железо. +Железо предполагалось не таким уж особенным, тогда 32битные машины с изменяемым микрокодом становились все более обычными, но в обычных машинах было недостаточно быстродействующей памяти для микрокода, который лисперы планировали писать для имплементации лиспа. Этот микрокод служил для проверок тегов в указателях, а также для имплементации некоторых фич, из которых самая важная для этой истории - "невидимые" указатели. +"Невидимые" указатели - это системы из указателей на указатели (на указатели и т.д.) на значения, которые для программиста видны как указатели на значения. +Ладно, но как быть с урезанием финансирования, желанием пользователей Macsyma работать на доступных машинах? Проще простого: разрабатывать собственное _дешевое_ железо. +Этим дешевым железом должен был стать не микропроцессор. Лисп-машина будущего строилась на элементной базе прошлого. Может быть они не знали про успехи VLSI? Сомнительно. Похоже, что они ожидали гораздо больших успехов, чем были реально достигнуты. Например, что скоро появятся твердотельные жесткие диски на полупроводниках. +Под влиянием работ в Palo Alto Research Center (Xerox PARC) было решено, что лисп-машина будет персональным компьютером. Но персональным миникомпьютером, а не микрокомпьютером [Gree74]. Т.е. "персональные" компьютеры все равно смонтированы в 19-дюймовых стойках, стоят в машинном зале, требуют специального охлаждения, в офисе только терминал. +Что же подразумевается под "персональностью"? То, что машины однопользовательские и к ним нельзя подключиться через ARPANet [Gree77]. Да, да, как мы помним, Лаборатория ИИ была спасена пользователями, которые работали на компьютерах лаборатории через ARPANet. Кроме того, (D)ARPA была и будет далее заинтересована в машинах для ARPANet. Ну что ж, этим займутся другие. +В 74-ом году Лисп-машинисты планировали, что машина будет стоить $70K ($436,855 в 2023). И, чтоб уложиться в эту сумму, собирались экономить на памяти. Да, главной целью создания Лисп-машин было выполнение программ на Лиспе, которым нужны мегабайты памяти. Но лисперы пока что предпочли считать, что нужны мегабайты адресного пространства, а память можно организовать в три уровня: 200-400Кб ферритовой памяти, 1-2Мб быстрого жесткого диска и 12Мб медленного [Gree74]. +К 77-му году, когда была наконец построена первая лисп-машина с памятью, планы немного поменялись на $80K ($406,166 в 2023) машину с 4Кб быстрого кеша для стека, 256Кб ферритовой/полупроводниковой памяти (в 5 раз медленнее) и 64Мб (все адресное пространство) на диске (еще в 25000 раз медленнее). +Но, разумеется, идея экономии на памяти оказалась не такой уж хорошей. Начало было многообещающим: в 77-м на первой лисп-машине работают одновременно две программы - MACSYMA и программа Вудса (William Woods) LUNAR - каждая из которых умещалась в адресное пространство PDP-10 только с помощью всяких ухищрений для частичной загрузки. По "предварительным" результатам "значительная часть" Macsyma работает на машине с 256Кб ферритовой памяти как на PDP-10 KA-10 [Gree77]. Однако же, к марту 1980 уже считалось, что и вдвое большей памяти недостаточно: MACSYMA на лисп-машине с 512Кб работала в 5 раз медленнее чем на PDP-10 KL-10. +На 1Мб производительность посчитали хорошей, так что планы снова изменились (причем не только настоящие но и прошлые, в 1980-ом лисперы пишут, что проект Лисп-машин стартовал с предположения о том, что 512Кб основной памяти будет достаточно, и это не то, что писали в отчете 74-го года). Решили что надо больше 800Kб памяти и на нескольких машинах уже поставили 1Мб. К счастью, память дешевела, так что скоро можно и 4Mб делать [Whit80]. +А что еще подешевело к 80-му? Правильно, микропроцессоры, и пока лисперы собирали прототипы Лисп-машин, другие люди собирали прототипы реальных персональных компьютеров на порядок дешевле, но об этом ниже. +Как же такое экзотическое железо как Лисп-машина могло повлиять на историю ФП? Непосредственно - очень незначительно. Да, разработчики ФЯ в МТИ (бывало и такое!) использовали Лисп-машины. Да, единственный имплементатор ФЯ, который пользовался машиной с Multics имел доступ также и к Лисп-машине, в числе прочего. Но, в отличие от машин с Multics, лисп-машины заработали примерно тогда же, когда и те, на которых велась основная разработка ФЯ. Реальный вклад лисп-машинизма в том, что он обескровил направления, которые могли бы быть полезными для имплементаторов ФЯ. Уменьшил влияние лисповой исследовательской программы на ФП. Например, Лисп-машина определила какой компилятор эдинбургского ФЯ скомпилирует себя первым. Разумеется, не тот, который пытались заставить работать на Лисп-машине. + +Не в звездах, нет, а в нас самих +-------------------------------- + +> Втайне Уэст боялся VAX. +> Т. Киддер. Душа новой машины [Kidd81]. + +Третьим направлением бегства МТИ-лисперов с PDP-10 был VAX-11. Внезапное для лисперов (но не для всех) 32бит продолжение PDP-11, которое DEC сделала вместо ожидаемого лисперами продолжения PDP-10. + +### STAR против сил Лиспа + +PDP-10 и Honeywell 6180, которые использовала или собиралась использовать ИИ-Лаб-культура 60-70-х были мэйнфреймами. Большими компьютерами, требующими специальных помещений, десятков тыс. долларов в месяц на поддержку, стоящими порядка сотен тыс. - миллионов долларов 68-го года. PDP-11 же и VAX-11 были миникомпьютерами, самыми небольшими и дешевыми компьютерами общего назначения, на момент появления это класса в 68-ом году. +PDP-11 был популярной и распространенной машиной, де факто стандартным 16бит миникомпьютером. Довольно быстро, впрочем, оказалось что 16бит адресное пространство было ошибкой DEC и серьезным ограничением. Быстро - по меркам DEC, которая рассчитывала начать разрабатывать замену гораздо позже, чем через шесть лет [Bell98]. +Вокруг PDP-11 существовала другая известная программистская культура, несущая отпечаток относительной нищеты и суровых ограничений: UNIX. Если минсковиты неиронично сравнивали себя с космической программой "Аполлон" [Crev93], то эта культура выросла из персонального проекта программистов Bell Labs, замученных сначала Multics, а потом отсутствием доступа к ней [Salu94]. Теперь перед ними должно было распахнуться бескрайнее адресное пространство. +Проект с кодовым названием STAR стартовал в DEC в 75 году, первая имплементация VAX (Virtual Address eXtension) - VAX-11/780 была представлена в 78-ом. В октябре 80-ого выйдет менее производительная, но более компактная и бюджетная версия - VAX-11/750. VAX-11 будет самым популярным 32бит миникомпьютером [Bell98], основным направлением в DEC и главным успехом. +Насколько "мини" был этот миникомпьютер? Ну, не настолько, чтоб мощность вентиляторов системы охлаждения не измерялась в лошадиных силах. Корпус с ЦП 1.53м x 1.17м x 0.77м (ВШГ) и весит полторы тонны. Обычно соединяется с корпусами других устройств и вся сборка имеет габариты 1.53м x 2.51м x 0.77м. Все это требовало помещение с кондиционером на 42000 БТЕ/час. +Машины, которые покупали лаборатории и университеты обычно имели 256-512Кб памяти, но ее можно было расширить до 2Мб в первый год продаж и до 8Мб позднее. Кроме того, большие программы могли работать благодаря страничной памяти. На предыдущем миникомпьютере DEC PDP-11 программа могла загружаться в физическую память только полностью. +У покупателей обычно были уже совместимые устройства для PDP-11 (жесткие диски, стриммеры, терминалы). Конфигурация, которую купили Bell Labs (512Кб и без всего, что можно было использовать из старого) стоила в феврале 78-го $241,255 ($1,180,534 в 2023), но DEC продала Bell Labs со скидкой за $200 242 ($979,845 в 2023) [Lond78]. +Понятно, что для какой-то заметной распространенности и доступности все это слишком дорого и велико, но уже в следующем году после начала проекта STAR в Motorola стартовал проект MACSS - Motorola's Advanced Computer System on Silicon - миникомпьютер в одном чипе. Первый процессор семейства MC68000 анонсирован в 79 и доступен в 80. С 16бит шиной памяти но 32бит регистрами и совместим с будущими полностью 32бит моделями. Адресное пространство 24бит, 16мб для данных и 16мб для кода. Поддержка виртуальной памяти пока только в отдельном чипе [Heer80]. +Так что в 80-ом году Форест Баскетт (Forest Baskett) в Стэнфорде обнаружил, что машина с 256кб памяти, поддержкой виртуальной памяти и ЦП сопоставимым по производительности с VAX-11/780 может быть собрана из микросхем стоимостью в тысячу долларов ($3,734 в 2023 году). Стоимость собранной рабочей станции оценили в $8,000 ($29,870 в 2023) [Bask80]. За два десятка лет от часа машинного времени до целой машины с сопоставимой производительностью за сумму того же порядка. +Работу над рабочей станцией Баскетт, Андреас Бехтольсхайм (Andreas Bechtolsheim), Майк Нильсен (Mike Nielsen) и Джон Симонс (John Seamons) начали в мае 80-го под руководством более известного другими своими работами Вона Пратта (Vaughan Pratt). И 30 ноября 1980 была готова первая итерация прототипа, смонтированная накруткой [Bech82]. +Если про VAX-11/780 имплементаторы ФЯ по крайней мере будут думать, что на этой машине будут нормально работать первые компиляторы ФЯ и почти вся разработка компиляторов ФЯ в первой половине 80-х будет на и для VAX-11, то от этого прототипа произойдут машины, на которых они действительно будут работать во второй половине 80-х годов. VAX-11 и MC68000 не совместимы, но, по словам имплементаторов компиляторов, достаточно похожи чтоб модификация кодогенератора требовала большого труда. И на этих машинах будет работать одна и та же ОС. + + +### Aut Caesar aut NIL + +> Некоторые более современные фичи языков программирования, такие как "ко-рутины" и "замыкания". +> Дж. Уайт, NIL: перспектива [Whit79] + +В 1978-ом [Stee82] в МТИ начали разработку расширенной версии MacLisp на новых дешевых машинах с большим адресным пространством (VAX-11, будущих микрокомпьютерах) для разработки больших программ на Лиспе, в первую очередь для MACSYMA [Whit79] [Mose08]. Её назвали NIL, что означало New Implementation of Lisp или NIL Is Lisp. [Whit79] Основными авторами на первом этапе были опытный имплементатор Лиспов Джон Уайт, Рик Брайан (Rick Bryan) и Боб Кернс (Bob Kerns) [Rees2010] [Weinreb]. +Это было как раз то, что хотел Macsyma-консорциум [Mose08], так что все финансирование с разработки MacLisp было переброшено на NIL [Stee82] [Stee96]. +Для истории ФЯ нужно отметить, что второстепенной целью было опробовать в Лиспе всякие новомодные фичи из ФЯ вроде "замыканий" и "лексической видимости" [Whit79] и в проекте участвовал автор компилятора RABBIT Гай Стил. У которого появился союзник в МТИ в деле функционализации Лиспа. Джон Уайт в 77-ом году поработал [Whit77] в IBM Research над тем самым функциональным лиспом Фреда Блэра. И, видимо, теперь считал, что это не такая плохая идея [Stee96]. Так отголоски обоекембриджской программы снова дошли до лисперов МТИ, но только после того как они переизобрели ФП самостоятельно. +Лексическая видимость, впрочем, теперь и в дальнейшем ее сторонники представляли не как функционализацию Лиспа, а как приведение области видимости в порядок. Прорывная идея заключалась в том, что область видимости в Лиспе должна работать одинаково независимо от того интерпретируется код или скомпилирован [Whit79]. +Код писали на MacLisp на PDP-10 со слоем совместимости, который делал MacLisp похожим на NIL - техника бутстрапа, которую будут и дальше применять многие имплементаторы лиспов и схем [Whit79]. +Рассчитывали не повторять ошибки прошлого с написанием кучи кода на ассемблере, сделать портабельную имплементацию, основанную на виртуальной машине [Whit79]. +Пока что все выглядит хорошо. Что же в этот раз было не так? +Возможно, цель написать Лисп на Лиспе была излишне амбициозной. Тут речь не только о компиляторе. Решение не писать рантайм на ассемблере было хорошим. Проблема была в том, что рантайм решили писать не на языке системного программирования для этой платформы, а на Лиспе [Whit79]. +И ошибки все равно повторили, стали писать кучу кода на ассемблере [Shivers] [Stee96]. А что делать? Macsyma нужна быстрая длинная арифметика [Weinreb]. +От проекта уже в том же 78-ом отделился дочерний проект по имплементации NIL для еще более карикатурно недоступной и нераспространенной платформы чем Multics - суперкомпьютера S-1 Mark IIA, разрабатываемого в Ливерморской лаборатории (Lawrence Livermore Laboratory). И Стил оказался как раз в нем. Вы, наверное, недоумеваете, уж как это-то могло оказать влияние на развитие ФП? Всему свое время. +S-1 NIL должен был быть следующим шагом после MacLisp по оптимизации арифметики и работе с массивами. Стил собирался использовать как свой опыт с RABBIT, так и передовые наработки по аллокации регистров и т.д. из мейнстримных компиляторов [Stee82] [Stee96], и под передовыми наработками подразумевался компилятор BLISS-11 [Wulf73]. +Различий между этими имплементациями становилось все больше, как в языке (в VAX NIL решили добавить ООП), так и в рантайме (теги на S-1, группировка в памяти однотипных объектов кучи на VAX) [Stee82]. +Но самой большой проблемой было то, что лисперы МТИ не любили Macsyma, не любили VAX. Не любили очень эмоционально и годы спустя придумывали странные истории [Stee96] о том, как это правильно - не делать имплементацию Лиспа для VAX-11. Например, почему помер InterLisp? Потому, что сделали его имплементацию для VAX, очевидно же. Лисперы МТИ не хотели работать над NIL. Они хотели работать над Лисп-машинами [Stee82] [Rees2010] [Weinreb]. Для истории ФЯ нужно отметить, что из-за этого на проекте поработал и приобрел опыт будущий важный имплементатор ФЯ. +С 79-го года имплементаторы писали, что NIL будет готов где-то через полгода [Whit79], но ни в 79, ни в 80 его не закончили. +Наступили 80-е, а лисперы МТИ, основных, самые успешные и опытные имплементаторы Лиспов в 70-е, так и не создали компилятора Лиспа, поддерживающего ФП фичи для перспективных машин. Не появилось бы вообще никакого, если бы один из разработчиков Macsyma, давно уже покинувший МТИ, не начал войну за наследство ИИ-лаборатории. + +Per aspera ad astra +------------------- + +Ричард Фейтман, тот самый разработчик Macsyma, который когда-то сравнивал MacLisp с Фортраном, с 1974-го работал в Университете Калифорнии в Беркли [Franz]. Анонс VAX-11 Фейтман встретил с энтузиазмом: в отличии от оставшихся в МТИ лисперов он считал, что это как раз то, что нужно для использования Macsyma [Fate81]. Он вообще по многим вопросом придерживался мнений, сильно отличающихся от мнений лисперов МТИ. Одним из ключевых было то, что Macsyma - это не какая-то собственность МТИ, а общественное достояние, ведь она сделана на деньги ARPA и Минэнерго [Stee96]. В МТИ могут сколько угодно придумывать оправдания для неспособности или нежелания сделать то, что хочет Macsyma-консорциум. Фейтман в это время сделает в точности то, что консорциум хочет. Это не так просто, но Фейтман не боится сложностей. +В это время в Беркли не было какой-то значимой производной МТИ ИИ и.т.д культуры. Было довольно чахлое ответвление UNIX культуры. Кен Томпсон провел там лето в 1976, установил UNIX на PDP-11 и начал писать компилятор Паскаля для него [McKus]. С тех пор в Беркли ни шатко ни валко занимались дописыванием этого компилятора (в 80-е он еще пригодится имплементаторам ФЯ), а Бил Джой (Bill Joy) собирал набор написанных в университете утилит. Не проблема, с чего-то надо начинать. + +Еще одно непопулярное у лисперов МТИ мнение Фейтмана: написание (переписывание) имплементации Лиспа на языке C лучше, чем подход NIL. Язык C, на тот момент новый и мало кем используемый, уже продемонстрировал свои возможности для системного программирования и разработки языков. Поэтому разработчики нового Лиспа для VAX решили, что никакие модификации его для их целей не потребуются. Для сравнения, Хейвенс (W. Havens) из Университета Висконсина писал VAX Interlisp на Паскале и Паскаль пришлось модифицировать, менять типизацию указателей. +Основным преимуществом C Фейтман и его единомышленники считали один портабельный компилятор, определяющий язык [Fate81]. Никаких проблем с несовпадением имплементаций, комитетами по стандартизации и так далее! +Университетский PDP-11/70 c UNIX UNIX ver 6 [Fate81] конечно же имеет слишком мало памяти, но разрабатывать какой-никакой Лисп можно уже на ней. И студенты Фейтмана приступили к разработке. Начали с Лиспа для PDP-11, написанного в Гарварде. Расширили для совместимости с MacLisp. Основными контрибьюторами в первоначальную систему были Майк Карри (Mike Curry), Джон Бридлов (John Breedlove) и Джеф Левински (Jeff Levinsky). Билл Роуэн (Bill Rowan) написал сборщик мусора и поддержку массивов. Кип Хикман (Kipp Hickman) и Чарльз Костер (Charles Koester) - поддержку плоских структур. Основными разработчиками имплементации далее были Джон Фодераро (John Foderaro), работавший над компилятором, Кейт Скловер (Keith Sklower), занимавшийся длинной арифметикой и оптимизациями и Кевин Лэйер (Kevin Layer), который позднее будет писать MC68K бэкенд [Fode83] [Franz]. +Заметная часть имплементации написана на Лиспе. Рантайм почти полностью написали на C, но не обошлось без пары страниц ассемблера. Компромисс между портируемостью и производительностью. +Основное предназначение имплементации - поддержка MACSYMA. Так что имплементаторы не особо стараются имплементировать фичи, которые для MACSYMA не нужны. Также не особенно работают над оптимизатором считая, что это можно компенсировать с помощью FFI. Писать часть кода на Фортране, Си и т.д. +Вызывать фортран код из PDP-10 Maclisp невозможно (Multics Maclisp есть FFI), на Лисп-машинах в это время нет компилятора Фортрана, готовых библиотек для HPC и прочих полезных вещей. На популярных платформах все это, конечно, есть. Библиотеки для FFT работает в 100 раз быстрее, чем написанная на Лиспе, так зачем тратить силы на оптимизации [Fate81]? Первая версия этого Лиспа, названного Franz Lisp заработала на PDP-11 весной 78-го [Fate81]. +Конечно, PDP-11 недостаточно чтоб запускать MACSYMA. Не проблема, Фейтман достанет VAX-11. Пока студенты Фейтмана занимались имплементацией Лиспа, он и еще 13 преподавателей составили заявку на грант NSF, который позволил, с добавлением из фондов департамента, приобрести VAX. Машину привезли осенью 1978. Сначала на ней работала ОС DEC VMS, но департамент хотел использовать UNIX, к которому привыкли на PDP-11 [McKus]. Конечно, для использования наработок студентов Фейтмана тоже нужен был UNIX. И для того, чтоб запускать большие программы вроде MACSYMA VMS не подходила. В VMS каждому процессу дается память одинаковая для всех процессов - резидентный набор. Страницы, которые не являются частью никаких резидентных наборов используются как дисковый кэш. Даже если работает один процесс - из его резидентного набора все равно отбирается память если много неработающих процессов. Разумеется это неудобно, процессы сильно отличаются по потреблению памяти. Также у процессов меняется поведение по запрашиванию памяти, Лисп-система во время сборки мусора сильно отличается таким поведением от времени работы мутатора. Это будет частично исправлено в VMS 2.1, но пока что этой версии системы не было [Baba81]. Не проблема, Фейтман достанет 32бит UNIX. +И действительно, вскоре после получения компьютера Фейтман раздобыл копию 32/V UNIX, написанного Джоном Рейзером (John Reiser) и Томом Лондоном (Tom London) в Bell Labs [McKus]. На момент анонса VAX-11 в Bell Labs уже был порт на более ранний 32бит миникомпьютер Interdata 8/32, и писать бэкенд для VAX-11 для так называемого "портабельного компилятора" Джонсона начали еще в середине декабря 1977. К тому времени, как им привезли железо 3-го марта 78-го, компилятор был уже такой степени готовности, что смог скомпилировать себя и ядро UNIX. +19 мая ОС заработала [Lond78]. +Но хотя 32/V и предоставлял Version 7 UNIX окружение на VAX, система за исключением машинно-зависимых частей кода оказалась похожа на систему для PDP-11. Аппаратная поддержка памяти на VAX не использовалась ни для чего кроме эмуляции PDP-11 сегментов. Поведение идентичное тому, что на PDP-11: аллокация для процессов непрерывных участков реальной памяти, которая сбрасывается в своп только целиком. Для выполнения процесса он должен быть загружен полностью, соответственно размер реальной памяти все еще предел для одного процесса [Baba81] [McKus]. Все еще хуже, чем на VMS! +Но для Franz Lisp этого уже достаточно. После получения VAX-11 прототип имплементации был перенесен на неё с PDP-11 и доработан до полноценной имплементации которая может собирать MACSYMA. Franz Lisp работает на VAX-11 UNIX с января 79 [Fate81]. +Для MACSYMA этого недостаточно. На машине первоначально 0.5Мб [Baba81] или 1Мб [McKus] памяти, нет возможности загрузить её всю целиком в реальную память. Не проблема, Фейтман организует поддержку страничной памяти. +Фейтман связался с профессором Доменико Феррари (Domenico Ferrari), преподавателем факультета систем в Беркли чтоб привлечь его группу к написанию системы виртуальной памяти для UNIX. Основной целью проекта называется предоставление большое адресное пространство для пользовательских процессов, а именно для лисп-программ таких как MACSYMA и для программ обработки изображений [Baba79]. Этим занялся его студент Озалп Бабаоглу (Ozalp Babaoglu). После имплементации прототипа Бабаоглу стал работать совместно с Биллом Джоем, который тогда поддерживал берклиевский дистрибутив UNIX. Билл Джой помог с интеграцией менеджера памяти в 32/V и отладкой [McKus]. Модификация Version 7 UNIX в систему со страничной памятью началась поздней весной 79 и первая версия была готова для использования в сентябре 79 [Baba81]. +Лисп является не только одним из основных бенчмарков для системы. Поддержка имплементации Лиспа закладывается в сам менеджер памяти. Добавлен системный вызов, чтоб перед началом сборки мусора можно было запросить более подходящую политику пейджинга [Baba81]. Система по умолчанию использует политику "дольше всего не использовалась". Но во время сборки Franz Lisp посещает так много страниц, что такая политика не годится. Поэтому системе посылается сигнал и она меняет ее на FIFO с удвоенной скоростью замены страниц. После окончания сборки системе посылается сигнал и она помещает все страницы лисповой программы в список свободных и возвращается к политике по умолчанию [Fode81]. +В декабре 1979 готов 3 BSD - дистрибутив UNIX с ядром с поддержкой страничной памяти и портированными программами, разработанными для 2 BSD. Например компилятором Паскаля, который начинал Кен Томпсон во время визита в Беркли [McKus]. Распространение системы началось в январе 1980, к весне распространено больше 50 копий. Популярна у тех, кто запускает большие программы, а не использует как обычную систему разделения времени [Baba81]. Сопоставимая поддержка больших программ появилась в Bell Labs UNIX только в System V Release 2 Version 4 в 84 году [Quar86]. Фейтман доволен результатами, Macsyma на VAX работает с 80-го года [Fode81]. В 81-ом "Vaxima" работает на VAX-11/780 c 2.5Мб физической памяти. Сегмент только для чтения, разделяемый между всеми пользователями 1.8Мб, объекты в динамической памяти 1.4Мб - 3.3Мб. В 81-ом году в Franz Lisp 15,000 строк на Си, немного ассемблера для аллокатора, сборщика мусора, длинных целых. Из-за того что в Си недостаточно инструментов для управления регистрами, пришлось передавать/возвращать на стеке во многих случаях, когда можно бы было в регистрах. Также лисперы недовольны медленным сишным соглашением о вызове. Но были довольны тем, как быстро удалось написать систему не сильно медленнее написанной на ассемблере. Со сборкой мусора все еще проблемы, даже поддержки от ОС недостаточно [Fode81], но к этому мы еще вернемся. +Лисперы МТИ не любят Franz LISP, хоть и признают, что это де факто стандартный Лисп на VAX. Пишут, что он написан "быстро и грязно" [Stee82] и что Franz Lisp не интересен потому, что "ничего не предлагает кроме портируемости" [Stee96]. +3BSD произвел хорошее впечатление на (D)ARPA и они выбрали берклиевский UNIX для использования как единую ОС для своих проектов, а значит для поддержки и финансирования [McKus]. Для начала, контракт на 18 месяцев с апреля 1980 на добавление фич, нужных для (D)ARPA. Билл Джой - руководитель проекта. В октябре 1980 выходит 4 BSD - основная система, на которой будут разрабатывать компиляторы ФЯ в 80-е годы. Дистрибутив включает Pascal и Franz Lisp, через год работает на ~500 машинах [McKus]. 3BSD и 4BSD де факто стандартный выбор UNIX на VAX, использовался даже в самой Bell AT&T. И многие, получив от них лицензию, даже не утруждали себя получением ленты с 32V [Quar86]. В (D)ARPA довольны дистрибутивом, заключают новый двухгодичный контракт, увеличивают финансирование в пять раз. И увеличив финансирование тут, в кое-каком другом месте они его существенно уменьшили, но это уже другая история. +Franz LISP входит в дистрибутив 3BSD и 4BSD. Статья про то, что страничная память имплементировалась для того, чтоб запускать Macsyma поставлялась с ним вместе с прочей документацией. Но чем больше лет проходило с тех пор тем меньше упоминаний о роли Macsyma и Лиспа в истории BSD и тем более расплывчатыми становятся оставшиеся. Лисперы исчезают из истории, как товарищи с фотографий со Сталиным. На момент написания этого текста, например, статья про BSD на Википедии почти полностью свободна от какого-то упоминания Лиспа, кроме скриншота на котором Franz LISP по какой-то причине. +По всей видимости, ранние историки BSD смело упоминали Лисп потому, что это было что-то важное и передовое, но чем дальше, тем страннее очередному автору очередного исторического очерка было видеть и тем сложнее оставить упоминания Лиспа. Который превратился в какую-то затянувшуюся шутку. И лисперов, которые стали известны как те, кто утверждают, что все изобрели и все могут сделать, но ничего не делают. +Да, как мы уже видели и еще увидим, лисперы часто присваивают себе достижения, которые от невнятной идеи лиспера до практически работающего воплощения доводились другими. Но этого достижения у них не отнять. Они сыграли важную роль в том, что UNIX стал ОС для рабочих станций, а не какой-нибудь нишевой системой для управления АТС. Но почему Лисп превратился в эту самую затянувшуюся шутку, с которой UNIX культуре некомфортно иметь что-то общее? Пока что лисперам, вроде бы, удалось спастись с тонущего PDP-10 и спасти главное из того ценного, что было сделано (но не компилятор с поддержкой ФП-фич). Но война за наследство ИИ-лаборатории только началась. И если МТИ проиграл первую битву, это еще не значит, что он не может сделать так, чтоб все проиграли войну. + +декабрьский апдейт +------------------ + +Итак, мы выяснили, что имплементаторы Лиспов не были особенно заинтересованы в поддержке функционального программирования. И стели делать что-то для этого только в конце 70-х. И даже когда стали делать, то не сделали ничего подходящего для того, чтоб взять и использовать как бэкенд для компиляции ФЯ. Причины для этого были разными, но результат один: в 70-е имплементаторы эдинбургских ФЯ могли использовать Stanford LISP на PDP-10, а в 80-е они вступили имея возможность использовать Franz LISP на VAX-11. Ни та ни другая имплементация, фактически, не имела для поддержки ФП ничего кроме сборщика мусора. Тут возникает вопрос: если имплементации Лиспа дают только сборщик мусора, то почему бы не рассмотреть использование каких-то других языков со сборщиками мусора? Зачем платить производительностью за поддержку в имплементации Лиспа ненужных ФЯ "динамических" фич? Сегодня относительно успешно имплементируют ФЯ трансляцией в мейнстримные языки со сборщиком мусора. Но десятилетия наработок в области эффективной имплементации скриптов все еще не дают ФЯ, транслируемому в JavaScript, догнать ФЯ, транслируемый в JVM. Но существовало ли в 70-е годы что-то соответствующее Java в этом сравнении? Мейнстримные языки со сборкой мусора могут быть существенно более современным явлением. Давайте выясним. + +Стандартизация — это путь обмана +----------------------------------- + +> Описание ALGOL 60 стало достойной демонстрацией языка. Хорошо организованное, маняще неполное, слегка двусмысленное, трудное для чтения, краткое, оно было идеальным холстом для языка, который обладал теми же свойствами. Как и Библия, оно было задумано не только для чтения, но и для толкования. +> Алан Перлис, Американская сторона разработки ALGOL [Perl78] + +> После Парижского заседания на Петера Наура снизошел Святой Дух. +> Фриц Бауэр, Европейская сторона разработки ALGOL [Naur78] + +Вернемся практически в самое начало нашей истории, когда работы над имплементацией CPL в Лондоне и том самом Кембридже завершились крахом. Выяснилось, что ожиданиям нового высокоуровневого языка в Кембридже и Королевском Институте Радиолокации не суждено сбыться. После этого мы отправились в другой Кембридж. Там сделали работающую, урезанную версию CPL практически полностью свободную от всех претензий на высокоуровневость. И потому не интересную для истории ФЯ. Понятно, что практически вся наша история ФЯ до сих пор была посвящена тому, что произошло от другого языка сделанного там, свободного от практически всех претензий на практичность. Что же происходило в том самом Кембридже и Институте Радиолокации? Похоже, что ни один из вариантов продолжения CPL от другого Кембриджа их полностью не устроил. Так что они поучаствовали в эксперименте, который определил, как много мечт о CPL могут быть воплощены в относительно практичном языке. Попробовали двигаться к компилятору ФЯ не как в Эдинбургской программе от ФЯ-скрипта к языку общего назначения, а от языка общего назначения к добавлению в него ФП фич. Как много ФП можно себе позволить с компилятором, который работает на обычной машине, а не на специально сконструированной без особых оглядок на дороговизну как в МТИ? +После гибели CPL, в Кембридже и Радиолокационном институте обратили внимание на другой язык, произошедший от ALGOL. Как и CPL. И общее происхождение только одно из многих общих свойств. Авторы CPL не без оснований заявляют [Camp85] о влиянии CPL на этот язык, как об одном из основных результатов их трудов. Язык, впрочем, начал разрабатываться достаточно давно, чтоб были основания предполагать его влияние на CPL. + +### Анизотропия фунарга + +Насколько ALGOL 60 хорош как основа для получения ФЯ? Да, мы уже рассказывали об одной неудачной попытке это сделать и скоро расскажем об еще одной. Но причина этих неудач не в ALGOL 60. Посмотрим на пример из учебника по ALGOL 60 [Ruti67]. В нем объявляется такая вот ФВП: + +```pascal +REAL PROCEDURE sum (p, q, term); + VALUE p, q; + INTEGER p, q; REAL PROCEDURE term; + BEGIN + REAL s; + INTEGER k; + s := 0; + FOR k := p STEP 1 UNTIL q DO s := s + term(k); + sum := s + END sum +``` + +А так она использована с объявлением локальной процедуры, которая замыкается на какие-то значения a и b выше по области видимости: + +```pascal +REAL PROCEDURE scalp(p); +INTEGER p; + scalp := a[p] x b[p]; +z := sum(1, n, scalp); +``` + +Что напоминает высокоуровневые обоекембриджские языки, но отличается экстремально тяжелым синтаксисом и тем, что все эти замыкания и передачи работают в одну сторону - вниз. Не только для функций, вернуть массив тоже не получится. Это, конечно не ФЯ, но очень амбициозный проект для 50-х годов. Память управляется автоматически и без сборщика мусора. Ну, пока кто-нибудь не сломает всю эту автоматику CPS-трансформацией, как Моррис. Имплементировать ALGOL 60 было не так легко, и обычно он не был имплементирован полностью [Naur78]. Но интересные для нашей истории фичи были имплементированы и довольно рано, в 1960-ом году [Dijk62]! ALGOL 60 - это не просто набор пожеланий, как CPL. И значит было известно из опыта, что можно и не отступать от набора пожеланий CPL так далеко, как отступили в BCPL. Но можно ли было отступать еще меньше? +Конечно, есть и другая, существенно менее интересная чем "работающий только вниз ISWIM с тяжелым синтаксисом", перспектива на ALGOL 60. Что это - Паскаль. Но действительно ли это Паскаль? +С одной стороны, это даже меньше, чем Паскаль. Описанный в репорте ALGOL 60 - не то, что мы бы сегодня ожидали от языка программирования. Это скорее что-то вроде языка формул калькулятора. Средств для описания структур данных в нем нет. Так что авторам имплементаций Алгола приходилось разрабатывать свои средства, и делать ALGOL 60 с расширениями. Как AED-0 [Ross61], на котором Ричардс написал первый компилятор BCPL для бутстрапа [Rich2000], как имплементация ALGOL 60 от Королевского Института Радиолокации [Wood66], как SIMULA. + +### План Йенсена + +С другой стороны - это больше, чем Паскаль. И ALGOL 60 не так просто имплементировать. В 1964 Дональд Кнут опубликовал [Knut64] код для проверки того, имплементирует ли компилятор ALGOL 60 основные фичи языка. И насколько эффективно. + +```pascal +BEGIN REAL PROCEDURE A(k, x1, x2, x3, x4, x5); + VALUE k; INTEGER k; + BEGIN REAL PROCEDURE B; + BEGIN k := k - 1; + B := A := A(k, B, x1, x2, x3, x4) + END; + IF k <= 0 THEN A := x4 + x5 ELSE B + END; + outreal(A(10, 1, -1, -1, 1, 0)) +END; +``` + +Да, как мы уже выяснили, работающая лексическая видимость это не то, на поддержку чего можно всегда рассчитывать в 60-е и 70-е годы. Код выглядит немного странно, но это всего лишь Паскаль с другим синтаксисом аннотации типов. Нет! Тест Кнута проверяет не только лексическую видимость. На Паскале этот код будет выглядеть так [Rosetta1]: + +```pascal +program manorboy(output); + +function zero: integer; begin zero := 0 end; +function one: integer; begin one := 1 end; +function negone: integer; begin negone := -1 end; + +function A( + k: integer; + function x1: integer; + function x2: integer; + function x3: integer; + function x4: integer; + function x5: integer +): integer; + + function B: integer; + begin k := k - 1; + B := A(k, B, x1, x2, x3, x4) + end; + +begin if k <= 0 then A := x4 + x5 else A := B +end; + +begin writeln(A(10, one, negone, negone, one, zero)) +end. +``` + +Да, в ALGOL 60 способ передачи аргументов по умолчанию - вызов по имени. Единственный аргумент из примера, который передается по значению декларирован как таковой: `VALUE k`. Это определенно не самое распространенное решение. Но интереснее то, как алголисты его использовали для имитации конструкции несколько более распространенной. +В ALGOL 60 нет лямбд. На первый взгляд это кажется нормальным. Их и в CPL/ISWIM обычно не планировали. В этой истории мы уже повидали несколько функциональных языков без лямбд, и в ALGOL 60 есть вложенные функции и функциональные параметры для передачи функций вниз. Но алголистам этого мало и они имитируют лямбды с помощью вызова по имени. Обычно программисты поступают наоборот и имитируют вызов по имени с помощью лямбд. +Чтоб паскалеобразность не мешала пониманию совсем не паскалеобразной семантики, рассмотрим пример на языке, в котором вызов по имени привычнее. Допустим нам нужно воспроизвести такую вот ФВП: + +```haskell +bar f = do + f 1 + f 2 +``` + +которая используется так: + +```haskell +> bar (\x -> print x) +1 +2 +``` + +Для этого алголисты использовали технику, называющуюся "устройство Йенсена" (Jensen's device), в честь одного из имплементаторов Алгола Йорна Йенсена (Jørn Jensen). +Заводим для каждой связанной переменной параметр функции и пишем в него нужные значения перед каждым "вызовом" "функции": + +```haskell +foo a f = do + writeIORef a 1 + f + writeIORef a 2 + f +``` + +Получаем тот же результат: + +```haskell +> x <- newIORef 0 +> foo x (print =<< readIORef x) +1 +2 +``` + +Но какой ценой? Отказ от имплементации лямбд ничего не дает. Разумеется, имплементировать передачу в функции функций и санков для вызова по имени одинаково трудно. Сложная для имплементации часть лямбды - свободные переменные. И в ALGOL 60 есть "лямбды" без конструкции связывания переменных. Все переменные в них свободные. Это не только не упрощает имплементации, но и в сочетании с другими фичами не запрещает использование "лямбд" в качестве лямбд. Но, может быть, так и было задумано? Да, обычно в ISWIM-ах не было лямбд, но в реально имплементированных ISWIM-ах лямбды как раз обычно были. И Йенсен очень хорошо позиционирован для того, чтоб продвинуть лямбду в Алгол, если он хочет. Он работает над компилятором вместе с основным автором Алгола и редактора репорта Петером Науром (Peter Naur). Но нет, вместо обычной лямбды - странный фокус. Алголисты вообще про лямбду-то знают? + +### Конвергенция? + +Корректная статическая видимость Алгола в сочетании с этим странным фокусом, по всей видимости, и породили мнение о том, что авторы Алгола не знали о лямбда-исчислении, но (недо)открыли его независимо. Статическая видимость необходима для того, чтоб откладывание вычисления параметров правильно работало. +Следовательно, предполагает Бердж [Burg75], а вслед за ним и Тернер [Turn12], имеет место конвергенция подходов "как надо делать по теории" и "как надо делать чтоб работало". Что должно подтверждать некоторую фундаментальную правильность подходов "по теории". Во что, конечно, хочется верить. +Как мы уже знаем, лисперы действительно столкнулись со сложностями, когда имплементировали видимость не как в ЛИ. С другой стороны, два десятка лет после открытия этих проблем лисперы чаще решали, что сойдет и так. +Но насколько правдоподобно то, что авторы Алгола совсем не знали ЛИ? +МакКарти уже понял что такое лямбда и как она должна работать [McCa60b] к тому времени как ALGOL 60 репорт был готов. Но в первые годы разработки ALGOL 60 МакКарти видимо знал про лямбды гораздо меньше, если судить по коду в первом отчете [McCa58] Лаборатории ИИ МТИ, в котором `map` применяется так, как будто написан с помощью техники Йенсена: + +```haskell +maplist (cdr(J),L,(L = K -> diff(L), L != K -> copy (L))) +``` + +Но Рутисхаузер утверждает, что техника Йенсена изобретена на два года позднее, в 1960 [Ruti67]. Возможно, МакКарти мог поучаствовать в обсуждениях только после того, как основные решения были приняты. И к тем участникам обсуждений, кто должен был знать ЛИ в это время можно добавить еще и Ландина. Хотя тогда он еще не состоял в комитете. +Есть серьезные основания сомневаться в идеях Берджа о том, что ALGOL 60 не проектировали со знанием ЛИ. И для странностей Алголов есть другое объяснение. Комитетчики совсем не ладили друг с другом и придерживались очень разных взглядов на то, что нужно добавлять, а что нет. Многие странности языка и неоднозначности его описания были определены тем, как проходили линии фронта на момент перемирия 1960-го года. + +### Амстердамский заговор + +Бауэр (Friedrich Ludwig Bauer) пишет [Naur78], что Самельсон (Klaus Samelson) и "другие" пытались добавить в Алгол прямую и логичную как в лямбда-исчислении передачу функций через аргументы. Неудачно. Алан Перлис (Alan Jay Perlis), по словам Бауэра, заявлял, что понимает, что вызов по имени - скрытое добавление в язык фич для передачи функций в функции, которые не удалось добавить явно. Бауэр жалуется, что Наур просто не оставил адекватных заметок об этих попытках "функционализации". Наур пишет, что в вопросах передачи функций через параметры не было не только общего согласия, но и общего понимания. Что полностью понимал о чем говорится в предложениях по передаче функций в функции только Перлис. Похоже, что не только он, но вполне возможно, что понимали не все. +Было бы чрезмерным упрощением разделять авторов ALGOL на про-ФП и анти-ФП фракции. Что "про", а что "анти-ФП" в те далекие времена было не совсем очевидно. И из нашего времени многие позиции выглядят противоречиво. По крайней мере, на первый взгляд. Например, те, кто были за лямбды - были против рекурсии. +Попытка американской части комитета (видимо МакКарти) добавить рекурсию с аннотацией `recursive` оказалась неудачной, предложение было отклонено с небольшим перевесом. Но рекурсию удалось добавить Ван Вейнгаардену с Дейкстрой в результате "Амстердамского заговора". В репорте обнаруживались или даже закладывались некоторые умолчания и утверждения, которые по отдельности не привлекали внимания тех, кого они не должны были привлекать, пока все вместе они не сложились в поддержку рекурсии. Когда противник уже измотан и потерял волю к борьбе с этой фичей. Наур вспоминает, что последнюю, решающую деталь, сконструированную так, чтоб Бауэр не обратил на неё внимание, предложил добавить Ван Вейнгаарден только при поддержке Дейкстры [Naur78], но Дейкстра приписывает исполнение хитрого плана себе [Dijk2001]. +Может быть, такая борьба комитетчиков даже полезна. В конце концов, авторы CPL так и не закончили его описание. Ведь для них оно было только рутинным документированием того, что прочие авторы согласны со Стрейчи. Другое дело - описание ALGOL. Каждая строчка в нем - памятник чьей-то победы. Свидетельство того, как кто-то был переигран и уничтожен. +С другой стороны, успехи борьбы за то, чтоб ALGOL 60 был функциональным языком, были очень ограниченными. Хак Йенсена - это, скорее всего не то, что вы хотели бы получить вместо лямбды. Да и с рекурсией все вышло не особенно хорошо. Рекурсия имплементирована не самым практичным для ФП образом и занимает стек в любом случае. Но в это время еще не знали как имплементировать рекурсию практично. ALGOL 60 репорт написан за четыре года до того самого выступления Вейнгаардена [Wijn66]. Возможно, поэтому у рекурсии и были противники среди тех, кто продвигал ФЯ фичи. Но не обязательно. Во время обсуждения доклада Самельсон отнесся без особого энтузиазма к идеям Вейнгаардена [Wijn66]. +Косвенным подтверждением истории про Самельсона, который хотел добавить в Алгол лямбды еще в 1960 можно считать то, как он изменил Алгол, получив такую возможность. + +Алгол с человеческим лицом +-------------------------- + +> Говорят, что один ирландец на вопрос, как добраться до какого-то отдаленного места, ответил, что если вы действительно хотели бы попасть в это место, то не стоило бы начинать путь отсюда. <..> Начинать с ALGOL 60 было ошибкой. +> Чарльз Линдси [Lind93] + +На выпуске репорта об ALGOL 60 разработка Алгола не закончилась, и вскоре линии фронтов стали двигаться вновь. На этот раз основной конфликт определить намного легче. Не все авторы хотели, чтоб новый Алгол был таким же новаторским и амбициозным каким был в свое время старый. Хотели, чтоб был просто таким же. ALGOL 60 был недоделан, не было средств объявлять типы данных вроде рекордов. Рекорды нужно добавить. В ALGOL 60 были недостатки вроде вызова по имени - это нужно убрать. Минимальные изменения ALGOL 60 описывались в репорте-кандидате Хоара (рекорды) и Никлауса Вирта (Niklaus Wirth) (все остальное) [Wirt66]. +Понятно, что этот подход, в основном, был направлен против функционализации Алгола. Но и члены этой партии продвигали некоторые изменения, которые делали Алгол более похожим на ФЯ. +В прошлый раз победил тот, кто первым написал хорошую заготовку для репорта. Так что победа партии умеренной модификации Алгола была весьма вероятна. В октябре 1965 в Сен-Пьер-де-Шартрёз минимальный инкремент Алгола был представлен как два документа от Хоара и Вирта. И комитет решил, что это примерно то, что нужно. Победное шествие, правда, натолкнулось на неожиданное препятствие. +Наш старый знакомый, непонятный изобретатель CPS-преобразования Адриан Ван Вейнгаарден, тоже подготовил заготовку репорта. Репорт вовсе не был готов, но комитету понравилось, как Ван Вейнгаарден описывает язык. Так что комитет решил: нужен язык Хоара и Вирта, описанный Ван Вейнгаарденом. +Из собственных взглядов на то, каким должен быть новый Алгол у Ван Вейнгаардена пока что были только идея "ортогональности" и то, что новый Алгол, называемый сначала Algol X, должен быть языком выражений. Последнее делает язык более похожим на ФЯ, у первого будут неоднозначные последствия для "функционализации". + +### Ускорение + +История добавления типов данных в CPL плохо задокументирована. Добавление типов данных в Алгол - совсем другое дело. Почему мы вдруг вспомнили про историю типов данных в CPL? Потому, что это скорее всего одна и та же история. +Джон МакКарти был автором первого предложения [McCa64] о том, как могут выглядеть типы данных для Алгола. Для этого он, еще в 1964, приспособил уже знакомую нам его идею [McCa61] о суммах и произведениях. Стековая дисциплина Алгола не особенно способствовала развесистым деревьям в памяти, так что в его примерах только плоские структуры вроде точек. Декларирующий типы программист должен придумать и описать имена конструкторов, селекторов и даже функций, возвращающих число-тег суммы: + +``` +CARTESIAN typename, ((field1, type1),...(fieldn, typen)), constructorname +UNION typename, ((cons1, sel1, type1),...(consn, seln, typen)), gettag +``` + +Да, никакой конструкции для деконструкции МакКарти не предложил. Нужно сравнивать числа-теги: + +``` +typename x := cons1(y) +type1 r := IF gettag(x) = 1 THEN sel1(x) ELSE y +``` + +Эту идею летом 65-го развил Хоар. Не только добавив побольше ключевых слов. + +``` +RECORD CLASS cons(head, tail); + BEGIN INTEGER head; + REFERENCE tail(cons) + END; +``` + +Хоар вводит ссылки. Рекорды Хоара совсем не плоские. Наоборот, это средства создания деревьев в куче. Тип рекорда и его конструктор имеют одно и то же имя: + +``` +BEGIN REFERENCE list (cons); + list := cons(1,NULL); + tail(list) := cons(0,NULL); +END +``` + +Удивительно, но синтаксис для юнионов не такой тяжелый: + +``` +UNION list(cons, nil); +``` + +Юнион тут - "неопределенная ссылка". +Легкость декларации компенсируется синтаксисом для деконструкции юниона с невероятными составными ключевыми словами: + +``` +IF x IS A cons THEN ... head(x) ... +OR IF IT IS A nil THEN ... +``` + +Хоар упустил возможность изобрести нотацию Бурсталла, при том что сам уже предлагал [Hoar64] для Алгола конструкцию такого вида: + +``` +CASE x OF ( + resIfx1 ELSE + resIfx2 ELSE + resIFx3 +) +``` + +Но для чисел, а не тегов юнионов. Идея настолько не предусматривает ничего кроме чисел, что даже самые примитивные "паттерны" в виде констант пока что отсутствуют, есть только порядок результатов. +Тут типы данных для Алгола сильнее всего сближаются с обсуждаемыми позднее [Stra67] типами данных для CPL. Этот обмен идеями мог осуществить Ландин, который принимал участие в проектировании обоих языков. Никаких признаний или даже претензий на заимствование в ту или иную сторону мы, правда, не видели. Да, у пишущих истории CPL есть мнение [Camp85], что новый Алгол происходит от CPL, хотя и без конкретных упоминаний заимствования типов данных. Историки Алгола CPL не вспоминают. +Хоар упоминает CPL в этом предложении фичи для Алгола, но не как язык, из которого он позаимствовал типы данных. Хоар пишет о ссылках как концепции из CPL, которую он предлагает не заимствовать. Уже в 65-ом году Хоар противник первоклассных ссылок и продвигает ссылочные типы. Продвижение которых и через десять лет после этого будет слишком смелым и опережающим свое время. Но в 70-е Хоар будет продвигать уже не МакКартиевские раздельные суммы и произведения, а суммы произведений. Это будет сближение с работами Бурсталла, а пока что пройдено сближение с CPL, дальше расхождения в типах данных с новым Алголом будут нарастать. +Разумеется, комитет Алгола 68, не принял это предложение Хоара в неизменном виде. Вместо ссылочных типов новый Алгол получил плоские типы, которые можно размещать и на стеке с явными первоклассными ссылками. Которыми можно имплементировать список с некоторым выбором представления так: + +``` +MODE LIST = UNION(NULL,CONS); +MODE NULL = VOID; +MODE CONS = REF STRUCT(INT h, LIST t); + + ┌───┬───┐ ┌───┬───┬───┐ + │001│ ├──►│ 1 │000│ │ + └───┴───┘ └───┴───┴───┘ + +``` +или так: + +``` +MODE LIST = REF UNION(NULL,CONS); +MODE NULL = VOID; +MODE CONS = STRUCT(INT h, LIST t); + + ┌───┐ ┌───┬───┬───┐ ┌───┬───┬───┐ + │ ├──►│001│ 1 │ ├──►│000│ │ │ + └───┘ └───┴───┴───┘ └───┴───┴───┘ + +``` + +Есть и еще одно важное отличие этих типов данных попавших в Алгол от того, что хотел продвинуть Хоар. Эти `MODE` декларации аналог `type` в Хаскеле, а Хоаровский `RECORD CLASS` - аналог `data`. `MODE` можно подставить в другой `MODE` и все продолжит работать, типизация структурная и ближе к первоначальной идее МакКарти: + +``` +MODE LIST = REF UNION(VOID,STRUCT(INT h, LIST t)); +MODE NULL = VOID; +MODE CONS = STRUCT(INT h, LIST t); +``` + +Хоар такого не хотел. +Вы, вероятно, не захотите использовать юнионы для объявления списков таким образом не только из-за неэффективного представления МакКартиевских данных в памяти (о котором мы уже писали в главах про LCF/ML и непосредственную имплементацию) но и потому, что новый Алгол пока что не имеет удобной конструкции для их разбора, пусть даже и с тяжелым синтаксисом как IF Хоара. Вместо удобной есть вот такая: + +``` +CASE nullvar, consvar ::= l IN + nullvar, + t of consvar +ESAC +``` + +Что тут происходит? Что-то вроде такого: + +```haskell +data Cons = Cons{ h :: Int, t :: List } +data List = Null () | NotNull Cons +case l of + Null null -> do + nullVar <- newIORef null + return nullVar + NotNull cons -> do + consvar <- newIORef cons + return . t =<< readIORef consvar +``` + +Да, вместо паттерна тут тоже только порядок. Что, возможно, даже хуже, чем вы ожидаете. Потому, что еще один автор нового Алгола, более известный другими своими работами, Ёнэда (Yoneda Nobuo) настоял, чтоб декларации `UNION(NULL,CONS)` и `UNION(CONS,NULL)` объявляли один и тот же тип. Порядок типов в юнионе не имеет значения. Одно хорошо - неправильный порядок вариантов в этом `CASE` не пройдет проверки типов. Так что пока лучше использовать рекорды и `null`, как собирались и в CPL. Не очень-то похоже на ФЯ. Юнионы даже хотели заменить на ссылку на любой юнион, но Хоар их отстоял. +Почему мечты Хоара потерпели крах? Это результат других планировавшихся изменений Алгола, идеи Ван Вейнгаардена об "ортогональности" и практических соображений. Если убрать из Алгола вызов по имени - нужно найти какую-то замену. Нельзя просто постоянно копировать структуры, передающиеся по значению. Нужно добавить передачу аргументов по ссылке. По замыслу Хоара и Вирта ссылочные типы - рекорды и юнионы - и способ передачи параметров - это отдельные фичи, никак не связанные. Но "ортогональность" Ван Вейнгаардена, что-то вроде минимальности в сочетании с первоклассностью, требует все это делать с помощью одной фичи - первоклассных ссылок. Видимо, решение делать все типы ссылочными тоже соответствовало бы "ортогональному" подходу. Но во-первых, из практических соображений авторы Алгола не хотели размещать все в куче. Наоборот, хотели иметь возможность размещать все на стеке, хотя куча со сборкой мусора и была добавлена. Но если вы переживаете за прочие функциональные фичи после такого-то решения вопроса о ссылочных типах, то у вас есть все основания. Во-вторых, это не устроило бы самих Хоара с Виртом. Рекорды Хоара должны были быть единственным ссылочным типом, передача параметра по ссылке должна была быть отдельной фичей. Хоар с Виртом были против "ортогональности" в принципе и называли свой подход "диагональным". Не повезло! Описывать все это в репорте хотел только Ван Вейнгаарден. Вернее, руководить своими студентами и аспирантами, которые описывают. Так что все больше и больше в Algol X было таким, как хотел Ван Вейнгаарден. Если кто-то предлагал какую-то фичу, которую Ван Вейнгаарден видеть в Алголе не хотел, он жаловался, что описание языка - тяжелый труд, что добавлять это уже поздно, но если так уж нужно предлагающий может сам описывать синтаксис и семантику. Изменения, которые Ван Вейнгаардену нравились он, конечно же, вносил быстро и с удовольствием. "Кто первый подготовит заготовку для репорта - тот и выиграл" - неверный урок из истории ALGOL 60. Выиграл тот, кто хотел делать скучную работу по редактированию репорта. Просто в случае ALGOL 60 это был один и тот же человек - Петер Наур. +Время шло. Ван Вейнгаарден все не дописывал кандидат-репорт до конца. Партия минимального изменения потеряла темп и Алгол догнали авторы серьезных изменений. Вирт потерял интерес к Algol X и имплементировал их совместное с Хоаром предложение как ALGOL W, который позднее развился в язык Pascal. Algol X же разовьется совсем не в Паскаль. Партия минимального изменения упустила свой шанс и проиграла. С осени 66-го года Ван Вейнгаарден работал над репортом один, без Вирта и Хоара. Началось время существенного изменения. +Погодите-ка, если Ван Вейнгаарден так усилился, пытался ли он использовать свой подход к описанию языков из того доклада про CPS, сделать в Алголе работающую рекурсию? Похоже что нет. Он усилился потому, что описывал язык не как рассказывал в том докладе. И сам, по всей видимости, потерял интерес к этому своему изобретению. + +### Перестройка + +На заседании комитета в октябре 66-го в Варшаве МакКарти предложил ad-hoc перегрузку операторов. Но самое важное для нашей истории изменение там предложил Клаус Самельсон. Передачи параметров по ссылке достаточно для того, чтоб заменить вызов по имени для многих. Но, конечно, не для тех, кто использовал Устройство Йенсена. Сторонники "функционализации" попытались обменять свою частичную победу из ALGOL 60 на победу более полную. Самельсон предложил добавить в Algol X лямбды [Same65]. +Вместо техники Йенсена применяющейся как здесь: + +```pascal +REAL PROCEDURE traps(a, b, n, f, x); + VALUE a, b, n; REAL a, b, f, x; INTEGER n; + BEGIN REAL s; INTEGER i; + x := a; + s := f/2; + FOR i := 1 STEP 1 UNTIL n - 1 DO + BEGIN x := x + (b - a)/n; + s := s + f END; + x := b; + traps := (b - a)/n * (s + f/2) + END + +traps(0, 1, n, exp(sin(w * t)), t) +``` + +Теперь можно, наконец, писать нормальную ФВП: + +```pascal +REAL PROCEDURE traps(a, b, n, f); + VALUE a, b, n; REAL a, b; INTEGER n; REAL PROCEDURE f; + BEGIN REAL s; INTEGER i; + s := (f(a) + f(b))/2; + FOR i := 1 STEP 1 UNTIL n - 1 DO + s := s + f(a + i * (b - a)/2); + traps := (b - a)/n * s + END + +traps(0, 1, n, REAL t : exp(sin(w * t))) +``` + +В предложении самельсона 65-го года лямбды точно не изобретены независимо, ЛИ упоминается явно. Но типы функций все еще неполные, как в ALGOL 60. И, конечно, из принципа "ортогональности" следует, что в языке должны быть лямбды и функциональные переменные, раз уж в нем есть функциональные параметры. К следующему заседанию комитета в Зандворте в мае 67-го Ван Вейнгаарден включил и перегрузку и лямбды в язык. +Для того, чтоб эти нововведения больше походили на Алгол, в Algol X добавили фичу, которой критики Алгола будут особенно недовольны - неявные приведения типов. Например, ссылки приводятся к значениям, а значения к лямбдам. Алголисты так вошли во вкус, что вместо специальных скобок добавили приведение элемента массива к массиву из одного элемента, для того чтоб `(1)` мог быть таким же нормальным литералом для массива как `()` и `(1,2)`. Как и МакКартиевские перегрузки операторов и структурные типы, это делало вывод типов в Алголе не особенно реалистичным, хотя этого, вроде бы, никто и не собирался делать. +Сходство с ALGOL 60, впрочем, уже перестало интересовать алголистов. Даже основной автор ALGOL 60 написал предложение [Naur64] о том, что функция должна возвращать значение последнего блока, а никаких присваиваний к одноименной переменной быть не должно. Алголисты сделали следующий логичный шаг и сделали Новый Алгол языком выражений, это была одна из первых идей Ван Вейнгаардена о том, каким должен быть новый Алгол. Еще 64-го года. +Наур сделал еще одно популярное предложение [Naur66] от октября 65-го о том, что аннотации типов и способов передачи аргумента + +``` +PROCEDURE P(a, b, c); VALUE b; INTEGER a, b; REAL b; +``` +должны быть в списке параметров: + +``` +PROCEDURE P(INTEGER a, INTEGER VALUE b, REAL c); +``` + +как, например, в CPL. По крайней мере, CPL в начале 66-го года [Stra66b]. Но еще в 63-ем в CPL псевдокоде [Barr63] типы уже были в списке параметров, хотя способы передачи параметров декларировались отдельно: +``` +ROUTINE Work [REAL a, b, c, INDEX d, LABEL e] + VALUE a, e; REF c; SUBST b, d +``` +Это нововведение приняла даже партия минимального изменения Алгола. +Синтаксис ALGOL 60 со множеством длинных ключевых слов подсократили и облегчили. Но легкость синтаксиса не навязывалась. У конструкций сделали и легкие и тяжелые синтаксические формы. + +### Сюрприз + +Наконец, в феврале 68 Ван Вейнгаарден распространил черновик репорта, который дошел даже до комитетчиков, которые старались не знать как там идут дела с описанием нового Алгола. В ужасе, они увидели там вместо Алгола язык, на котором можно писать что-то такое: + +``` +MODE LIST = REF NODE; +STRUCT NODE = (INT head, LIST tail); + +OP >> = (INT x, LIST xs)LIST: HEAP NODE := (x,xs); +PRIO >> = 9; +LIST nil = NIL; + +PROC map = (PROC(INT)INT f, LIST xs)LIST: + (xs IS NIL | NIL | f(head OF xs) >> map(f,tail OF xs)); + +INT y = 1; + +map((INT x)INT: x + y, 1 >> (2 >> (3 >> nil))) +``` + +Удивительно, но язык, похожий на ALGOL 60 примерно в той же степени, что и CPL, все равно назвали Алгол. Хотя, после различий между SASL и SASL, такое уже не должно удивлять. По крайней мере авторы языка сами дописали год к названию, получив ALGOL 68. +Конечно, бросается в глаза отсутствие параметризованных типов, но в остальном язык выглядит скорее как очередной модный и молодежный язык с элементами ФП начала двадцать первого века. Так что, не смотря на то, что долгое время Паскаль от партии ограниченной модификации Алгола был популярнее, понятно кто в конечном итоге оказался на верной стороне истории. И рассказ о параметризованных типах еще впереди, алголисты продвинулись тут значительно дальше смутных идей авторов CPL. +Да, функции всегда определяются с помощью лямбд. Ортогональность! Обратите внимание на то, что все объявления по умолчанию - это объявления констант. У обоекембриджских языков, как мы помним, с этим не все хорошо, это больше похоже на будущие эдинбургские языки. Функции не обязаны быть константами, можно объявить и мутабельную ссылку на функцию. В некоторых случаях даже нужно. Например, чтоб разбить взаимную рекурсию в том случае, если компилятор ALGOL 68 её не поддерживает. И компиляторы ALGOL 68 что-то не поддерживают не так и редко. +Думаем, уже из этого примера понятно, почему авторам первого компилятора Пролога и первого компилятора ML нравился ALGOL 68. Понятно, почему алголисты считают, что ML и даже эдинбургские ФЯ вообще произошли от ALGOL 68. Эта гипотеза выглядит очень правдоподобной. Установить происхождение ML от CPL можно только читая малодоступный обоекембриджский самиздат и подмечая какие-то необычные детали. И по воспоминаниям участников Эдинбургской Программы. +Понятны и претензии обоекембриджцев на влияние и даже происхождение ALGOL 68 от CPL. Какой из этих языков повлиял на какой и как - не так просто определить. Особенно потому, что участие Ландина в проектировании этих языков не особенно хорошо документировано. +Известно, что Ландин был в "ближнем круге" ван Вейнгаардена вместе с Ёнэдой, командой имплементаторов Герхарда Гооса (Gerhard Goos) из Мюнхена и другой командой имплементаторов из Брюсселя и получал черновики репорта еще в 67-ом. Если учесть такой-то состав "ближнего круга", а также то, что Гоос работал в одном университете с Самельсоном и Бауэром, неожиданная метаморфоза Алгола уже не выглядит такой уж неожиданной. +Но странно, что при всем при этом одно важное обстоятельство вскрылось только в Июне 1968 на встрече комитета в Тиррении. Там Ландин продемонстрировал, что функциональное программирование на ALGOL 68, каким его планировали сделать на тот момент, невозможно: + +``` +PROC curryplus = (REAL u)PROC(REAL)REAL: (REAL v)REAL: u+v; +PROC(REAL)REAL addthree = curryplus (3); +addthree(5) +``` + +возвращаемая из функции `curryplus` функция `addthree` ссылается на окружение, время жизни которого закончилось вместе с этим возвращением. Лямбды и тут неправильные. И алголисты не готовы к типам, значения которых не могут лежать на стеке. Поэтому возвращение функций, ссылающихся на окружение из которого они возвращаются, было запрещено. Так что МакКарти поучаствовал в создании как минимум двух языков с, по крайней мере, двумя разновидностями неправильных лямбд. +Но все эти функциональные проблемы затмила реакция комитетчиков не из "ближнего круга". Интересно, что еще сильнее чем язык, комитетчиков возмутил способ его описания. Хотя Ван Вейнгаарден получил в свое время контроль над репортом потому, что комитетчики хотели, чтоб новый Алгол описывали именно так. +После распространения черновика весной 68-го из комитета ушли Наур и Вирт, еще несколько комитетчиков, включая Хоара и Дейкстру, доработали в комитете до завершения первой редакции, но написали особое мнение [Dijk70] в котором назвали репорт слишком большим, а язык устаревшим. +Почему они считали его устаревшим? Это неочевидно из самого особого мнения. Но если учесть хорошее отношение Хоара к SIMULA и прочие работы и идеи подписантов особого мнения, то можно предположить, что из-за отсутствия каких-то конструкций для группировки функций. Вроде классов и, в недалеком будущем, модулей и АТД. По крайней мере, отсутствие модулей будет одной из основных проблем, которые авторы ALGOL 68 будут решать в 70-е. +Действительно ли репорт большой? Ну, даже если не считать все цитаты и одну иллюстрацию из «Винни-Пуха» Милна, зачем-то включенных в репорт, ALGOL 68 больше, чем SML 97. Но меньше, чем Haskell 98. Правда, в последствии репорт стал больше, проектирование языка не закончено в 60-е годы. Комитет планировал выпустить репорт и дать время имплементаторам его использовать. А после, уже с участием имплементаторов, написать окончательный репорт, к которому можно только добавлять фичи. Но не убирать и менять фичи из "окончательного" репорта. С момента выбора названия они опасались, что в 68-ом году он не будет готов. Так и вышло, первое издание репорта [Wijn69] было закончено в 1969. + +## Посчитать компиляции в Молверне + +> Более десяти тысяч успешных компиляций было выполнено вычислительной службой Королевского радарного института. +> Филип Вудвард, Практический опыт ALGOL 68 [Wood72] + +Конференция пытающихся имплементировать Алгол 68 состоялась в июле следующего, 1970 года в Мюнхене. Над имплементацией работали еще во время написания репорта. Две команды имплементаторов из Мюнхена и Брюсселя, как мы помним, пристально следили за работой Ван Вейнгаардена. Не дожидаясь очередных сюрпризов на плановых заседаниях комитета на одном из очередных морских или лыжных курортов. Но, неожиданно для всех участников [Lind93] [Bond2001], гонку выиграли не они. + +### ALGOL 68-R + +Первый компилятор написала команда имплементаторов из Королевского Института Радиолокации в Молверне: Иан Карри (Ian F. Currie), Сьюзен Бонд (Susan G. Bond) и Джон Морисон (John D. Morison). Перед докладом [Curr70] выступил с краткой историей проекта их руководитель, начальник отдела Филип Вудвард. Тот самый Вудвард, который рассказывал на летней школе 63-го года по "нечисленным вычислениям" [Fox66] про CPL и диалект ALGOL 60, который произвел впечатление на Поплстоуна [Popplestone]. +В 68-ом году в Радиолокационном институте начался переход на новые машины. И хотелось еще более современный и выразительный язык, чем Алгол 60. В 63-ем Вудвард с энтузиазмом ожидал CPL, который, по его рассказам, должен был "объединить все полезные средства LISP и ALGOL, да еще и добавить новых". В 68-ом было уже ясно, что разработка CPL провалилась. Но не время впадать в отчаянье. Как раз заканчивают описание почти такого же амбициозного языка - ALGOL 68. Успеют ли в институте имплементировать компилятор ALGOL 68 за два года, до полного перехода на новые машины, или лучше снова имплементировать ALGOL 60? (Портировать тот компилятор, про который Вудвард рассказывал в 63-ем невозможно - он написан на ассемблере). Решение было сложным. Но если говорить о выборе языка, на который они хотели бы перейти с Алгола 60, то тут выбора (уже?) не было. +В результате Вудвард и др. решили, что напишут новый компилятор примерно за год. И писали его с января 69-го. Компилятор написан на расширенном подмножестве Алгол 60, который им все же пришлось имплементировать для новой машины, ICL 1907F. Для нее еще не было ничего, кроме посредственного компилятора Фортрана. В апреле 70-го компилятор подмножества Алгола 68 уже был готов для пользователей в институте, в октябре 70-го запланирован полный переход на новые компьютеры. +Да, имплементировано только подмножество Алгола 68, которое разработчики из Молверна называют ALGOL 68-R. Но это не частичная имплементация из тех что были для CPL, когда почти ничего не было имплементировано. В данном случае было имплементировано почти все. Изменения в языке нужны для того, чтоб код можно было компилировать в один проход. Мы практики, объясняет Вудвард, для нас важна высокая скорость компиляции. Для этого пришлось пожертвовать несколькими фичами вроде автоматического преобразования обычных обращений по имени в лямбды и сделать декларации перед использованием обязательными. Такое объявление типов - расширение языка: + +``` +MODE X; +MODE Y = STRUCT (INT a, REF X x); +MODE X = STRUCT (REAL r, REF Y y) +``` + +Взаимная рекурсия двух функций разбивается уже имеющимися средствами: + +``` +PROC p1,q1; +PROC p = VOID: ( ... ;q1; ... ); +PROC q = VOID: ( ... ;p1; ... ); +p1 := p; q1 := q; ... +``` + +Что, конечно, было бы не особенно приятно делать в ФЯ. +Для компиляции нужно по крайней мере 96Кб (32K 24-бит. слов). Из них 60Кб - код самого компилятора. Разработчики считают, что это довольно много. +Можно использовать рантайм с компактифицирующим сборщиком мусора, но для желающих писать программы не использующие кучу - всего оверхеда связанного со сборкой мусора можно избежать. Производительность генерируемого кода для авторов тоже важна. +Возможно, Стрейчи и др. все-таки стоило доделать описание CPL. Может быть, в Радиолокационном Институте имплементировали бы какое-то более интересное его подмножество, чем имплементировал Ричардс. + +### Algol68C + +Но Вудвард, конечно, не единственный и даже не первый герой нашей истории, безрезультатно ожидавший CPL. CPL ждал еще один наш старый знакомый - Морис Уилкс. И, конечно же, в кембриджской компьютерной лаборатории под его началом тоже обнаружили новый язык с похожими на CPL возможностями. +В 1970, в том году, когда первый компилятор Алгола 68 уже передали первым пользователям, более известный другими своими проектами Стивен Борн (Stephen Richard Bourne) и Майкл Гай (Michael J. T. Guy) начали имплементацию языка под названием Z70 [Birr77]. Z70 был языком выражений для калькулятора с двумя типами: целыми числами и функциями, небольшим приблизительным подмножеством ALGOL 68. Z70 развивался как ISWIM, от минимального языка выражений, до языка совсем не минимального. Но если ISWIM долгое время рос только на бумаге, то Z70 рос как имплементация. Да еще и на том компьютере, для которого в свое время делали CPL. Имплементирован на том самом Titan с помощью компилятора компиляторов PSYCO. Можно ли имплементировать Z70, который еще больше? Раз за разом оказывалось, что можно! Шло время, и Z70 включал все больше фич Алгола 68, а все большую часть компилятора Z70 писали на Z70. К середине 72-го года Z70 был все еще подмножеством, но уже узнаваемым диалектом Алгола 68 и его переименовали в Algol68C. +В том же 72-ом году было принято решение писать портабельный компилятор Алгол 68, написанный на Алгол 68. Гай в этом проекте принимает уже не особенно активное участие. Первая версия была написана на подмножестве языка,доступном имплементации бывшего Z70. Портируемость достигалась использованием виртуальной машины ZCODE, как достигал BCPL, транслирующийся в OCODE. +Этот компилятор не был однопроходным, стадий компиляции у него было больше, чем у типичного компилятора Лиспа, но не так много, как у RABBIT, конечно. Компилятор в код ВМ компилировал в три стадии: парсер, тайпчекер и генератор ZCODE. ZCODE транслируется в нативный код нужной машины в один проход. +Компилятор, к сожалению, реализовывал гораздо менее интересное для нашей истории подмножество Алгола 68: только не требующую сборки мусора часть языка. +В сентябре 72 Борн начал переписывать парсер на Алгол 68. С января 73-го новый участник проекта - аспирант Эндрю Биррелл (Andrew Birrell) - пишет тайпчекер и работает над системой раздельной компиляции. К июню 73-го парсер и тайпчекер работают достаточно хорошо, чтоб распарсить и тайпчекнуть себя. Биррелл, еще один аспирант Уолкер (I. Walker) и научный сотрудник Эндрюс (A. Andrews) на основе кодогенератора Z70 пишут новый кодогенератор и транслятор ZCODE для Titan. Наконец, в конце сентября 1973 кембриджский компилятор Алгола 68 благополучно компилирует себя на Titan, за неделю до окончательного прекращения его работы. +Ура? Мечта Мориса Уилкса и других кембриджцев сбылась? По крайней мере, в урезанном виде. После апгрейда машины с увеличением памяти в четыре раза [Lavi12]. И слишком поздно. Да, это не воплощение самых смелых фантазий о CPL, не ML и даже не ФЯ вовсе. Но демонстрирует, что BCPL-пораженчество не было единственным практически возможным результатом обоекембриджской программы. Практичный неФЯ мог бы быть в большей степени ФЯ, чем сделал Ричардс. +Но мы уже выяснили, что компилятор полноценного ФЯ был неосуществим в то время и на том железе, которое было в распоряжении имплементаторов CPL. Но что насчет 70-х и более нового железа? CPL на рубеже 60-х и 70-х мертв, а ALGOL 68 пока еще нет. Dum spiro spero! Сегодня нам известны мейнстримные языки, такие как Java и С#, в которых изначально не было полноценных лямбд и параметризованных типов, но со временем они были добавлены. Могли ли их добавить в ALGOL 68? + +Эдикт Фонтенбло +--------------- + +Такие пожелания появились практически сразу же после конференции имплементаторов. Как оказалось, имплементировать Алгол 68 не так сложно, как ожидали его авторы. Так почему-бы не сделать язык еще более амбициозным? Тем более, что партия минимальной модификации и все сочувствующие окончательно разгромлены. + +### ALGOL 68 Revised + +Первое заседание комитета, который готовил второй, окончательный репорт состоялось через неделю после конференции имплементаторов, в июле 1970, в Абе-ла-Нёв [Lind93]. На нем Ханс Бекич (Hans Bekić) начал свою компанию за полноценные лямбды в Алголе 68. Но не стоит удивляться, что в начале 70-х кто-то, о ком мы пока еще не писали, вдруг озаботился возможностью возвращать функции из функций. Ханс Бекич работал с Ландином в колледже королевы Марии в 1968–69 [Jone99]. Работал в Венской лаборатории IBM, цитировал [Beki84] Бурсталла, разрабатывал META IV в 1973-75 вместе с Джонсом [Jone78]. И таким образом был частью той социальной сети вокруг Берджа и Йорктаун Хайтс. Тех узких кругов, в которых возвращение функций из функций было хорошо известно. +Проблема в том, что полноценные лямбды, размещающие замыкание в куче, а не на стеке делают сборку мусора гораздо важнее. Авторы Алгола 68 предпочитают его видеть в первую очередь стековым языком, имплементация которого может и не включать сборщик мусора. Так что у таких идей найдутся противники как среди старого поколения комитетчиков, которые в свое время не пропустили ссылочные типы Хоара, так и среди новых комитетчиков-имплементаторов. +Но, как мы уже выяснили, идеи о возвращении функций стали занимать умы как раз в это время. От возвращения функций в 70-м отмахнуться уже чуть сложнее, чем в 68-м. Поэтому против полноценных лямбд выдвинули два контрпропозала. Что посчитали главной проблемой полноценных лямбд Бекича [ALGOL72b]? То, что компилятор должен как-то отличать лямбды, замыкание которых нужно размещать в куче от тех, которым требуется только дисплей и стек. И все объявления процедур в Алголе 68 лямбды, так что отличать придется часто. Компилятор Стила будет решать эту проблему в конце 70-х. И не то чтобы комитетчики опасались, что не смогут решить её в начале 70-х, но считали, что это может заметно замедлить и усложнить компиляцию. Вполне разумное предположение, если учесть системные требования RABBIT. При этом комитетчики считают, что если задача будет решена и замыкания на стеке будут создавать только функции, которым это необходимо, то не придется платить за это тем, кто таких функций не пишет. Так что оба контрпропозала были направлены на облегчение решения именно этой задачи. +Первый контрпропозал - окружения Бома. Хендрик Бом (Hendrik Boom) предложил явно обозначать область видимости замыкания аннотацией `glob`: + +``` +MODE M = PROC(REAL)REAL; +OP O = (GLOB M f, g)M: (REAL X)REAL f(g(x)); +M cs = cos O sin; +``` + +Второй контрпропозал - частичная параметризация. Сначала так называли декларацию каррированных функций. Вместо многословия нескольких лямбд `(X x)PROC (X)X:(X y)X: x+y` просто `(X x)(X y)X: x+y`. + +``` +MODE F = PROC(REAL)REAL; +PROC compose = (F f)(F g)(REAL x)REAL: f(g(x)); +F g = compose(cos)(sin); +F h = g(1.0); +``` + +Окружения каррированных функций отправляются в кучу. +В обоих случаях компилятору очень легко определить где размещать окружения. +Но, конечно, без параметризованных типов функциональное программирование хорошо не пойдет. И как раз такой пропозал выдвинул Радиолокационный Институт весной 71-го [ALGOL71]. Типы в Алголе 68 называются "modes" и эта фича получила название "modals". +В ранней версии сами типы еще не были параметризованы. Предлагалось объявлять абстрактные типы без правой части и потом работать с ссылками на этот тип: + +``` +MODE X; +PROC sort = (REF[] X vec, PROC(REF X, REF X)BOOL cmp): ... +``` + +Фича демонстрируется на ФВП: + +``` +MODE X, LIST = STRUCT(REF X value, REF LIST next); +PROC apply = (REF LIST l, PROC(REF X) f): + (REF LIST ll := l; REF LIST empty = nil; + WHILE ll ISNT empty DO (f(value OF ll); ll := next OF ll) + ); +``` + +Тут `MODE X, LIST = ...` не какой-то новый синтаксис, это просто краткая запись для `MODE X; MODE LIST = ...` +В обобщенном коде со значениями такого абстрактного синонима, конечно, ничего нельзя сделать, можно только оперировать ссылками на него. Иначе такой код не скомпилировать отдельно от используемых потом конкретных типов. Обычная идея об "универсальном представлении". + +``` +MODE INTLIST = STRUCT(REF INT value, REF INTLIST next), + BOOLIST = STRUCT(REF BOOL value, REF BOOLIST next); +INTLIST il := ...; BOOLIST bl := ...; +apply(il, (REF INT i): (i := -i)); +apply(bl, (REF BOOL b): (b := NOT b)); +``` + +Совсем не удивительно, что разработчики кембриджской имплементации без сборщика мусора стали противниками [Bour72] этого предложения разработчиков имплементации со сборщиком мусора. Кембриджцы обеспокоены сложностью имплементации и считают, что полагаться на работоспособность универсального представления на компьютерах будущего опасно: в будущем ссылка на значение одного типа может отличаться от ссылки на значение другого. +Пропозал развивался и в 72-ом году "modal", который называют также и "explicit free mode" становится параметром [ALGOL72b]: + +``` +PROC sort = (MODE X, REF[]X a, PROC(REF X, REF X)BOOL swap)VOID: ... +``` + +параметризованная типом процедура используется так: + +``` +sort(INT, row, swap) +``` + +и раскрывается в + +``` +(MODE X = INT, REF[]X r = row, PROC(REF X, REF X)BOOL swp = swap ... sort(r,swp)) +``` + +планируют разрешить частичную параметризацию для аргументов-типов такого вида: + +``` +PROC gensort = (MODE X)(REF[]X a, ... +PROC(REF[]INT, ...) intsort = gensort(INT); +``` + +даже если соответствующий пропозал для обычных параметров не пройдет. +Теперь можно параметризовать и типы-синонимы: + +``` +MODAL LIST = (MODE Y) STRUCT(REF Y a, REF LIST(Y) next); +``` + +с похожим на лямбду синтаксисом. "Modals" называют функциями времени компиляции. +Появилось и предложение по улучшению конструкции для разбора композитных типов +[ALGOL72] [ALGOL72b]: + +``` +CASE xs IN + (NULL): ... , + (CONS l): ... l ... +ESAC +``` + +Позже, чем похожая конструкция описана у Бурсталла [Burs69] (1968). Но раньше, чем похожая конструкция описанная Хоаром [Hoar75], ссылающимся на Бурсталла (октябрь 73). Раньше, чем похожая конструкция появилась в _неисполняемом_ языке описания спецификаций META IV [Jone78] над которым работал Бекич (1973-75). Раньше, чем такая конструкция появилась в описании SCRATCHPAD [Jenk74] (апрель 74). Конструкция могла быть предложена МакКарти или Ландином. МакКарти слушал доклад Бурсталла [Burs69], но мы не видели, чтоб МакКарти когда-нибудь использовал её. И ни Ландин, ни МакКарти не принимали активного участия в доделывании Алгола 68. Может быть, конструкция была предложена Хоаром, который писал о ней позже и уже мог знать в это время. Но не принимал участия в доделывании Алгола 68 вовсе. Но скорее всего, если кто и предложил её - то Бекич, который на следующий год спроектировал язык с такой конструкцией и мог познакомится с ней в Йорктаун Хайтс. Или увидеть в статье Ландина и Бурсталла, в которой она вероятно использовалась и на которую Бекич ссылается в своих работах [Beki84]. "Вероятно" тут потому, что статья не отсканирована, и судить о её содержании можно только по косвенным признакам. Не исключено, даже не смотря на все это столпотворение людей, которые её видели, что конструкция была изобретена независимо. +Разумеется, у конструкции для разбора юнионов была и версия с облегченным синтаксисом, так что `map` на Алгол 68 теперь можно написать так: + +``` +PROC map = (PROC(INT)INT f, LIST xs)LIST: (xs| + (NULL): null, + (CONS l): f(h OF l) >> map(f,t OF l)); +``` + +К сожалению, "паттерн матчинг" одноуровневый, как в трактовке идеи Бурсталла Хоаром. И в системе МакКарти это еще хуже, чем в случае сумм произведений как у Хоара. Там хотя-бы можно связать хвост и голову в паттерне, а не доставать их из рекорда селекторами. +Итак, в 1972 Алгол 68 был близок к тому, чтоб стать "Эдинбургским ФЯ" до появления Эдинбургских ФЯ. Да, типы данных - не суммы произведений. Да, не ссылочные типы с понятными последствиями для полиморфизма. Но это детали и провести границу было бы труднее. Было бы. Такой Алгол 68 не состоялся. +На очередном заседании комитета в апреле 72 в Фонтенбло по всем требующим сборку мусора ФП планам был нанесен сокрушительный удар [ALGOL72a]. Параметризованные типы отклонили. Ареол первых имплементаторов Алгола 68 не помог. Отклонили и полноценные лямбды Бекича и обе их альтернативы. Линдси вспоминает, что полноценные лямбды вполне могли быть приняты. Но не были. Активность Ханса Бекича по отстаиванию полноценных лямбд произвела впечатление на его коллег по алголостроению и хорошо им запомнилась, если судить по его некрологу [Lind83]. Но её не было достаточно для победы. +Конструкции для разбора юнионов, правда, пережили разгром в Фонтенбло в неопределенном статусе и была принята в сентябре 72 в Вене [ALGOL73]. Так что имплементация `map` из примера выше работает. В сентябре 1973 в Лос-Анджелесе был принят окончательный ALGOL 68 репорт [Wijn77]. Опубликован в марте 75-го. + +### Расширения + +Окончательность репорта, правда, не означала полного краха функционализации Алгола 68. Из него больше ничего нельзя убрать или существенно переделать, но можно добавить. Это даже проще, процесс для принятия расширений Алгола 68 не такой сложный. Конечно, лямбды Бекича уже не принять: нельзя менять как работают лямбды. Но частичная параметризация и параметризованные типы становятся такими пропозалами расширений. +Параметризованным типам новый процесс принятия не особенно помог. Алголистам не особенно нравилось, что можно работать только со ссылками на типы-параметры. Хотя это ограничение было с самого начала, со временем эта ссылочная версия "модов" стала называться "ограниченными модами". Но и добавлять гипотетические неограниченные "моды", то есть фичу, которая не позволяет раздельную компиляцию они тоже не хотели [Lind74b]. +Но надежду решить более сложную проблему фунарга алголисты не потеряли. Частичная параметризация была переработана [Lind74a]. Это больше не способ объявлять каррированные лямбды. Фича теперь работает в месте применения, никак не влияя на объявление: + +``` +PROC compose = (PROC(REAL)REAL f, g, REAL x)REAL: f(g(x)); +PROC (REAL)REAL cossin = compose(cos,sin, ) +``` + +И прямо совместима с окончательным Алголом 68. Все лямбды продолжают компилироваться как раньше, частичное применение должно копировать данные со стека в кучу и создавать функцию, которая перед вызовом частично примененной функции вернет все что нужно из кучи на стек. Это, конечно, менее эффективно для пользователей, передающих функции вверх, но передающий их только вниз не платит за возможность передавать вверх. +В январе 75-го в Бостоне был собран специальный подкомитет по частичной параметризации из Бекича, Линдси и еще нескольких имплементаторов. В августе 75-го в Мюнхене расширение было принято. Описание добавлений в репорт опубликовали [Lind76] в начале 76-го. Алгол 68 все-таки стал, хотя-бы на бумаге, неудобным ФЯ без параметрических типов. Чем-то вроде POP-2, но на десяток лет позже. +Еще одно принятое по новому процессу расширение - модули. И в компилятор Радиолокационного института [Bond77] и в Кембриджский компилятор [Birr77] добавили модули довольно рано, для поддержки раздельной компиляции. В радиолокационном Алголе они были даже параметризованными. Стандартные модули начали обсуждать в Фонтенбло в 72, рассмотрели несколько пропозалов [Schu74] [Curr76] [Lind76b] [ALGOL78]. Некоторые идеи оттуда могли повлиять на работу с пространствами имен в ФЯ. Например оттуда в OCaml могла попасть квалификация для выражения вида `Module.(name1 + name2)`. Но это могло быть и изобретено независимо. Модули приняли в Яблонне в августе 78 [Lind78], на чем процесс расширения Алгола 68 и закончился. +Комитет, пишущий Алгол-репорт - так называемая "Рабочая Группа 2.1 IFIP по алгоритмическим языкам и исчислениям" - существует и сегодня [IFIP21], но, к сожалению, больше не выпускал никаких Алголов. В этом комитете побывали Дэвид Тернер, Конор МакБрайд, Олег Киселев, Ричард Берд и многие другие выдающиеся мыслители. Так что больно думать о том, какие потенциальные Алголы мы потеряли. +Но не все смирились с гибелью мечты о функциональном алголе 72-го года. Бауэр и Самельсон в том же роковом году решили делать в Мюнхене свой функциональный Алгол [Baue76]. + +### ALGOL 77 + +Конечно же, функции в Алгол 77 могут возвращать функции [Baue81] и в нем даже планировались аннотации ленивости [Baue79c]. С 73-го года этот язык стал языком CIP-L (Computer-aided Intuition-guided Programming) для системы трансформации CIP-S как S-0/NPL/Hope был в Эдинбурге и Лондоне языком для систем Дарлингтона и Фезера. CIP-L не синоним Алгола 77, в Мюнхене делали еще и ФП-Паскаль. Более-менее активная работа началась только в 75. +Чтоб направлять и проверять трансформации в Алгол добавили параметризованные АТД со спецификациями как в CLEAR Бурсталла и Гогена. Можно сказать, что проект и был повторением работ Бурсталла и других с той разницей, что вместо CPL/ISWIM был ALGOL 68. Это не было какой-то конвергенцией с переизобретением того же самого независимо. Бауэр и другие явно ссылаются на работы Дарлингтона по трансформации кода и работы Бурсталла, Циллеса, Гогена, Гуттага над языками спецификации [Baue76] [Baue79b]. + +``` +TYPE Stack ≡ (MODE U) STACK U, nullstack, isnull, top, rest, append: + mode STACK U, + FUNCT STACK U nullstack, + FUNCT(STACK U) bool isnull, + FUNCT(STACK U s : ¬ isnull(s)) U top, + FUNCT(STACK U s : ¬ isnull(s)) STACK U rest, + FUNCT(STACK U, U) STACK U append, + LAW A : ¬ isnull(s) => append(rest(s), top(s)) = s, + LAW R : rest(append(s,x)) = s, + LAW T : top(append(s,x)) = x, + LAW E : isnull(nullstack), + LAW NE: ¬ isnull(append(s,x)) ENDOFTYPE +``` + +не смотря на очевидно исполняемый пример спецификации [Baue79], имплементации у Бауэра не отмирают, их нужно писать + +``` +[ MODE STACK U ≡ EMPTY | (STACK U trunk, U item), + FUNCT nullstack ≡ STACK U: () + FUNCT isnull ≡ (STACK U s) BOOL: s = nullstack, + FUNCT top ≡ (STACK U s : ¬ isnull(s)) U: item OF s, + FUNCT rest ≡ (STACK U s : ¬ isnull(s)) STACK U: trunk OF s, + FUNCT append ≡ (STACK U s, U s) STACK U: (s, x) ] +``` + +Обратите внимание на то, что типы должны быть ссылочными, разрешена рекурсия без явных ссылок. Облегченный синтаксис для декларации типов данных, но первоначально была еще система МакКарти, со ссылкой на него. Но вскоре ей на смену пришли суммы произведений как у Хоара, со ссылкой на него [Baue81] + +``` +MODE LIST ≡ ATOMIC{nil} | cons(M head, LIST tail) +``` + +Если эдинбургские коллеги Бауэра в конце концов пришли к языку в котором и типы и модули параметризованы, то Алгол 77 начал как язык в котором и типы (modes) и модули (types) параметризованы, но стал языком, в котором параметризованы только модули. +Не смотря на цитирование Бурсталла, добавление в язык АлгТД и уравнений для спецификаций, никакого паттерн-матчинга, только многоветочный `if` + +``` +IF l IS cons THEN ... head OF l ... tail OF l +| l IS nil THEN ... +``` + +Но Бауэр позаимствовал выражение `where`. + + +Ограниченное могущество кучи +---------- + +Разумеется, все это богатство ссылочных фич Алгола 77 не было имплементировано в 70-е [Part84]. А что было имплементировано? И что можно было использовать для имплементации эдинбургских ФЯ? + +### Алголы 70-х + +Параметризованные типы (ну или "моды") были имплементированы в диалекте Алгола 68, называющемся Mary [Conr74]. Но Mary это не более свободный в использовании кучи язык, а наоборот более низкоуровневый и системный, с более явными ссылками. И параметризованные типы там жертвуют раздельной компиляцией, а не принимают универсальное представление: + +``` +MODE LIST(M) = (REF LIST(M) NEXT, M VALUE); +LIST(INT) LI := (NIL,2); +LIST(REAL) LR := (LR,5.0); +``` + +по крайней мере некоторые их разновидности. Это имплементировано не позднее лета 1974. +С поддержкой "ссылочных" фич в имплементациях Алгола 68 же все обстояло не особенно хорошо. Предложение Радиолокационного института про параметрические типы не было имплементировано даже Радиолокационным Интститутом и никто не имплементировал в 70-е годы частичную параметризацию [Lind93]. Сегодня имплементация, в которой это расширение поддерживается существует - Algol68g - но имплементировано оно неправильно. +Первоначальный Алгол 68 69-го года никогда не был полностью имплементирован. Но окончательный Алгол 68 75-го года был имплементирован один раз полностью. Но большинство имплементаций было частичными. Конечно, окончательный Алгол 68 не стал ФЯ, но если есть хорошо работающий сборщик мусора и хороший генератор кода для актуальных платформ, то можно использовать все это для имплементации ФЯ эдинбургской программы. Но есть ли все это? +Начинались работы над многими имплементациями Алгола 68 [Hunt77], но только две более-менее законченные имплементации обеспечивали хорошую производительность генерируемого кода. Уже знакомые нам компиляторы Радиолокационного Института и Кембриджа [Curr70] [Wich76] [Hunt77] [ALGOL80]. Например, Algol68C может быть быстрее BCPL. Получается, что сколько не жертвуй высокоуровневыми фичами, при плохом генераторе кода от этого много толку не будет. +Радиолокационный институт в 76-ом году начал работы над ALGOL 68RS - портируемым компилятором с ВМ, написанным на подмножестве Алгола 68. Летом 77-го года он уже работал [Bond77]. Бутстрап был, разумеется, осуществлен с помощью ALGOL 68-R. Но в Радиолокационном институте делали компиляторы для тех машин, которые использовали там [ALGOL81]. Разработчики ФЯ их не использовали. В 81-82 годах для ALGOL 68RS написали бэкенд для ненужного уже разработчикам ФЯ Multics. Только в начале 83-го года начались работы над генераторами кода для VAX и Motorola 68K [Finn83]. +В Кембридже, компилятор Algol68C заработал на новом компьютере IBM370/165 в конце декабря 73-го. Борн руководил его разработкой до начала 75-го. В 77-ом компилятор - это программа из 18 тыс. строк на Алголе 68 [Birr77]. +Следующей машиной после IBM370, на которой заработал компилятор была ICL 4130, та самая, которую использовали в Эдинбурге до PDP-10. По крайней мере в 77-ом он как-то работал. Когда в Эдинбурге эту машину уже не использовали. +Университет Эссекса портировал [Gard77] кембриджский Algol68C на PDP-10. Работы велись с осени 75 до весны 77. В лучших традициях таких проектов 70-х наработали за этот период только семь человеко-месяцев. +Транслятор из ZCODE в нативный написали на BCPL и переписали на Algol68C после бутстрапа. +Считают, что важно добиться того, чтоб код компилятора умещался в 35K слов (~140Кб), что удалось. И маленькая программа должна компилироваться секунд за шесть. Это не удалось, компилируется более 10 секунд. +Но в 77-ом году Algol68C еще считается недоделанным. Весной 77 первый релиз ожидают летом того же года, но релиз будет выпущен существенно позже. В январе 80-го [ALGOL80]. +И к релизу сборщик мусора все еще не заработал. Сборщика мусора в Algol68C нет не потому, что так и задумано. Разработчики компилятора обещают его в версии 2, работы над которой уже идут. Но если бы кто-то начал работы над компилятором ФЯ, который использует Algol68C как бэкенд, то это было бы ошибкой. Сборщик мусора так никогда не доделают и версия 2 никогда не выйдет. Последней версией будет 1.3. +Algol68C работал на VAX c 81-го года [ALGOL81], и хотя его невозможно было использовать как бэкенд и рантайм для ФЯ, его использовали в 80-е для написания имплементации ФЯ. +Из требований Algol68C можно составить некоторое представление о том, сколько памяти в конце 70-х может позволить себе требовать компилятор. Которым кто-то будет пользоваться на "обычной" машине, а не собранной специально под программы требующие гигантскую память, как RABBIT. +Первый релиз Algol68C на IBM 360/370 требовал минимум 180Кб и 200-220Кб для более реалистичных программ. Версия для PDP-10 требует минимум в 70K слов (~280Кб). Описатели этих требований обычно избегают указания конкретных размеров таких "реалистичных программ", но есть и исключения. В требованиях Algol68C для Telefunken TR440/TR445 говориться, что может потребоваться 50K слов (325Кб) для компиляции "больших" программ в 30 страниц [ALGOL80]. Судя по числу строк в распечатках программ на других языках, которые нам приходилось видеть до сих пор, это 1-2 тысячи строк. Надо полагать, что это для случая без раздельной компиляции. Потому, что компилятор они как-то компилируют. +Опциональность сборщика мусора в ALGOL 68-R/ALGOL 68RS и полное его отсутствие в Algol68C, а также успешное сопротивление фичам, которые сделали бы использование сборщика мусора более важным, от ссылочных типов и до полноценных лямбд, делает Алгол 68 не Java 70-х, которая при годилась бы для имплементации ФЯ, а чем-то вроде языка D 70-х. Разумеется, Алголом 68 и D список этих языков не исчерпывается. Даже Лисп, а точнее MacLISP пытался быть таким языком в 70-е. Тут мы имеем дело с интересным, но трагичным явлением, которое мы будем называть "язык семидесятых". Трагизм явления не только в неудачах вроде Алгола 68, но и в успехах вроде C++. +Авторы "языка семидесятых" уже знакомы со многими фичами, которые можно себе позволить в языке со сборщиком мусора. Но обязательный сборщик мусора не могут себе позволить авторы языка. Так что одни фичи приобретают странные формы и особенности, а другие отсутствуют, но не из-за незнания о них. Они сознательно запрещены потому, что авторы недостаточно смелые и неразборчивые для того, чтоб добавить эту фичу в странной форме и с особенностями. Если, конечно, авторы недостаточно смелые. +Ограниченная или неправильная первоклассность функций в одних языках 70-х могла мотивировать запрет на первоклассность функций в других языках 70-х. Так, в требованиях к языку, которым позднее станет Ada, запрет на первоклассность функций прописан явно [IRON77]. +Да, авторы некоторых языков не могут себе позволить сборщик мусора и сегодня. Но сегодня это скорее исключение. В 70-е это было правилом. И редкими исключениями были первые примеры того, что мы будем называть "язык восьмидесятых". Давайте посмотрим, какие языки в 70-е решили полагаться на наличие сборщика мусора, сделать его обязательным. Поищем настоящую Java 70-х. + +### Антиалголы 80-х + +ФЯ всегда были "языками восьмидесятых", хотя их неудачливые имплементаторы в 60-е этого и не знали. Но не все "языки восьмидесятых" - это ФЯ. Многие ФЯ в гораздо меньшей степени, чем Алгол 68. В том числе и из-за плохого отношения их авторов к Алголу 68 [Lisk93]. И плохое отношение к Алголу 68 довольно распространенный сентимент. Не беда, ФЯ можно сделать из любого "языка восьмидесятых" просто добавлением фронтенда. +Первым таким языком был, по всей видимости, еще один произошедший непосредственно от ALGOL 60 язык - SIMULA. Точнее поздние его версии с обязательным сборщиком мусора вроде SIMULA 67. +Для пользователей IBM 370 компилятор SIMULA 67 был готов в мае 72-го [Holm98]. Пользователи PDP-10 получили его позже. Но читатели, думаем, уже привыкли к тому, что это платформа второго сорта для всего, кроме Лиспа. +Компилятор SIMULA 67 для PDP-10 написали в Стокгольме. Он заработал в сентябре 74-го и первый публичный релиз состоялся только в январе 75-го. Да, в случае SIMULA 67 номер года в названии создает не намного менее обманчивое впечатление о том, когда его можно было использовать, чем номер года в названии ALGOL 68. +Для VAX компилятора SIMULA, правда, не будет. Но гипотетические имплементаторы ФЯ, которые могли бы решиться использовать компилятор SIMULA как основу их компилятора, этого еще не могли знать. Была проблема очевидная уже тогда - плохая производительность [Wich76] [Stro93]. +Но SIMULA 67 все-таки была полезна для развития ФП и повлияла на историю. Своим отсутствием на VAX мотивировала автора первого компилятора ML писать компилятор ML. +Другой ранний "язык восьмидесятых" - это уже знакомый нам по истории абстрактных типов язык CLU. Но до 77 он не может быть альтернативой компиляции через Лисп, так как сам транслировался в MDL - Лисп для антипрологов Хьюита. И практичный компилятор, который по оценке авторов был готов для использования, появился только в 1980 на PDP-10 и "позднее" на VAX и Motorola 68K [Lisk93]. Опять слишком поздно. +С прочими "языками восьмидесятых", которые начали делать в 70-х, на рубеже десятилетий все еще хуже. Наш будущий герой Дэвид Мэттьюз (David Charles James Matthews) в своей диссертации [Matt83] подготовил краткий обзор таких языков в начале 80-х. Russell - еще не имплементирован. Cedar Mesa - Mesa со сборщиком мусора, еще не имплементирован. Alphard - не имплементирован. +Да, транслировать эдинбургский ФЯ в Лисп - еще и не такая плохая идея. Все остальное готово в лучшем случае в той же степени или еще меньше. И похоже, что только трансляция в Алгол 68 производила бы код быстрее лиспового. Если б еще Algol 68RS был для имеющихся у разработчиков ФЯ машин. Если б только доделали вовремя сборщик мусора для Algol68C. +Все эти победы (отсутствия) сборщика мусора над Бекичем, "языками восьмидесятых" и ФП, конечно, не должны быть ни для кого сюрпризом. Ны уже ознакомились с плачевной ситуацией с памятью в то время. Сегодня мы знаем, что со временем сборщики мусора начали работать нормально, хотя и не без проблем. Но видно ли из 1980-го года хотя-бы отблески этого счастливого времени где-то в далеком будущем? Конечно, к описываемому времени сборщики мусора как-то существуют уже два десятка лет. Но мы уже видели, каким жалким и условным бывает такое существование на примере лямбд. Символично, что после ухода из проекта Борна, руководить разработкой Algol68C стал сапожник без сапог, более известный как раз статьей о сборщике мусора - Крис Чейни (Chris J. Cheney). Давайте выясним, насколько плохо шла сборка мусора, когда функционального программирования не было. + +Сколько тысяч слов – все впустую +-------------------------------- + +> Как только мы определились со сборкой мусора, ее реальную реализацию можно было отложить, потому что мы писали только игрушечные программы. +> Джон МакКарти, История Лиспа. [McCa78] + +Первые лисперы не хотели управлять памятью вручную, но выбирали между счетчиками ссылок и сборщиком мусора. Выбрали сборщик потому, что хотели, чтоб `cons`-ячейка умещалась в одно слово. На IBM 704 это 36 бит. Адрес - 15 бит. Две группы по три бита - слишком мало для счетчика [McCa78]. Довольно удачное стечение обстоятельств для функционального программирования, которое не редко создает циклические ссылки. Но это удачное стечение обстоятельств, вероятно, не было критичным для появления ФЯ. Например, авторы SIMULA со временем отказались от подсчета ссылок в пользу сборки мусора потому, что проводили эксперименты и сравнивали производительность сочетания подсчета ссылок со сборщиком мусора для сборки циклов в куче и компактифицирующего сборщика. По крайней мере вспоминают про это [Dahl78]. Но, наверное, была какая-то польза от того, что лисперы начали работать над сборкой мусора раньше. +Сделав выбор между счетчиком ссылок и сборщиком мусора, лисперы отложили имплементацию сборщика мусора потому, что писали игрушечные программы, и сборка мусора пока что не требовалась. + +### THE GARBAGE COLLECTOR HAS BEEN CALLED + +Ко времени первой публичной демонстрации Лиспа в 60-ом или 61-ом году сборщик уже работал. И был продемонстрирован, хотя это и не планировалось. Во время репетиции он не включался, но репетиция заняла достаточно памяти, чтоб сборщик мусора начал работать во время демонстрации и работал все оставшееся время выделенное для нее, печатая статистику [McCa78]. Трудно сказать, почему лисперы не хотели показывать свое главное, по мнению Тернера [Turn12], достижение. Но, к счастью, все кончилось хорошо. +В выводе статистики сборки сборщик мусора уже гордо именуется сборщиком мусора, но в первой статье о Лиспе [McCa60] его еще стесняются так называть. МакКарти там слишком серьезный, но это скомпенсировано другими лисперами в других статьях. +МакКарти пишет, что процесс "рекламации" занимает "несколько секунд". Это так, когда памяти 8192 слов. Но ко времени демонстрации сборщика это уже 32768 слов и процесс занимает по крайней мере четыре раза по "несколько" секунд. Не очень хорошо. Главная проблема [Wilk92] сборщика МакКарти: даже если живых объектов почти и не осталось к моменту сборки - все равно нужно обходить всю кучу, время работы сборщика зависит от размера кучи. +И сторонники подсчета ссылок практически с самого начала критикуют [Coll60] метод МакКарти за то, что время работы сборщика не зависит от того, сколько памяти освобождается. Но то, что время работы не зависит от того, сколько памяти освобождается - это даже хорошо. Плохо в нем то, что время работы не зависит только от количества живых объектов. +Получается, что рост памяти, которому мы так радовались на протяжении этой главы, несет не только новые возможности, но и новые вызовы. До следующей сборки проходит все больше времени, в которое можно уместить уже не только демонстрацию (но не вместе с её репетицией), но уже и какую-то более интересную работу. Но и перерыв в работе становится все более мучительным. +Конечно, для функционального программирования совсем не подходит такой взгляд на проблему. ФП код аллоцирует достаточно много, чтоб быстро занять память. ФП может извлекать пользу из роста памяти, если время сборки не растет так быстро как память. Но лисперы МТИ пока не собираются функционально программировать. И если отказаться от ФП - можно придерживаться стековой дисциплины, избежать аллокации массы короткоживущих объектов в куче. Так что Гринблатт и другие делают MacLISP, который использует преимущественно стек. И если аллоцировать в куче поменьше, переиспользовать структуры данных, изменяя их на месте, то можно сохранять эту иллюзию решения, отодвигать сборку в будущее, а там может и работа уже закончена и можно не собирать вовсе. +Такой подход маклисперов делает маловажной для них и другую проблему сборщика МакКарти, которая для ФЯ очень важна. Раз уж работа функциональной программы сводится к аллокации в такой значительной степени - аллокация должна быть быстрой. И получение памяти из списка свободных ячеек - не самый быстрый способ. Маклиспер просто аллоцирует на стеке - это быстро. Второй недостаток сборщика МакКарти особенно обостряется в сочетании с третьим. +Первоначальные аллокатор и сборщик МакКарти не имеют этого недостатка потому, что оперируют только парами. Так что каждая ячейка в списке свободных подходит. Сегодня кажется само собой разумеющимся, что в языке со сборщиком мусора можно аллоцировать не только объекты с двумя полями, но когда-то было иначе. В статье про ленивый эвалуатор [Hend76] как раз про такую кучу только для пар и говорится как про само собой разумеющуюся. Еще в начале 80-х это была важная и распространенная разновидность сборщиков [Cohe81]. +Но, если вы не хотите в своем ФЯ использовать типы данных с представлением в памяти, как у композитных типов МакКарти, то вам нужен другой сборщик. И лучше этого не хотеть. +Конечно, со временем объекты кучи разных размеров захочет получить главный заказчик MACLISP-фич Мозес. И его поддержат менее важные заказчики - создатели Антипрологов Хьюит и Сассман [Feni71]. Один из авторов PAL Эванс ознакомит их с работами имплементатора PAL Ричардса и работами над сборщиками мусора для Алгола 68. Но не с эдинбургскими работами над такими сборщиками. А Поплстоун претендует [Popp2002] на написания одного из первых таких сборщиков. Но первый контакт маклисперов с разработчиками сборщиков для Алгола скорее всего состоялся раньше и по другой причине, но об этом позже. +Так вот, для интересующихся аллокацией объектов разных размеров сборщик МакКарти еще хуже. Поиск подходящего куска памяти требует больше времени. Память фрагментируется и используется менее эффективно. Уоррен считал, что сборщик мусора определенно создает больше проблем, если нужно аллоцировать объекты разных размеров. И потому для его имплементации Пролога еще важнее, чем для Лиспа, больше использовать стек и меньше использовать сборщик мусора [Warr77]. +Можно предположить, что у имплементатора ФЯ нет никакой надежды дождаться помощи от маклисперов и более естественный его союзник - имплементатор объектно-ориентированного языка, который в первое время почему-то не хотел использовать стек, где это возможно. Мы уже выяснили, что у этого нежелания были серьезные негативные последствия в виде непрактичности "языка восьмидесятых" SIMULA 67 и плохого впечатления от языков со сборкой мусора вообще. Но у этого есть и положительная сторона. Для разработчиков таких ООЯ тоже важна скорость аллокации и нет надежды сделать все что нужно до исчерпания памяти. +Предположить можно, это логично. Но такая логика не может предсказать действия маклисперов, которые изобрели подходящий для ФЯ сборщик мусора. Сборщик, полностью или частично решающий все проблемы сборщика МакКарти, они изобрели, чтоб решить совсем другую проблему. + +### _Миллиарды_ + +Памяти нужно все больше, и физическая память растет недостаточно быстро. Лисперы готовятся к переезду на машину с виртуальной памятью - пишут MacLISP для Multics. И это обостряет еще одну проблему сборщика МакКарти: нелокальность ссылок. Освободившиеся `cons`-ячейки находятся все дальше друг от друга. Пары, с которыми ведется работа в данный момент, занимают одну страницу с парами, которые не скоро понадобятся и содержат ссылки на пары в других страницах. Переход к ним заставит читать данные с диска! +Мински изобретает решение проблемы: нужно обходить только живые пары в куче и сериализовать их в файл. После этого файл можно десериализовать в память и куча станет удобной компактной кучкой. Да, в наши дни сохранение сессии REPL в файл не особенно ассоциируется со сборкой мусора, но лисперы считают это изобретением копирующего сборщика мусора. +Память тем временем еще выросла. Уже можно копировать прямо в неё, предлагают [Feni69] Роберт Фенихель (Robert Fenichel) и Джером Иохельсон (Jerome Yochelson). В старые времена, рассказывают они, сборка мусора была нужна для переиспользования небольшого адресного пространства. Сегодня, в ревущем 1968-ом адресное пространство практически безгранично. На Multics в него поместятся _миллиарды_ `cons`-ячеек (выделено Фенихелем и Иохельсоном). Лисп-система может работать "почти бесконечно". Но, конечно, производительность будет деградировать из-за нелокальности ссылок. Поэтому, делают вывод Фенихель и Иохельсон, современный сборщик мусора не ищет свободное место, а дефрагментирует кучу. Сборку надо начинать не при исчерпании свободного места, а реагировать на замедление работы. Ведь сборка никогда не необходима, а только желательна чтоб ускорять работу, когда фрагментация зашла слишком далеко. И уж конечно сканировать всю кучу, как придумал МакКарти, практически неосуществимо. Обходится только живая её часть. И небольшая живая часть может быть еще один ориентир для начала сборки. Например, можно ориентироваться на короткий стек. Короткий стек, вероятно, удерживает мало объектов - самое время копировать. Копирование из памяти в память означает, что нужно в два раз больше памяти, но это не беда, памяти полно. Но полно ли? +Можно только позавидовать маклисперам, которые писать в ФП стиле не собираются, ведь даже первый компилятор ФЯ, написанный на ФЯ и использующий их сборщик мусора, аллоцирует с такой скоростью, что "почти бесконечность" 32бит адресного пространства закончится до конца рабочего дня. Даже на совсем не быстрых машинах того времени. +Само изобретение Фенихеля и Иохельсона и делает эту скорость возможной, да и вообще делает возможным функциональное программирование. Для ФП очень полезно, что можно аллоцировать простым прибавлением числа, как при аллокации на стеке, а не выискивая подходящий свободный кусок памяти, как нужно делать при использовании сборщика МакКарти. И, конечно, при такой-то скорости аллокаций короткоживущих объектов в ФП очень полезно то, что нужно обходить только долгоживущие. Типичный ФП код аллоцирует гораздо больше короткоживущих объектов, чем долгоживущих. Это означает, что добавляя память, можно сделать цену сборки меньше и меньше. Копирование живых объектов занимает примерно столько же времени, а освобождает все больше памяти. +Изобретение, правда, требует некоторой доработки напильником практичности. Алгоритм придуманный лисперами не совсем подходит для практического использования из-за того, что требует аллоцировать память на стеке во время обхода объектов кучи. Фенихель и Иохельсон пишут, что можно этого избежать с помощью разворота указателей [Scho67], хотя сами этого не делают. Но проблему можно решить проще и сделать алгоритм быстрее. Наш знакомый алголист из предыдущей главы Крис Чейни придумал в 1970-ом году другой алгоритм копирования [Chen70]. Он обходит граф объектов кучи в ширину, используя уже скопированные объекты в качестве очереди для их обхода. Да, обход в ширину производит не такую хорошую локальность ссылок, как обход в глубину, но комбинированный обход, копирующий детей объекта вместе с ним, в основном решает эту проблему. Но борьба сторонников этих двух разных обходов будет продолжаться еще какое-то время. +Получившийся практический полезный копирующий сборщик обычно называют именем Чейни. Лисперы называют его "алгоритм Мински-Фенихеля-Иохельсона-Чейни-Арнборга" [Bake77]. Арнборг (Stefan Arnborg) - это имплементатор SIMULA 67 для PDP-10, который использовал в своей имплементации и несколько доработал этот сборщик. Сам Чейни-то, как мы помним, оказался слишком практичным, чтоб имплементировать сборщик для своего Algol68C. +Арнборг как раз из тех имплементаторов ООЯ, которые приветствуют [Arnb72] перенос всей сложности из аллокатора в сборщик мусора, но мало что придумали для этого раньше лисперов, не очень этим интересовавшихся в описываемое время. +В статье Фенихеля и Иохельсона очень нехватает экспериментов и измерений, проверяющих их идеи, но этого не хватает во многих статьях о которых мы еще расскажем в этой главе. Маклисперы пишут в основном о том, что планируют сделать, а не о том, что сделано. Но так или иначе идеи были проверены разными имплементациями Лиспа. В MULTICS MACLISP сделали копирующий сборщик [Moon74]. Это направление бегства с PDP-10 было быстро заброшено и мало что написано про результаты. Но впечатление о том, с чем столкнулись Лисперы МТИ, можно составить по более позднему опыту имплементаторов Interlisp на VAX [Bate82]. И они, испытав сборщик Чейни, сомневаются в его применимости для машин с виртуальной памятью. Если куча не умещается в физическую память - секунды сборки становятся минутами. +Алголисты больше пишут о том, что сделано и публикуют какие-то измерения. Уже летом 71-го они проделали какие-то опыты, которые показали, что, возможно, копирующий сборщик не очень хорошая идея, когда надо работать на машине с виртуальной памятью и физической памяти не много. Статья [Baec72b] опубликована в конце 72-го. +Возможно, что эта разность подходов повлияла на то, что к началу 80-х лисперы опубликовали больше всего материалов по сборке мусора, а алголисты заняли только второе место, по оценке Коэна в его обзоре сборщиков [Cohe81]. +Да, копирующий сборщик приводит кучу в состояние, желательное на машине с виртуальной памятью. Но приводит крайне неудачным для машины с виртуальной памятью путем. В процессе сборки нужно пройтись по значительной части кучи, поднять в физическую память с диска множество страниц и, чтоб освободить для них место, задвинуть на диск множество страниц из физической памяти. При большом количестве долгоживущих объектов это не намного лучше подхода МакКарти и может быть даже хуже. Потому, что копирующий сборщик требует в два раза больше памяти для сборки. +С тем, для чего его придумали лисперы копирующий сборщик не справляется. Но он не полностью бесполезен для функционального программирования. Если вся куча находится в физической памяти, то он работает достаточно хорошо, хотя все еще делает много ненужной работы, обходя долгоживущие объекты чаще чем нужно. Но делает меньше ненужной работы, чем сборщик МакКарти. Не говоря о прочих его преимуществах, важных для имплементации ФЯ. Так что нужно просто подождать лет 15 до того времени, когда физической памяти будет достаточно. Но в МТИ, как мы помним, хотят запускать большие программы на Лиспе уже в начале 70-х. При этом надеются, что смогут для этого использовать машины с большим адресным пространством и диском, но небольшой быстрой памятью [Gree74]. И у лисперов МТИ есть кое-какие идеи о том, как можно исправить копирующий сборщик. + +### Лисп-машина атакует! + +> Следование ссылкам переадресации займет некоторое время. +> К. Хьюит, Х. Либерман [Lieb80] + +Не обязательно делать паузу на всю сборку всей кучи. Можно делать сборку параллельно, как предложил Стил [Stee75b] или хотя-бы инкрементально, как предложил МТИ-лиспер Бейкер (Henry G. Baker) [Bake77]. Сборщик Бейкера тоже копирующий, и проблема двойного потребления памяти при сборке еще усугубляется: сборка идет всегда и сборщику нужно тянуть в физическую память с диска все эти ненужные мутатору страницы. Компактность рабочей области хуже, рабочий набор больше. Есть и другие проблемы, особенно для ФЯ. Алгоритм Бейкера - из тех, которые заставляют больше платить за чтение объекта по ссылке, а не за изменение ссылки. Для доступа к объекту в куче нужно перейти по ссылке два раза. Естественно, для ФЯ предпочтительнее, когда нужно больше платить за изменение. Лисперы не собираются имплементировать этот метод сборки для Multics MacLISP, или какой-нибудь другой обычной машины. Только для Лисп-машин [Gree77]. И в 70-е все только собираются и собираются. +Но самое главное - это то, что принципиальная проблема все равно не решается. Сборщик делает ненужную работу, регулярно обходя граф долгоживущих объектов, на смерть которых нет особой надежды. Если объект кучи быстро не помер - он проживет долго. Но копирующий сборщик Бейкера или Чейни может быть частью более сложного сборщика, который действительно будет работать. +Если работать с одной большой кучей слишком тяжело, то почему бы не работать с множеством маленьких кучек? Алголист Беккер (H. D. Baecker) предложил [Baec72] добавить в Алгол 68 фичу из PL/I - возможность для программиста выбирать в какую арену аллоцировать. В отличие от PL/I, в Алголе 68 с этими аренами работал бы сборщик мусора. Явная работа программиста с аренами, конечно, нормальное решение для "языка семидесятых", но для ФЯ хотелось бы чего-то более автоматического и менее явного. +Арнольд Рошфилд (Arnold Rochfeld) из Эдинбургского Университета предложил [Roch71] собирать копирующим сборщиком общую кучу (т.е. слишком много) и окружение блока кода (т.е. слишком мало) при выходе из этого блока (т.е. слишком часто). Автор подозревает, что работать сборщик будет слишком часто и предлагает аннотации для блоков, которые бы указывали, нужно ли собирать при выходе из такого блока. Статья получена издательством в октябре 69-го, но опубликована только в декабре 71-го года. +Придумать, что нужно собирать мусор только в части кучи легко. Сложнее придумать, как это сделать, если между этой частью кучи и оставшейся могут быть ссылки. Для кого решение этой проблемы важнее всего в МТИ? Правильно, для антипрологосторителя Хьюита, которому так просто не обойтись стеком для всего короткоживущего, как прочим пользователям MacLISP-диалектов. Питер Бишоп (Peter B. Bishop), пишущий в МТИ диссертацию под руководством Хьюита, ссылается на эти работы и предлагает [Bish75] два решения. Можно делать ссылки между разными кучами двойными, ссылками на ссылки из одной кучи в другую. Можно поддерживать для сборщика мусора список ссылок на поля или объекты, в которых есть ссылки из одной кучи в другую. Второй вариант, конечно, лучше для ФЯ, но Бишоп, разумеется, выбирает первый. Да что не так с лисперами МТИ и этими их ссылками на ссылки? Почему они так хотят биться о память? Во всем этом виноваты Лисп-машины. Помните про поддержку этих ссылок на ссылки в них? Поддержку сборки мусора в части кучи или в одной из многих куч тоже планируют делать для Лисп-машин [Gree74] [Gree77]. Не игнорировать же фичу Лисп-машины? Надо пользоваться. Лисперы в МТИ, как мы помним, теперь хотят делать что-то только для Лисп-машин. +Но 70-е годы подошли к концу и только в последний год десятилетия, 26 апреля 1980 выходит отчет [Lieb80] Хьюита и Генри Либермана (Henry Lieberman) в котором наконец-то обозначена концепция сборщика мусора, который обладает хорошей локальностью и быстро освобождает много места. То, что и нужно для функционального программирования. +Маклисперы и Уоррен решают проблему аллокации короткоживущих объектов с помощью стека. Но, пишут Хьюит и Либерман, стековая дисциплина не совместима с решением FUNARG-проблемы и объектно-ориентированным программированием. Они предполагают, что не смотря на эту несовместимость, подавляющее большинство объектов в ООЯ и ФЯ все равно короткоживущие. Важно, чтоб размещение короткоживущих объектов в имплементациях таких языков было быстрым. Цель Либермана и Хьюита - сделать так, чтоб аллокация временных структур не была сильно медленнее аллокации на стеке, чтоб программист не наказывался рантаймом за написание более понятных программ. Как мы помним, ООП - это ответ Хьюита на Пролог и логическое программирование, а FUNARG-проблемой и ФП вообще к этому времени заинтересовался его коллега Сассман. Хьюит хочет освободиться от ограничений стековой дисциплины, но сохранить разумную эффективность. +И у Либермана с Хьюитом есть идея, как уменьшить стоимость работы сборщика с короткоживущими объектами настолько, что сборщик мусора будет соперничать со стеком. +Кучу надо разделять на части по возрасту объектов. Не нужно тратить время на бессмысленный обход объектов, которые уже пережили несколько сборок и, судя по всему, помирать в ближайшее время не собираются. Вместо этого нужно сначала работать с теми частями кучи, в которых располагаются молодые объекты. Обход такой области и копирование из нее быстро закончится, ведь большинство объектов долго не живут. Много места освобождается очень быстро. +Так что больше нет куч A и B и структур для обозначения ссылок из A в B и из B в A. Есть куча 1 со старыми объектами и куча 2 с молодыми и ссылки молодых на старые - самые обычные. Их не нужно никак отслеживать и обозначать. +На этом общепризнанные сегодня идеи заканчиваются и начинаются своеобразные. Некоторое своеобразие довольно безобидное. Например, поколения нумеруются не в том порядке, в каком их нумеруют сегодня. Но много необычных идей совсем не так безобидны. Поколения нумеруются не в том порядке, в каком объекты движутся по ним, переживая сборки, а в порядке создания поколений. Да, поколения постоянно создаются, их должно быть много. Как минимум, два самых молодых поколения должны содержать небольшую долю долгоживущих объектов. Потому, что для сборки самого молодого поколения n нужно обходить еще и поколение n-1. Дело в том, что если ссылки между поколениями "назад" во времени теперь самые обычные, вместо последовательно двойных, то для учета ссылок "вперед" во времени строятся структуры еще хуже, чем решил строить Бишоп. Казалось бы, куда еще хуже? Но хуже всегда может быть. Чтоб можно было ограничится обходом только одного поколения более старого, чем собираемое, все ссылки "вперед" могут быть только в соседнее поколение. N поколений означают (n-1)-кратную максимальную косвенность таких ссылок. Не только чтение обходится недешево, но и изменение ссылок может требовать создания множества объектов во множестве поколений для многократной переадресации. +Но и это еще не все. Либерман с Хьюитом хотят использовать еще и специальную технику, которая увеличивает число таких ссылок. Сегодня есть такая техника уменьшения числа ссылок между поколениями - повышение. Новый объект, на который ссылается старый объект помещают в поколение старого объекта. Хьюит и Либерман планируют делать наоборот, понижать старый объект, перемещая его в молодое поколение, сборку которого он, конечно, переживет. И все ссылки на него из остальных старых объектов теперь превратились во множество скачков по памяти. Справедливости ради, в отчете пишут, что, возможно, стоит рассмотреть и повышение, но как и в случае со способами обозначить ссылки между поколениями у Бишопа, выбирают не то что нужно. +Все это пока что только концепция. Хьюит с Либерманом не проверили на опыте гипотезу поколений о том, что большинство объектов умирают молодыми, не проверили насколько хорошо работают их идеи об организации ссылок между поколениями. Ну, хотя-бы сформулировали ясную цель для имплементаторов ФЯ. +Хьюит и Либерман "говорили с Дэвидом Муном", который имплементирует сборщик мусора для Лисп-машин. Имплементация такого сборщика шла. Как шла и имплементация инкрементального сборщика Бейкера без поколений. Но в 70-е так никуда и не пришла. +Не смотря на то, что в главном центре Лисп-разработки все работы над сборщиками мусора были для Лисп-машин, эти сборщики задержались существенно сильнее, чем Лисп-машины. Ранние Лисп-машины в МТИ не имели сборщика мусора много лет, а когда получили инкрементальный, пользователи предпочитали его отключать [Stee96]. Даже Хьюит и Либерман, изобретающие принципиальное решение проблемы, рассматривают отсутствие сборки как реальную альтернативу. И дело не только в Лисп-машинах. Имплементаторы Interlisp с копирующим сборщиком для обычного железа рекомендовали сборщиком мусора не пользоваться [Bate82]. Для нас такой итог разработки сборщиков мусора лисперами выглядит необычно. +Сегодня не так просто сказать, насколько ненормальным и неправильным является то, что для имплементации Алгол 68 никак не могут сделать сборщик мусора. Какой код писали на Алголе 68? Может, все в порядке, сборщик мусора и не нужен? Ну а как насчет Лиспа, которому никак не могут сделать сборщик мусора? Как это вообще работает? + +### GC considered harmful + +Новый план лисперов идентичен их плану для первой демонстрации Лиспа, состоявшейся за пару десятков лет до того: работаем, пока память не кончится. Прошла пара десятилетий, а имплементация работающего сборщика все откладывается. Но что делать, когда память закончится? Ведь есть уже несколько неигрушечных программ. И не закончится ли память довольно быстро? +Не обязательно. Лисперы стараются писать программы на диалекте MacLISP для Лисп-машин так же, как старались писать на всех прочих диалектах MacLISP - используя по возможности стек. Большинство программ, таких как компиляторы и текстовые редакторы были написаны так, чтоб избегать аллокации, явно переиспользовать структуры данных на месте. MacLISP делали "языком семидесятых" и потому разработка на нем EMACS для MULTICS и Лисп-машин - это не такой, скажем, "смелый" проект, как, например, попытка Netscape написать браузер на Java. Хотя и кажется таким на первый взгляд. +После всех этих усилий по сокращению аллокаций, размера адресного пространства Лисп-машин хватало, а виртуальная память работала достаточно хорошо, чтоб Лисп-машиной можно было пользоваться дни. И когда эти в лучшем случае дни, а в не самом лучшем случае - один рабочий день проходят и место подходит к концу, нужно сохранить образ системы. Включить сериализацию кучи на диск и идти домой отдыхать. Чтоб на следующий день с новыми силами вернуться на работу и загрузить сохраненный образ, сделав таким образом сборку мусора а-ля Мински [Stee96]. +Да, с программой на функциональном языке так не поработаешь. И не все программы на Лиспе написаны как Гринберговский EMACS. Есть очень важная для лисперов программа, которая не так сильно отличается от компилятора ФЯ. MACSYMA аллоцирует 36Кб в секунду [Fode81]. Достаточно, чтоб заполнить адресное пространство Лисп-машины за полчаса работы. Получается не очень продуктивный рабочий день. +Но адресное пространство-то можно еще увеличить, а где хранить столько страниц? Прогресс не стоит на месте, утешают себя лисперы, можно будет использовать дешевые средства хранения для совсем старых страниц. Например, VideoDisc. В этих страницах все равно почти гарантированный мусор, но если вдруг среди этих гигабайтов мусора затеряется уж очень долгоживущий и невостребованный объект, то когда он понадобится - система просто подождет, пока диск не будет найден и нужная страница считана. За ночь или даже за выходные, пока оператор Лисп-машины отдыхает, эти архивы страниц можно затягивать в гигантскую память специальной машины, выделенной для сжатия лисповых куч. +Все это звучит не очень привлекательно. Что если Лисп-машины просто не будут использовать? Об этом Лисп-машинисты, уже поставившие будущее Лиспа на то, что программисты будут платить за компьютер в десятки и сотни раз больше только для того, чтоб использовать Лисп, старались не думать. Возможно, будет даже ниша для такого бизнеса, фантазирует [Whit80] важный имплементатор Лиспов Уайт. Машина будет машиной и в другом смысле, перемещаться по городу от офиса одного из многочисленных пользователей Лисп-машин к другому, как передвижной пылесос начала XX века, подключаться к их сетям и сжимать кучи за кучами. +Эта леденящая душу антиутопия, в которой никакого ФП, разумеется, не могло бы быть, вычеркнута из истории Лиспа. В черновой версии статьи [Stee96] Стила и Гэбриела есть пометка авторов о том, что надо бы рассказать про неё. Но пометка так и осталась только пометкой. Вместо того, чтоб познакомить читателей с этим пиком лисповой мысли 70-х Стил и Гэбриел предпочитают объяснить еще несколько лисповых шуточных акронимов. Мы-то стараемся читателей не подвести. +Но на рубеже десятилетий этот ужас был всегда рядом, не давал себя забыть разработчикам и имплементаторам сборщиков мусора для Лиспа. И Хьюит с Либерманом и разработчики Interlisp ссылаются на статью Уайта некритически. Десятилетие безуспешных попыток освоить виртуальную память деморализовало их. +В результате такого потерянного десятилетия в разработке сборщиков мусора в 1980-ом имплементаторы ФЯ не могли позаимствовать у лисперов ничего готового из того, чего не было в 1970-ом. Практически полезный, подходящий для ФП, Лиспа и для обычных и даже для Лисп-машин сборщик с поколениями изобретут не лисперы, а имплементаторы другого языка, который в 70-е годы вовсе сборку мусора не использовал. + +Похищение огня +-------------- + +Времена, когда функционального программирования не было, подходят к концу. А вместе с ними и нулевая часть нашей истории. В которой мы попытались разобраться что было, когда функционального программирования не было. И почему того, что уже было еще не было достаточно для того, чтоб функциональное программирование было. +Могло ли функциональное программирование появится раньше? Почти наверняка не могло в 60-е годы. Но в 70-е годы история ФП могла сложится иначе, при более удачном, но крайне маловероятном стечении обстоятельств. Если бы герои этой истории больше интересовались работами друг друга и внимательнее слушали доклады своих коллег. Если бы авторы PAL использовали для испытания своих последующих изобретений PAL. Если б те, кто хотел делать ФЯ в 70-е, имели доступ к таким же дорогим компьютерам, к которым имели доступ те, кто хотел делать ФЯ в 60-е. Если б лисперы МТИ захотели делать ФЯ раньше. Если бы разработки Йорктаун Хайтс были более открыты. +Уоррен и алголисты могли бы сделать компиляторы функциональных языков, отличающихся от языков эдинбургской программы гораздо меньше, чем нам было бы удобно отличать. +Избежало бы это более раннее функциональное программирование ловушку PDP-10? Могло бы выиграть от ИИ-бума? Было бы оно уничтожено ИИ-зимой? Ну, это был не последний ИИ-бум и не последняя ИИ-зима, так что мы еще увидим как и то и другое влияет на ФП. +Конечно, не всегда обстоятельства складывались неудачно, не все возможности были упущены. Участники Эдинбургской программы могли бы и не оказаться в Эдинбурге и его окрестностях и продолжить обмениваться идеями со скоростью переизобретателей продолжений. Могли бы не иметь доступа и к тем машинам, к которым имели. Милнер мог бы увлечься резолюционизмом или ООП. Бурсталл мог бы не разговориться с незнакомцем в книжном магазине. Мики мог бы не забросить генетику. Поплстоун мог бы не спасти паттерн-матчинг. Функциональное программирование могло бы появиться и еще на десяток лет позже, дождавшись интернета и компьютеров необходимой мощности доступных даже хоббистам. Вырасти в 90-е на руинах языков уравнений вроде Axiom, и обломках Лиспов, выкинутых на мороз после провалившейся коммерциализации. Или не появиться вовсе. +60-е и 70-е годы произвели много важных идей о функциональном программировании, но были не лучшим временем для имплементаторов этих идей. Идеи этих двадцати лет имеют определяющее влияние. Но влияние имплементаций - серьезно ограничено. Двадцатилетняя история разработки какого-нибудь сборщика мусора в 1980 году мало что значит. Потому, что за двадцать лет из этих двадцати мало что было сделано. Но сложно в этом винить тех немногих, кто имел к доступ компьютеру с памятью, как у сегодняшнего микроконтроллера. Достаточный доступ хотя-бы для того, чтоб написать 1KLOC за год, запуская компилятор не чаще раза в день. +Новые, более доступные машины с большой памятью изменили это навсегда. Нужно констатировать, что микрокомпьютеры и интернет в 80-е и далее так увеличили производительность и связность имплементаторов, что наработки, которые копили до того десятилетиями были воспроизведены и улучшены за годы и даже месяцы. Жалкие построения 60-х и 70-х были сметены следующими, все большими волнами воспроизводителей и переизобретателей. +Да, имплементаторы ФЯ пока что не умеют как следует использовать новую большую память, но это уже не остановит имплементацию. В последнюю зиму семидесятых в Эдинбурге, на новой машине с большой памятью, уже идет работа над первым компилятором функционального языка. +Но не все будет хорошо и легко для имплементаторов ФЯ в 80-е. Их ждут и новые испытания. Посещавший SRI Фурукава Коити (Furukawa Koichi) уже нашел там ксерокопию ксерокопии ксерокопии распечатки FORTRAN-исходников Марсельского интерпретатора Пролога. Это событие не окажет влияние на то, быть функциональному программированию или не быть. Это уже решено. Функциональное программирование обречено состояться. Но это событие окажет решающее влияние на то, какие функциональные языки используются сегодня. + +Литература +========== + +[Abda74]: Abdali, Syed Kamal. "A combinatory logic model of programming languages." PhD diss., University of Wisconsin--Madison, 1974. +[Abda76]: Abdali, S. K. (1976). An Abstraction Algorithm for Combinatory Logic. The Journal of Symbolic Logic, 41(1), 222. doi:10.2307/2272961 +[Abra81]: Harvey Abramson, D. A. Turner, SASL Reference Manual. TM-81-26, October 1981 https://www.cs.ubc.ca/sites/default/files/tr/1981/TM-81-26.pdf +[Acke28]: Ackermann, W. (1928). Zum Hilbertschen Aufbau der reellen Zahlen. Mathematische Annalen, 99(1), 118–133. doi:10.1007/bf01459088  +[Aiel74]: Aiello, Jack Michael. An Investigation of Current Language Support for the Data Requirements of Structured Programming. Massachusetts Institute of Technology, Project MAC, 1974. https://ia802901.us.archive.org/34/items/bitsavers_mitlcstmMI_32305830/MIT-LCS-TM-051_text.pdf +[ALGOL71]: AB32.3.3 C, Modals. Algol Bulletin No. 32, May 1971 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A32/P33.HTM +[ALGOL72]: AB33.3.3 IFIP WG2.1 Subcommittee: Maintenance of and Improvements to ALGOL 68, Algol Bulletin No. 33, March 1972 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A33/P33.HTM +[ALGOL72a]: AB34.3.1 Report on the meeting of WG2.1 at Fontainebleau Algol Bulletin No. 34, July 1972 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A34/P31.HTM +[ALGOL72b]: AB34.3.2 Report on considered improvements (Fontainebleau 9), Algol Bulletin No. 34, July 1972 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A34/P32.HTM +[ALGOL73]: AB35.3.1 Further Report on Improvements to ALGOL 68, Algol Bulletin No. 35, March 1973 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A35/P31.HTM +[ALGOL78]: AB42.1.1 Modules and Separate Compilation, Algol Bulletin No. 42, May 1978 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A42/P11.HTM +[ALGOL80]: AB45.4.2 ALGOL 68 Implementations - ALGOL 68C - Release 1, Algol Bulletin No. 45, January 1980 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A45/P42.HTM +[ALGOL81]: AB47.3.3 Survey of viable ALGOL 68 Implementations, Algol Bulletin No. 47, August 1981 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A47/P33.HTM +[Arnb72]: Stefan Arnborg. 1972. Storage administration in a virtual memory Simula system. BIT 12, 2 (Jun 1972), 125–141. doi:10.1007/BF01932808 +[ATLAS]: London Atlas http://www.chilton-computing.org.uk/acl/technology/atlas/p010.htm +[Atki78]: Atkinson, M. P., & Jordan, M. J. (1978). An effective program development environment for BCPL on a small computer. Software: Practice and Experience, 8(3), 265–275. doi:10.1002/spe.4380080304 +[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 +[Baba79]: Babaoglu O, Joy W, Porcar J. Design and implementation of the Berkeley virtual memory extensions to the UNIX operating system. Department of Electrical Engineering and Computer Science, University of California, Berkeley. 1979 Dec 10. +[Baba81]: Babaoglu, Özalp and William N. Joy. “Converting a swap-based system to do paging in an architecture lacking page-referenced bits.” TR 81-474 October 1981. +[Back59]: J. W. Backus. The Syntax and Semantics of the Proposed International Algebraic Language of the Zurich ACM-GAMM Conference. Proceedings of the International Conference on Information Processing, UNESCO, 1959, pp.125-132. Typewritten preprint. +[Back63]: J. W. Backus, F. L. Bauer, J. Green, C. Katz, J. McCarthy, A. J. Perlis, H. Rutishauser, K. Samelson, B. Vauquois, J. H. Wegstein, A. van Wijngaarden, M. Woodger, and P. Naur. 1963. Revised report on the algorithm language ALGOL 60. Commun. ACM 6, 1 (Jan. 1963), 1–17. doi:10.1145/366193.366201 +[Baec72]: H. D. Baecker. 1972. On a missing mode in ALGOL 68. SIGPLAN Not. 7, 12 (December 1972), 20–30. doi:10.1145/987059.987062 +[Baec72b]: H. D. Baecker. 1972. Garbage collection for virtual memory computer systems. Commun. ACM 15, 11 (Nov. 1972), 981–986. doi:10.1145/355606.361886 +[Bake77]: Henry G. Baker. 1978. List processing in real time on a serial computer. Commun. ACM 21, 4 (April 1978), 280–294. doi:10.1145/359460.359470 +[Bake78]: Henry G. Baker. 1978. Shallow binding in Lisp 1.5. Commun. ACM 21, 7 (July 1978), 565–569. https://doi.org/10.1145/359545.359566 +[Bare92]: Barendregt, Henk P. "Lambda calculi with types." (1992). +[Barr63]: Barron, D.W., Buxton, J.N., Hartley, D.F., Nixon, E., and Strachey, C. The main features of CPL. Computer Journal 6(2) (1963) 134–143. +[Barr68]: Barron, David William. Recursive Techniques in Programming (1968) +[Bask80]: Forest Baskett, Andreas Bechtolsheim, Bill Nowicki and John Seamons, The SUN Workstation. March 1980 +[Bate82]: Raymond L. Bates, David Dyer, and Johannes A. G. M. Koomen. 1982. Implementation of Interlisp on the VAX. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 81–87. doi:10.1145/800068.802138 +[Baue76]: F. L. Bauer. 1976. Programming as an evolutionary process. In Proceedings of the 2nd international conference on Software engineering (ICSE '76). IEEE Computer Society Press, Washington, DC, USA, 223–234. doi:10.5555/800253.807679 +[Baue79]: Bauer, F.L. (1979). Program development by stepwise transformations — The project CIP. In: Bauer, F.L., et al. Program Construction. Lecture Notes in Computer Science, vol 69. Springer, Berlin, Heidelberg. doi:10.1007/BFb0014671 +[Baue79b]: Bauer, F.L. (1979). From specification to implementation — The formal approach. In: Bauer, F.L., et al. Program Construction. Lecture Notes in Computer Science, vol 69. Springer, Berlin, Heidelberg. doi:10.1007/BFb0014670 +[Baue79c]: Bauer, F.L. (1979). Detailization and lazy evaluation, infinite objects and pointer representation. In: Bauer, F.L., et al. Program Construction. Lecture Notes in Computer Science, vol 69. Springer, Berlin, Heidelberg. https://doi.org/10.1007/BFb0014675 +[Baue81]: Bauer, F. L., Broy, M., Dosch, W., Gnatz, R., Krieg-Brückner, B., Laut, A., … Wössner, H. (1981). Programming in a wide spectrum language: a collection of examples. Science of Computer Programming, 1(1-2), 73–114. doi:10.1016/0167-6423(81)90006-x  +[BBC73]: The Lighthill debate on Artificial Intelligence https://www.youtube.com/watch?v=03p2CADwGF8&t=1682s +[Bech82]: Andreas Bechtolsheim, Forest Baskett, Vaughan Pratt. The SUN Workstation Architecture. Technical Report No. 229, March 1982 +[Beki84]: Bekić, H. (1984). Towards a mathematical theory of processes. In: Jones, C.B. (eds) Programming Languages and Their Definition. Lecture Notes in Computer Science, vol 177. Springer, Berlin, Heidelberg. doi:10.1007/BFb0048944 +[Bell98]: Bell G, Strecker WD. Retrospective: what have we learned from the PDP-11—what we have learned from VAX and Alpha. In 25 years of the international symposia on Computer Architecture (selected papers) 1998 Aug 1 (pp. 6-10). doi:10.1145/285930.285934 +[Birr77]: Andrew Birrell. System Programming in a High Level Language. Ph.D. Thesis, University of Cambridge. December 1977. +[Bish75]: Bishop, Peter B. "Garbage collection in a very large address space." (1975). +[Blai70]: Fred W. Blair. Structure of the Lisp Compiler. IBM Research, Yorktown Heights, circa 1970. https://www.softwarepreservation.org/projects/LISP/ibm/Blair-StructureOfLispCompiler.pdf +[Blai79]: Blair, F. W. "The Definition of LISP 1.8+0.3i." IBM Thomas J Watson Research Center, Internal Report (1979). https://www.softwarepreservation.org/projects/LISP/ibm/Blair-Definition_of_LISP1_8_0_3i-1979.pdf +[Bour72]: S.R.Bourne and M.J.T.Guy. AB33.3.8: Comments on suggested improvements to ALGOL 68, Algol Bulletin No. 33, March 1972 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A33/P38.HTM +[Boye75]: Robert S. Boyer and J Strother Moore. 1975. Proving Theorems about LISP Functions. J. ACM 22, 1 (Jan. 1975), 129–144. doi:10.1145/321864.321875 +[Bobr66]: Bobrow, Daniel G., D. Lucille Darley, Daniel L. Murphy, Cynthia Ann Solomon and Warren Teitelman. “THE BBN-LISP SYSTEM.” (1966). +[Bobr67]: Daniel G. Bobrow and Daniel L. Murphy. 1967. Structure of a LISP system using two-level storage. Commun. ACM 10, 3 (March 1967), 155–159. https://doi.org/10.1145/363162.363185 +[Bobr73]: Daniel G. Bobrow and Ben Wegbreit. 1973. A model and stack implementation of multiple environments. Commun. ACM 16, 10 (Oct. 1973), 591–603. doi:10.1145/362375.362379 +[Bond77]: Bond, Susan Gillian, Philip Mayne Woodward, and ROYAL RADAR ESTABLISHMENT MALVERN (ENGLAND). Introduction to the 'RS' Portable Algol 68 Compiler. Royal Radar Establishment, Procurement Executive, Ministry of Defence, 1977. +[Bond2001]: Susan Bond. An oral history conducted in 2001 by Janet Abbate, IEEE History Center, New Brunswick, NJ, USA. https://ethw.org/Oral-History:Susan_Bond +[Brat86]: Bratko, Ivan. Prolog programming for artificial intelligence. 1986. +[Broo14]: Stephen Brookes, Peter W. O'Hearn, and Uday Reddy. 2014. The essence of Reynolds. SIGPLAN Not. 49, 1 (January 2014), 251–255. doi:10.1145/2578855.2537851 +[Bund84]: Bundy, Alan, ed. "Catalogue of artificial intelligence tools." (1984). +[Bund21]: Alan Bundy, The early years of AI in Edinburgh https://www.youtube.com/watch?v=VSdnsfGcz_A +[Burg64]: W. H. Burge. 1964. The evaluation, classification and interpretation of expressions. In Proceedings of the 1964 19th ACM national conference (ACM '64). Association for Computing Machinery, New York, NY, USA, 11.401–11.4022. doi:10.1145/800257.808888 +[Burg66]: William H. Burge. 1966. A reprogramming machine. Commun. ACM 9, 2 (Feb. 1966), 60–66. doi:10.1145/365170.365174 +[Burg71]: W. H. Burge. 1971. Some examples of the use of function-producing functions. In Proceedings of the second ACM symposium on Symbolic and algebraic manipulation (SYMSAC '71). Association for Computing Machinery, New York, NY, USA, 238–241. doi:10.1145/800204.806292 +[Burg72]: Burge, W. H. (1972). Combinatory Programming and Combinatorial Analysis. IBM Journal of Research and Development, 16(5), 450–461. doi:10.1147/rd.165.0450 +[Burg75]: Burge, William H. "Recursive programming techniques." (1975). +[Burg75b]: Burge, W. H. (1975). Stream Processing Functions. IBM Journal of Research and Development, 19(1), 12–25. doi:10.1147/rd.191.0012  +[Burg89]: Burge, W.H., Watt, S.M. (1989). Infinite structures in scratchpad II. In: Davenport, J.H. (eds) Eurocal '87. EUROCAL 1987. Lecture Notes in Computer Science, vol 378. Springer, Berlin, Heidelberg. doi:10.1007/3-540-51517-8_103 +[Burg90]: Burg, Jennifer J. "Constraint-based programming: A survey." (1990). +https://core.ac.uk/download/pdf/236248615.pdf +[Burs04]: ROD BURSTALL and VICTOR LESSER, Robin Popplestone https://www-robotics.cs.umass.edu/ARCHIVE/remembrance.html +[Burs67]: Burstall, R. M., & Fox, L. (1967). Advances in Programming and Non-Numerical Computation. The Mathematical Gazette, 51(377), 277. doi:10.2307/3613292 +[Burs68]: Burstall, Rodney Martineau, John Stuart Collins, and Robin John Popplestone. POP-2 papers. Edinburgh & London: Oliver & Boyd, 1968. +[Burs69]: Burstall, Rod M. "Proving properties of programs by structural induction." The Computer Journal 12.1 (1969): 41-48. doi:10.1093/comjnl/12.1.41 +[Burs70]: Burstall, R. M. Formal description of program structure and semantics in first—order logic. Machine Intelligence 5 (Meltzer & Michie, Eds.). American Elsevier, New York 79 (1970): 98. +[Burs71]: Burstall, R. M., J. S. Collins, R. J. Popplestone. Programming in POP-2 (1971). +[Burs72]: Burstall, Rodney M. "Some techniques for proving correctness of programs which alter data structures." Machine intelligence 7, no. 23-50 (1972): 3. +[Burs77]: R. M. Burstall and J. A. Goguen. 1977. Putting theories together to make specifications. In Proceedings of the 5th international joint conference on Artificial intelligence - Volume 2 (IJCAI'77). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 1045–1058. +[Burs77b]: R. M. Burstall. Design considerations for a functional programming language. In Infotech State of the Art Conference, Copenhagen, Denmark, 1977. +[Burs79]: Burstall, Rod M.. “Applicative programming.” International Conference on Software Engineering (1979). +[Burs80]: R. M. Burstall, D. B. MacQueen, and D. T. Sannella. 1980. HOPE: An experimental applicative language. In Proceedings of the 1980 ACM conference on LISP and functional programming (LFP '80). Association for Computing Machinery, New York, NY, USA, 136–143. DOI:10.1145/800087.802799 +[Burs80b]: Burstall, R. M. (1980). Electronic category theory. Lecture Notes in Computer Science, 22–39. doi:10.1007/bfb0022493  +[Burs92]: Burstall, R.M. (1992). Computing: Yet Another Reality Construction. In: Floyd, C., Züllighoven, H., Budde, R., Keil-Slawik, R. (eds) Software Development and Reality Construction. Springer, Berlin, Heidelberg. doi:10.1007/978-3-642-76817-0_6 +[Burs2000]: Burstall, R. Christopher Strachey—Understanding Programming Languages. Higher-Order and Symbolic Computation 13, 51–55 (2000). doi:10.1023/a:1010052305354  +[Burs2006]: Burstall, R. (2006). My Friend Joseph Goguen. In: Futatsugi, K., Jouannaud, JP., Meseguer, J. (eds) Algebra, Meaning, and Computation. Lecture Notes in Computer Science, vol 4060. Springer, Berlin, Heidelberg. doi:10.1007/11780274_2 +[Burstall]: Rod Burstall's home page https://web.archive.org/web/20181021215651/http://homepages.inf.ed.ac.uk/rburstall/ +[Böhm72]: Böhm, C., & Dezani, M. (1972). A CUCH-machine: The automatic treatment of bound variables. International Journal of Computer & Information Sciences, 1(2), 171–191. doi:10.1007/bf00995737 +[Cadi72]: J. M. Cadiou and Zohar Manna. 1972. Recursive definitions of partial functions and their computations. In Proceedings of ACM conference on Proving assertions about programs. Association for Computing Machinery, New York, NY, USA, 58–65. doi:10.1145/800235.807072 +[Camp85]: Campbell-Kelly, Martin. “Christopher Strachey, 1916-1975: A Biographical Note.” Annals of the History of Computing 7 (1985): 19-42. +[Card2006]: Cardone, Felice and Roger Hindley. “History of Lambda-calculus and Combinatory Logic.” (2006). +[Chen70]: C. J. Cheney. 1970. A nonrecursive list compacting algorithm. Commun. ACM 13, 11 (Nov 1970), 677–678. doi:10.1145/362790.362798 +[Chio2001]: S. Chiou et al., "A Marriage of Convenience: The Founding of the MIT Artificial Intelligence Laboratory" +[Coel82]: Coelho, H., Cotta JC, and L. M. Pereira. "How to Solve it in Prolog, July 1982." Laboratório Nacional de Engenhara Civil, Lisbon, Portugal. +[Cohe81]: Jacques Cohen. 1981. Garbage Collection of Linked Data Structures. ACM Comput. Surv. 13, 3 (Sept. 1981), 341–367. doi:10.1145/356850.356854 +[Cohe88]: Jacques Cohen. 1988. A view of the origins and development of Prolog. Commun. ACM 31, 1 (Jan. 1988), 26–36. doi:10.1145/35043.35045 +[Coll60]: George E. Collins. 1960. A method for overlapping and erasure of lists. Commun. ACM 3, 12 (Dec. 1960), 655–657. doi:10.1145/367487.367501 +[Colm96]: Alain Colmerauer and Philippe Roussel. 1996. The birth of Prolog. History of programming languages---II. Association for Computing Machinery, New York, NY, USA, 331–367. doi:10.1145/234286.1057820 +[Conr74]: R. Conradi, P. Holager, MARY Textbook, RUNIT rapport, 1974 +[Coro83]: Corovessis, Jiannis. A parallel implementation of SASL. University of St. Andrews (United Kingdom), 1983. +[Coul68]: Coulouris, George, T. J. Goodey, R. W. Hill, R. W. Keeling and D. Levin. “The London CPL1 compiler.” Comput. J. 11 (1968): 26-30. +[Coul]: George Coulouris http://www.eecs.qmul.ac.uk/~gc/ +[Crev93]: Crevier, Daniel, AI: the tumultuous history of the search for artificial intelligence, 1993. +[Curr58]: Haskell B. Curry, Robert Feys, William Craig. "Combinatory Logic: Volume I" (1958). +[Curr70]: Currie, Ian F., Susan G. Bond and J. D. Morison. “Algol 68-R.” ALGOL 68 Implementation (1970). +[Curr76]: I.F. Currie, AB39.4.1: Modular Programming in ALGOL 68, Algol Bulletin No. 39, February 1976 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A39/P41.HTM +[Dahl78]: Kristen Nygaard and Ole-Johan Dahl. 1978. The development of the SIMULA languages. History of programming languages. Association for Computing Machinery, New York, NY, USA, 439–480. doi:10.1145/800025.1198392 +[Darl72]: Darlington, John. "A semantic approach to automatic program improvement." (1972). +[Darl73]: J. Darlington and R. M. Burstall. 1973. A system which automatically improves programs. In Proceedings of the 3rd international joint conference on Artificial intelligence (IJCAI'73). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 479–485. +[Darl75]: R. M. Burstall and John Darlington. 1975. Some transformations for developing recursive programs. In Proceedings of the international conference on Reliable software. Association for Computing Machinery, New York, NY, USA, 465–472. doi:10.1145/800027.808470 +[Darl76]: Darlington, J., & Burstall, R. M. (1976). A system which automatically improves programs. Acta Informatica, 6(1). doi:10.1007/bf00263742   +[Darl77]: Burstall, R. M., Darlington, J. (1977). A Transformation System for Developing Recursive Programs. Journal of the ACM, 24(1), 44–67. doi:10.1145/321992.321996   +[Darl81]: Darlington, J. (1981). An experimental program transformation and synthesis system. Artificial Intelligence, 16(1), 1–46. doi:10.1016/0004-3702(81)90014-x  +[Davi76]: D. J. M. Davies, POP-10 User's Manual, 29 May 1976. http://www.cs.otago.ac.nz/staffpriv/ok/pop2.d/POP-10.pdf +[Dewa79]: Dewar, Robert BK. The SETL programming language. Bell Laboratories, 1979. +[Dijk60]: Dijkstra, E. W. (1960). Recursive Programming. Numerische Mathematik, 2(1), 312–318. doi:10.1007/bf01386232 +[Dijk62]: DIJKSTRA, E. W. (1962). "An ALGOL60 Translator for the X1" Automatic Programming Bulletin, No. 13. +[Dijk70]: AB31.1.1.1 "Minority Report", Algol Bulletin No. 31, March 1970 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A31/P111.HTM +[Dijk2001]: Dijkstra, Edsger Wybe. "Oral history interview with Edsger W. Dijkstra." (2001). +[Dugg96]: Dominic Duggan and Constantinos Sourelis. 1996. Mixin modules. SIGPLAN Not. 31, 6 (June 15, 1996), 262–273. doi:10.1145/232629.232654 +[Dunn70]: Raymond D. Dunn. "POP-2/4100 Users' Manual". School of Artificial Intelligence. University of Edinburgh (February 1970) +[Eager]: Bob Eager, More on the ICL 2900 Series http://www.tavi.co.uk/icl/bob.htm +[Eage22]: Bob Eager, Edinburgh Multi Access System (EMAS) https://www.youtube.com/watch?v=khnu7R3Pffo +[Emde76]: M. H. Van Emden and R. A. Kowalski. 1976. The Semantics of Predicate Logic as a Programming Language. J. ACM 23, 4 (Oct. 1976), 733–742. doi:10.1145/321978.321991 +[Emde90]: Cheng, Mantis HM, Maarten H. van Emden, and B. E. Richards. "On Warren's method for functional programming in logic." In ICLP, pp. 546-560. 1990. +[Emde06]: Maarten van Emden, The Early Days of Logic Programming: A Personal Perspective https://dtai.cs.kuleuven.be/projects/ALP/newsletter/aug06/nav/articles/article4/article.html +[Emde19]: Emden, Maarten van. “Reflecting Back on the Lighthill Affair.” IEEE Annals of the History of Computing 41 (2019): 119-123. +[Emer91]: Emerson W. Pugh, Lyle R. Johnson, and John H. Palmer. IBM's 360 and Early 370 Systems. Cambridge, Mass.: MIT Press, 1991 +[Earl73]: Earley, J. (1973). Relational level data structures for programming languages. Acta Informatica, 2(4), 293–309. doi:10.1007/bf00289502  +[Edinburgh]: https://www.ed.ac.uk/informatics/about/history-school-of-informatics/brief-history-of-the-school-of-informatics +[Evan68]: Evans Jr, Arthur. "Pal—a language designed for teaching programming linguistics." In Proceedings of the 1968 23rd ACM national conference, pp. 395-403. 1968. +[Evan68b]: A. Evans. PAL - A Reference Manual and a Primer. Department of Electrical Engineering, Massachusetts Institute of Technology, February 1968, 185 pages. +[EventML]: EventML https://nuprl.org/software/ +[Fate71]: Richard J. Fateman. 1971. The user-level semantic matching capability in MACSYMA. In Proceedings of the second ACM symposium on Symbolic and algebraic manipulation (SYMSAC '71). Association for Computing Machinery, New York, NY, USA, 311–323. doi:10.1145/800204.806300 +[Fate73]: R. J. Fateman. 1973. Reply to an editorial. SIGSAM Bull., 25 (March 1973), 9–11. doi:10.1145/1086803.1086804 +[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). +[Feat79]: Feather, Martin S. “A system for developing programs by transformation.” (1979). +[Feni69]: Robert R. Fenichel and Jerome C. Yochelson. 1969. A LISP garbage-collector for virtual-memory computer systems. Commun. ACM 12, 11 (Nov. 1969), 611–612. doi:10.1145/363269.363280 +[Feni71]: Robert R. Fenichel. 1971. List tracing in systems allowing multiple cell-types. Commun. ACM 14, 8 (Aug. 1971), 522–526. doi:10.1145/362637.362646 +[Finn83]: Gavin Finnie, AB49.2.1 RS ALGOL 68 Implementors Group (RIG), Algol Bulletin No. 49, May 1983 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A49/P21.HTM +[Fisc93]: Fischer, M. J. (1993). Lambda-calculus schemata. LISP and Symbolic Computation, 6(3-4), 259–287. doi:10.1007/bf01019461  +[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). +[Fode83]: Foderaro JK, Sklower KL, Layer K. The FRANZ Lisp Manual. Regents of the University of California; 1983 Jun. +[Fox66]: FOX, Leslie (ed.). Advances in programming and non-numerical computation. 1966 ISBN:978-0-08-011356-2, 0080113567 +[Franz]: History of Franz Inc. https://franz.com/about/company.history.lhtml +[Frie76]: Friedman, Daniel P., and David S. Wise. CONS should not evaluate its arguments. Computer Science Department, Indiana University, 1976. https://help.luddy.indiana.edu/techreports/TRNNN.cgi?trnum=TR44 +[Frie76b]: Friedman, Daniel P. and David S. Wise. “CONS Should Not Evaluate its Arguments.” International Colloquium on Automata, Languages and Programming (1976). +[Full76]: Samuel H. Fuller. 1976. Price/performance comparison of C.mmp and the PDP-10. In Proceedings of the 3rd annual symposium on Computer architecture (ISCA '76). Association for Computing Machinery, New York, NY, USA, 195–202. doi:10.1145/800110.803580 +[Gabb98]: Gabbay, Dov M., Christopher John Hogger, and John Alan Robinson, eds. Handbook of logic in artificial intelligence and logic programming: Volume 5: Logic programming. Clarendon Press, 1998. +[Gard77]: P. J. Gardner. 1977. A transportation of ALGOL68C. In Proceedings of the Strathclyde ALGOL 68 conference. Association for Computing Machinery, New York, NY, USA, 95–101. doi:10.1145/800238.807148 +[GEDANK]: GEDANKEN. Scanned source listing. https://www.softwarepreservation.org/projects/GEDANKEN/Reynolds-GEDANKEN-MakeTranslator.pdf +[GEDANKb]: GEDANKEN. Scanned execution listing https://www.softwarepreservation.org/projects/GEDANKEN/Reynolds-GEDANKEN-Test_Ch_II_Run.pdf +[GHC23]: GHC User’s Guide https://downloads.haskell.org/ghc/latest/docs/users_guide/ +[Gilm63]: GILMORE, P. C. (1963). "An Abstract Computer with a LISP-like Machine Language without a Label Operator," in Computer Programming and Formal Systems, ed. Braffort, P., and Hirschberg, D., Amsterdam, North Holland Publishing Co. +[Gira72]: Girard, Jean-Yves. "Interprétation fonctionnelle et élimination des coupures de l'arithmétique d'ordre supérieur." PhD diss., Éditeur inconnu, 1972. +[Gogu79]: Goguen, J.A. (1979). Some design principles and theory for OBJ-0, a language to express and execute algebraic specifications of programs. In: Blum, E.K., Paul, M., Takasu, S. (eds) Mathematical Studies of Information Processing. Lecture Notes in Computer Science, vol 75. Springer, Berlin, Heidelberg. doi:10.1007/3-540-09541-1_36 +[Gogu82]: Joseph Goguen and Jose Meseguer. 1982. Rapid prototyping: in the OBJ executable specification language. SIGSOFT Softw. Eng. Notes 7, 5 (December 1982), 75–84. doi:10.1145/1006258.1006273 +[Gogu85]: Futatsugi, K., Goguen, J. A., Jouannaud, J.-P., & Meseguer, J. (1985). Principles of OBJ2. Proceedings of the 12th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages - POPL ’85. doi:10.1145/318593.318610  +[Gogu88]: Goguen, Joseph. Higher order functions considered unnecessary for higher order programming. SRI International, Computer Science Laboratory, 1988. +[Gogu2000]: Goguen, J. A., Winkler, T., Meseguer, J., Futatsugi, K., & Jouannaud, J.-P. (2000). Introducing OBJ. Software Engineering with OBJ, 3–167. doi:10.1007/978-1-4757-6541-0_1  +[Gold70]: Golden, Jeffrey P. "A User's Guide to the AI Group LISCOM LISP Complier: Interim Report." (1970). +[Gord78]: Gordon, Michael J. C., Robin Milner, L. Morris, Malcolm C. Newey and Christopher P. Wadsworth. “A Metalanguage for interactive proof in LCF.” Proceedings of the 5th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (1978): n. pag. DOI:10.1145/512760.512773 +[Gord79]: Gordon, Michael J. C.. “Edinburgh LCF: A mechanised logic of computation.” (1979). +[Gord2000]: Gordon M. From LCF to HOL: a short history. In Proof, language, and interaction 2000 Jul 24 (pp. 169-186). +[Gord2000b]: Gordon, M. Christopher Strachey: Recollections of His Influence. Higher-Order and Symbolic Computation 13, 65–67 (2000). doi:10.1023/a:1010097524009  +[Gord10]: Gordon, M. ICFP 2010 - Tribute to Robin Milner https://vimeo.com/15325077 +[Gree69]: GREEN, Cordell. "APPLICATION OF THEOREM PROVING TO PROBLEM SOLVING." In Proc. of IJCAI-69. 1969. +[Gree74]: Richard Greenblatt, THE LISP MACHINE, Working Paper 79 November 1974 +[Gree75]: E. M. Greenwalt, Robert A. Amsler, Jonathon Slocum, and Mabry Tyson. LISP Reference Manual : CDC - 6000. CCUM 2, Computation Center, University of Texas at Austin, December 1975. +[Gree77]: Bawden A, Greenblatt R, Holloway J, Knight T. LISP Machine Progress Report. MASSACHUSETTS INST OF TECH CAMBRIDGE ARTIFICIAL INTELLIGENCE LAB; 1977 Aug 1. +[Gree96]: Greenberg BS. The Multics MACLISP Compiler. The Basic Hackery–a Tutorial. MIT Press. 1977;1988:1996. https://multicians.org/lcp.html +[Gree96b]: Greenberg BS. Multics Emacs: The History, Design and Implementation. Technical report, 1996 http://www.multicians.org/mepap.html +[Gunt93]: Gunter, Carl A.. “Semantics of programming languages - structures and techniques.” Foundations of computing (1993). +[Guti82]: Claudio Gutierrez. 1982. Prolog compared with LISP. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 143–149. doi:10.1145/800068.802144 +[Gutt76]: John V. Guttag, Ellis Horowitz, and David R. Musser. 1976. The design of data type specifications. In Proceedings of the 2nd international conference on Software engineering (ICSE '76). IEEE Computer Society Press, Washington, DC, USA, 414–420. +[Gutt78]: John V. Guttag, Ellis Horowitz, and David R. Musser. 1978. Abstract data types and software validation. Commun. ACM 21, 12 (Dec. 1978), 1048–1064. doi:10.1145/359657.359666 +[Harr70]: M. C. Harrison. BALM-SETL: A simple implementation of SETL. 5 November 1970. https://www.softwarepreservation.org/projects/SETL/setl/newsletter/setl_001_1970-11-05.pdf +[Hart62]: T. Hart and M. Levin. The New Compiler. Memo 39, Artificial Intelligence Project, RLE and MIT Computation Center, no date (circa 1962?) http://www.bitsavers.org/pdf/mit/ai/aim/AIM-039.pdf +[Hart88]: Hartel, P. H., & Veen, A. H. (1988). Statistics on graph reduction of SASL programs. Software: Practice and Experience, 18(3), 239–253. doi:10.1002/spe.4380180305 +[Hart13]: Hartley, D. (2013). CPL: Failed Venture or Noble Ancestor? IEEE Annals of the History of Computing, 35(3), 55–63. doi:10.1109/mahc.2012.37 +[Hart2000]: Hartley, D. (2000). Higher-Order and Symbolic Computation, 13(1/2), 69–70. doi:10.1023/a:1010001708080 +[Heer80]: Heering J. The Intel 8086, the Zilog Z8000, and the motorola MC68000 microprocessors. Euromicro Newsletter. 1980 May 1;6(3):135-43. +[Hend76]: Peter Henderson and James H. Morris. 1976. A lazy evaluator. In Proceedings of the 3rd ACM SIGACT-SIGPLAN symposium on Principles on programming languages (POPL '76). Association for Computing Machinery, New York, NY, USA, 95–103. doi:10.1145/800168.811543 +[Hewi74]: Hewitt, C., Bishop, P., Steiger, R., Greif, I., Smith, B., Matson, T., & Hale, R. (1974). Behavioral semantics of nonrecursive control structures. Programming Symposium, 385–407. doi:10.1007/3-540-06859-7_147  +[Hewi09]: Carl Hewitt. Middle History of Logic Programming. 2009 doi:10.48550/arXiv.0904.3036 +[Hind07]: Hindley, J. R. (2007). M. H. Newman’s Typability Algorithm for Lambda-calculus. Journal of Logic and Computation, 18(2), 229–238. doi:10.1093/logcom/exm001   +[Hoar64]: C. A. R. Hoare. AB18.3.7: Case expressions, pages 20-22, Algol Bulletin No. 18, October 1964 +[Hoar65]: C. A. R. Hoare. AB21.3.6: Record Handling, Algol Bulletin No. 21, November 1965 +[Hoar72]: Hoare, Charles Antony Richard. "Chapter II: Notes on data structuring." In Structured programming, pp. 83-174. 1972. +[Hoar75]: Hoare, C.A.R. Recursive data structures. International Journal of Computer and Information Sciences 4, 105–132 (1975). doi:10.1007/BF00976239 +[Hoar89]: Hoare, C.A.R. Recursive data structures. Essays in computing science. Prentice-Hall, Inc., USA. (1989) doi:10.5555/63445.C1104369 +[Holm98]: Holmevik, Jan Rune. "Compiling Simula: A historical study of technological genesis." (1998) https://staff.um.edu.mt/jskl1/simula.html +[Honeywell72]: Honeywell Series 6000 User Manual +[Howe07]: Jim Howe - ARTIFICIAL INTELLIGENCE AT EDINBURGH UNIVERSITY : A PERSPECTIVE http://www.inf.ed.ac.uk/about/AIhistory.html +[Hud07]: 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 +[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 +[Hugh83]: Hughes, Robert John Muir. "The design and implementation of programming languages." Ph. D. Thesis, Oxford University 130 (1983). +[Hulz83]: Van Hulzen, J. A., & Calmet, J. (1983). Computer Algebra Systems. Computer Algebra, 221–243. doi:10.1007/978-3-7091-7551-4_14  +[Hunt77]: R.B. Hunter et al. AB41.4.6 Some ALGOL 68 compilers, Algol Bulletin No. 41, July 1977 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A41/P46.HTM +[IFIP21]: IFIP Working Group 2.1 on Algorithmic Languages and Calculi https://ifipwg21wiki.cs.kuleuven.be/IFIP21/WebHome +[Ilif61]: ILIFFE, J. K. (1961). "The use of the GENIE System in numerical calculation," Annual Review in Automatic Programming, Vol. 2, Pergamon Press DOI:10.1016/S0066-4138(61)80002-5 +[IRON77]: High Order Language Working Group. "Department of Defense Requirement for High-Order Computer Programming Languages-Revised IRONMAN." (1977). 5D. RESTRICTIONS ON VALUES +[Jenk71]: Griesmer, J. H., & Jenks, R. D. (1971). SCRATCHPAD/1. Proceedings of the Second ACM Symposium on Symbolic and Algebraic Manipulation - SYMSAC ’71. doi:10.1145/800204.806266  +[Jenk74]: Jenks, R. D. (1974). The SCRATCHPAD language. ACM SIGSAM Bulletin, 8(2), 20–30. doi:10.1145/1086830.1086834  +[Jenk75b]: Griesmer, J. H., Jenks, R. D., & Yun, D. Y. Y. (1975). A SCRATCHPAD solution to problem #7. ACM SIGSAM Bulletin, 9(3), 13–17. doi:10.1145/1088309.1088314 +[Jenk76]: Richard D. Jenks. 1976. A pattern compiler. In Proceedings of the third ACM symposium on Symbolic and algebraic computation (SYMSAC '76). Association for Computing Machinery, New York, NY, USA, 60–65. doi:10.1145/800205.806324   +[Jenk79]: Jenks, R. D. (1979). SCRATCHPAD/360. ACM SIGSAM Bulletin, 13(1), 16–26. doi:10.1145/1088282.1088285   +[Jenk84]: Jenks, R. Scratchpad II, an experimental computer algebra system, abbreviated primer and examples. IBM Thomas Watson Research Center, Yorktown Heights, NY (1984) +[Jone78]: Jones, C.B. (1978). The meta-language: A reference manual. In: Bjørner, D., Jones, C.B. (eds) The Vienna Development Method: The Meta-Language. Lecture Notes in Computer Science, vol 61. Springer, Berlin, Heidelberg. doi:10.1007/3-540-08766-4_10 +[Jone78b]: Henhapl, W., Jones, C.B. (1978). A formal definition of ALGOL 60 as described in the 1975 modified report. In: Bjørner, D., Jones, C.B. (eds) The Vienna Development Method: The Meta-Language. Lecture Notes in Computer Science, vol 61. Springer, Berlin, Heidelberg. doi:10.1007/3-540-08766-4_12 +[Jone99]: Jones, C.B. (1999). Scientific Decisions which Characterize VDM. In: Wing, J.M., Woodcock, J., Davies, J. (eds) FM’99 — Formal Methods. FM 1999. Lecture Notes in Computer Science, vol 1708. Springer, Berlin, Heidelberg. doi:10.1007/3-540-48119-2_2 +[Kidd81]: Tracy Kidder, The Soul Of A New Machine. 1981 +[Klee52]: S.C. Kleene. Introduction to Metamathematics, North-Holland Publishing Co. (1952) +[Knut64]: Donald Knuth, AB17.2.4: Man or boy?, Algol Bulletin No. 17, July 1964 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A17/P24.HTM +[Knut73]: Knuth, Donald Ervin. A Review of "Structured Programming". Stanford, California: Computer Science Department, Stanford University, 1973. +[Korn22]: Körner P, Leuschel M, Barbosa J, Costa VS, Dahl V, Hermenegildo MV, Morales JF, Wielemaker J, Diaz D, Abreu S, Ciatto G. Fifty years of Prolog and beyond. Theory and Practice of Logic Programming. 2022 Nov;22(6):776-858. +[Kowa74]: Kowalski, Robert. Logic for problem solving. Department of Computational Logic, Edinburgh University, 1974. +[Kowa88]: Robert A. Kowalski. 1988. The early years of logic programming. Commun. ACM 31, 1 (Jan. 1988), 38–43. doi:10.1145/35043.35046 +[KRC81]: EMAS KRC https://www.cs.kent.ac.uk/people/staff/dat/krc/emas-krc.tgz +[KRC2016]: UNIX KRC https://www.cs.kent.ac.uk/people/staff/dat/krc/krc-2016-03-31.tgz +[Land64]: Landin, Peter J. "The mechanical evaluation of expressions." The computer journal 6, no. 4 (1964): 308-320. DOI: 10.1093/comjnl/6.4.308 +[Land65a]: Landin, Peter J. "Correspondence between ALGOL 60 and Church's Lambda-notation: part I." Communications of the ACM 8, no. 2 (1965): 89-101. DOI: 10.1145/363744.363749 +[Land65b]: Landin, Peter J. "A correspondence between ALGOL 60 and Church's Lambda-notations: Part II." Communications of the ACM 8, no. 3 (1965): 158-167. DOI: 10.1145/363791.363804 +[Land66]: Landin PJ. The next 700 programming languages. Communications of the ACM. 1966 Mar 1;9(3):157-66. DOI: 10.1145/365230.365257 +[Land66b]: Landin, Peter J. A λ-Calculus Approach in Advances in programming and non-numerical computation. 1966 +[Land98]: Landin, Peter J. "A generalization of jumps and labels." Higher-Order and Symbolic Computation 11, no. 2 (1998): 125-143. doi:10.1023/a:1010068630801 +[Landin]: Archive of Peter Landin https://archives.bodleian.ox.ac.uk/repositories/2/resources/9658 +[Lavi12]: Lavington, Simon. "The Atlas story." In Atlas Symposium, vol. 6. 2012. +[LCF77]: Code for LCF Version 5, Oct 1977 https://github.com/theoremprover-museum/LCF77 +[Leav71]: B. M. Leavenworth. 1971. Transition functions: A method for semantic extensions. In Proceedings of the international symposium on Extensible languages. Association for Computing Machinery, New York, NY, USA, 96–103. doi:10.1145/800006.807989 +[Lennox]: The GTL Programming Language http://web.archive.org/web/20020601161934/http://www.lennox.com.au/products/gtl.html +[Levi75]: Levi, G., Sirovich, F. (1975). Proving program properties, symbolic evaluation and logical procedural semantics. In: Bečvář, J. (eds) Mathematical Foundations of Computer Science 1975 4th Symposium, Mariánské Lázně, September 1–5, 1975. MFCS 1975. Lecture Notes in Computer Science, vol 32. Springer, Berlin, Heidelberg. doi:10.1007/3-540-07389-2_211 +[Levi82]: Bellia, Marco, Pierpaolo Degano, and Giorgio Levi. "The call by name semantics of a clause language with functions." Logic Programming 1 (1982): J82. +[Li96]: Li, X. (1996). Program Sharing: A new implementation approach for Prolog. Programming Languages: Implementations, Logics, and Programs, 259–273. doi:10.1007/3-540-61756-6_90  +[Lieb80]: Lieberman H, Hewitt C. A Real Time Garbage Collector that can Recover Temporary Storage Quickly. MASSACHUSETTS INST OF TECH CAMBRIDGE ARTIFICIAL INTELLIGENCE LAB; 1980 Apr. +[Ligh72]: James Lighthill, Artificial Intelligence: A General Survey (1972) http://www.chilton-computing.org.uk/inf/literature/reports/lighthill_report/p001.htm +[Lind74a]: C.H. Lindsey, AB37.4.2: Partial Parametrization, Algol Bulletin No. 37, July 1974 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A37/P42.HTM +[Lind74b]: C.H. Lindsey, AB37.4.3: Modals, Algol Bulletin No. 37, July 1974 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A37/P43.HTM +[Lind76]: C.H. Lindsey, AB39.3.1: Specification of Partial Parametrization Proposal, Algol Bulletin No. 39, February 1976 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A39/P31.HTM +[Lind76b]: C.H. Lindsey, AB39.4.2: Proposal for a Modules Facility in ALGOL 68, Algol Bulletin No. 39, February 1976 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A39/P42.HTM +[Lind78]: C. H. Lindsey and H. J. Boom, AB43.3.2: A Modules and Separate Compilation facility for ALGOL 68, Algol Bulletin No. 43, December 1978 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A43/P32.HTM +[Lind83]: C. H. Lindsey. AB49.1.1 Hans Bekic, Algol Bulletin No. 49, May 1983 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A49/P11.HTM +[Lind93]: C. H. Lindsey. 1993. A history of ALGOL 68. In The second ACM SIGPLAN conference on History of programming languages (HOPL-II). Association for Computing Machinery, New York, NY, USA, 97–132. doi:10.1145/154766.155365 +[Lisk74]: Barbara Liskov and Stephen Zilles. 1974. Programming with abstract data types. In Proceedings of the ACM SIGPLAN symposium on Very high level languages. Association for Computing Machinery, New York, NY, USA, 50–59. doi:10.1145/800233.807045 +[Lisk93]: Liskov, B. (1993). A history of CLU. ACM SIGPLAN Notices, 28(3), 133–147. doi:10.1145/155360.155367 +[Lond78]: London, Thomas B., and John F. Reiser. "A UNIX™ Operating System for the DEC VAX-11/780 Computer." +[MACLISP]: MACLISP changelog LISP-NEWS.DOC +[MacQueen]: David MacQueen at LinkedIn https://www.linkedin.com/in/david-macqueen-463788b +[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 +[MacQ20]: MacQueen, David B., Robert Harper and John H. Reppy. “The history of Standard ML.” Proceedings of the ACM on Programming Languages 4 (2020): 1 - 100.DOI:10.1145/3386336 +[MacQ22]: D. MacQueen, A New Match Compiler for Standard ML of New Jersey https://icfp22.sigplan.org/details/mlfamilyworkshop-2022-papers/3/A-New-Match-Compiler-for-Standard-ML-of-New-Jersey +[Mann70]: Manna, Zohar, and John McCarthy. Properties of programs and partial function logic. Machine Intelligence 5 (Meltzer & Michie, Eds.). American Elsevier, New York 79 (1970): 27. +[Mann71]: Zohar Manna and Richard J. Waldinger. 1971. Toward automatic program synthesis. Commun. ACM 14, 3 (March 1971), 151–165. doi:10.1145/362566.362568 +[Mart76]: Martelli A., Montanari U. Unification in linear time and space: a structured presentation. Internal note IEI-B76-16, 1976. +[Mart82]: Alberto Martelli and Ugo Montanari. 1982. An Efficient Unification Algorithm. ACM Trans. Program. Lang. Syst. 4, 2 (April 1982), 258–282. doi:10.1145/357162.357169 +[Mart2008]: Martelli, A. (2008). The Seventies. In: Degano, P., De Nicola, R., Meseguer, J. (eds) Concurrency, Graphs and Models. Lecture Notes in Computer Science, vol 5065. Springer, Berlin, Heidelberg. doi:10.1007/978-3-540-68679-8_49 +[Matt83]: Matthews, David Charles James. Programming language design with polymorphism. No. UCAM-CL-TR-49. University of Cambridge, Computer Laboratory, 1983. +[McBr69]: McBride, F. V., D. J. T. Morrison, and R. M. Pengelly. "A symbol manipulation system." Machine Intelligence 5 (1969): 337-347. +[McCa58]: J. McCarthy: An Algebraic Language for the Manipulation of Symbolic Expressions. MIT AI Lab., AI Memo No. 1, Cambridge, Sept. 1958. +[McCa60]: John McCarthy. 1960. Recursive functions of symbolic expressions and their computation by machine, Part I. Commun. ACM 3, 4 (April 1960), 184–195. doi:10.1145/367177.367199 +[McCa60b]: J. McCarthy, R. Brayton, D. Edwards, P. Fox, L. Hodes, D. Luckham, K. Maling, D. Park and S. Russell. LISP I Programmer's Manual. Computation Center and Research Laboratory of Electronics, Massachusetts Institute of California, March 1, 1960. https://www.softwarepreservation.org/projects/LISP/book/LISP%20I%20Programmers%20Manual.pdf +[McCa61]: John McCarthy. 1961. A basis for a mathematical theory of computation, preliminary report. In Papers presented at the May 9-11, 1961, western joint IRE-AIEE-ACM computer conference (IRE-AIEE-ACM '61 (Western)). Association for Computing Machinery, New York, NY, USA, 225–238. doi:10.1145/1460690.1460715 +[McCa62]: McCarthy, J., Abrahams, P. W., Edwards, D. J., Hart, T. P., & Levin, M. I. (1962). LISP 1.5 programmer's manual. MIT press. +[McCa64]: J. McCarthy, AB18.3.12: Definition of new data types in ALGOL x, Algol Bulletin No. 18, October 1964 +[McCa74]: McCarthy, J. (1974). Artificial intelligence: a paper symposium. Artificial Intelligence, 5(3), 317–322. doi:10.1016/0004-3702(74)90016-2  +[McCa78]: McCarthy J. History of LISP. In History of programming languages 1978 Jun 1 (pp. 173-185). +[McDe84]: Drew McDermott et al., "The Dark Ages of AI: A Panel Discussion at AAAI-84," AI Magazine, Fall 1984, pp. 122-34. +[McKus]: Marshall Kirk McKusick. A BERKELEY ODYSSEY: Ten years of BSD history. +[Miln72]: Robin Milner. 1972. Implementation and applications of Scott's logic for computable functions. SIGPLAN Not. 7, 1 (January 1972), 1–6. doi:10.1145/942578.807067 +[Miln75]: Milner, R., Morris, L., Newey, M.: A logic for computable functions with reflexive and polymorphic types. Proving and improving programs, Arc et Senans 1975, pp. 371-394. IRIA, Cahier, 1975 +[Miln76]: Milner R. Program Semantics and mechanized proof. In Mathematical Centre Tracts. Vol. 82. Amsterdam: Mathematisch Centrum. 1976. p. 3-44 +[Miln78]: Milner, R. (1978). A theory of type polymorphism in programming. Journal of Computer and System Sciences, 17(3), 348–375. doi:10.1016/0022-0000(78)90014-4 +[Miln82]: Milner, Robin. “How ML evolved.” (1982). +[MilnBird84]: Milner, R., & Bird, R. S. (1984). The Use of Machines to Assist in Rigorous Proof [and Discussion]. Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences, 312(1522), 411–422. doi:10.1098/rsta.1984.0067  +[Miln90]: Milner, Robin, Mads Tofte, and Robert Harper. "The definition of Standard ML." (1990). +[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/ +[Moon74]: David A. Moon. MacLISP Reference Manual, Revision 0. Project MAC, Massachusetts Institute of Technology, April 8, 1974 +[Moor73]: Moore, J Strother. Computational Logic: Structure sharing and proof of program properties. http://hdl.handle.net/1842/2245 +[Moor75]: Moore, J Strother. Introducing iteration into the pure LISP theorem prover. IEEE Transactions on Software Engineering 3 (1975): 328-338. +[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 +[Moor13]: Moore, J Strother and Claus-Peter Wirth. “Automation of Mathematical Induction as part of the History of Logic.” ArXiv abs/1309.6226 (2013) +[Moor18]: J Strother Moore, The PLTP Archive. https://www.cs.utexas.edu/users/moore/best-ideas/pltp/index.html +[Moor18b]: J Strother Moore, The PLTP Archive. Listing I https://www.cs.utexas.edu/users/moore/best-ideas/pltp/scanned-images/Listing-I-OCR.pdf +[Moor18c]: J Strother Moore, Structure Sharing https://www.cs.utexas.edu/users/moore/best-ideas/structure-sharing/index.html +[Morr68]: Morris, J.H.: Lambda calculus models of programming languages. M.I.T. MAC-TR-57, 1968 +[Morr72]: James H. Morris. 1972. A bonus from van Wijngaarden's device. Commun. ACM 15, 8 (Aug. 1972), 773. doi:10.1145/361532.361558 +[Morr73a]: James H. Morris. 1973. Types are not sets. In Proceedings of the 1st annual ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '73). Association for Computing Machinery, New York, NY, USA, 120–124. doi:10.1145/512927.512938 +[Morr73b]: James H. Morris. 1973. Protection in programming languages. Commun. ACM 16, 1 (Jan. 1973), 15–21. doi:10.1145/361932.361937 +[Morr93]: Morris, L. The next 700 formal language descriptions. LISP and Symbolic Computation 6, 249–257 (1993). doi:10.1007/BF01019460 +[Mose70]: Joel Moses. 1970. The function of FUNCTION in LISP or why the FUNARG problem should be called the environment problem. SIGSAM Bull., 15 (July 1970), 13–27. doi:10.1145/1093410.1093411 +[Mose74]: Joel Moses. 1974. MACSYMA - the fifth year. SIGSAM Bull. 8, 3 (August 1974), 105–110. doi:10.1145/1086837.1086857 +[Mose08]: Moses, J. (2012). Macsyma: A personal history. Journal of Symbolic Computation, 47(2), 123–130. doi:10.1016/j.jsc.2010.08.018 +[Mull73]: Mullish, Henry, and Max Goldstein. A SETLB Primer: With Over 100 Illustrative Programettes. Courant Institute of Mathematical Sciences, New York University, 1973. +[MULTICS1]: https://www.multicians.org/benchmarks.html#INRIA +[Muss74]: Ralph L. London and David R. Musser. 1974. The application of a symbolic mathematical system to program verification. In Proceedings of the 1974 annual conference - Volume 1 (ACM '74). Association for Computing Machinery, New York, NY, USA, 265–273. doi:10.1145/800182.810412 +[Muss80a]: Musser, D. R. (1980). Abstract Data Type Specification in the Affirm System. IEEE Transactions on Software Engineering, SE-6(1), 24–32. doi:10.1109/tse.1980.230459  +[Muss80b]: David R. Musser. 1980. On proving inductive properties of abstract data types. In Proceedings of the 7th ACM SIGPLAN-SIGACT symposium on Principles of programming languages (POPL '80). Association for Computing Machinery, New York, NY, USA, 154–162. doi:10.1145/567446.567461 +[Naur64]: P. Naur. Proposals for a new language, Algol Bulletin No. 18, October 1964 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A18/P39.HTM +[Naur66]: P. Naur. The form of specifications, Algol Bulletin No. 22, February 1966 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A22/P37.HTM +[Naur78]: Naur, Peter. "The European side of the last phase of the development of ALGOL 60." In History of programming languages, pp. 92-139. 1978. +[Neum23]: Von JOHANN v. NEUMANN, Zur Einführung der transfiniten Zahlen. Acta litt. Acad. Sc. Szeged X. 1(1923) p.199-208 +[Newe75]: Newey, Malcolm C.. “Formal semantics of lisp with applications to program correctness.” (1975). +[NUPRL2002]: NuPRL Manual https://nuprl.org/html/02cucs-NuprlManual-appendixB.pdf +[O'Do82]: Christoph M. Hoffmann and Michael J. O'Donnell. 1982. Programming with Equations. ACM Trans. Program. Lang. Syst. 4, 1 (Jan. 1982), 83–112. doi:10.1145/357153.357158 +[O'Do84]: Christoph M. Hoffmann and Michael J. O'Donnell. 1984. Implementation of an interpreter for abstract equations. In Proceedings of the 11th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '84). Association for Computing Machinery, New York, NY, USA, 111–121. doi:10.1145/800017.800522 +[O'Do87]: O'Donnell, M.J. (1987). Term-rewriting implementation of equational logic programming. In: Lescanne, P. (eds) Rewriting Techniques and Applications. RTA 1987. Lecture Notes in Computer Science, vol 256. Springer, Berlin, Heidelberg. doi:10.1007/3-540-17220-3_1 +[O'Ke83]: R. A. O'Keefe. 1983. Prolog compared with LISP? SIGPLAN Not. 18, 5 (May 1983), 46–56. doi:10.1145/948249.948255 +[Padg88]: Padget, Julian. "Three Uncommon Lisps." In First International Workshop on Lisp Evolution and Standardization. 1988. +[PAL360]: PAL https://www.softwarepreservation.org/projects/lang/PAL/ +[Part84]: Partsch, H. (1984). The CIP Transformation System. In: Pepper, P. (eds) Program Transformation and Programming Environments. NATO ASI Series, vol 8. Springer, Berlin, Heidelberg. doi:10.1007/978-3-642-46490-4_27 +[Pate76]: M. S. Paterson and M. N. Wegman. 1976. Linear unification. In Proceedings of the eighth annual ACM symposium on Theory of computing (STOC '76). Association for Computing Machinery, New York, NY, USA, 181–186. doi:10.1145/800113.803646 +[Perl78]: Alan J. Perlis. 1978. The American side of the development of ALGOL. History of programming languages. Association for Computing Machinery, New York, NY, USA, 75–91. doi:10.1145/800025.1198352 +[Pett78]: Pettorossi, A. (1978). Improving memory utilization in transforming recursive programs. In: Winkowski, J. (eds) Mathematical Foundations of Computer Science 1978. MFCS 1978. Lecture Notes in Computer Science, vol 64. Springer, Berlin, Heidelberg. doi:10.1007/3-540-08921-7_89 +[Pigo95]: Diarmuid Pigott, HOPL: an interactive Roster of Programming Languages, HOPE https://hopl.info/showlanguage.prx?exp=810 +[Plot2000]: Plotkin, Gordon D., Colin Stirling, and Mads Tofte. "A brief scientific biography of Robin Milner." In Proof, Language, and Interaction, pp. 1-18. 2000. DOI:10.7551/mitpress/5641.003.0004 +[Plot10]: Gordon Plotkin - Robin Milner: A Craftsman of Tools for the Mind https://www.youtube.com/watch?v=Jg5VCLb2cMo +[Popp2002]: Robin Popplestone. 2002. POP, A Broad-Spectrum Programming Language, 1967–2002. Form. Asp. Comput. 13, 3–5 (Jul 2002), 196–213. doi:10.1007/s001650200009 +[Popplestone]: Popplestone, R. J. The Early Development of POP https://www-robotics.cs.umass.edu/Popplestone/pop_development.html +[Prat73]: Vaughan R. Pratt. 1973. Top down operator precedence. In Proceedings of the 1st annual ACM SIGACT-SIGPLAN symposium on Principles of programming languages (POPL '73). Association for Computing Machinery, New York, NY, USA, 41–51. doi:10.1145/512927.512931 +[Prolog]: Prolog and Logic Programming Historical Sources Archive https://www.softwarepreservation.org/projects/prolog/index.html#Edinburgh +[Prolog75]: PROLOG to DEC 10 Machine Code Compiler, Version 13 Sep 1975 https://www.softwarepreservation.org/projects/prolog/edinburgh/src/Warren-Prolog_Compiler-1975_09_13.pdf +[Prolog81]: DEC10 Prolog version 3 https://saildart.org/[PRO,SYS]/ +[Quar86]: JOHN S. QUARTERMAN, ABRAHAM SILBERSCHATZ, and JAMES L. PETERSON. 4.2BSD and 4.3BSD as Examples of the UNIX System +[RABBIT]: RABBIT Scheme compiler http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/scheme/impl/rabbit/rabbit.lsp +[Rand64]: Randell, Brian. & Russell, L. J. (1964). ALGOL 60 implementation : the translation and use of ALGOL 60 programs on a computer. London ; New York : Published for the Automatic Programming Information Centre, Brighton College of Technology, England, by Academic Press +[Rees2010]: Jonathan Rees, The T Project http://mumble.net/~jar/tproject/ +[Reev86]: Peter G. Harrison and Mike Reeve. 1986. The parallel graph reduction machine, Alice. In Proceedings of the Workshop on Graph Reduction. Springer-Verlag, Berlin, Heidelberg, 181–202. +[Reyn69]: Reynolds, John C.. “GEDANKEN: A SIMPLE TYPELESS LANGUAGE WHICH PERMITS FUNCTIONAL DATA STRUCTURES AND COROUTINES.” (1969). +[Reyn70]: Reynolds, John C.. “GEDANKEN—a simple typeless language based on the principle of completeness and the reference concept.” Communications of the ACM 13 (1970): 308 - 319. +[Reyn72]: John C. Reynolds. 1972. Definitional interpreters for higher-order programming languages. In Proceedings of the ACM annual conference - Volume 2 (ACM '72). Association for Computing Machinery, New York, NY, USA, 717–740. doi:10.1145/800194.805852 +[Reyn74]: Reynolds, John C. "Towards a theory of type structure." In Programming Symposium, pp. 408-425. Springer, Berlin, Heidelberg, 1974. +[Reyn93]: Reynolds, John C. "The discoveries of continuations." Lisp and symbolic computation 6, no. 3 (1993): 233-247. doi:10.1007/bf01019459  +[Reyn98]: Reynolds, John C. "Definitional interpreters revisited." Higher-Order and Symbolic Computation 11, no. 4 (1998): 355-361. doi:10.1023/A:1010075320153 +[Reyn2012]: John C. Reynolds https://www.softwarepreservation.org/projects/lang/GEDANKEN +[Rich13]: Richards, Martin. “How BCPL Evolved from CPL.” Comput. J. 56 (2013): 664-670. +[Rich2000]: Richards, M. Christopher Strachey and the Cambridge CPL Compiler (2000). Higher-Order and Symbolic Computation, 13(1/2), 85–88. doi:10.1023/a:1010014110806 +[Rich69]: Richards, M. (1969, May). BCPL: A tool for compiler writing and system programming. In Proceedings of the May 14-16, 1969, spring joint computer conference (pp. 557-566). doi:10.1145/1476793.1476880 +[Rich74]: RICHARDS, Martin; EVANS JR, Arthur; MABEE, Robert F. The BCPL reference manual. MASSACHUSETTS INST OF TECH CAMBRIDGE PROJECT MAC, 1974. +[Ritc93]: Ritchie, Dennis M. "The development of the C language." ACM Sigplan Notices 28.3 (1993): 201-208. +[Robi76]: Robinson, Lawrence, and Oliver Roubine. Special: A specification and assertion language. Menlo Park, Ca.: Stanford Research Institute, 1976. +[Roch71]: Arnold Rochfeld. 1971. New LISP techniques for a paging environment. Commun. ACM 14, 12 (Dec. 1971), 791–795. doi:10.1145/362919.362937 +[Rosetta1]: Man or boy test in Pascal https://rosettacode.org/wiki/Man_or_boy_test#Pascal +[Ross61]: ROSS, DOUGLAS T., and STEVEN A. COONS. INVESTIGATIONS IN COMPUTER-AIDED DESIGN. MASSACHUSETTS INST OF TECH CAMBRIDGE ELECTRONIC SYSTEMS LAB, 1961. +[Ruti67]: Rutishauser, Heinz. "Description of ALGOL 60, volume 1, edited by Bauer, FL." (1967). +[Ryde82]: Rydeheard, David Eric. "Applications of category theory to programming and program specification." (1982). +[Ryde2002]: RYDEHEARD, D. E., & SANNELLA, D. T. (2002). A Collection of Papers and Memoirs Celebrating the Contribution of Rod Burstall to Advances in Computer Science. Formal Aspects of Computing, 13(3-5), 187–193. doi:10.1007/s001650200006 +[Salu94]: Salus PH. A quarter century of UNIX. ACM Press/Addison-Wesley Publishing Co.; 1994 Dec. +[Same65]: K. Samelson, AB20.3.3. Functionals and functional transformations, Algol Bulletin No. 20, July 1965 https://archive.computerhistory.org/resources/text/algol/algol_bulletin/A20/P33.HTM +[Sann82]: Sannella, Donald. "Semantics, implementation and pragmatics of Clear, a program specification language." (1982). +[Sann94]: Sannella, Donald and Martin Wirsing. “Specification Languages” (1994). +[Sann14]: D. Sannella, CV https://homepages.inf.ed.ac.uk/dts/cv.html +[Scho67]: H. Schorr and W. M. Waite. 1967. An efficient machine-independent procedure for garbage collection in various list structures. Commun. ACM 10, 8 (Aug. 1967), 501–506. doi:10.1145/363534.363554 +[Schu74]: S.A. Schuman, AB37.4.1: Toward Modular Programming in High-Level Languages, Algol Bulletin No. 37, July 1974 http://archive.computerhistory.org/resources/text/algol/algol_bulletin/A37/P41.HTM +[Schw71]: Jacob T. Schwartz. Abstract algorithms and a set theoretic language for their expression. Computer Science Department, Courant Institute of Mathematical Sciences, New York University. Preliminary draft, first part. 1970-1971, 16+289 pages. This copy scanned from NYU Library, courtesy of Alex Kennedy-Grant. http://www.softwarepreservation.net/projects/SETL/setl/doc/Schwartz-Abstract_Algorithms-1971.pdf +[Schw82]: Schwarz, J. (1982). Using Annotations to Make Recursion Equations Behave. IEEE Transactions on Software Engineering, SE-8(1), 21–33. doi:10.1109/tse.1982.234771  +[Somm77]: Sommerville JF. An experiment in high-level microprogramming. University of St. Andrews (United Kingdom); 1977. +[SPJ82]: Simon L Peyton Jones. 1982. An investigation of the relative efficiencies of combinators and lambda expressions. In Proceedings of the 1982 ACM symposium on LISP and functional programming (LFP '82). Association for Computing Machinery, New York, NY, USA, 150–158. doi:10.1145/800068.802145 +[SPJ85]: Jones, S. L. P. (1985). Yacc in sasl — an exercise in functional programming. Software: Practice and Experience, 15(8), 807–820. doi:10.1002/spe.4380150807 +[SPJ87]: Peyton Jones, Simon L. The implementation of functional programming languages (prentice-hall international series in computer science). Prentice-Hall, Inc., 1987. +[Scot93]: Dana S. Scott, A type-theoretical alternative to ISWIM, CUCH, OWHY, Theoretical Computer Science, Volume 121, Issues 1–2, 1993, Pages 411-440, ISSN 0304-3975, doi:10.1016/0304-3975(93)90095-B +[Shivers]: Olin Shivers, History of T http://www.paulgraham.com/thist.html +[Slom89]: Sloman, Aaron. "The Evolution of Poplog and Pop-11 at Sussex University." POP-11 Comes of Age: The Advancement of an AI Programming Language (1989): 30-54. +[Smit73]: Smith, David Canfield and Enea, Horace J. (1973) MLISP 2 http://i.stanford.edu/TR/CS-TR-73-356.html +[Stee75]: Sussman, Gerald J. and Guy L. Steele. “An Interpreter for Extended Lambda Calculus: SCHEME,.” (1975). +[Stee75b]: Guy L. Steele. 1975. Multiprocessing compactifying garbage collection. Commun. ACM 18, 9 (Sept. 1975), 495–508. doi:10.1145/361002.361005 +[Stee76]: Steele Jr, Guy Lewis. "LAMBDA: The ultimate declarative." (1976). +[Stee76b]: Steele, Guy L. and Gerald J. Sussman. “Lambda: The Ultimate Imperative.” (1976). +[Stee77]: Guy Lewis Steele. 1977. Debunking the “expensive procedure call” myth or, procedure call implementations considered harmful or, LAMBDA: The Ultimate GOTO. In Proceedings of the 1977 annual conference (ACM '77). Association for Computing Machinery, New York, NY, USA, 153–162. doi:10.1145/800179.810196 +[Stee77m]: Guy Lewis Steele. 1977. Macaroni is better than spaghetti. In Proceedings of the 1977 symposium on Artificial intelligence and programming languages. Association for Computing Machinery, New York, NY, USA, 60–66. https://doi.org/10.1145/800228.806933 +[Stee77b]: Steele Jr GL. Fast Arithmetic in MacLISP. MASSACHUSETTS INST OF TECH CAMBRIDGE ARTIFICIAL INTELLIGENCE LAB; 1977 Sep 1. +[Stee78]: Steele, Jr. Guy L. “Rabbit: A Compiler for Scheme.” (1978). +[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. History of programming languages---II. Association for Computing Machinery, New York, NY, USA, 233–330. doi:10.1145/234286.1057818 +[Stee98]: Sussman, Gerald Jay, and Guy L. Steele Jr. "The first report on Scheme revisited." Higher-Order and Symbolic Computation 11, no. 4 (1998): 399-404. +[Stra61]: Strachey, C., & Wilkes, M. V. (1961). Some proposals for improving the efficiency of ALGOL 60. Communications of the ACM, 4(11), 488-491. +[Stra66]: BARRON D. W. and STRACHEY C. Programming in Advances in programming and non-numerical computation. 1966 +[Stra66b]: CPL Elementary Programming Manual https://web.archive.org/web/20190813130006/http://www.ancientgeek.org.uk/CPL/CPL_Elementary_Programming_Manual.pdf +[Stra67]: Strachey, Christopher S.. “Fundamental Concepts in Programming Languages.” Higher-Order and Symbolic Computation 13 (2000): 11-49. +DOI:10.1023/A:1010000313106 +[Strachey]: Catalogue of the papers and correspondence of Christopher Strachey https://archives.bodleian.ox.ac.uk/repositories/2/resources/2561 +[Stro70]: H. R. Strong. 1970. Translating recursion equations into flow charts. In Proceedings of the second annual ACM symposium on Theory of computing (STOC '70). Association for Computing Machinery, New York, NY, USA, 184–197. doi:10.1145/800161.805164 +[Stro93]: Bjarne Stroustrup. 1993. A history of C++: 1979–1991. In The second ACM SIGPLAN conference on History of programming languages (HOPL-II). Association for Computing Machinery, New York, NY, USA, 271–297. doi:10.1145/154766.155375 +[Thom90]: Lins, Rafael Dueire, and Simon J. Thompson. "Implementing SASL using categorical multi-combinators." Software - Practice and Experience 20, no. 11 (1990): 1137-1165. +[TITAN]: Cambridge Atlas http://www.chilton-computing.org.uk/acl/technology/atlas/p011.htm +[Teit74]: Teitelman, Warren. “The interlisp reference manual.” (1974). https://www.softwarepreservation.org/projects/LISP/interlisp/Interlisp-Oct_1974.pdf +[Teit78]: Warren Teitelman with contributions by J. W. Goodwin, A. K. Hartley, D. C. Lewis, J. J. Vittal, M. D. Yonke, D. G. Bobrow, R. M. Kaplan, L. M. Masinter, and B. A. Sheil. Interlisp Reference Manual. Bolt, Beranek & Newman and Xerox Corporation, October 1978. https://www.softwarepreservation.org/projects/LISP/interlisp/Interlisp-Oct_1978.pdf +[Teit2008]: Teitelman, W. (2008). History of Interlisp. Celebrating the 50th Anniversary of Lisp on - LISP50. doi:10.1145/1529966.1529971  +[Tenn77]: Tennent, R. D. (1977). On a new approach to representation independent data classes. Acta Informatica, 8(4), 315–324. doi:10.1007/bf00271340 +[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  +[Turn79b]: Turner, D. A. (1979). Another algorithm for bracket abstraction . The Journal of Symbolic Logic, 44(02), 267–270. doi:10.2307/2273733 +[Turn81]: D. A. Turner. 1981. The semantic elegance of 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, 85–92. doi:10.1145/800223.806766 +[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.” +[Turn83]: Turner, D. A. "SASL language manual (revised version)." University of Kent (1983). +[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. +[Turn16]: Kent Recursive Calculator https://www.cs.kent.ac.uk/people/staff/dat/krc/ +[Turn19]: David Turner. 2019 Peter Landin Semantics Seminar https://www.youtube.com/watch?v=ezFZIPuSQU8 +[Vase85]: Vasey, Philip Edgar. First-order logic applied to the description and derivation of programs. (1985). +[Vuil73]: Jean Vuillemin. 1973. Correct and optimal implementations of recursion in a simple programming language. In Proceedings of the fifth annual ACM symposium on Theory of computing (STOC '73). Association for Computing Machinery, New York, NY, USA, 224–239. doi:10.1145/800125.804054 +[Vuil74]: B. Courcelle and J. Vuillemin. 1974. Semantics and axiomatics of a simple recursive language. In Proceedings of the sixth annual ACM symposium on Theory of computing (STOC '74). Association for Computing Machinery, New York, NY, USA, 13–26. doi:10.1145/800119.803880 +[Wads71]: Wadsworth, P. L. "Semantics and paragmatics of the lambda calculus." PhD thesis, Oxford Univ. (1971). +[Wads2000]: Wadsworth, C.P. Continuations Revisited. Higher-Order and Symbolic Computation 13, 131–133 (2000). doi:10.1023/a:1010074329461   +[Walt75]: Waltz, David L. "Understanding line drawings of scenes with shadows." The psychology of computer vision (1975): 19-91. +http://www1.cs.columbia.edu/~waltz/Papers/Understanding%20Line%20Drawing%20of%20Scenes%20with%20Shadows%20PH%20Winston%201975.pdf +[Wand77]: Wand, M. Algebraic theories and tree rewriting systems. Indiana University. Computer Science Department Technical Report 66 (1977). +[Wand80]: Wand, M. First-order identities as a defining language. Acta Informatica 14, 337–357 (1980). doi:10.1007/BF00286491 +[Warr75]: David H. D. Warren. Example 1: Quicksort. Circa 1975. Example of generated code from PROLOG to DEC 10 Machine Code Compiler. https://www.softwarepreservation.org/projects/prolog/edinburgh/src/Warren-Prolog_Compiler_Example_1.pdf +[Warr77]: David H D Warren, Luis M. Pereira, and Fernando Pereira. 1977. Prolog - the language and its implementation compared with Lisp. In Proceedings of the 1977 symposium on Artificial intelligence and programming languages. Association for Computing Machinery, New York, NY, USA, 109–115. doi:10.1145/800228.806939 +[Warr77b]: David H D Warren. 1977. Prolog - the language and its implementation compared with Lisp. Slides https://www.softwarepreservation.org/projects/prolog/edinburgh/doc/slides-ACM1977.pdf +[Warr78]: Warren, David HD. "Applied logic: its use and implementation as a programming tool." (1978). +[Warr81]: Warren, David HD. Higher-order extensions to Prolog - are they needed? Department of Artificial Intelligence, University of Edinburgh, 1981. +[Weinreb]: Dan Weinreb on NIL http://www.paulgraham.com/weinreb.html +[Weiz68]: Weizenbaum, Joseph. "The FUNARG Problem Explained. unpublished memorandum." (1968). +[Well76]: Welliver, Leon. "IDEA: a symbolic integration program." PhD diss., University of St Andrews, 1976. +[Whit70]: White, John L.. “An Interim LISP User's Guide.” (1970). +[Whit77]: White, Jon L. "Lisp: Program is Data: A historical perspective on MACLISP." In Proceedings of the 1977 MACSYMA Users' Conference, MIT Laboratory for Computer Science, Cambridge, Mass, pp. 181-189. 1977. +[Whit78]: Jon L White. 1978. LISP/370: a short technical description of the implementation. SIGSAM Bull. 12, 4 (November 1978), 23–27. doi:10.1145/1088276.1088280 +[Whit79]: Jon L. White. NIL: A perspective. Proceedings of 1979 MACSYMA Users' Conference, Washington, D.C., June 1979. https://www.softwarepreservation.org/projects/LISP/MIT/White-NIL_A_Perspective-1979.pdf +[Whit80]: White JL. Address/memory management for a gigantic Lisp environment or, GC considered harmful. InProceedings of the 1980 ACM Conference on LISP and Functional Programming 1980 Aug 25 (pp. 119-127). +[Wich76]: Wichmann, B.A. Ackermann's function: A study in the efficiency of calling procedures. BIT 16, 103–110 (1976). doi:10.1007/BF01940783 +[Wijn66]: van Wijngaarden, Adriaan. Recursive Definition of Syntax and Semantics : (proceedings IFIP Working Conference on Formal Language Description Languages, Vienna 1966, P 13-24). Stichting Mathematisch Centrum. Rekenafdeling. Stichting Mathematisch Centrum, 1966. +[Wijn69]: A. van Wijngaarden (Ed.), Mailloux, B. J., Peck, J. E. L., & Koster, C. H. A. (1969). Report on the Algorithmic Language ALGOL 68. Numerische Mathematik, 14(2), 79–218. doi:10.1007/bf02163002 +[Wijn77]: A. van Wijngaarcien, B. J. Mailloux, J. E. L. Peck, C. H. A. Kostcr, M. Sintzoff, C. H. Lindsey, L. G. L. T. Meertens, and R. G. Fisker. 1977. Revised Report on the Algorithmic Language ALGOL 68. SIGPLAN Not. 12, 5 (May 1977), 1–70. doi:10.1145/954652.1781176 +[Wilk92]: Wilkes, M. V. (1992). EDSAC 2. IEEE Annals of the History of Computing, 14(4), 49–56. doi:10.1109/85.194055  +[Wils92]: Wilson, P.R. (1992). Uniprocessor garbage collection techniques. In: Bekkers, Y., Cohen, J. (eds) Memory Management. IWMM 1992. Lecture Notes in Computer Science, vol 637. Springer, Berlin, Heidelberg. doi:10.1007/BFb0017182 +[Wirs95]: Wirsing, M. (1995). Algebraic specification languages: An overview. Lecture Notes in Computer Science, 81–115. doi:10.1007/bfb0014423 +[Wirt66]: Niklaus Wirth and C. A. R. Hoare. 1966. A contribution to the development of ALGOL. Commun. ACM 9, 6 (June 1966), 413–432. doi:10.1145/365696.365702 +[Wirt76]: Wirth, Niklaus. "MODULA: a language for modular multiprogramming." Berichte des Instituts für Informatik 18 (1976). doi:10.3929/ethz-a-000199440 +[Wood66]: Woodward, Philip M. List Programming in Advances in programming and non-numerical computation. 1966 +[Wood72]: Woodward, Philip M.. “Practical experience with algol 68.” Software: Practice and Experience 2 (1972) doi:10.1002/spe.4380020103 +[Woze71]: J. M. Wozencraft and A. Evans. Notes on Programming Linguistics. M.I.T. Department of Electrical Engineering, February 1971 +[Wray86]: Wray, Stuart Charles. Implementation and programming techniques for functional languages. No. UCAM-CL-TR-92. University of Cambridge, Computer Laboratory, 1986. https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-92.pdf +[Wulf73]: Wulf, William Allan et al. "The design of an optimizing compiler." (1973). +[Zill70]: Stephen N. Zilles. An Expansion of the Data Structuring Capabilities of PAL. Report MIT-LCS-TM-015, Laboratory for Computer Science, Massachusetts Institute of Technology, October 1, 1970. +[Zill74]: Zilles, Stephen N. "Algebraic specification of data types." Project MAC Progress Report 11 (1974): 28-52. +[Zill75]: Barbara Liskov and Stephen Zilles. 1975. Specification techniques for data abstractions. In Proceedings of the international conference on Reliable software. Association for Computing Machinery, New York, NY, USA, 72–87. doi:10.1145/800027.808426 +[Берд83]: Бердж В. Методы рекурсивного программирования/Пер. с англ. С. П. Забродина, В. Г. Иваненко, Ю. П. Кулябичева; Под ред. Н. И. Иващенко. —М.: Машиностроение, 1983.