Esquema (lenguaje de programación)

Ajustar Compartir Imprimir Citar
Dialecto de Lisp

Scheme es un dialecto de la familia de lenguajes de programación Lisp. Scheme fue creado durante la década de 1970 en el MIT AI Lab y publicado por sus desarrolladores, Guy L. Steele y Gerald Jay Sussman, a través de una serie de memorandos que ahora se conocen como Lambda Papers. Fue el primer dialecto de Lisp en elegir el alcance léxico y el primero en requerir implementaciones para realizar la optimización de llamadas de cola, lo que brinda un soporte más sólido para la programación funcional y técnicas asociadas, como los algoritmos recursivos. También fue uno de los primeros lenguajes de programación en admitir continuaciones de primera clase. Tuvo una influencia significativa en el esfuerzo que condujo al desarrollo de Common Lisp.

El lenguaje Scheme está estandarizado en el estándar oficial IEEE y en un estándar de facto llamado Revisedn Informe sobre el Esquema de Lenguaje Algorítmico (RnRS). Un estándar ampliamente implementado es R5RS (1998). El estándar de Scheme ratificado más recientemente es "R7RS-pequeño" (2013). El R6RS más expansivo y modular fue ratificado en 2007. Ambos trazan su descendencia del R5RS; el cronograma a continuación refleja el orden cronológico de ratificación.

Historia

Orígenes

Scheme comenzó en la década de 1970 como un intento de comprender el modelo Actor de Carl Hewitt, para lo cual Steele y Sussman escribieron un "intérprete de Lisp minúsculo" usando Maclisp y luego "mecanismos agregados para crear actores y enviar mensajes". Scheme originalmente se llamaba "Schemer", en la tradición de otros lenguajes derivados de Lisp como Planner o Conniver. El nombre actual resultó de los autores' uso del sistema operativo ITS, que limitaba los nombres de archivo a dos componentes de un máximo de seis caracteres cada uno. Actualmente, "Schemer" se usa comúnmente para referirse a un programador de Scheme.

R6RS

Se inició un nuevo proceso de estandarización del lenguaje en el taller de Scheme de 2003, con el objetivo de producir un estándar R6RS en 2006. Este proceso rompió con el enfoque de unanimidad RnRS anterior.

R6RS cuenta con un sistema de módulos estándar, lo que permite una división entre el lenguaje principal y las bibliotecas. Se publicaron varios borradores de la especificación R6RS, siendo la versión final R5.97RS. Una votación exitosa resultó en la ratificación de la nueva norma, anunciada el 28 de agosto de 2007.

Actualmente, las versiones más recientes de varias implementaciones de Scheme son compatibles con el estándar R6RS. Hay una implementación de referencia portátil de las bibliotecas propuestas implícitamente en fase para R6RS, llamada psyntax, que se carga y arranca correctamente en varias implementaciones anteriores de Scheme.

Una característica de R6RS es el descriptor de tipo de registro (RTD). Cuando se crea y utiliza un RTD, la representación del tipo de registro puede mostrar el diseño de la memoria. También calculó la máscara de bits de campo de objeto y las máscaras de bit de campo de objeto de esquema mutable, y ayudó al recolector de elementos no utilizados a saber qué hacer con los campos sin recorrer toda la lista de campos que se guardan en el RTD. RTD permite a los usuarios expandir el RTD básico para crear un nuevo sistema de registro.

R6RS introduce numerosos cambios significativos en el lenguaje. El código fuente ahora se especifica en Unicode, y ahora puede aparecer un gran subconjunto de caracteres Unicode en los símbolos e identificadores de Scheme, y hay otros cambios menores en las reglas léxicas. Los datos de caracteres ahora también se especifican en Unicode. Muchos procedimientos estándar se han trasladado a las nuevas bibliotecas estándar, que a su vez forman una gran expansión del estándar, que contiene procedimientos y formas sintácticas que antes no formaban parte del estándar. Se ha introducido un nuevo sistema de módulos y los sistemas para el manejo de excepciones ahora están estandarizados. Las reglas de sintaxis se han reemplazado con una función de abstracción sintáctica más expresiva (caso de sintaxis) que permite el uso de todo Scheme en el momento de la macro expansión. Las implementaciones compatibles ahora son requeridas para admitir la torre numérica completa de Scheme, y la semántica de los números se ha ampliado, principalmente en la dirección de la compatibilidad con el estándar IEEE 754 para la representación numérica de punto flotante.

R7RS

El estándar R6RS ha causado controversia porque algunos lo ven como una desviación de la filosofía minimalista. En agosto de 2009, el Comité Directivo de Scheme, que supervisa el proceso de estandarización, anunció su intención de recomendar dividir Scheme en dos lenguajes: un gran lenguaje de programación moderno para programadores; y una versión pequeña, un subconjunto de la versión grande que conserva el minimalismo elogiado por educadores e implementadores casuales. Se crearon dos grupos de trabajo para trabajar en estas dos nuevas versiones de Scheme. El sitio de Scheme Reports Process tiene enlaces a los grupos de trabajo' estatutos, debates públicos y sistema de seguimiento de problemas.

El noveno borrador de R7RS (lenguaje pequeño) estuvo disponible el 15 de abril de 2013. La votación para ratificar este borrador se cerró el 20 de mayo de 2013 y el informe final ha estado disponible desde el 6 de agosto de 2013, describiendo "el 'pequeño' lenguaje de ese esfuerzo: por lo tanto, no puede ser considerado de forma aislada como el sucesor de R6RS".

1955 1960 1965 1970 1975 1980 1985 1990 1995 2000 2005 2010 2015 2020
LISP 1, 1,5, LISP 2(abandonado)
Maclisp
Interlisp
MDL
Lisp Machine Lisp
Plan R5RS R6RS R7RS pequeño
NIL
ZIL (Zork Implementation Language)
Franz Lisp
Lisp común ANSI standard
Le Lisp
MIT Plan
T
Chez Scheme
Emacs Lisp
AutoLISP
PicoLisp
Gambit
EuLisp
ISLISP
OpenLisp
PLT Plan Racket
GNU Guile
Visual LISP
Clojure
Arc
LFE
Hy

