Scheme — диалект семейства языков программирования Lisp . Scheme был создан в 1970-х годах в Лаборатории компьютерных наук и искусственного интеллекта Массачусетского технологического института (MIT CSAIL) и выпущен его разработчиками Гаем Л. Стилом и Джеральдом Джеем Сассманом в виде серии меморандумов, ныне известных как Lambda Papers . Это был первый диалект Lisp, выбравший лексическую область видимости и первый, потребовавший от реализаций выполнения оптимизации хвостового вызова , что обеспечило более сильную поддержку функционального программирования и связанных с ним методов, таких как рекурсивные алгоритмы. Он также был одним из первых языков программирования, поддерживающих первоклассные продолжения . Он оказал значительное влияние на усилия, которые привели к разработке Common Lisp . [2]
Язык Scheme стандартизирован в официальном стандарте Института инженеров по электротехнике и электронике (IEEE) [3] и стандарте де-факто , который называется Revised n Report on the Algorithmic Language Scheme (R n RS). Широко применяемым стандартом является R5RS (1998). [4] Самым последним ратифицированным стандартом Scheme является «R7RS-small» (2013). [5] Более обширный и модульный R6RS был ратифицирован в 2007 году. [6] Оба ведут свое происхождение от R5RS; временная шкала ниже отражает хронологический порядок ратификации.
Scheme появился в 1970-х годах как попытка понять модель акторов Карла Хьюитта , для чего Стил и Сассман написали «крошечный интерпретатор Lisp» с использованием Maclisp , а затем «добавили механизмы для создания акторов и отправки сообщений». [7] Первоначально Scheme назывался «Schemer», в традициях других языков, производных от Lisp, таких как Planner или Conniver . Нынешнее название возникло в результате использования авторами операционной системы ITS , которая ограничивала имена файлов двумя компонентами по шесть символов каждый. В настоящее время «Schemer» обычно используется для обозначения программиста Scheme.
На семинаре Scheme 2003 года начался новый процесс стандартизации языка с целью разработки стандарта R6RS в 2006 году. Этот процесс порвал с более ранним подходом R n RS, предполагавшим единогласие.
R6RS представляет собой стандартную модульную систему, позволяющую разделить основной язык и библиотеки . Было выпущено несколько проектов спецификации R6RS, финальная версия — R5.97RS. Успешное голосование привело к ратификации нового стандарта, объявленного 28 августа 2007 года. [6]
В настоящее время новейшие выпуски различных реализаций Scheme [8] поддерживают стандарт R6RS. Существует переносимая эталонная реализация предлагаемых неявно фазированных библиотек для R6RS, называемая psyntax, которая загружает и самозагружается должным образом на различных старых реализациях Scheme. [9]
Особенностью R6RS является дескриптор типа записи (RTD). Когда RTD создается и используется, представление типа записи может отображать структуру памяти. Он также вычисляет битовую маску поля объекта и битовые маски изменяемых полей объекта Scheme, а также помогает сборщику мусора узнать, что делать с полями, не проходя по всему списку полей, сохраненных в RTD. RTD позволяет пользователям расширять базовый RTD для создания новой системы записей. [10]
R6RS вносит многочисленные существенные изменения в язык. [11] Исходный код теперь указан в Unicode , и большое подмножество символов Unicode теперь может появляться в символах и идентификаторах Scheme , а также есть другие незначительные изменения в лексических правилах. Символьные данные теперь также указаны в Unicode. Многие стандартные процедуры были перемещены в новые стандартные библиотеки, которые сами по себе образуют большое расширение стандарта, содержащее процедуры и синтаксические формы, которые ранее не были частью стандарта. Была введена новая модульная система, и системы обработки исключений теперь стандартизированы. Syntax-rules был заменен более выразительным средством синтаксической абстракции (syntax-case), которое позволяет использовать всю Scheme во время расширения макроса. Теперь для поддержки полной числовой башни Scheme требуются совместимые реализации , а семантика чисел была расширена, в основном в направлении поддержки стандарта IEEE 754 для числового представления с плавающей точкой.
Стандарт R6RS вызвал споры, поскольку некоторые считают его отходом от минималистской философии. [12] [13] В августе 2009 года Руководящий комитет Scheme, который курирует процесс стандартизации, объявил о своем намерении рекомендовать разделение Scheme на два языка: большой современный язык программирования для программистов; и малую версию, подмножество большой версии, сохраняющее минимализм, восхваляемый преподавателями и случайными разработчиками. [14] Для работы над этими двумя новыми версиями Scheme были созданы две рабочие группы. На сайте Scheme Reports Process есть ссылки на уставы рабочих групп, публичные обсуждения и систему отслеживания проблем.
Девятый проект R7RS (малый язык) был представлен 15 апреля 2013 года. [15] Голосование по ратификации этого проекта завершилось 20 мая 2013 года, [16] а окончательный отчет был доступен с 6 августа 2013 года, описывая «малый» язык этой попытки: поэтому его нельзя рассматривать изолированно как преемника R6RS». [5]
Scheme — это в первую очередь функциональный язык программирования . Он разделяет многие характеристики с другими членами семейства языков программирования Lisp. Очень простой синтаксис Scheme основан на s-выражениях , списках в скобках, в которых за префиксным оператором следуют его аргументы. Таким образом, программы Scheme состоят из последовательностей вложенных списков. Списки также являются основной структурой данных в Scheme, что приводит к близкой эквивалентности между исходным кодом и форматами данных ( гомоиконичность ). Программы Scheme могут легко динамически создавать и оценивать фрагменты кода Scheme.
Зависимость от списков как структур данных свойственна всем диалектам Lisp. Scheme наследует богатый набор примитивов обработки списков, таких как cons, car
иcdr
от своих предшественников Lisp. Scheme использует строго, но динамически типизированные переменные и поддерживает процедуры первого класса . Таким образом, процедуры могут быть назначены как значения переменным или переданы как аргументы процедурам.
В этом разделе основное внимание уделяется инновационным возможностям языка, включая те, которые отличают Scheme от других Lisp. Если не указано иное, описания возможностей относятся к стандарту R5RS. В примерах, приведенных в этом разделе, обозначение "===> result" используется для указания результата вычисления выражения в непосредственно предшествующей строке. Это то же соглашение, которое используется в R5RS.
Scheme — очень простой язык, гораздо более простой в реализации, чем многие другие языки сопоставимой выразительной мощности . [17] Эта простота объясняется использованием лямбда-исчисления для выведения большей части синтаксиса языка из более примитивных форм. Например, из 23 синтаксических конструкций на основе s-выражений, определенных в стандарте R5RS Scheme, 14 классифицируются как производные или библиотечные формы, которые могут быть записаны как макросы, включающие более фундаментальные формы, в основном лямбда. Как говорится в R5RS (§3.1): «Самой фундаментальной из конструкций связывания переменных является лямбда-выражение, потому что все другие конструкции связывания переменных можно объяснить в терминах лямбда-выражений». [4]
Пример: макрос для реализации let
в виде выражения, используемого lambda
для выполнения привязки переменных.
( define-syntax let ( syntax-rules () (( let (( var expr ) ... ) body ... ) (( lambda ( var ... ) body ... ) expr ... ))))
Таким образом, использование let
реализации Scheme, как определено выше, перепишет " (let ((a 1)(b 2)) (+ b a))
" как " ((lambda (a b) (+ b a)) 1 2)
", что сводит задачу реализации к кодированию инстанцирований процедур.
В 1998 году Сассман и Стил отметили, что минимализм Scheme не был сознательной целью дизайна, а скорее непреднамеренным результатом процесса дизайна. «Мы на самом деле пытались построить что-то сложное и обнаружили, по счастливой случайности, что мы случайно спроектировали что-то, что отвечало всем нашим целям, но было намного проще, чем мы предполагали... мы поняли, что лямбда-исчисление — небольшой, простой формализм — может служить ядром мощного и выразительного языка программирования». [7]
Как и большинство современных языков программирования и в отличие от более ранних Lisp, таких как Maclisp , Scheme имеет лексическую область действия: все возможные привязки переменных в программном модуле могут быть проанализированы путем чтения текста программного модуля без учета контекстов, в которых он может быть вызван. Это контрастирует с динамической областью действия, которая была характерна для ранних диалектов Lisp, из-за затрат на обработку, связанных с примитивными методами текстовой подстановки, используемыми для реализации алгоритмов лексической области действия в компиляторах и интерпретаторах того времени. В тех Lisp вполне возможно было, чтобы ссылка на свободную переменную внутри процедуры ссылалась на совершенно разные привязки, внешние по отношению к процедуре, в зависимости от контекста вызова.
Импульс к включению лексического охвата, который был необычной моделью охвата в начале 1970-х годов, в их новую версию Lisp, пришел из исследований Сассмана ALGOL . Он предположил, что лексические механизмы охвата, подобные ALGOL, помогут реализовать их первоначальную цель внедрения модели акторов Хьюитта в Lisp. [7]
Ключевые идеи о том, как ввести лексическую область видимости в диалект Lisp, были популяризированы в статье Сассмана и Стила 1975 года «Схема: интерпретатор для расширенного лямбда-исчисления» [18] , где они приняли концепцию лексического замыкания (на странице 21), которая была описана в AI Memo в 1970 году Джоэлом Мозесом , который приписал эту идею Питеру Дж. Ландину . [19]
Математическая нотация Алонзо Чёрча , лямбда-исчисление, вдохновила Lisp на использование «лямбда» в качестве ключевого слова для введения процедуры, а также повлияла на развитие методов функционального программирования, включающих использование функций высшего порядка в Lisp. Но ранние Lisp не были подходящими выражениями лямбда-исчисления из-за их обработки свободных переменных . [7]
Формальная лямбда-система имеет аксиомы и полное правило вычисления. Она полезна для анализа с использованием математической логики и инструментов. В этой системе вычисление можно рассматривать как направленную дедукцию. Синтаксис лямбда-исчисления следует рекурсивным выражениям из x, y, z, ..., скобок, пробелов, точки и символа λ. [20] Функция лямбда-исчисления включает: Во-первых, служить отправной точкой мощной математической логики. Во-вторых, оно может снизить потребность программистов в рассмотрении деталей реализации, поскольку его можно использовать для имитации машинной оценки. Наконец, лямбда-исчисление создало существенную метатеорию. [21]
Введение лексической области видимости решило проблему, установив эквивалентность между некоторыми формами лямбда-нотации и их практическим выражением в рабочем языке программирования. Сассман и Стил показали, что новый язык может быть использован для элегантного вывода всей императивной и декларативной семантики других языков программирования, включая ALGOL и Fortran , а также динамической области видимости других Lisp, используя лямбда-выражения не как простые инстанциации процедур, а как «управляющие структуры и модификаторы окружения». [22] Они ввели стиль продолжения-передачи вместе со своим первым описанием Scheme в первой из Lambda Papers, а в последующих статьях они продолжили демонстрировать чистую мощь этого практического использования лямбда-исчисления.
Scheme наследует свою блочную структуру от более ранних блочно-структурированных языков, в частности ALGOL . В Scheme блоки реализуются тремя конструкциями связывания : let, let*
и letrec
. Например, следующая конструкция создает блок , в котором вызываемый символ var
связан с числом 10:
( define var "goose" ) ;; Любая ссылка на var здесь будет привязана к "goose" ( let (( var 10 )) ;; Здесь идут операторы. Любая ссылка на var здесь будет привязана к 10. ) ;; Любая ссылка на var здесь будет привязана к "goose"
Блоки могут быть вложены для создания произвольно сложных структур блоков в соответствии с потребностями программиста. Использование структурирования блоков для создания локальных привязок снижает риск коллизии пространств имен , которая в противном случае могла бы возникнуть.
Один из вариантов let
, let*
, позволяет привязкам ссылаться на переменные, определенные ранее в той же конструкции, таким образом:
( let* (( var1 10 ) ( var2 ( + var1 12 ))) ;; Но определение var1 не может ссылаться на var2 )
Другой вариант, letrec
, предназначен для того, чтобы позволить взаимно рекурсивным процедурам быть связанными друг с другом.
;; Расчет мужских и женских последовательностей Хофштадтера в виде списка пар( define ( hofstadter-male-female n ) ( letrec (( female ( lambda ( n ) ( if ( = n 0 ) 1 ( - n ( male ( female ( - n 1 ))))))) ( male ( lambda ( n ) ( if ( = n 0 ) 0 ( - n ( female ( male ( - n 1 )))))))) ( let loop (( i 0 )) ( if ( > i n ) ' () ( cons ( cons ( female i ) ( male i )) ( loop ( + i 1 ))))))) ( hofstadter-мужчина-женщина 8 ) ===> (( 1 . 0 ) ( 1 . 0 ) ( 2 . 1 ) ( 2 . 2 ) ( 3 . 2 ) ( 3 . 3 ) ( 4 . 4 ) ( 5 . 4 ) ( 5 . 5 ) )
( Определения, используемые в этом примере, см. в последовательности мужчин и женщин Хофштадтера .)
Все процедуры, связанные в одну, letrec
могут ссылаться друг на друга по имени, а также на значения переменных, определенных ранее в той же процедуре letrec
, но они не могут ссылаться на значения, определенные позднее в той же процедуре letrec
.
Вариант let
формы "named let" имеет идентификатор после let
ключевого слова. Это связывает переменные let с аргументом процедуры, имя которой — заданный идентификатор, а тело — тело формы let. Тело может быть повторено по желанию путем вызова процедуры. Именованный let широко используется для реализации итерации.
Пример: простой счетчик
( пусть цикл (( n 1 )) ( если ( > n 10 ) ' () ( cons n ( цикл ( + n 1 ))))) ===> ( 1 2 3 4 5 6 7 8 9 10 )
Как и любая процедура в Scheme, процедура, созданная в именованном let, является объектом первого класса.
В Scheme есть конструкция итерации, do
но в Scheme более идиоматично использовать хвостовую рекурсию для выражения итерации . Соответствующие стандарту реализации Scheme должны оптимизировать хвостовые вызовы, чтобы поддерживать неограниченное количество активных хвостовых вызовов (R5RS sec. 3.5) [4] — свойство, которое отчет Scheme описывает как правильную хвостовую рекурсию — что позволяет программистам Scheme безопасно писать итерационные алгоритмы с использованием рекурсивных структур, которые иногда более интуитивны. Хвостовые рекурсивные процедуры и именованнаяlet
форма обеспечивают поддержку итерации с использованием хвостовой рекурсии.
;; Создание списка квадратов от 0 до 9: ;; Примечание: loop — это просто произвольный символ, используемый в качестве метки. Подойдет любой символ.( определить ( список-квадратов n ) ( пусть цикл (( i n ) ( рез ' ())) ( если ( < i 0 ) рез ( цикл ( - i 1 ) ( cons ( * i i ) рез ))))) ( список-квадратов 9 ) ===> ( 0 1 4 9 16 25 36 49 64 81 )
Продолжения в Scheme являются объектами первого класса . Scheme предоставляет процедуру call-with-current-continuation
(также известную как call/cc
) для захвата текущего продолжения путем упаковки его в качестве процедуры выхода, связанной с формальным аргументом в процедуре, предоставленной программистом. (R5RS раздел 6.4) [4] Продолжения первого класса позволяют программисту создавать нелокальные управляющие конструкции, такие как итераторы , сопрограммы и возвраты .
Продолжения могут использоваться для эмуляции поведения операторов return в императивных языках программирования. Следующая функция find-first
, заданная function func
и list lst
, возвращает первый элемент x
в lst
таком виде, что (func x)
возвращает true.
( define ( find-first func lst ) ( call-with-current-continuation ( lambda ( return-immediately ) ( for-each ( lambda ( x ) ( if ( func x ) ( return-immediately x ))) lst ) #f ))) ( найти первое целое число? ' ( 1/2 3/4 5.6 7 8/9 10 11 )) ===> 7 ( найти первый ноль? ' ( 1 2 3 4 )) ===> #f
Следующий пример, традиционная головоломка программиста, показывает, что Scheme может обрабатывать продолжения как объекты первого класса, привязывая их к переменным и передавая их в качестве аргументов процедурам.
( пусть* (( инь (( лямбда ( cc ) ( отобразить "@" ) cc ) ( вызов-с-текущим-продолжением ( лямбда ( c ) c )))) ( ян (( лямбда ( cc ) ( отобразить "*" ) cc ) ( вызов-с-текущим-продолжением ( лямбда ( c ) c ))))) ( инь янь ))
При выполнении этого кода отображается последовательность подсчета:@*@**@***@****@*****@******@*******@********...
В отличие от Common Lisp, все данные и процедуры в Scheme имеют общее пространство имен, тогда как в Common Lisp функции и данные имеют отдельные пространства имен, что позволяет функции и переменной иметь одно и то же имя и требует специальной нотации для ссылки на функцию как на значение. Это иногда известно как различие " Lisp-1 против Lisp-2 ", ссылаясь на единое пространство имен Scheme и отдельные пространства имен Common Lisp. [23]
defun
В Scheme те же примитивы, которые используются для манипулирования и связывания данных, могут использоваться для связывания процедур. Эквивалент Common Lisp и примитивов отсутствует #'
.
;; Переменная, связанная с числом: ( define f 10 ) f ===> 10 ;; Мутация (изменение связанного значения) ( set! f ( + f f 6 )) f ===> 26 ;; Назначение процедуры той же переменной: ( set! f ( lambda ( n ) ( + n 12 ))) ( f 6 ) ===> 18 ;; Назначение результата выражения той же переменной: ( set! f ( f 1 )) f ===> 13 ;; функциональное программирование: ( apply + ' ( 1 2 3 4 5 6 )) ===> 21 ( set! f ( lambda ( n ) ( + n 100 ))) ( map f ' ( 1 2 3 )) ===> ( 101 102 103 )
В этом подразделе документируются проектные решения, которые принимались на протяжении многих лет и которые придали Scheme особый характер, но не являются прямыми результатами первоначального проекта.
Scheme определяет сравнительно полный набор числовых типов данных, включая комплексные и рациональные типы, который в Scheme известен как числовая башня (R5RS sec. 6.2 [4] ). Стандарт рассматривает их как абстракции и не обязывает разработчика к каким-либо конкретным внутренним представлениям.
Числа могут обладать качеством точности. Точное число может быть получено только последовательностью точных операций, включающих другие точные числа — неточность, таким образом, заразительна. Стандарт определяет, что любые две реализации должны давать эквивалентные результаты для всех операций, приводящих к точным числам.
Стандарт R5RS определяет процедуры exact->inexact
и , inexact->exact
которые могут использоваться для изменения точности числа. inexact->exact
производит «точное число, которое численно ближе всего к аргументу». exact->inexact
производит «неточное число, которое численно ближе всего к аргументу». Стандарт R6RS исключает эти процедуры из основного отчета, но определяет их как процедуры совместимости с R5RS в стандартной библиотеке (rnrs r5rs (6)).
В стандарте R5RS реализации Scheme не обязаны реализовывать всю числовую башню, но они должны реализовывать «согласованное подмножество, соответствующее как целям реализации, так и духу языка Scheme» (R5RS, раздел 6.2.3). [4] Новый стандарт R6RS требует реализации всей башни и «точных целочисленных объектов и точных рациональных объектов практически неограниченного размера и точности, а также реализации определенных процедур... чтобы они всегда возвращали точные результаты при задании точных аргументов» (R6RS, раздел 3.4, раздел 11.7.1). [6]
Пример 1: точная арифметика в реализации, поддерживающей точные рациональные комплексные числа.
;; Сумма трех рациональных действительных чисел и двух рациональных комплексных чисел ( define x ( + 1/3 1/4 -1/5 -1/3i 405/50+2/3i )) x ===> 509/60+1/3i ;; Проверка на точность. ( exact? x ) ===> #t
Пример 2: Та же арифметика в реализации, которая не поддерживает ни точные рациональные числа, ни комплексные числа, но принимает действительные числа в рациональной нотации.
;; Сумма четырех рациональных действительных чисел ( define xr ( + 1/3 1/4 -1/5 405/50 )) ;; Сумма двух рациональных действительных чисел ( define xi ( + -1/3 2/3 )) xr ===> 8,48333333333333 xi ===> 0,333333333333333 ;; Проверка на точность. ( exact? xr ) ===> #f ( exact? xi ) ===> #f
Обе реализации соответствуют стандарту R5RS, но вторая не соответствует R6RS, поскольку не реализует полную числовую башню.
Схема поддерживает отложенную оценку посредством delay
формы и процедуры force
.
( определить a 10 ) ( определить eval-aplus2 ( задержка ( + a 2 ))) ( установить! a 20 ) ( заставить eval-aplus2 ) ===> 22 ( определить eval-aplus50 ( задержка ( + a 50 ))) ( пусть (( a 8 )) ( заставить eval-aplus50 )) ===> 70 ( установить! a 100 ) ( заставить eval-aplus2 ) ===> 22
Лексический контекст исходного определения обещания сохраняется, и его значение также сохраняется после первого использования force
. Обещание оценивается только один раз.
Эти примитивы, которые создают или обрабатывают значения, известные как обещания , могут использоваться для реализации расширенных конструкций ленивой оценки, таких как потоки . [24]
В стандарте R6RS они больше не являются примитивами, а вместо этого предоставляются как часть библиотеки совместимости R5RS (rnrs r5rs (6)).
В R5RS предлагается реализация delay
and force
, реализующая обещание как процедуру без аргументов ( thunk ) и использующая мемоизацию для обеспечения того, чтобы оно было оценено только один раз, независимо от того, сколько раз force
оно вызывается (R5RS, раздел 6.4). [4]
SRFI 41 позволяет выражать как конечные, так и бесконечные последовательности с необычайной экономией. Например, это определение последовательности Фибоначчи с использованием функций, определенных в SRFI 41: [24]
;; Определить последовательность Фибоначчи: ( define fibs ( stream-cons 0 ( stream-cons 1 ( stream-map + fibs ( stream-cdr fibs ))))) ;; Вычислить сотое число в последовательности: ( stream-ref fibs 99 ) ===> 218922995834555169026
Большинство Lisp-ов определяют порядок оценки аргументов процедуры. Scheme этого не делает. Порядок оценки — включая порядок, в котором оценивается выражение в позиции оператора — может быть выбран реализацией на основе вызова-за-вызовом, и единственным ограничением является то, что «эффект любой одновременной оценки выражений оператора и операнда ограничен, чтобы соответствовать некоторому последовательному порядку оценки». (R5RS sec. 4.1.3) [4]
( let (( ev ( lambda ( n ) ( display "Вычисляется " ) ( display ( if ( procedure? n ) "procedure" n )) ( newline ) n ))) (( ev + ) ( ev 1 ) ( ev 2 ))) ===> 3
Оценка 1 Оценка 2 Процедура оценки
ev — это процедура, которая описывает переданный ей аргумент, а затем возвращает значение аргумента. В отличие от других Lisp, появление выражения в позиции оператора (первый элемент) выражения Scheme вполне допустимо, пока результатом выражения в позиции оператора является процедура.
При вызове процедуры " + " для сложения 1 и 2 выражения (ev +), (ev 1) и (ev 2) могут быть оценены в любом порядке, пока эффект не будет таким, как если бы они были оценены параллельно. Таким образом, следующие три строки могут быть отображены в любом порядке стандартной схемой при выполнении приведенного выше примера кода, хотя текст одной строки не может быть перемежен с другим, поскольку это нарушит ограничение последовательной оценки.
В стандарте R5RS, а также в более поздних отчетах, синтаксис Scheme может быть легко расширен с помощью макросистемы. Стандарт R5RS ввел мощную гигиеническую макросистему, которая позволяет программисту добавлять новые синтаксические конструкции в язык, используя простой подъязык сопоставления шаблонов (R5RS sec 4.3). [4] До этого гигиеническая макросистема была отнесена к приложению стандарта R4RS, как система «высокого уровня» наряду с макросистемой «низкого уровня», обе из которых рассматривались как расширения Scheme, а не как существенная часть языка. [25]
Реализации гигиенической макросистемы, также называемой syntax-rules
, должны соблюдать лексическую область видимости остальной части языка. Это обеспечивается специальными правилами именования и области видимости для расширения макросов и позволяет избежать распространенных ошибок программирования, которые могут возникнуть в макросистемах других языков программирования. R6RS определяет более сложную систему преобразования, syntax-case
, которая некоторое время была доступна как языковое расширение для R5RS Scheme.
;; Определите макрос для реализации варианта "if" с множественным выражением ;; с ветвью true и без ветвей false. ( define-syntax when ( syntax-rules () (( when pred exp exps ... ) ( if pred ( begin exp exps ... )))))
Вызовы макросов и процедур очень похожи — оба являются s-выражениями, — но обрабатываются по-разному. Когда компилятор встречает s-выражение в программе, он сначала проверяет, определен ли символ как синтаксическое ключевое слово в текущей лексической области. Если да, то он пытается расширить макрос, обрабатывая элементы в хвосте s-выражения как аргументы без компиляции кода для их оценки, и этот процесс повторяется рекурсивно до тех пор, пока не останется ни одного вызова макроса. Если это не синтаксическое ключевое слово, компилятор компилирует код для оценки аргументов в хвосте s-выражения, а затем для оценки переменной, представленной символом в начале s-выражения, и вызывает его как процедуру с переданными ему в качестве аргументов оцененными выражениями хвоста.
Большинство реализаций Scheme также предоставляют дополнительные макросистемы. Среди популярных — синтаксические замыкания , явные макросы переименования и define-macro
негигиеничная макросистема, похожая на defmacro
систему, предоставляемую в Common Lisp .
Невозможность указать, является ли макрос гигиеничным или нет, является одним из недостатков макросистемы. Альтернативные модели расширения, такие как наборы областей действия, предоставляют потенциальное решение. [26]
До R5RS в Scheme не было стандартного эквивалента процедуры eval
, которая повсеместно встречается в других Lisp, хотя в первой статье Lambda она была описана evaluate
как «похожая на функцию LISP EVAL» [18] , а в первом пересмотренном отчете 1978 года она была заменена на enclose
, которая принимала два аргумента. Во втором, третьем и четвертом пересмотренных отчетах не было никаких эквивалентов eval
.
Причина этой путаницы в том, что в Scheme с его лексической областью действия результат оценки выражения зависит от того, где оно оценивается. Например, неясно, должен ли результат оценки следующего выражения быть 5 или 6: [27]
( пусть (( имя '+ )) ( пусть (( + * )) ( вычислить ( имя списка 2 3 ))))
Если он вычисляется во внешней среде, где name
определено, результатом является сумма операндов. Если он вычисляется во внутренней среде, где символ "+" был привязан к значению процедуры "*", результатом является произведение двух операндов.
R5RS устраняет эту путаницу, указывая три процедуры, которые возвращают среды, и предоставляя процедуру eval
, которая принимает s-выражение и среду и оценивает выражение в предоставленной среде. (R5RS раздел 6.5) [4] R6RS расширяет это, предоставляя процедуру, вызываемую environment
с помощью которой программист может точно указать, какие объекты следует импортировать в среду оценки.
При использовании современной схемы (обычно совместимой с R5RS) для оценки этого выражения необходимо определить функцию evaluate
, которая может выглядеть следующим образом:
( определить ( оценить выражение ) ( оценить выражение ( взаимодействие-среда )))
interaction-environment
это глобальная среда переводчика.
В большинстве диалектов Lisp, включая Common Lisp, по соглашению значение NIL
вычисляется как значение false в булевом выражении. В Scheme, начиная со стандарта IEEE 1991 года, [3] все значения, кроме #f
, включая NIL
эквивалент 's в Scheme, который записывается как '()
, вычисляются как значение true в булевом выражении. (R5RS sec. 6.3.1) [4]
Если в большинстве языков Lisp константа, представляющая логическое значение true, — T
, то в Scheme это #t
.
В Scheme примитивные типы данных не пересекаются. Только один из следующих предикатов может быть истинным для любого объекта Scheme: boolean?
, pair?
, symbol?
, number?
, char?
, string?
, vector?
, port?
, procedure?
. (R5RS sec 3.2) [4]
В численном типе данных, напротив, числовые значения перекрываются. Например, целочисленное значение удовлетворяет всем предикатам integer?
, rational?
, real?
, complex?
и number?
одновременно. (R5RS sec 6.2) [4]
В схеме есть три различных типа эквивалентности между произвольными объектами, обозначенными тремя различными предикатами эквивалентности , реляционными операторами для проверки равенства eq?
, eqv?
и equal?
:
eq?
оценивается как #f
, если его параметры не представляют тот же объект данных в памяти;eqv?
в целом то же самое, что eq?
и , но обрабатывает примитивные объекты (например, символы и числа) особым образом, так что числа, представляющие одно и то же значение, являются eqv?
четными, если они не относятся к одному и тому же объекту;equal?
сравнивает структуры данных, такие как списки, векторы и строки, чтобы определить, имеют ли они конгруэнтную структуру и eqv?
содержимое. (R5RS раздел 6.1) [4]В Scheme также существуют операции эквивалентности, зависящие от типа: string=?
и string-ci=?
сравнивает две строки (последняя выполняет сравнение, не зависящее от регистра); char=?
и char-ci=?
сравнивает символы; =
сравнивает числа. [4]
До стандарта R5RS стандартным комментарием в Scheme была точка с запятой, которая делала остальную часть строки невидимой для Scheme. Многочисленные реализации поддерживали альтернативные соглашения, позволяющие комментариям распространяться более чем на одну строку, и стандарт R6RS допускает два из них: целое s-выражение может быть превращено в комментарий (или «закомментировано»), если предварить его #;
(введено в SRFI 62 [28] ), а многострочный комментарий или «блочный комментарий» может быть получен путем окружения текста #|
и |#
.
Ввод и вывод Scheme основаны на типе данных порта . (R5RS sec 6.6) [4] R5RS определяет два порта по умолчанию, доступных с помощью процедур current-input-port
и current-output-port
, которые соответствуют понятиям Unix стандартного ввода и стандартного вывода . Большинство реализаций также предоставляют current-error-port
. Перенаправление ввода и стандартного вывода поддерживается в стандарте стандартными процедурами, такими как with-input-from-file
и with-output-to-file
. Большинство реализаций предоставляют строковые порты с аналогичными возможностями перенаправления, что позволяет выполнять многие обычные операции ввода-вывода над строковыми буферами вместо файлов, используя процедуры, описанные в SRFI 6. [29] Стандарт R6RS определяет гораздо более сложные и эффективные процедуры порта и много новых типов портов.
Следующие примеры написаны в строгой схеме R5RS.
Пример 1: С выходом по умолчанию (текущий-выходной-порт):
( пусть (( hello0 ( лямбда () ( дисплей "Привет, мир" ) ( новая строка )))) ( hello0 ))
Пример 2: Как 1, но с использованием необязательного аргумента порта для вывода процедур
( пусть (( привет1 ( лямбда ( p ) ( вывести "Привет, мир" p ) ( новая строка p )))) ( привет1 ( текущий-выходной-порт )))
Пример 3: Как 1, но вывод перенаправляется во вновь созданный файл
;; Примечание: with-output-to-file — необязательная процедура в R5RS ( let (( hello0 ( lambda () ( display "Hello world" ) ( newline )))) ( with-output-to-file "helloworldoutputfile" hello0 ))
Пример 4: Как 2, но с явным открытием файла и закрытием порта для отправки вывода в файл
( let (( hello1 ( lambda ( p ) ( display "Hello world" p ) ( newline p ))) ( output-port ( open-output-file "helloworldoutputfile" ))) ( hello1 output-port ) ( close-output-port output-port ))
Пример 5: То же, что и 2, но с использованием call-with-output-file для отправки вывода в файл.
( let (( hello1 ( lambda ( p ) ( display "Hello world" p ) ( newline p )))) ( call-with-output-file "helloworldoutputfile" hello1 ))
Аналогичные процедуры предусмотрены для ввода. Схема R5RS предоставляет предикаты input-port?
и output-port?
. Для ввода и вывода символов предоставляются write-char
, read-char
и peek-char
. Для записи и чтения выражений Scheme, Scheme предоставляет и . При операции чтения возвращаемый результат является объектом конца файла, если порт ввода достиг конца файла, и это можно проверить с помощью предиката .char-ready?
read
write
eof-object?
В стандарте SRFI 28 также определяется базовая процедура форматирования, напоминающая format
функцию Common Lisp, в честь которой она и названа. [30]
В Scheme процедуры привязаны к переменным. В R5RS стандарт языка формально предписал, что программы могут изменять привязки переменных встроенных процедур, фактически переопределяя их. (R5RS "Изменения языка") [4] Например, +
можно расширить, чтобы принимать строки, а также числа, переопределив его:
( set! + ( let (( original+ + )) ( lambda args ( apply ( if ( or ( null? args ) ( string? ( car args ))) string-append original+ ) args )))) ( + 1 2 3 ) ===> 6 ( + "1" "2" "3" ) ===> "123"
В R6RS каждая привязка, включая стандартную, принадлежит некоторой библиотеке, и все экспортированные привязки являются неизменяемыми. (R6RS sec 7.1) [6] Из-за этого переопределение стандартных процедур мутацией запрещено. Вместо этого можно импортировать другую процедуру под именем стандартной, что по сути аналогично переопределению.
В стандартной схеме процедуры, преобразующие один тип данных в другой, содержат в своем имени строку символов "->", предикаты заканчиваются на "?", а процедуры, изменяющие значение уже выделенных данных, заканчиваются на "!". Эти соглашения часто соблюдаются программистами Scheme.
В формальных контекстах, таких как стандарты Scheme, слово «процедура» используется вместо слова «функция» для обозначения лямбда-выражения или примитивной процедуры. В обычном использовании слова «процедура» и «функция» используются взаимозаменяемо. Применение процедуры иногда формально называют комбинацией .
Как и в других Lisp, термин " thunk " используется в Scheme для обозначения процедуры без аргументов. Термин "правильная хвостовая рекурсия" относится к свойству всех реализаций Scheme, заключающемуся в том, что они выполняют оптимизацию хвостовых вызовов, чтобы поддерживать неограниченное количество активных хвостовых вызовов .
Форма заголовков документов стандартов, начиная с R3RS, «Пересмотренный отчет по схеме алгоритмического языка», является ссылкой на заголовок стандартного документа ALGOL 60 , «Пересмотренный отчет по алгоритмическому языку Algol 60». Страница «Сводка» R3RS во многом скопирована со страницы «Сводка» отчета ALGOL 60. [31] [32]
Язык формально определен в стандартах R5RS (1998) [4] и R6RS (2007). [6] Они описывают стандартные «формы»: ключевые слова и сопутствующий синтаксис, которые обеспечивают структуру управления языком, а также стандартные процедуры, которые выполняют общие задачи.
В этой таблице описаны стандартные формы в Scheme. Некоторые формы появляются в более чем одной строке, поскольку их нельзя легко классифицировать в одну функцию в языке.
Формы, отмеченные в этой таблице буквой «L», классифицируются в стандарте как производные «библиотечные» формы и часто реализуются как макросы с использованием на практике более фундаментальных форм, что значительно упрощает задачу реализации по сравнению с другими языками.
Хотя begin
в R5RS определен как библиотечный синтаксис, расширитель должен знать о нем, чтобы достичь функции сплайсинга. В R6RS это больше не библиотечный синтаксис.
В следующих двух таблицах описываются стандартные процедуры в схеме R5RS. R6RS гораздо более обширен, и краткое изложение такого типа было бы непрактичным.
Некоторые процедуры появляются более чем в одной строке, поскольку их нельзя легко отнести к одной функции в языке.
Строковые и символьные процедуры, содержащие «-ci» в своих именах, выполняют независимые от регистра сравнения между своими аргументами: версии одного и того же символа в верхнем и нижнем регистре считаются равными.
Реализации - и /, которые принимают более двух аргументов, определены, но оставлены необязательными в R5RS.
Из-за минимализма Scheme многие общие процедуры и синтаксические формы не определены стандартом. Чтобы сохранить небольшой размер основного языка, но способствовать стандартизации расширений, сообщество Scheme использует процесс «Scheme Request for Implementation» (SRFI), с помощью которого библиотеки расширений определяются путем тщательного обсуждения предложений по расширениям. Это способствует переносимости кода. Многие из SRFI поддерживаются всеми или большинством реализаций Scheme.
SRFI с довольно широкой поддержкой в различных реализациях включают: [33]
Элегантный минималистский дизайн сделал Scheme популярной целью для разработчиков языков, любителей и преподавателей, а из-за своего небольшого размера, типичного интерпретатора , он также является популярным выбором для встраиваемых систем и сценариев . Это привело к появлению множества реализаций, [34] большинство из которых отличаются друг от друга настолько, что перенос программ из одной реализации в другую становится довольно сложным, а небольшой размер стандартного языка означает, что написание полезной программы любой большой сложности на стандартной переносимой Scheme практически невозможно. [14] Стандарт R6RS определяет гораздо более широкий язык в попытке расширить его привлекательность для программистов.
Почти все реализации предоставляют традиционный цикл чтения–вычисления–печати в стиле Lisp для разработки и отладки. Многие также компилируют программы Scheme в исполняемый двоичный файл. Поддержка встраивания кода Scheme в программы, написанные на других языках, также распространена, поскольку относительная простота реализаций Scheme делает его популярным выбором для добавления возможностей скриптинга в более крупные системы, разработанные на таких языках, как C. Интерпретаторы Scheme Gambit , Chicken и Bigloo компилируют Scheme в C, что значительно упрощает встраивание. Кроме того, компилятор Bigloo можно настроить для генерации байт-кода для виртуальной машины Java (JVM), и он имеет экспериментальный генератор байт-кода для .NET .
Некоторые реализации поддерживают дополнительные функции. Например, Kawa и JScheme обеспечивают интеграцию с классами Java , а компиляторы Scheme to C часто упрощают использование внешних библиотек, написанных на C, вплоть до встраивания кода C в исходный код Scheme. Другим примером является Pvts, который предлагает набор визуальных инструментов, поддерживающих обучение Scheme.
Scheme широко используется несколькими [35] школами; в частности, несколько вводных курсов по информатике используют Scheme в сочетании с учебником Structure and Interpretation of Computer Programs (SICP). [36] В течение последних 12 лет PLT реализует проект ProgramByDesign (ранее TeachScheme!), который познакомил около 600 учителей старших классов и тысячи учеников старших классов с элементарным программированием на Scheme. Старый вводный курс программирования 6.001 Массачусетского технологического института преподавался на Scheme, [37] Хотя 6.001 был заменен более современными курсами, SICP продолжает преподаваться в MIT. [38] Аналогичным образом, вводный курс в Калифорнийском университете в Беркли , CS 61A, до 2011 года преподавался полностью на Scheme, за исключением небольших отклонений в Logo для демонстрации динамического охвата. Сегодня, как и Массачусетский технологический институт, Беркли заменил учебную программу более современной версией, которая в основном преподается на Python 3 , но текущая учебная программа по-прежнему основана на старой программе, и части занятий по-прежнему преподаются на Scheme. [39]
Учебник «Как разрабатывать программы» Маттиаса Феллейзена, в настоящее время находящийся в Северо-Восточном университете, используется некоторыми высшими учебными заведениями для вводных курсов по информатике. И Северо-Восточный университет , и Вустерский политехнический институт используют Scheme исключительно для своих вводных курсов «Основы информатики» (CS2500) и «Введение в проектирование программ» (CS1101) соответственно. [40] [41] Rose-Hulman использует Scheme в своем более продвинутом курсе «Концепции языка программирования». [42] Основной курс Университета Брандейса «Структура и интерпретации компьютерных программ» (COSI121b) также преподается исключительно на Scheme теоретиком-компьютерщиком Гарри Мейрсоном . [43] Вводный курс Университета Индианы , C211, преподается полностью на Scheme. Самостоятельная версия курса, CS 61AS, продолжает использовать Scheme. [44] Вводные курсы по информатике в Йельском университете и Гриннелл-колледже также преподаются на Scheme. [45] Programming Design Paradigms, [46] обязательный курс для аспирантов по информатике в Северо-Восточном университете , также широко использует Scheme. Бывший вводный курс по информатике в Университете Миннесоты - Города-побратимы, CSCI 1901, также использовал Scheme в качестве основного языка, за которым последовал курс, знакомивший студентов с языком Java; [47] однако, следуя примеру MIT, факультет заменил 1901 на CSCI 1133 на основе Python, [48] в то время как функциональное программирование подробно рассматривается в курсе третьего семестра CSCI 2041. [49]
Схема также использовалась/использовалась для следующего:
{{cite web}}
: CS1 maint: несколько имен: список авторов ( ссылка )Полезная метафора для различия между FUNCTION и QUOTE в LISP — думать о QUOTE как о пористом или открытом покрытии функции, поскольку свободные переменные ускользают в текущее окружение. FUNCTION действует как закрытое или непористое покрытие (отсюда термин «замыкание», используемый Ландином). Таким образом, мы говорим об «открытых» лямбда-выражениях (функции в LISP обычно являются лямбда-выражениями) и «закрытых» лямбда-выражениях. [...] Мой интерес к проблеме окружения возник, когда Ландин, который имел глубокое понимание проблемы, посетил Массачусетский технологический институт в 1966-67 гг. Затем я осознал соответствие между списками FUNARG, которые являются результатами оценки «закрытых» лямбда-выражений в
LISP
, и лямбда-замыканиями
ISWIM
.