Подтвердить что ты не робот

Почему в Common Lisp не существует примитива 'call-with-current-продолжений'?

В последнее время я изучал различия между Scheme и Common Lisp в отношении подхода, который эти два языка имеют к продолжениям.

Я заметил, что общий подход Lisp более консервативен, чем схема подхода.

Кроме того, схема предлагает примитивный call-with-current-continuation, обычно сокращенный call/cc, который не имеет эквивалента в спецификации ANSI Common Lisp (хотя есть некоторые библиотеки, которые пытаются их реализовать).

Кто-нибудь знает причину, по которой было принято решение не создавать аналогичный примитив в спецификации ANSI Common Lisp?

Спасибо заранее.

4b9b3361

Ответ 1

Common Lisp имеет подробную модель компиляции файлов как часть стандартного языка. Модель поддерживает компиляцию программы в объектные файлы в одной среде и загрузку их в изображение в другой среде. В Схеме нет ничего сопоставимого. Нет eval-when, или compile-file, load-time-value или такие понятия, как внешний объект, как семантика в скомпилированном коде должна согласовываться с интерпретируемым кодом. Lisp имеет способ иметь встроенные функции или не иметь их встроенных, и поэтому в основном вы с большой точностью контролируете, что происходит, когда скомпилированный модуль перезагружается.

В отличие от этого, до недавнего пересмотра отчета схемы, язык схемы полностью умалчивался о том, как программа Scheme разбита на несколько файлов. Для этого не было предусмотрено никаких функций или макросов. Посмотрите на R5RS под 6.6.4 Системный интерфейс. Все, что у вас есть, есть очень слабо определенная функция load:

необязательная процедура: (загрузить имя файла)

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

Обоснование: для переносимости загрузка должна работать с исходными файлами. Его реализация на других типах файлов обязательно зависит от реализаций.

Итак, если это объем вашего видения о том, как приложения создаются из модулей, и все детали, выходящие за рамки этого, оставляются разработчикам для разработки, конечно же, небо - это предел в отношении изобретения семантики языка программирования. Частично обратите внимание на часть Rationale: если load определяется как работающий с исходными файлами (при этом все остальное является бонусом, предоставленным разработчиками), то это не что иное, как механизм текстового включения, например #include на языке C, и поэтому приложение Scheme - это всего лишь один текст, который физически распространяется на несколько текстовых файлов, спрятанных вместе load.

Если вы планируете добавить какую-либо функцию в Common Lisp, вам нужно подумать о том, как она вписывается в ее подробную динамическую модель загрузки и компиляции, сохраняя при этом хорошую производительность, которую ожидают пользователи.

Если функция, о которой вы думаете, требует глобальной оптимизации всей программы (в связи с чем система должна видеть структурный исходный код всего), чтобы программы пользователей не работали плохо (и, в частности, программы, которые не используйте эту функцию), тогда он не будет летать.

В частности, в отношении семантики продолжений возникают проблемы. В обычной семантике области блока, как только мы покинем область действия и выполним очистку, она исчезнет; мы не можем вернуться к этой области во времени и возобновить вычисление. Обычный Lisp является обычным. У нас есть конструкция unwind-protect, которая выполняет безусловные действия по очистке при завершении области действия. Это является основой для таких функций, как with-open-file, который предоставляет объект дескриптора открытого файла для области блока и гарантирует, что он закрыт независимо от того, как заканчивается область блока. Если продолжение из этой области продолжения, это продолжение больше не имеет действительного файла. Мы не можем просто не закрывать файл, когда покидаем область действия, потому что нет уверенности в том, что продолжение будет использоваться; то есть мы должны предположить, что область фактически оставлена ​​навсегда и своевременно очищает ресурс. Решение для групповой помощи для такого рода проблем - dynamic-wind, что позволяет добавлять обработчики при входе и выходе в область блока. Таким образом, мы можем повторно открыть файл, когда блок будет перезапущен продолжением. И не только повторно открывайте его, но и фактически позиционируйте поток в точно такой же позиции в файле и так далее. Если поток был на полпути через декодирование некоторого символа UTF-8, мы должны поместить его в одно и то же состояние. Поэтому, если Lisp получил продолжение, либо они были бы разбиты различными конструкциями with-, которые выполняют очистку (плохая интеграция), или же эти конструкции должны были бы получить гораздо более волосатую семантику.

Существуют альтернативы продолжениям. Некоторые применения продолжений несущественны. По сути, такую ​​же организацию кода можно получить с помощью замыканий или перезапуска. Кроме того, существует мощная конструкция языка/операционной системы, которая может конкурировать с продолжением: а именно, поток. Хотя в продолжениях есть аспекты, которые не очень хорошо моделируются потоками (и не говоря уже о том, что они не вводят взаимоблокировки и условия гонки в код), они также имеют недостатки по сравнению с потоками: например, отсутствие фактического concurrency для использования нескольких процессоров, или приоритизации. Многие проблемы, выражающиеся в продолжениях, могут быть выражены нитями почти так же легко. Например, продолжения позволяют написать рекурсивный спуск-парсер, который выглядит как объект, подобный потоку, который просто возвращает прогрессивные результаты при анализе. Код на самом деле является рекурсивным парсером спуска, а не конечным автоматом, который имитирует один. Темы позволяют нам делать то же самое: мы можем поместить синтаксический анализатор в поток, завернутый в "активный объект", который имеет некоторый метод "получить следующую вещь", который вытаскивает вещи из очереди. В качестве парсеров потока вместо того, чтобы возвращать продолжение, он просто бросает объекты в очередь (и, возможно, блокирует какой-то другой поток, чтобы удалить их). Продолжение выполнения обеспечивается возобновлением этого потока; его контекст потока является продолжением. Не все модели нитей страдают от условий гонки (столько же); существует, например, совместная потоковая передача, при которой один поток запускается за раз, а поточные переключатели могут потенциально иметь место, когда поток делает явный вызов в ядре потоковой передачи. В большинстве распространенных реализаций Lisp на протяжении десятилетий были легкие потоки (обычно называемые "процессы" ) и постепенно переходили к более сложной потоковой обработке с поддержкой многопроцессорности. Поддержка потоков уменьшает потребность в продолжениях и является более значительным приоритетом реализации, поскольку время выполнения без поддержки потоков не соответствует технологическим недостаткам: невозможность в полной мере использовать аппаратные ресурсы.

Ответ 2

Это то, что Кент М. Питман, один из разработчиков Common Lisp, должен был сказать по теме: из comp.lang.lisp

Ответ 3

Конструкция схемы основывалась на использовании вызовов функций для замены большинства общих структур управления. Вот почему Scheme требует устранения хвостового вызова: он позволяет преобразовать цикл в рекурсивный вызов без потенциального выхода из пространства стека. И основным подходом этого является стиль продолжения прохождения.

Общий Lisp более практичный и менее педагогический. Он не диктует стратегии реализации, и для его реализации не требуется продолжения.

Ответ 4

Общий Lisp является результатом усилий по стандартизации на нескольких вариантах практических (применяемых) Lisps (таким образом, "Common" ). CL ориентирован на приложения реального времени, поэтому он имеет более "конкретные" функции (например, handler-bind) вместо call/cc.

Схема была разработана как небольшой чистый язык для обучения CS, поэтому он имеет фундаментальный call/cc, который можно использовать для реализации других инструментов.

См. также Можно ли использовать функцию call-with-current-continue только с lambdas и закрытием?