Características distintivas

Scheme es principalmente un lenguaje de programación funcional. Comparte muchas características con otros miembros de la familia de lenguajes de programación Lisp. La sintaxis muy simple de Scheme se basa en expresiones s, listas entre paréntesis en las que un operador de prefijo va seguido de sus argumentos. Por lo tanto, los programas de esquema consisten en secuencias de listas anidadas. Las listas también son la principal estructura de datos en Scheme, lo que lleva a una estrecha equivalencia entre el código fuente y los formatos de datos (homoiconicidad). Los programas Scheme pueden crear y evaluar piezas de código Scheme de forma dinámica.

La confianza en las listas como estructuras de datos es compartida por todos los dialectos de Lisp. Scheme hereda un rico conjunto de primitivas de procesamiento de listas como cons, car y cdr de sus progenitores Lisp. Scheme utiliza variables tipeadas estricta pero dinámicamente y admite procedimientos de primera clase. Por lo tanto, los procedimientos pueden asignarse como valores a variables o pasarse como argumentos a procedimientos.

Esta sección se concentra principalmente en las características innovadoras del lenguaje, incluidas aquellas características que distinguen a Scheme de otros Lisps. A menos que se indique lo contrario, las descripciones de las características se relacionan con el estándar R5RS. En los ejemplos proporcionados en esta sección, la notación "===> resultado" se utiliza para indicar el resultado de evaluar la expresión en la línea inmediatamente anterior. Esta es la misma convención utilizada en R5RS.

Minimalismo

Scheme es un lenguaje muy simple, mucho más fácil de implementar que muchos otros lenguajes de poder expresivo comparable. Esta facilidad se atribuye al uso del cálculo lambda para derivar gran parte de la sintaxis del lenguaje a partir de formas más primitivas. Por ejemplo, de las 23 construcciones sintácticas basadas en expresiones s definidas en el estándar R5RS Scheme, 14 se clasifican como formas derivadas o de biblioteca, que se pueden escribir como macros que involucran formas más fundamentales, principalmente lambda. Como dice R5RS (§3.1): "La más fundamental de las construcciones de vinculación de variables es la expresión lambda, porque todas las demás construcciones de vinculación de variables se pueden explicar en términos de expresiones lambda."

Formas fundamentales: definir, lambda, citar, si, definir-syntax, let-syntax, letrec-syntax, sintaxis-rules, set!
Formas derivadas: do, let, let*, letrec, cond, case, and, or, begin, named let, delay, unquote, unquote-splicing, quasiquote

Ejemplo: una macro para implementar let como una expresión usando lambda para realizar los enlaces de variables.

()define-syntax Deja ()sintaxis-reglas () ()Deja ()Var expreso) ...) cuerpo ...) ()lambda ()Var ...) cuerpo ...) expreso ...)))

Por lo tanto, usar let como se definió anteriormente, una implementación de Scheme reescribiría "(let ((a 1)(b 2)) (+ b a))& #34; como "((lambda (a b) (+ b a)) 1 2)", que reduce la tarea de implementación a la de codificar instancias de procedimientos.

En 1998, Sussman y Steele comentaron que el minimalismo de Scheme no era un objetivo de diseño consciente, sino el resultado no deseado del proceso de diseño. "En realidad, estábamos tratando de construir algo complicado y descubrimos, por casualidad, que accidentalmente habíamos diseñado algo que cumplía con todos nuestros objetivos pero que era mucho más simple de lo que habíamos previsto... nos dimos cuenta de que el cálculo lambda, un pequeño, formalismo simple—podría servir como el núcleo de un lenguaje de programación poderoso y expresivo."

Alcance léxico

Al igual que la mayoría de los lenguajes de programación modernos y a diferencia de Lisps anteriores como Maclisp, Scheme tiene un alcance léxico: todos los enlaces de variables posibles en una unidad de programa se pueden analizar leyendo el texto de la unidad de programa sin tener en cuenta los contextos en los que puede estar. llamó. Esto contrasta con el alcance dinámico que era característico de los primeros dialectos de Lisp, debido a los costos de procesamiento asociados con los métodos primitivos de sustitución textual utilizados para implementar algoritmos de alcance léxico en compiladores e intérpretes de la época. En esos Lisps, era perfectamente posible que una referencia a una variable libre dentro de un procedimiento se refiriera a enlaces externos al procedimiento bastante distintos, dependiendo del contexto de la llamada.

El ímpetu para incorporar el alcance léxico, que era un modelo de alcance inusual a principios de la década de 1970, en su nueva versión de Lisp, provino de los estudios de ALGOL de Sussman. Sugirió que los mecanismos de alcance léxico similares a ALGOL ayudarían a alcanzar su objetivo inicial de implementar el modelo Actor de Hewitt en Lisp.

Las ideas clave sobre cómo introducir el alcance léxico en un dialecto Lisp se popularizaron en el Lambda Paper de 1975 de Sussman y Steele, "Scheme: An Interpreter for Extended Lambda Calculus", donde adoptaron el concepto de la clausura léxica (en la página 21), que había sido descrito en un AI Memo en 1970 por Joel Moses, quien atribuyó la idea a Peter J. Landin.

Cálculo lambda

La notación matemática de Alonso Church, el cálculo lambda, ha inspirado el uso de Lisp de "lambda" como palabra clave para introducir un procedimiento, además de influir en el desarrollo de técnicas de programación funcional que implican el uso de funciones de orden superior en Lisp. Pero los primeros Lisps no eran expresiones adecuadas del cálculo lambda debido a su tratamiento de variables libres.

Un sistema lambda formal tiene axiomas y una regla de cálculo completa. Es útil para el análisis usando lógica y herramientas matemáticas. En este sistema, el cálculo puede verse como una deducción direccional. La sintaxis del cálculo lambda sigue las expresiones recursivas de x, y, z,..., paréntesis, espacios, el punto y el símbolo λ. La función del cálculo lambda incluye: Primero, servir como punto de partida de una poderosa lógica matemática. En segundo lugar, puede reducir el requisito de que los programadores consideren los detalles de implementación, ya que puede usarse para imitar la evaluación de la máquina. Finalmente, el cálculo lambda creó una metateoría sustancial.

