Scheme es un dialecto de la familia de lenguajes de programación Lisp . Scheme fue creado durante la década de 1970 en el Laboratorio de Ciencias de la Computación e Inteligencia Artificial del MIT (MIT AI Lab) y publicado por sus desarrolladores, Guy L. Steele y Gerald Jay Sussman , a través de una serie de memorandos ahora conocidos 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 , brindando un mayor soporte para la programación funcional y técnicas asociadas, como los algoritmos recursivos. También fue uno de los primeros lenguajes de programación que admitió continuaciones de primera clase . Tuvo una influencia significativa en el esfuerzo que condujo al desarrollo de Common Lisp . [2]
El lenguaje Scheme está estandarizado en el estándar oficial del Instituto de Ingenieros Eléctricos y Electrónicos (IEEE) [3] y en un estándar de facto llamado Informe n revisado sobre el esquema de lenguaje algorítmico (R n RS). Un estándar ampliamente implementado es R5RS (1998). [4] La norma del Esquema ratificada más recientemente es "R7RS-small" (2013). [5] El R6RS, más expansivo y modular, fue ratificado en 2007. [6] Ambos descienden del R5RS; El cronograma a continuación refleja el orden cronológico de ratificación.
Scheme comenzó en la década de 1970 como un intento de comprender el modelo Actor de Carl Hewitt , para cuyo propósito Steele y Sussman escribieron un "pequeño intérprete de Lisp" usando Maclisp y luego "añadieron mecanismos para crear actores y enviar mensajes". [7] Scheme se llamó originalmente "Schemer", en la tradición de otros lenguajes derivados de Lisp como Planner o Conniver . El nombre actual surgió del uso por parte de los autores del sistema operativo ITS , que limitaba los nombres de archivos a dos componentes de como máximo seis caracteres cada uno. Actualmente, "Schemer" se usa comúnmente para referirse a un programador de Scheme.
Un nuevo proceso de estandarización del lenguaje comenzó en el taller Scheme de 2003, con el objetivo de producir un estándar R6RS en 2006. Este proceso rompió con el enfoque anterior de unanimidad R n RS.
R6RS presenta 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 la R5.97RS. Una votación exitosa resultó en la ratificación de la nueva norma, anunciada el 28 de agosto de 2007. [6]
Actualmente, las versiones más recientes de varias implementaciones de Scheme [8] admiten el estándar R6RS. Existe una implementación de referencia portátil de las bibliotecas implícitamente escalonadas propuestas para R6RS, llamada psyntax, que se carga y arranca correctamente en varias implementaciones de Scheme más antiguas. [9]
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 del campo de objeto y las máscaras de bits de campo de objeto de esquema mutables, y ayudó al recolector de basura a saber qué hacer con los campos sin recorrer toda la lista de campos que están guardados en el RTD. RTD permite a los usuarios ampliar el RTD básico para crear un nuevo sistema de registro. [10]
R6RS introduce numerosos cambios significativos en el lenguaje. [11] El código fuente ahora está especificado en Unicode , y un gran subconjunto de caracteres Unicode ahora puede aparecer 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 y contienen procedimientos y formas sintácticas que anteriormente 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 han sido reemplazadas por una función de abstracción sintáctica más expresiva (syntax-case) que permite el uso de todo Scheme en el momento de la expansión macro. Ahora se requieren implementaciones compatibles para soportar la torre numérica completa de Scheme , y la semántica de los números se ha ampliado, principalmente en la dirección de soporte para el estándar IEEE 754 para representación numérica de punto flotante.
El estándar R6RS ha causado controversia porque algunos lo ven como un alejamiento de la filosofía minimalista. [12] [13] 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 ocasionales. [14] Se crearon dos grupos de trabajo para trabajar en estas dos nuevas versiones de Scheme. El sitio del Proceso de Informes del Esquema tiene enlaces a los estatutos de los grupos de trabajo, 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. [15] Una votación que ratificó este borrador se cerró el 20 de mayo de 2013, [16] y el informe final ha estado disponible desde el 6 de agosto de 2013, describiendo "el lenguaje 'pequeño' de ese esfuerzo: por lo tanto, no puede considerarse de forma aislada como el sucesor de R6RS". [5]
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 tanto, los programas de esquema constan de secuencias de listas anidadas. Las listas también son la estructura de datos principal 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 fácilmente fragmentos de código Scheme de forma dinámica.
Todos los dialectos Lisp comparten la dependencia de las listas como estructuras de datos. Scheme hereda un rico conjunto de primitivas de procesamiento de listas como consy de sus progenitores Lisp. Scheme utiliza variables escritas de forma estricta pero dinámica y admite procedimientos de primera clase . Por tanto, los procedimientos pueden asignarse como valores a variables o pasarse como argumentos a procedimientos.car
cdr
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 funciones se refieren al 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.
Scheme es un lenguaje muy simple, mucho más fácil de implementar que muchos otros lenguajes de poder expresivo comparable . [17] Esta facilidad es atribuible 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 pueden escribirse como macros que involucran formas más fundamentales, principalmente lambda. Como dice R5RS (§3.1): "La más fundamental de las construcciones de unión de variables es la expresión lambda, porque todas las demás construcciones de unión de variables se pueden explicar en términos de expresiones lambda". [4]
Ejemplo: una macro para implementar let
como una expresión que se utiliza lambda
para realizar las vinculaciones de variables.
( definir-sintaxis let ( reglas-sintaxis () (( let (( var expr ) ... ) cuerpo ... ) (( lambda ( var ... ) cuerpo ... ) expr ... ))))
Por lo tanto, usar let
como se definió anteriormente una implementación de Scheme reescribiría " (let ((a 1)(b 2)) (+ b a))
" como " ((lambda (a b) (+ b a)) 1 2)
", lo que reduce la tarea de la 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 más bien el resultado no deseado del proceso de diseño. "En realidad estábamos tratando de construir algo complicado y descubrimos, por casualidad, que habíamos diseñado accidentalmente 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 formalismo pequeño y simple— podría servir como núcleo de un lenguaje de programación potente y expresivo". [7]
Como la mayoría de los lenguajes de programación modernos y a diferencia de Lisps anteriores como Maclisp , Scheme tiene un alcance léxico: todas las posibles vinculaciones de variables 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 se puede llamar. Esto contrasta con el alcance dinámico que era característico de los primeros dialectos 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 los compiladores e intérpretes de la época. En esos Lisps, era perfectamente posible que una referencia a una variable libre dentro de un procedimiento hiciera referencia a enlaces bastante distintos externos al procedimiento, dependiendo del contexto de la llamada.
El impulso 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 Sussman sobre ALGOL . Sugirió que los mecanismos de alcance léxico similares a ALGOL ayudarían a lograr su objetivo inicial de implementar el modelo Actor de Hewitt en Lisp. [7]
Las ideas clave sobre cómo introducir el alcance léxico en un dialecto Lisp se popularizaron en el artículo Lambda de Sussman y Steele de 1975, "Scheme: An Interpreter for Extended Lambda Calculus", [18] donde adoptaron el concepto de cierre léxico (en la página 21). ), que había sido descrito en un Memorando de AI en 1970 por Joel Moses , quien atribuyó la idea a Peter J. Landin . [19]
La notación matemática de Alonzo Church , el cálculo lambda, ha inspirado el uso de "lambda" por parte de Lisp 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 . [7]
Un sistema lambda formal tiene axiomas y una regla de cálculo completa. Es útil para el análisis utilizando 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 λ. [20] 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 la necesidad de que los programadores consideren los detalles de implementación, porque puede usarse para imitar la evaluación de la máquina. Finalmente, el cálculo lambda creó una metateoría sustancial. [21]
La introducción del alcance léxico resolvió el problema haciendo 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 simples instancias de procedimientos sino como "control". modificadores de estructuras y entornos". [22] Introdujeron el estilo de paso de continuación junto con su primera descripción de Scheme en el primero de los documentos Lambda, y en artículos posteriores, procedieron a demostrar el poder bruto de este uso práctico del cálculo lambda.
Scheme hereda su estructura de bloques de lenguajes estructurados en bloques anteriores, particularmente ALGOL . En Scheme, los bloques se implementan mediante tres construcciones vinculantes : lety . Por ejemplo, la siguiente construcción crea un bloque en el que un símbolo llamado está vinculado al número 10:let*
letrec
var
( defina var "ganso" ) ;; Cualquier referencia a var aquí estará vinculada a "goose" ( let (( var 10 )) ;; las declaraciones van aquí. Cualquier referencia a var aquí estará vinculada a 10. ) ;; Cualquier referencia a var aquí estará vinculada a "ganso".
Los bloques se pueden anidar para crear estructuras de bloques arbitrariamente complejas según las necesidades del programador. El uso de 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 que los enlaces hagan referencia a variables definidas anteriormente en la misma construcción, por lo tanto:
( let* (( var1 10 ) ( var2 ( + var1 12 ))) ;; Pero la definición de var1 no podría referirse a var2 )
La otra variante, letrec
está diseñada para permitir que los procedimientos recursivos entre sí se vinculen entre sí.
;; Cálculo de las secuencias masculinas y femeninas de Hofstadter como lista de pares( definir ( hofstadter-hombre-mujer n ) ( letrec (( mujer ( lambda ( n ) ( if ( = n 0 ) 1 ( - n ( hombre ( mujer ( - n 1 ))))))) ( hombre ( lambda ( n ) ( if ( = n 0 ) 0 ( - n ( hembra ( masculino ( - n 1 ) ) )))))) ( let loop (( i 0 )) ( if ( > in ) ' () ( contras ( contras ( hembra i ) ( macho i )) ( bucle ( + i 1 ))))))) ( hofstadter-hombre-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 conocer las definiciones utilizadas en este ejemplo).
Todos los procedimientos enlazados en un solo letrec
pueden hacer referencia entre sí por su nombre, así como a valores de variables definidas anteriormente en el mismo letrec
, pero no pueden hacer referencia a valores definidos más adelante en el mismo letrec
.
Una variante de let
, la forma "named let", tiene un identificador después de la let
palabra clave. Esto vincula las variables let al argumento de un procedimiento cuyo nombre es el identificador dado y cuyo cuerpo es el cuerpo del formulario let. El cuerpo podrá repetirse según se desee llamando al procedimiento. El let nombrado se usa ampliamente para implementar la iteración.
Ejemplo: un contador simple
( let loop (( n 1 )) ( if ( > n 10 ) ' () ( contras n ( loop ( + 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.
Scheme tiene una construcción de iteración, do
pero es más idiomático en Scheme usar recursividad de cola para expresar iteración . Se requieren implementaciones de Scheme que cumplan con los estándares para optimizar las llamadas de cola a fin de admitir un número ilimitado de llamadas de cola activas (R5RS sec. 3.5) [4] , una propiedad que el informe de Scheme describe como recursividad de cola adecuada , lo que hace que sea seguro para los programadores de Scheme Escriba algoritmos iterativos utilizando estructuras recursivas, que a veces son más intuitivas. Los procedimientos recursivos de cola y el formulario con nombrelet
brindan soporte para la iteración utilizando la recursividad de cola.
;; Construyendo una lista de cuadrados del 0 al 9: ;; Nota: bucle es simplemente un símbolo arbitrario utilizado como etiqueta. Cualquier símbolo servirá.( definir ( lista de cuadrados n ) ( let loop (( i n ) ( res ' ())) ( if ( < i 0 ) res ( loop ( - i 1 ) ( contras ( * i i ) res )) ))) ( lista de cuadrados 9 ) ===> ( 0 1 4 9 16 25 36 49 64 81 )
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 argumento formal en un procedimiento proporcionado por el programador. (R5RS sec. 6.4) [4] Las continuaciones de primera clase permiten al programador crear construcciones de control no locales como iteradores , corrutinas y retrocesos .
Las continuaciones se pueden utilizar para emular el comportamiento de las 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
de lst
modo que (func x)
devuelve verdadero.
( definir ( buscar-primera función lst ) ( llamada-con-continuación-actual ( lambda ( retorno-inmediatamente ) ( para cada ( lambda ( x ) ( if ( func x ) ( retorno-inmediatamente x ))) lst ) # f ))) ( ¿encontrar el primer entero? ' ( 1/2 3/4 5.6 7 8/9 10 11 )) ===> 7 ( encontrar el primer cero? ' ( 1 2 3 4 )) ===> #f
El siguiente ejemplo, un rompecabezas de programador tradicional, muestra que Scheme puede manejar continuaciones como objetos de primera clase, vinculándolas a variables y pasándolas como argumentos a procedimientos.
( let* (( yin (( lambda ( cc ) ( display "@" ) cc ) ( llamada-con-continuación-actual ( lambda ( c ) c )))) ( yang (( lambda ( cc ) ( display "* " ) cc ) ( llamada con continuación actual ( lambda ( c ) c ))))) ( yin yang ))
Cuando se ejecuta, este código muestra una secuencia de conteo:@*@**@***@****@*****@******@*******@********...
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 un funcionar como un valor. Esto a veces se conoce como la distinción " Lisp-1 vs. Lisp-2 ", en referencia al espacio de nombres unificado de Scheme y los espacios de nombres separados de Common Lisp. [23]
En Scheme, las mismas primitivas que se utilizan para manipular y vincular datos se pueden utilizar para vincular procedimientos. No existe un equivalente de Common Lisp defun
y #'
primitivos.
;; Variable vinculada a un número: ( defina f 10 ) f ===> 10 ;; Mutación (alterando el valor ligado) ( set! f ( + f f 6 )) f ===> 26 ;; Asignando un procedimiento a la misma variable: ( set! f ( lambda ( n ) ( + n 12 ))) ( f 6 ) ===> 18 ;; Asignando el resultado de una expresión a la misma variable: ( set! f ( f 1 )) f ===> 13 ;; programación funcional: ( aplicar + ' ( 1 2 3 4 5 6 )) ===> 21 ( set! f ( lambda ( n ) ( + n 100 ))) ( mapa f ' ( 1 2 3 )) === > ( 101 102 103 )
Esta subsección documenta las decisiones de diseño que se han tomado a lo largo de los años y que han dado al Esquema un carácter particular, pero que no son el resultado directo del diseño original.
Scheme especifica un conjunto comparativamente completo de tipos de datos numéricos que incluyen tipos complejos y racionales , lo que se conoce en Scheme como la torre numérica (R5RS sec. 6.2 [4] ). El estándar los trata como abstracciones y no compromete al implementador a ninguna representación interna particular.
Los números pueden tener la cualidad de exactitud. Un número exacto sólo puede producirse mediante una secuencia de operaciones exactas que impliquen otros números exactos; por tanto, la inexactitud es contagiosa. El estándar especifica que dos implementaciones cualesquiera deben producir resultados equivalentes para todas las operaciones que den como resultado números exactos.
El estándar R5RS especifica procedimientos exact->inexact
que inexact->exact
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 R5RS en la biblioteca estándar (rnrs r5rs (6)).
En el estándar R5RS, las implementaciones de Scheme no están obligadas a implementar toda la torre numérica, pero deben implementar "un subconjunto coherente tanto con los propósitos de la implementación como con el espíritu del lenguaje Scheme" (R5RS sec. 6.2.3). [4] 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 devuelvan resultados exactos cuando se les dan argumentos exactos". " (R6RS sec. 3.4, sec. 11.7.1). [6]
Ejemplo 1: aritmética exacta en una implementación que admite números complejos racionales exactos.
;; Suma de tres números reales racionales y dos números complejos racionales ( defina x ( + 1/3 1/4 -1/5 -1/3i 405/50+2/3i )) x ===> 509/60+1/ 3i ;; Verifique la exactitud. ( ¿exacto? x ) ===> #t
Ejemplo 2: La misma aritmética en una implementación que no admite números racionales exactos ni números complejos, pero acepta números reales en notación racional.
;; Suma de cuatro números reales racionales ( defina xr ( + 1/3 1/4 -1/5 405/50 )) ;; Suma de dos números reales racionales ( definir xi ( + -1/3 2/3 )) xr ===> 8.48333333333333 xi ===> 0.333333333333333 ;; Verifique 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.
El esquema apoya la evaluación retrasada a través del delay
formulario y el procedimiento force
.
( define un 10 ) ( define eval-aplus2 ( retraso ( + a 2 ))) ( set! a 20 ) ( fuerza eval-aplus2 ) ===> 22 ( define eval-aplus50 ( retraso ( + a 50 ))) ( let (( a 8 )) ( forzar eval-aplus50 )) ===> 70 ( set! a 100 ) ( forzar eval-aplus2 ) ===> 22
Se conserva el contexto léxico de la definición original de promesa, y su valor también se conserva después del primer uso de force
. La promesa sólo se evalúa una vez.
Estas primitivas, que producen o manejan valores conocidos como promesas , se pueden utilizar para implementar construcciones de evaluación diferidas avanzadas , como secuencias . [24]
En el estándar R6RS, estos ya no son primitivos, sino que se proporcionan como parte de la biblioteca de compatibilidad R5RS (rnrs r5rs (6)).
En R5RS, se proporciona una implementación sugerida de delay
y force
, implementando la promesa como un procedimiento sin argumentos (un procesador ) y usando la memorización para garantizar que solo se evalúe una vez, independientemente del número de veces force
que se llame (R5RS sec. 6.4 ). [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: [24]
;; Defina la secuencia de Fibonacci: ( defina mentiras ( stream-cons 0 ( stream-cons 1 ( stream-map + fibs ( stream-cdr fibs ))))) ;; Calcule el centésimo número en la secuencia: ( stream-ref fibs 99 ) ===> 218922995834555169026
La mayoría de Lisps especifican un orden de evaluación para los argumentos de los procedimientos. El esquema no. 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 del operador y Las expresiones de operandos están obligadas a ser coherentes con algún orden secuencial de evaluación". (R5RS sección 4.1.3) [4]
( let (( ev ( lambda ( n ) ( mostrar "Evaluando " ) ( mostrar ( if ( procedimiento? n ) "procedimiento" n )) ( nueva línea ) n ))) (( ev + ) ( ev 1 ) ( ev 2 ))) ===> 3
Evaluación 1 Evaluación 2 Procedimiento 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 del 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 del 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 y cuando el efecto no sea como si fueran evaluadas en paralelo. . Por lo tanto, las siguientes tres líneas pueden mostrarse en cualquier orden según el esquema estándar cuando se ejecuta el código de ejemplo anterior, aunque el texto de una línea no puede intercalarse con otra porque eso violaría la restricción de evaluación secuencial.
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 potente sistema macro higiénico que permite al programador agregar nuevas construcciones sintácticas al lenguaje utilizando un sublenguaje de coincidencia de patrones simple (R5RS sec 4.3). [4] Antes de esto, el macrosistema higiénico había sido relegado a un apéndice del estándar R4RS, como un sistema de "alto nivel" junto con un macrosistema de "bajo nivel", los cuales fueron tratados como extensiones de Scheme en lugar de un parte esencial de la lengua. [25]
Se requieren implementaciones del macrosistema higiénico, también llamado syntax-rules
, para respetar el alcance léxico del resto del lenguaje. Esto está garantizado 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 "if" con una multiexpresión ;; rama verdadera y ninguna rama falsa. ( definir-sintaxis cuando ( reglas-sintaxis () (( cuando pred exp exps ... ) ( si pred ( comenzar exp exps ... )))))
Las invocaciones de macros y procedimientos guardan un gran parecido (ambas 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 al final de la expresión s como argumentos sin compilar código para evaluarlos, y este proceso se repite de forma recursiva hasta que no queden invocaciones de macro. Si no es una palabra clave sintáctica, el compilador compila código para evaluar los argumentos al final de la expresión s y luego evaluar la variable representada por el símbolo al principio de la expresión s y llamarla como un procedimiento con la expresiones de cola evaluadas que se le pasan como argumentos.
La mayoría de las implementaciones de Scheme también proporcionan macrosistemas adicionales. Entre los más populares se encuentran los cierres sintácticos , el cambio de nombre explícito de macros y define-macro
un sistema de macros no higiénico similar al defmacro
sistema proporcionado en Common Lisp .
La incapacidad de especificar si una macro es higiénica o no es una de las deficiencias del sistema macro. Los modelos alternativos de expansión, como los conjuntos de alcance, proporcionan una solución potencial. [26]
Antes de R5RS, Scheme no tenía un equivalente estándar del eval
procedimiento que es omnipresente en otros Lisps, aunque el primer documento Lambda lo describió evaluate
como "similar a la función LISP EVAL" [18] y el primer informe revisado en 1978 lo reemplazó con enclose
, que Tomó 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úa. Por ejemplo, no está claro si el resultado de evaluar la siguiente expresión debe ser 5 o 6: [27]
( let (( nombre '+ )) ( let (( + * )) ( evaluar ( lista nombre 2 3 ))))
Si se evalúa en el entorno externo, donde name
está definido, el resultado es la suma de los operandos. Si se evalúa en el entorno interno, donde el símbolo "+" ha sido 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) [4] 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, es necesario definir una función evaluate
que puede verse así:
( definir ( evaluar expr ) ( eval expr ( entorno de interacción )))
interaction-environment
es el entorno global del intérprete.
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, [3] todos los valores excepto #f
, incluido NIL
el equivalente de Scheme que se escribe como '()
, se evalúan como el valor verdadero en una expresión booleana. (R5RS sección 6.3.1) [4]
Mientras que la constante que representa el valor booleano de verdadero es T
en la mayoría de Lisps, en Scheme es #t
.
En Scheme, los tipos de datos primitivos están separados. Sólo uno de los siguientes predicados puede ser verdadero para cualquier objeto Scheme: boolean?
, pair?
, symbol?
, number?
, char?
, string?
, vector?
, port?
, procedure?
. (R5RS seg 3.2) [4]
En el tipo de datos numérico, por el contrario, los valores numéricos se superponen. Por ejemplo, un valor entero satisface todos los predicados , , y integer?
al rational?
mismo real?
tiempo . (R5RS seg. 6.2) [4]complex?
number?
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 equal?
:
eq?
se evalúa #f
a menos que sus parámetros representen el mismo objeto de datos en la memoria;eqv?
es generalmente lo mismo eq?
pero trata los objetos primitivos (por ejemplo, caracteres y números) especialmente para que los números que representan el mismo valor sean eqv?
incluso si no se refieren al mismo objeto;equal?
compara estructuras de datos como listas, vectores y cadenas para determinar si tienen estructura y eqv?
contenidos congruentes. (R5RS sec. 6.1) [4]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=?
comparar personajes; =
compara números. [4]
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 a más de una línea, y el estándar R6RS permite dos de ellas: una expresión s completa puede convertirse en un comentario (o "comentarse") precediéndola con #;
(introducido en SRFI 62 [28] ) y se puede producir un comentario de varias líneas o "comentario de bloque" rodeando el texto con #|
y |#
.
La entrada y salida del esquema se basan en el tipo de datos del puerto . (R5RS sec 6.6) [4] R5RS define dos puertos predeterminados, accesibles con los procedimientos current-input-port
y current-output-port
, que corresponden a las nociones 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 está respaldada por el estándar mediante procedimientos estándar como with-input-from-file
y with-output-to-file
. La mayoría de las implementaciones proporcionan 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 procedimientos descritos en SRFI 6. [29] El estándar R6RS especifica procedimientos de puerto mucho más sofisticados y capaces y muchos nuevos tipos de puerto.
Los siguientes ejemplos están escritos en estricto esquema R5RS.
Ejemplo 1: con la salida predeterminada en (puerto-salida-actual):
( let (( hola0 ( lambda () ( mostrar "Hola mundo" ) ( nueva línea )))) ( hola0 ))
Ejemplo 2: como 1, pero usando el argumento de puerto opcional para generar procedimientos
( let (( hola1 ( lambda ( p ) ( mostrar "Hola mundo" p ) ( nueva línea p )))) ( hola1 ( puerto de salida actual )))
Ejemplo 3: como 1, pero la salida se redirige a un archivo recién creado
;; NB: with-output-to-file es un procedimiento opcional en R5RS ( let (( hola0 ( lambda () ( mostrar "Hola mundo" ) ( nueva línea )))) ( with-output-to-file "helloworldoutputfile" hello0 ) )
Ejemplo 4: Como 2, pero con el archivo abierto explícito y el puerto cerrado para enviar la salida al archivo
( let (( hola1 ( lambda ( p ) ( mostrar "Hola mundo" p ) ( nueva línea p ))) ( puerto de salida ( abrir archivo de salida "helloworldoutputfile " ))) ( puerto de salida hola1 ) ( cerrar salida -puerto puerto-salida ))
Ejemplo 5: Como 2, pero usando call-with-output-file para enviar resultados a un archivo.
( let (( hola1 ( lambda ( p ) ( mostrar "Hola mundo" p ) ( nueva línea p )))) ( llamada-con-archivo-de-salida "helloworldoutputfile" hola1 ))
Se proporcionan procedimientos similares para la entrada. El esquema R5RS proporciona los predicados input-port?
y output-port?
. Para la entrada y salida de caracteres, write-char
se proporcionan read-char
, peek-char
y . char-ready?
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?
.
Con el estándar, SRFI 28 también define un procedimiento de formateo básico que se asemeja a la función de Common Lisp format
, del cual recibe su nombre. [30]
En Scheme, los procedimientos están vinculados a variables. En R5RS, el estándar del lenguaje exigía formalmente que los programas pudieran cambiar los enlaces variables de los procedimientos integrados, redefiniéndolos efectivamente. (R5RS "Cambios de idioma") [4] Por ejemplo, +
se puede ampliar para aceptar cadenas y números redefiniéndolo:
( 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"
En R6RS, todos los enlaces, incluidos los estándar, pertenecen a alguna biblioteca y todos los enlaces exportados son inmutables. (R6RS sec 7.1) [6] Debido a esto, la redefinición de procedimientos estándar mediante mutación está prohibida. En cambio, es posible importar un procedimiento diferente bajo el nombre de estándar, lo que en realidad es similar a una redefinición.
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 "?" y los procedimientos que cambian el valor de datos ya asignados terminan con "!". Los programadores de Scheme suelen seguir estas convenciones.
En contextos formales como los estándares Scheme, la palabra "procedimiento" se usa preferentemente a "función" para referirse a una expresión lambda o un procedimiento primitivo. En el uso normal, las palabras "procedimiento" y "función" se utilizan 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 "recursividad de cola adecuada" se refiere a la propiedad de todas las implementaciones de Scheme de que realizan una 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, " Informe n revisado sobre el esquema de lenguaje algorítmico", es una referencia al título del documento estándar ALGOL 60 , "Informe revisado sobre el lenguaje algorítmico Algol 60", la página de resumen de R3RS está estrechamente modelado en la página Resumen del Informe ALGOL 60. [31] [32]
El lenguaje está definido formalmente en los estándares R5RS (1998) [4] y R6RS (2007). [6] Describen "formas" estándar: palabras clave y la sintaxis que las acompaña, que proporcionan la estructura de control del lenguaje, y procedimientos estándar que realizan tareas comunes.
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.
Los formularios marcados con "L" en esta tabla se clasifican como formularios de "biblioteca" derivados en el estándar y, a menudo, se implementan como macros utilizando formas 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.
Si bien begin
se define como una sintaxis de biblioteca en R5RS, el expansor debe conocerla para lograr la función de empalme. En R6RS ya no es una sintaxis de biblioteca.
Las siguientes dos tablas describen los procedimientos estándar en el esquema R5RS. 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 lenguaje.
Los procedimientos de cadenas y caracteres que contienen "-ci" en sus nombres realizan comparaciones independientes entre mayúsculas y minúsculas entre sus argumentos: las versiones en mayúsculas y minúsculas del mismo carácter se consideran iguales.
Las implementaciones de - y / que toman más de dos argumentos se definen pero se dejan opcionales en R5RS.
Debido al minimalismo de Scheme, el estándar no define muchos procedimientos y formas sintácticas comunes. Para mantener el lenguaje central pequeño pero facilitar la estandarización de las extensiones, la comunidad Scheme tiene un proceso de "Solicitud de implementación de esquema" (SRFI) mediante el cual las bibliotecas de extensiones se definen mediante una discusión cuidadosa de las propuestas de extensión. Esto promueve la portabilidad del código. Muchas de las SRFI cuentan con el respaldo de todas o la mayoría de las implementaciones del Esquema.
Las SRFI con un apoyo bastante amplio en diferentes implementaciones incluyen: [33]
El diseño elegante y minimalista ha convertido a Scheme en un objetivo popular para diseñadores de lenguajes, 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 resultado en decenas de implementaciones, [34] la mayoría de las cuales difieren tanto entre sí que portar 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 un esquema estándar y portátil es casi imposible. [14] 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 de lectura, evaluación e impresión de estilo Lisp tradicional para el desarrollo y la depuración. Muchos también compilan programas Scheme en binario ejecutable. También es común el soporte para incrustar código Scheme en programas escritos en otros lenguajes, ya que la relativa simplicidad de las implementaciones de Scheme lo convierte en una opción popular para agregar capacidades de scripting a sistemas más grandes desarrollados en lenguajes como C. Los intérpretes de Scheme Gambit , Chicken y Bigloo compilan Scheme en C, lo que facilita mucho la incrustación. Además, el compilador de Bigloo se puede configurar para generar código de bytes para la máquina virtual Java (JVM) y tiene un generador de código de bytes experimental para .NET .
Algunas implementaciones admiten funciones adicionales. Por ejemplo, Kawa y JScheme proporcionan integración con clases de 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 en el código fuente de Scheme. Otro ejemplo es Pvts, que ofrece un conjunto de herramientas visuales que respaldan el aprendizaje de Scheme.
El esquema es ampliamente utilizado por varias [35] escuelas; en particular, varios 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). [36] Durante los últimos 12 años, PLT ha ejecutado el proyecto ProgramByDesign (anteriormente TeachScheme!), que ha expuesto a cerca de 600 profesores de secundaria y miles de estudiantes de secundaria a la programación Scheme rudimentaria. La antigua clase de introducción a la programación del MIT 6.001 se impartía en Scheme, [37] Aunque 6.001 ha sido reemplazada por cursos más modernos, SICP continúa impartiéndose en el MIT. [38] Asimismo, la clase introductoria en UC Berkeley , CS 61A, se impartió hasta 2011 íntegramente en Scheme, salvo pequeñas desviaciones hacia Logo para demostrar el alcance dinámico. Hoy en día, al igual que el MIT, Berkeley ha reemplazado el programa de estudios con una versión más moderna que se enseña principalmente en Python 3 , pero el programa de estudios actual todavía se basa en el plan de estudios antiguo y partes de la clase todavía se imparten en Scheme. [39]
El libro de texto Cómo diseñar programas de Matthias Felleisen, actualmente en la Universidad Northeastern, es utilizado por algunos institutos de educación superior para sus cursos de introducción a la informática. Tanto la Universidad Northeastern como el Instituto Politécnico de Worcester utilizan Scheme exclusivamente para sus cursos introductorios Fundamentos de Ciencias de la Computación (CS2500) e Introducción al diseño de programas (CS1101), respectivamente. [40] [41] Rose-Hulman utiliza Scheme en su curso más avanzado de Conceptos de lenguaje de programación. [42] El curso básico de la Universidad Brandeis , Estructura e interpretaciones de programas informáticos (COSI121b), también se imparte exclusivamente en Scheme por el informático teórico Harry Mairson . [43] La clase introductoria de la Universidad de Indiana , C211, se imparte íntegramente en Scheme. Una versión del curso a su propio ritmo, CS 61AS, continúa utilizando Scheme. [44] Los cursos de introducción a la informática en Yale y Grinnell College también se imparten en Scheme. [45] Paradigmas de diseño de programación, [46] un curso obligatorio para los estudiantes de posgrado en informática de la Northeastern University , también utiliza ampliamente Scheme. El antiguo curso de introducción a la informática en la Universidad de Minnesota - Twin Cities, CSCI 1901, también utilizó Scheme como idioma principal, seguido de un curso que introdujo a los estudiantes al lenguaje Java; [47] sin embargo, siguiendo el ejemplo del MIT, el departamento reemplazó 1901 con el CSCI 1133 basado en Python, [48] mientras que la programación funcional se cubre en detalle en el curso de tercer semestre CSCI 2041. [49]
El esquema también se utilizó/fue utilizado para lo siguiente:
{{cite web}}
: Mantenimiento CS1: varios nombres: lista de autores ( enlace )Una metáfora útil para la diferencia entre FUNCIÓN y CITA en LISP es pensar en QUOTE como una cubierta porosa o abierta de la función ya que las variables libres escapan al entorno actual. FUNCIÓN actúa como un revestimiento cerrado o no poroso (de ahí el término "cierre" utilizado por Landin). Por lo tanto, hablamos de expresiones Lambda "abiertas" (las funciones en LISP suelen ser expresiones Lambda) y expresiones Lambda "cerradas". [...] Mi interés por el problema medioambiental comenzó mientras Landin, que tenía un profundo conocimiento del problema, visitó el MIT durante 1966-67. Luego me di cuenta de la correspondencia entre las listas FUNARG que son los resultados de la evaluación de expresiones Lambda "cerradas" en
LISP
y
los cierres Lambda de
ISWIM .