Парадигмы | Мультипарадигма : функциональная , императивная , метапарадигмальная |
---|---|
Семья | Лисп |
Разработано | Гай Л. Стил Джеральд Джей Сассман |
Впервые появился | 1975 ( 1975 ) |
Стабильный релиз | R7RS / 2013 ( 2013 ) |
Дисциплина печати | Динамичный , скрытый , сильный |
Объем | Лексический |
Расширения имени файла | .scm, .ss |
Веб-сайт | www.scheme.org |
Основные внедрения | |
Многие (см. Реализации схем ) | |
Под влиянием | |
АЛГОЛ , Лисп , MDL | |
Под влиянием | |
Clojure , Common Lisp , Dylan , EuLisp , Haskell , Hop , JavaScript , Julia , Lua , MultiLisp , Python , R , Racket , Ruby , Rust , [1] S , Scala , T | |
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]
1958 | 1960 | 1965 | 1970 | 1975 | 1980 | 1985 | 1990 | 1995 | 2000 | 2005 | 2010 | 2015 | 2020 | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LISP 1, 1.5, LISP 2 (заброшен) | |||||||||||||||
Маклисп | |||||||||||||||
Интерлисп | |||||||||||||||
молдавский лей | |||||||||||||||
Лисп-машина Лисп | |||||||||||||||
Схема | Р5РС | Р6РС | R7RS маленький | ||||||||||||
НОЛЬ | |||||||||||||||
ZIL (язык реализации Zork) | |||||||||||||||
Франц Лисп | |||||||||||||||
Общий Лисп | стандарт ANSI | ||||||||||||||
Ле Лисп | |||||||||||||||
Схема Массачусетского технологического института | |||||||||||||||
XLISP | |||||||||||||||
Т | |||||||||||||||
Chez Схема | |||||||||||||||
Emacs Лисп | |||||||||||||||
АвтоЛИСП | |||||||||||||||
ПикоЛисп | |||||||||||||||
Гамбит | |||||||||||||||
EuLisp | |||||||||||||||
ИСЛИСП | |||||||||||||||
OpenLisp | |||||||||||||||
Схема PLT | Ракетка | ||||||||||||||
новыйLISP | |||||||||||||||
GNU-хитрость | |||||||||||||||
Визуальный ЛИСП | |||||||||||||||
Кложур | |||||||||||||||
Дуга | |||||||||||||||
ЛФЭ | |||||||||||||||
Хай |
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 Paper описывала ее 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: С выходом по умолчанию (текущий-выходной-порт):
( let (( hello0 ( lambda () ( display "Hello world" ) ( newline )))) ( 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», классифицируются в стандарте как производные «библиотечные» формы и часто реализуются как макросы с использованием на практике более фундаментальных форм, что значительно упрощает задачу реализации по сравнению с другими языками.
Цель | Формы |
---|---|
Определение | определять |
Связывающие конструкции | лямбда, делать (Л), пусть (Л), пусть* (Л), летрек (Л) |
Условная оценка | если, условие (L), случай (L), и (L), или (L) |
Последовательная оценка | начинать (*) |
Итерация | лямбда, do (L), названный let (L) |
Синтаксическое расширение | define-синтаксис, let-синтаксис, letrec-синтаксис, синтаксические правила (R5RS), синтаксис-регистр (R6RS) |
Цитата | цитировать('), снять цитирование(,), квазицитировать(`), снять цитирование-склеивание(,@) |
Назначение | набор! |
Отсроченная оценка | задержка (Л) |
Хотя begin
в R5RS он определен как библиотечный синтаксис, расширитель должен знать о нем, чтобы достичь функции сплайсинга. В R6RS он больше не является библиотечным синтаксисом.
В следующих двух таблицах описываются стандартные процедуры в схеме R5RS. R6RS гораздо более обширен, и краткое изложение такого типа было бы непрактичным.
Некоторые процедуры появляются более чем в одной строке, поскольку их нельзя легко отнести к одной функции в языке.
Цель | Процедуры |
---|---|
Строительство | вектор, make-вектор, make-строка, список |
Предикаты эквивалентности | eq?, eqv?, equal?, string=?, string-ci=?, char=?, char-ci=? |
Преобразование типа | вектор->список, список->вектор, число->строка, строка->число, символ->строка, строка->символ, символ->целое число, целое число->символ, строка->список, список->строка |
Числа | См. отдельную таблицу |
Струны | string?, make-string, string, string-length, string-ref, string-set!, string=?, string-ci=?, string<? string-ci<?, string<=? string-ci<=?, string>? string-ci>?, string>=? string-ci>=?, подстрока, string-append, string->list, list->string, string-copy, string-fill! |
Персонажи | char?, char=?, char-ci=?, char<? char-ci<?, char<=? char-ci<=?, char>? char-ci>?, char>=? char-ci>=?, char-alphabetic?, char-numeric?, char-whitespace?, char-upper-case?, char-lower-case?, char->integer, integer->char, char-upcase, char-downcase |
Векторы | создать-вектор, вектор, вектор?, длина вектора, ссылка на вектор, набор векторов!, вектор->список, список->вектор, заполнение вектора! |
Символы | символ->строка, строка->символ, символ? |
Пары и списки | пара?, минусы, автомобиль, cdr, set-car!, set-cdr!, null?, список?, список, длина, добавление, обратный, хвост списка, ссылка на список, memq. memv. член, assq, assv, assoc, список->вектор, вектор->список, список->строка, строка->список |
Предикаты идентичности | логическое значение?, пара?, символ?, число?, символ?, строка?, вектор?, порт?, процедура? |
Продолжения | вызов-с-текущим-продолжением (вызов/cc), значения, вызов-со-значениями, динамический-ветер |
Окружающая среда | eval, схема-отчет-среда, нулевая среда, взаимодействие-среда (необязательно) |
Ввод/вывод | display, newline, read, write, read-char, write-char, peek-char, char-ready?, eof-object? open-input-file, open-output-file, close-input-port, close-output-port, input-port?, output-port?, current-input-port, current-output-port, call-with-input-file, call-with-output-file, with-input-from-file (необязательно), with-output-to-file (необязательно) |
Интерфейс системы | загрузка (необязательно), транскрипция включена (необязательно), транскрипция выключена (необязательно) |
Отсроченная оценка | сила |
Функциональное программирование | процедура?, применить, карта, для каждого |
Булевы значения | булево? нет |
Строковые и символьные процедуры, содержащие «-ci» в своих именах, выполняют независимые от регистра сравнения между своими аргументами: версии одного и того же символа в верхнем и нижнем регистре считаются равными.
Цель | Процедуры |
---|---|
Базовые арифметические операторы | +, -, *, /, abs, частное, остаток, модуль, gcd, lcm, expt, sqrt |
Рациональные числа | числитель, знаменатель, рациональный?, рационализировать |
Приближение | пол, потолок, усеченный, круглый |
Точность | неточный->точный, точный->неточный, точный?, неточный? |
Неравенства | <, <= , >, >=, = |
Разные предикаты | ноль?, отрицательно?, положительно? нечетное? четное? |
Максимум и минимум | макс, мин |
Тригонометрия | грех, потому что, тан, асин, акос, атан |
Экспоненты | exp, журнал |
Комплексные числа | сделать-прямоугольным, сделать-полярным, действительная-часть, мнимая-часть, величина, угол, комплексный? |
Ввод-вывод | число->строка, строка->число |
Тип предикатов | целое?, рациональное?, действительное?, комплексное?, число? |
Реализации - и /, которые принимают более двух аргументов, определены, но оставлены необязательными в 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] Технологический институт Роуз-Халман использует 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
.