La introducción del alcance léxico resolvió el problema al hacer una equivalencia entre algunas formas de notación lambda y su expresión práctica en un lenguaje de programación funcional. Sussman y Steele demostraron que el nuevo lenguaje podría usarse para derivar elegantemente toda la semántica imperativa y declarativa de otros lenguajes de programación, incluidos ALGOL y Fortran, y el alcance dinámico de otros Lisps, mediante el uso de expresiones lambda no como instancias de procedimientos simples sino como &# 34;estructuras de control y modificadores de entorno". Introdujeron el estilo de paso de continuación junto con su primera descripción de Scheme en el primero de los Lambda Papers, y en artículos posteriores, procedieron a demostrar el poder bruto de este uso práctico del cálculo lambda.

Estructura de bloques

Scheme hereda su estructura de bloques de lenguajes estructurados de bloques anteriores, particularmente ALGOL. En Scheme, los bloques se implementan mediante tres construcciones vinculantes: let, let* y letrec. Por ejemplo, la siguiente construcción crea un bloque en el que un símbolo llamado var está vinculado al número 10:

()definir Var "goose"); Cualquier referencia al var aquí estará obligado a "goose"()Deja ()Var 10) ; las declaraciones van aquí. Cualquier referencia al var aquí estará atada a 10. ); Cualquier referencia al var aquí estará obligado a "goose"

Los bloques se pueden anidar para crear estructuras de bloques arbitrariamente complejas según las necesidades del programador. El uso de la estructuración de bloques para crear enlaces locales alivia el riesgo de colisión de espacios de nombres que de otro modo podría ocurrir.

Una variante de let, let*, permite vinculaciones para referirse a variables definidas anteriormente en la misma construcción, por lo tanto:

(). ()var1 10) ()var2 ()+ var1 12)) ; Pero la definición de var1 no podría referirse al var2 )

La otra variante, letrec, está diseñada para permitir que los procedimientos mutuamente recursivos se vinculen entre sí.

;; Cálculo de las secuencias masculinas y femeninas de Hofstadter como una lista de pares()definir ()hofstadter-mujer n) ()Letrec ()hembra ()lambda ()n) ()si ()= n 0) 1 ()- n ()Hombre ()hembra ()- n 1)))))) ()Hombre ()lambda ()n) ()si ()= n 0) 0 ()- n ()hembra ()Hombre ()- n 1))))))) ()Deja bucle ()i 0) ()si ()i n) '() ()cons ()cons ()hembra i) ()Hombre i)()bucle ()+ i 1))))))()hofstadter-mujer 8)=== ()1 . 0) ()1 . 0) ()2 . 1) ()2 . 2) ()3 . 2) ()3 . 3) ()4 . 4) ()5 . 4) ()5 . 5)

(Consulte las secuencias masculinas y femeninas de Hofstadter para ver las definiciones utilizadas en este ejemplo).

Todos los procedimientos enlazados en un único letrec pueden referirse entre sí por su nombre, así como a los valores de las variables definidas anteriormente en el mismo letrec, pero es posible que no consulte valores definidos más adelante en el mismo letrec.

Una variante de let, el "let llamado" formulario, tiene un identificador después de la palabra clave let. Esto vincula las variables let con el argumento de un procedimiento cuyo nombre es el identificador dado y cuyo cuerpo es el cuerpo de la forma let. El cuerpo puede repetirse como se desee llamando al procedimiento. El let nombrado se usa ampliamente para implementar la iteración.

Ejemplo: un contador simple

()Deja bucle ()n 1) ()si ()n 10) '() ()cons n ()bucle ()+ n 1))))=== ()1 2 3 4 5 6 7 8 9 10)

Como cualquier procedimiento en Scheme, el procedimiento creado en el let nombrado es un objeto de primera clase.

Recurrencia de cola adecuada

Scheme tiene una construcción de iteración, do, pero es más idiomático en Scheme usar recursividad de cola para expresar la iteración. Se requieren implementaciones de Scheme que cumplan con los estándares para optimizar las llamadas de cola para admitir un número ilimitado de llamadas de cola activas (R5RS sec. 3.5), una propiedad que el informe de Scheme describe como recurrencia de cola adecuada, lo que lo hace seguro para que los programadores de Scheme escriban algoritmos iterativos utilizando estructuras recursivas, que a veces son más intuitivas. Los procedimientos recursivos de cola y la forma named let brindan soporte para la iteración usando la recursividad de cola.

; Construcción de una lista de plazas de 0 a 9:;; Nota: el bucle es simplemente un símbolo arbitrario utilizado como etiqueta. Cualquier símbolo lo hará.()definir ()list-of-squares n) ()Deja bucle ()i n) ()res '()))) ()si (). i 0) res ()bucle ()- i 1) ()cons ()* i i) res))))()list-of-squares 9)=== ()0 1 4 9 16 25 36 49 64 81)

Continuaciones de primera clase

Las continuaciones en Scheme son objetos de primera clase. Scheme proporciona el procedimiento call-with-current-continuation (también conocido como call/cc) para capturar la continuación actual empaquetándola como un procedimiento de escape vinculado a un formal argumento en un procedimiento proporcionado por el programador. (R5RS sec. 6.4) Las continuaciones de primera clase permiten al programador crear construcciones de control no locales, como iteradores, corrutinas y retroceso.

Las continuaciones se pueden usar para emular el comportamiento de declaraciones de retorno en lenguajes de programación imperativos. La siguiente función find-first, dada la función func y la lista lst, devuelve el primer elemento x en lst tal que (func x) devuelve verdadero.

()definir ()primera func lst) ()call-with-current-continuation ()lambda ()retorno inmediato) ()para cada ()lambda ()x) ()si ()func x) ()retorno inmediato x)) lst) #f))()primera ¿Integer? '()1/2 3/4 5.6 7 8/9 10 11)=== 7()primera ¿Nero? '()1 2 3 4)=== #f

El siguiente ejemplo, un rompecabezas tradicional para programadores, muestra que Scheme puede manejar continuaciones como objetos de primera clase, vinculándolas a variables y pasándolas como argumentos a procedimientos.

(). ()Yin ()lambda ()cc) ()pantalla "@") cc) ()call-with-current-continuation ()lambda ()c) c))) ()Yang ()lambda ()cc) ()pantalla "*) cc) ()call-with-current-continuation ()lambda ()c) c)))) ()Yin Yang)

Cuando se ejecuta, este código muestra una secuencia de conteo: @*@**@***@****@*****@******@******* @********...

Espacio de nombres compartido para procedimientos y variables

A diferencia de Common Lisp, todos los datos y procedimientos en Scheme comparten un espacio de nombres común, mientras que en Common Lisp las funciones y los datos tienen espacios de nombres separados, lo que hace posible que una función y una variable tengan el mismo nombre, y requieren una notación especial para referirse a una función como un valor. Esto a veces se conoce como "Lisp-1 vs. Lisp-2" distinción, refiriéndose al espacio de nombres unificado de Scheme y los espacios de nombres separados de Common Lisp.

En Scheme, las mismas primitivas que se usan para manipular y enlazar datos se pueden usar para enlazar procedimientos. No existe un equivalente de las primitivas defun y #' de Common Lisp.

; Variable bound to a number:()definir f 10)f=== 10; Mutación (alterando el valor consolidado)()¡Listo! f ()+ f f 6)f=== 26; Asignar un procedimiento a la misma variable:()¡Listo! f ()lambda ()n) ()+ n 12))()f 6)=== 18; Asignar el resultado de una expresión a la misma variable:()¡Listo! f ()f 1)f=== 13;; programación funcional:()aplicación + '()1 2 3 4 5 6)=== 21()¡Listo! f ()lambda ()n) ()+ n 100))()mapa f '()1 2 3)=== ()101 102 103)

Estándares de implementación

Esta subsección documenta las decisiones de diseño que se han tomado a lo largo de los años y que le han dado a Scheme un carácter particular, pero que no son el resultado directo del diseño original.

Torre numérica

Scheme especifica un conjunto comparativamente completo de tipos de datos numéricos, incluidos tipos complejos y racionales, que se conoce en Scheme como la torre numérica (R5RS sec. 6.2). El estándar los trata como abstracciones y no compromete al implementador con ninguna representación interna en particular.

Los números pueden tener la cualidad de la exactitud. Un número exacto sólo puede ser producido por una secuencia de operaciones exactas que involucren otros números exactos; por lo tanto, la inexactitud es contagiosa. El estándar especifica que cualquiera de las dos implementaciones debe producir resultados equivalentes para todas las operaciones que den como resultado números exactos.

El estándar R5RS especifica los procedimientos exact->inexact y inexact->exact que se pueden utilizar para cambiar la exactitud de un número. inexact->exact produce "el número exacto que es numéricamente más cercano al argumento". exact->inexact produce "el número inexacto que es numéricamente más cercano al argumento". El estándar R6RS omite estos procedimientos del informe principal, pero los especifica como procedimientos de compatibilidad con R5RS en la biblioteca estándar (rnrs r5rs (6)).

En el estándar R5RS, las implementaciones de Scheme no están obligadas a implementar la torre numérica completa, pero deben implementar "un subconjunto coherente con los propósitos de la implementación y el espíritu del lenguaje Scheme" (R5RS sec. 6.2.3). El nuevo estándar R6RS requiere la implementación de toda la torre, y "objetos enteros exactos y objetos de números racionales exactos de tamaño y precisión prácticamente ilimitados, e implementar ciertos procedimientos... para que siempre arrojen resultados exactos cuando se les dan argumentos exactos& #34; (R6RS sec. 3.4, sec. 11.7.1).

Ejemplo 1: aritmética exacta en una implementación que admite números complejos racionales.

; Suma de tres números reales racionales y dos números complejos racionales()definir x ()+ 1/3 1/4 -1/5 -1/3i 405/50+2/3i)x=== 509/60+1/3i;; Comprobar la exactitud.()¿Exacto? x)=== #

Ejemplo 2: Misma aritmética en una implementación que no admite ni exacta números racionales ni números complejos, pero acepta números reales en notación racional.

; Suma de cuatro números reales racionales()definir xr ()+ 1/3 1/4 -1/5 405/50); Suma de dos números reales racionales()definir xi ()+ -1/3 2/3)xr=== 8.483333333333xi=== 0.33333333333333333333333;; Comprobar la exactitud.()¿Exacto? xr)=== #f()¿Exacto? xi)=== #f

Ambas implementaciones cumplen con el estándar R5RS, pero la segunda no cumple con R6RS porque no implementa la torre numérica completa.

Evaluación retrasada

Scheme admite la evaluación diferida mediante el formulario delay y el procedimiento force.

()definir a 10)()definir eval-aplus2 ()demora ()+ a 2))()¡Listo! a 20)()fuerza eval-aplus2)=== 22()definir eval-aplus50 ()demora ()+ a 50))()Deja ()a 8) ()fuerza eval-aplus50)=== 70()¡Listo! a 100)()fuerza eval-aplus2)=== 22

El contexto léxico de la definición original de la promesa se conserva y su valor también se conserva después del primer uso de force. La promesa solo se evalúa una vez.

Estas primitivas, que producen o manejan valores conocidos como promesas, se pueden usar para implementar construcciones avanzadas de evaluación diferida, como secuencias.

En el estándar R6RS, ya no son primitivos, sino que se proporcionan como parte de la biblioteca de compatibilidad R5RS (rnrs r5rs (6)).

En R5RS, se ofrece una implementación sugerida de delay y force, implementando la promesa como un procedimiento sin argumentos (un thunk) y utilizando la memorización para garantizar que solo se evalúa una vez, independientemente del número de veces que se llame a force (R5RS sec. 6.4).

SRFI 41 permite la expresión de secuencias finitas e infinitas con una economía extraordinaria. Por ejemplo, esta es una definición de la secuencia de Fibonacci usando las funciones definidas en SRFI 41:

; Definir la secuencia Fibonacci:()definir fibs ()stream-cons 0 ()stream-cons 1 ()stream-map + fibs ()stream-cdr fibs)))); Computar el número cien en la secuencia:()stream-ref fibs 99)=== 218922995834555169026

Orden de evaluación de los argumentos del procedimiento

La mayoría de los Lisps especifican un orden de evaluación para los argumentos de los procedimientos. El esquema no lo hace. El orden de evaluación, incluido el orden en que se evalúa la expresión en la posición del operador, puede ser elegido por una implementación llamada por llamada, y la única restricción es que "el efecto de cualquier evaluación concurrente de las expresiones de operador y operando están restringidas para ser consistentes con algún orden secuencial de evaluación." (R5RS sec. 4.1.3)

()Deja ()ev ()lambda()n) ()pantalla "Evaluando") ()pantalla ()si ()¿Procedimiento? n) "procedimiento" n) ()newline) n)) ()ev +) ()ev 1) ()ev 2))=== 3
Evaluación 1Evaluación 2Procedimiento de evaluación

ev es un procedimiento que describe el argumento que se le pasa y luego devuelve el valor del argumento. A diferencia de otros Lisps, la aparición de una expresión en la posición de operador (el primer elemento) de una expresión de Scheme es bastante legal, siempre que el resultado de la expresión en la posición de operador sea un procedimiento.

Al llamar al procedimiento "+" para sumar 1 y 2, las expresiones (ev +), (ev 1) y (ev 2) pueden evaluarse en cualquier orden, siempre que el efecto no sea como si se evaluaran en paralelo. Por lo tanto, las siguientes tres líneas se pueden mostrar en cualquier orden mediante Scheme estándar cuando se ejecuta el código de ejemplo anterior, aunque el texto de una línea no se puede intercalar con otra porque violaría la restricción de evaluación secuencial.

Macros higiénicos

En el estándar R5RS y también en informes posteriores, la sintaxis de Scheme se puede ampliar fácilmente a través del sistema de macros. El estándar R5RS introdujo un poderoso sistema de macro higiénico que permite al programador agregar nuevas construcciones sintácticas al lenguaje usando un sublenguaje simple de coincidencia de patrones (R5RS sec 4.3). Antes de esto, el sistema macro higiénico había sido relegado a un apéndice del estándar R4RS, como un "nivel alto" junto con un sistema de "nivel bajo" sistema macro, los cuales fueron tratados como extensiones de Scheme en lugar de una parte esencial del lenguaje.

Las implementaciones del sistema macro higiénico, también llamado reglas de sintaxis, son necesarias para respetar el alcance léxico del resto del idioma. Esto está asegurado por reglas especiales de nomenclatura y alcance para la expansión de macros y evita errores de programación comunes que pueden ocurrir en los sistemas de macros de otros lenguajes de programación. R6RS especifica un sistema de transformación más sofisticado, syntax-case, que ha estado disponible como una extensión de lenguaje para R5RS Scheme durante algún tiempo.

; Definir una macro para implementar una variante de "si" con una multiexpresión;; verdadera rama y ninguna rama falsa.()define-syntax cuando ()sintaxis-reglas () ()cuando pred exp Gastos ...) ()si pred ()comenzar exp Gastos ...))))

Las invocaciones de macros y procedimientos tienen un gran parecido (ambos son expresiones s), pero se tratan de manera diferente. Cuando el compilador encuentra una expresión s en el programa, primero verifica si el símbolo está definido como una palabra clave sintáctica dentro del alcance léxico actual. Si es así, intenta expandir la macro, tratando los elementos en la cola de la expresión s como argumentos sin compilar código para evaluarlos, y este proceso se repite recursivamente hasta que no quedan invocaciones de macro. Si no es una palabra clave sintáctica, el compilador compila el código para evaluar los argumentos en la cola de la expresión s y luego para evaluar la variable representada por el símbolo en la cabeza de la expresión s y llamarla como un procedimiento con el Expresiones de cola evaluadas pasadas como argumentos reales.

La mayoría de las implementaciones de Scheme también proporcionan macrosistemas adicionales. Entre los más populares se encuentran los cierres sintácticos, macros de cambio de nombre explícito y define-macro, un sistema de macros no higiénico similar al sistema defmacro proporcionado en Common Lisp.

La incapacidad de especificar si una macro es o no higiénica es una de las deficiencias del sistema macro. Los modelos alternativos para la expansión, como los conjuntos de alcance, brindan una solución potencial.

Entornos y evaluación

Antes de R5RS, Scheme no tenía un equivalente estándar del procedimiento eval que es omnipresente en otros Lisps, aunque el primer Lambda Paper había descrito evaluate como " similar a la función LISP EVAL" y el primer Informe revisado en 1978 lo reemplazó con enclose, que tomaba dos argumentos. Los informes revisados segundo, tercero y cuarto omitieron cualquier equivalente de eval.

La razón de esta confusión es que en Scheme con su alcance léxico, el resultado de evaluar una expresión depende de dónde se evalúe. Por ejemplo, no está claro si el resultado de evaluar la siguiente expresión debe ser 5 o 6:

()Deja ()Nombre '+) ()Deja ()+ *) ()evaluación ()lista Nombre 2 3)))

Si se evalúa en el entorno externo, donde se define name, el resultado es la suma de los operandos. Si se evalúa en el ambiente interior, donde el símbolo "+" se ha vinculado al valor del procedimiento "*", el resultado es el producto de los dos operandos.

R5RS resuelve esta confusión especificando tres procedimientos que devuelven entornos y proporcionando un procedimiento eval que toma una expresión s y un entorno y evalúa la expresión en el entorno proporcionado. (R5RS sec. 6.5) R6RS amplía esto proporcionando un procedimiento llamado environment mediante el cual el programador puede especificar exactamente qué objetos importar al entorno de evaluación.

Con un esquema moderno (generalmente compatible con R5RS) para evaluar esta expresión, se necesita definir una función evaluate que puede verse así:

()definir ()evaluación expreso) ()eval expreso ()interacción-ambiente))

interaction-environment es el entorno global del intérprete.

Tratamiento de valores no booleanos en expresiones booleanas

En la mayoría de los dialectos de Lisp, incluido Common Lisp, por convención, el valor NIL se evalúa como falso en una expresión booleana. En Scheme, desde el estándar IEEE en 1991, todos los valores excepto #f, incluido el equivalente de NIL' en Scheme, que se escribe como '(), evalúa el valor verdadero en una expresión booleana. (R5RS sec. 6.3.1)

Donde la constante que representa el valor booleano de verdadero es T en la mayoría de Lisps, en Scheme es #t.

Disjunción de tipos de datos primitivos

En Scheme, los tipos de datos primitivos son disjuntos. Solo uno de los siguientes predicados puede ser verdadero para cualquier objeto Scheme: boolean?, pair?, symbol?, number?, char?, string?, vector?, port?, procedure?. (R5RS sec 3.2)

Dentro del tipo de datos numérico, por el contrario, los valores numéricos se superponen. Por ejemplo, un valor entero satisface todos los integer?, ration?, real?, complex? y number? predicados al mismo tiempo. (R5RS sec 6.2)

Predicados de equivalencia

Scheme tiene tres tipos diferentes de equivalencia entre objetos arbitrarios denotados por tres predicados de equivalencia diferentes, operadores relacionales para probar la igualdad, eq?, eqv? y igual?:

Las operaciones de equivalencia dependientes del tipo también existen en Scheme: string=? y string-ci=? comparan dos cadenas (esta última realiza una comparación independiente de mayúsculas y minúsculas); char=? y char-ci=? comparan caracteres; = compara números.

Comentarios

Hasta el estándar R5RS, el comentario estándar en Scheme era un punto y coma, lo que hace que el resto de la línea sea invisible para Scheme. Numerosas implementaciones han admitido convenciones alternativas que permiten que los comentarios se extiendan por más de una sola línea, y el estándar R6RS permite dos de ellas: una expresión s completa se puede convertir en un comentario (o "comentada") por precediéndolo de #; (introducido en SRFI 62) y un comentario de varias líneas o "comentario de bloque" puede producirse rodeando el texto con #| y |#.

Entrada/salida

La entrada y la salida del esquema se basan en el tipo de datos del puerto. (R5RS sec 6.6) R5RS define dos puertos predeterminados, accesibles con los procedimientos current-input-port y current-output-port, que corresponden a las nociones de Unix de entrada estándar y salida estándar. La mayoría de las implementaciones también proporcionan current-error-port. La redirección de entrada y salida estándar se admite en el estándar, mediante procedimientos estándar como with-input-from-file y with-output-to-file. La mayoría de las implementaciones brindan puertos de cadena con capacidades de redirección similares, lo que permite que muchas operaciones normales de entrada y salida se realicen en búferes de cadena en lugar de archivos, utilizando los procedimientos descritos en SRFI 6. El estándar R6RS especifica procedimientos de puerto mucho más sofisticados y capaces y muchos tipos nuevos de Puerto.

Los siguientes ejemplos están escritos en un estricto esquema R5RS.

Ejemplo 1: con salida predeterminada a (puerto de salida actual):

()Deja ()Hola. ()lambda() ()pantalla "Hola mundo") ()newline))) ()Hola.)

Ejemplo 2: como 1, pero usando un argumento de puerto opcional para generar procedimientos

()Deja ()Hola1 ()lambda ()p) ()pantalla "Hola mundo" p) ()newline p))) ()Hola1 ()actual-output-port))

Ejemplo 3: como 1, pero la salida se redirige a un archivo recién creado

; NB: with-output-to-file is an Optional procedure in R5RS()Deja ()Hola. ()lambda () ()pantalla "Hola mundo") ()newline))) ()con salida a archivo "helloworldoutputfile" Hola.)

Ejemplo 4: como 2, pero con un archivo explícito abierto y un puerto cerrado para enviar la salida al archivo

()Deja ()Hola1 ()lambda ()p) ()pantalla "Hola mundo" p) ()newline p)) ()producción-puerto ()open-output-file "helloworldoutputfile")) ()Hola1 producción-puerto) ()close-output-port producción-puerto)

Ejemplo 5: como 2, pero con el uso de llamada con archivo de salida para enviar la salida a un archivo.

()Deja ()Hola1 ()lambda ()p) ()pantalla "Hola mundo" p) ()newline p))) ()call-with-output-file "helloworldoutputfile" Hola1)

Se proporcionan procedimientos similares para la entrada. R5RS Scheme proporciona los predicados input-port? y output-port?. Para la entrada y salida de caracteres, write-char, read-char, peek-char y char-ready? están provistos. Para escribir y leer expresiones de Scheme, Scheme proporciona read y write. En una operación de lectura, el resultado devuelto es el objeto de fin de archivo si el puerto de entrada ha llegado al final del archivo, y esto se puede probar usando el predicado eof-object?.

Además del estándar, SRFI 28 define un procedimiento de formateo básico que se asemeja a la función format de Common Lisp, que le da su nombre.

Redefinición de procedimientos estándar

En Scheme, los procedimientos están vinculados a variables. En R5RS, el estándar de lenguaje exigía formalmente que los programas pudieran cambiar los enlaces variables de los procedimientos integrados, redefiniéndolos efectivamente. (R5RS "Cambios de idioma") Por ejemplo, uno puede extender + para aceptar cadenas además de números al redefinirlo:

()¡Listo! + ()Deja ()original+ +) ()lambda args ()aplicación ()si ()o ()null? args) ()¿Clase? ()coche args)) string-append original+) args)))()+ 1 2 3)=== 6()+ "1" "2" "3")=== "123"

En R6RS, todos los enlaces, incluidos los estándar, pertenecen a alguna biblioteca y todos los enlaces exportados son inmutables. (R6RS sec 7.1) Debido a esto, está prohibida la redefinición de procedimientos estándar por mutación. En cambio, es posible importar un procedimiento diferente bajo el nombre de uno estándar, que en efecto es similar a la redefinición.

Nomenclatura y convenciones de nombres

En el esquema estándar, los procedimientos que convierten de un tipo de datos a otro contienen la cadena de caracteres "->" en su nombre, los predicados terminan con un "?", y los procedimientos que cambian el valor de los datos ya asignados terminan con un "!". Los programadores de Scheme suelen seguir estas convenciones.

En contextos formales como los estándares del Esquema, la palabra "procedimiento" se usa con preferencia a "función" para referirse a una expresión lambda o procedimiento primitivo. En el uso normal, las palabras "procedimiento" y "función" se usan indistintamente. La aplicación del procedimiento a veces se denomina formalmente combinación.

Como en otros Lisps, el término "thunk" se utiliza en Scheme para referirse a un procedimiento sin argumentos. El término "recursión de cola adecuada" se refiere a la propiedad de todas las implementaciones de Scheme, que realizan la optimización de llamadas de cola para admitir un número indefinido de llamadas de cola activas.

La forma de los títulos de los documentos estándar desde R3RS, "Revisedn Report on the Algorithmic Language Scheme", es una referencia al título del documento estándar ALGOL 60, "Informe revisado sobre el lenguaje algorítmico Algol 60," La página Resumen de R3RS está estrechamente modelada en la página Resumen del Informe ALGOL 60.

Revisión de formularios y procedimientos estándar

El lenguaje se define formalmente en los estándares R5RS (1998) y R6RS (2007). Describen "formularios" estándar: palabras clave y sintaxis adjunta, que proporcionan la estructura de control del lenguaje y procedimientos estándar que realizan tareas comunes.

Formularios estándar

Esta tabla describe los formularios estándar en Scheme. Algunas formas aparecen en más de una fila porque no se pueden clasificar fácilmente en una sola función en el idioma.

Formularios marcados con "L" en esta tabla se clasifican como "biblioteca" formularios en el estándar y, a menudo, se implementan como macros utilizando formularios más fundamentales en la práctica, lo que hace que la tarea de implementación sea mucho más fácil que en otros lenguajes.

Formas estándar en el lenguaje R5RS Scheme
PropósitoFormas
Definicióndefinir
Construcciones vinculanteslambda, do (L), let (L), let* (L), letrec (L)
Evaluación condicionalsi, cond (L), caso (L), y (L), o (L)
Evaluación secuencial(*)
Iteraciónlambda, do (L), llamado let (L)
Extensión sintácticadefinir-sintax, let-syntax, letrec-syntax, sintaxis-rules (R5RS), sintaxis-case (R6RS)
Quotingquote('), unquote(,), quasiquote(`), unquote-splicing(,@)
Asignación¡Listo!
Evaluación retrasadademora (L)

Tenga en cuenta que begin se define como una sintaxis de biblioteca en R5RS, pero el expansor debe saberlo para lograr la funcionalidad de empalme. En R6RS ya no es una sintaxis de biblioteca.

Procedimientos estándar

Las siguientes dos tablas describen los procedimientos estándar en R5RS Scheme. R6RS es mucho más extenso y un resumen de este tipo no sería práctico.

Algunos procedimientos aparecen en más de una fila porque no se pueden clasificar fácilmente en una sola función en el idioma.

Procedimientos estándar en el idioma R5RS
PropósitoProcedimientos
Construcciónvector, máquina de hacer, lista de reproducción
Equivalencia predicaeq, eqv, igual?, string=?, string-ci=?, char=?, char-ci=?
Conversión de tipovector- título, lista- títulovector, número- títulos, cadena- título, sign- títulos, string- títulos, string- contactos, char- títulointeger, integer- títulochar, string- títulolist, list-
NúmerosVéase la tabla separada
Pendientesstring?, make-string, string-length, string-ref, string-set!, string=?, string-ci=?, string? ¿Terre-ci? string-ci贸n=?, string? string-ci titulada?, string=? string-ci plata=?, substring, string-append, string- títulolist, list- título, string-copy, string-fill!
Personajes¿Char, char=? ¿Char-ci? ¿Charlie? ¿Char-ci Príncipe? ¿Char-ci `=?, char-alphabetic?, char-numeric?, char-whitespace?, char-upper-case?, char-lower-case?, char-propinteger, integer-prop, char-upcase, char-downcase
Vectoresvector, vector, vector, vector, vector, vector-longitud, vector-ref, vector-set!, vector- espíritu, lista- título, vector-fill!
Símbolossign- título, cadena- títulosymbol, símbolo?
Parejas y listaspar?, cons, coche, cdr, set-car!, set-cdr!, null?, lista?, longitud, apéndice, inverso, list-tail, list-ref, memq. memv. member, assq, assv, assoc, list- títulovector, vector- confíalist, list- títulostring, string-
Problemas de identidad¿Boolean?, par?, ¿símbolo?, ¿número?, char?, vector?, port?, ¿procedimiento?
Continuacionescall-with-current-continuation (call/cc), values, call-with-values, dynamic-wind
Environmentseval, esquema-report-environment, null-environment, interaction-environment (opcional)
Input/outputdisplay, newline, read, write, read-char, write-char, peek-char, char-ready?, eof-object? open-input, open-output, close-input-port, close-output-port, input-port?, output-port?, current-input-port, current-output-port, call-with-input, call-with-output, with-input-from-file(opcional), with-output-to-file(opcional)
Interfaz del sistemacarga (opcional), transcripción-on (opcional), transcripción-off (opcional)
Evaluación retrasadafuerza
Programación funcionalprocedimiento?, aplicar, mapa, para cada
Booleans¿Boolean? no

Procedimientos de cadenas y caracteres que contienen "-ci" en sus nombres realizan comparaciones independientes de mayúsculas y minúsculas entre sus argumentos: las versiones en mayúsculas y minúsculas del mismo carácter se consideran iguales.

Procedimientos numéricos estándar en el lenguaje R5RS Scheme
PropósitoProcedimientos
Operadores aritméticos básicos+, -, *, /, abs, cociente, resto, modulo, gcd, lcm, expt, sqrt
Números racionalesnumerador, denominador, racional?, racionalizar
Aproximaciónsuelo, techo, poda, redondo
Exactitudinexacto, exacto, exacto, exacto, ¿exacto?
Inequalities, =
Problemas diversos¿Nero, negativo, positivo? ¿ extraño? ¿Incluso?
Máximo y mínimomax, min
Trigonometríapecado, porque, tan, asin, acos, atan
Exponencialesexp, log
Números complejosmake-rectangular, make-polar, real-part, imag-part, magnitud, ángulo, complejo?
Input-outputnúmero- título, cadena- título
Tipo predicados¿Integer, racional, real?, complejo?

Las implementaciones de - y / que toman más de dos argumentos están definidas pero son opcionales en R5RS.

Solicitudes de esquema para la implementación

Debido al minimalismo de Scheme, el estándar no define muchos procedimientos comunes y formas sintácticas. Para mantener el lenguaje central pequeño pero facilitar la estandarización de las extensiones, la comunidad de Scheme tiene una "Solicitud de implementación de Scheme" (SRFI) proceso por el cual las bibliotecas de extensión se definen a través de una discusión cuidadosa de las propuestas de extensión. Esto promueve la portabilidad del código. Muchos de los SRFI son compatibles con todas o la mayoría de las implementaciones del Esquema.

Las SRFI con soporte bastante amplio en diferentes implementaciones incluyen:

Implementaciones

El diseño elegante y minimalista ha convertido a Scheme en un objetivo popular para diseñadores de idiomas, aficionados y educadores, y debido a su pequeño tamaño, el de un intérprete típico, también es una opción popular para sistemas integrados y secuencias de comandos. Esto ha dado como resultado decenas de implementaciones, la mayoría de las cuales difieren tanto entre sí que trasladar programas de una implementación a otra es bastante difícil, y el pequeño tamaño del lenguaje estándar significa que escribir un programa útil de gran complejidad en estándar, Esquema portátil es casi imposible. El estándar R6RS especifica un lenguaje mucho más amplio, en un intento de ampliar su atractivo para los programadores.

Casi todas las implementaciones proporcionan un bucle tradicional de lectura, evaluación e impresión al estilo Lisp para el desarrollo y la depuración. Muchos también compilan programas Scheme en binario ejecutable. La compatibilidad con el código de Scheme incrustado en programas escritos en otros lenguajes también es común, ya que la relativa simplicidad de las implementaciones de Scheme lo convierte en una opción popular para agregar capacidades de secuencias de comandos a sistemas más grandes desarrollados en lenguajes como C. Los intérpretes de Gambit, Chicken y Bigloo Scheme compile Scheme en C, lo que hace que la incrustación sea particularmente fácil. Además, el compilador de Bigloo se puede configurar para generar código de bytes JVM y también cuenta con un generador de código de bytes experimental para.NET.

Algunas implementaciones admiten funciones adicionales. Por ejemplo, Kawa y JScheme brindan integración con clases Java, y los compiladores de Scheme a C a menudo facilitan el uso de bibliotecas externas escritas en C, hasta permitir la incrustación de código C real en el código fuente de Scheme. Otro ejemplo es Pvts, que ofrece un conjunto de herramientas visuales para apoyar el aprendizaje de Scheme.

Uso

Scheme es ampliamente utilizado por varias escuelas; en particular, una serie de cursos de introducción a la informática utilizan Scheme junto con el libro de texto Estructura e interpretación de programas informáticos (SICP). Durante los últimos 12 años, PLT ha ejecutado el proyecto ProgramByDesign (anteriormente TeachScheme!), que ha expuesto a cerca de 600 maestros de secundaria y miles de estudiantes de secundaria a la programación rudimentaria de Scheme. La antigua clase de introducción a la programación 6.001 del MIT se impartía en Scheme. Aunque la 6.001 ha sido reemplazada por cursos más modernos, SICP sigue impartiéndose en el MIT. Del mismo modo, la clase introductoria en UC Berkeley, CS 61A, hasta 2011 se enseñó completamente en Scheme, salvo desviaciones menores en Logo para demostrar el alcance dinámico. Hoy, al igual que el MIT, Berkeley ha reemplazado el plan de estudios con una versión más moderna que se enseña principalmente en Python 3, pero el plan de estudios actual todavía se basa en el plan de estudios antiguo y partes de la clase todavía se enseñan en Scheme.

El libro de texto Cómo diseñar programas de Matthias Felleisen, actualmente en Northeastern University, es utilizado por algunos institutos de educación superior para sus cursos de introducción a la informática. Tanto Northeastern University como Worcester Polytechnic Institute utilizan Scheme exclusivamente para sus cursos introductorios Fundamentos de la informática (CS2500) e Introducción al diseño de programas (CS1101), respectivamente. Rose-Hulman utiliza Scheme en su curso de conceptos de lenguaje de programación más avanzado. El curso básico de la Universidad de Brandeis, Estructura e interpretaciones de programas informáticos (COSI121b), también se imparte exclusivamente en Scheme por el científico informático teórico Harry Mairson. La clase introductoria de la Universidad de Indiana, C211, se imparte completamente en Scheme. Una versión del curso a su propio ritmo, CS 61AS, sigue utilizando Scheme. Los cursos de introducción a la informática en Yale y Grinnell College también se imparten en Scheme. Paradigmas de diseño de programación, un curso obligatorio para los estudiantes de posgrado en informática de la Universidad Northeastern, también utiliza ampliamente Scheme. El anterior curso introductorio de Ciencias de la Computación en la Universidad de Minnesota - Twin Cities, CSCI 1901, también usó Scheme como su idioma principal, seguido de un curso que introdujo a los estudiantes al lenguaje de programación Java; sin embargo, siguiendo el ejemplo del MIT, el departamento reemplazó 1901 con el CSCI 1133 basado en Python, mientras que la programación funcional se cubre en detalle en el curso del tercer semestre CSCI 2041. En la industria del software, Tata Consultancy Services, Asia's la firma de consultoría de software más grande, utiliza Scheme en su programa de capacitación de un mes para recién graduados universitarios.

El esquema también se utiliza para lo siguiente: