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 CSAIL) 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 , dando un soporte más fuerte para la programación funcional y técnicas asociadas como algoritmos recursivos. También fue uno de los primeros lenguajes de programación en soportar 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 revisado sobre el lenguaje algorítmico Scheme (R n RS). Un estándar ampliamente implementado es el R5RS (1998). [4] El estándar de Scheme ratificado más recientemente es el "R7RS-small" (2013). [5] El R6RS, más expansivo y modular, fue ratificado en 2007. [6] Ambos descienden del R5RS; la cronología 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 de actores de Carl Hewitt , para lo cual 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 resultó del uso por parte de los autores del sistema operativo ITS , que limitaba los nombres de archivo a dos componentes de seis caracteres cada uno como máximo. Actualmente, "Schemer" se usa comúnmente para referirse a un programador de Scheme.
En el taller Scheme de 2003 se inició un nuevo proceso de estandarización del lenguaje, con el objetivo de producir un estándar R6RS en 2006. Este proceso rompió con el enfoque anterior de unanimidad de R n RS.
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 la R5.97RS. Una votación exitosa dio como resultado la ratificación del nuevo estándar, anunciado el 28 de agosto de 2007. [6]
Actualmente, las versiones más nuevas de varias implementaciones de Scheme [8] son compatibles con el estándar R6RS. Existe una implementación de referencia portátil de las bibliotecas de fases implícitas propuestas para R6RS, llamada psyntax, que se carga y se inicia de manera adecuada 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 se utiliza un RTD, la representación del tipo de registro puede mostrar la disposición de la memoria. También calcula la máscara de bits del campo de objeto y las máscaras de bits del campo de objeto de Scheme mutables, y ayuda al recolector de basura 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 registros. [10]
R6RS introduce numerosos cambios significativos en el lenguaje. [11] El código fuente ahora se especifica en Unicode , y un gran subconjunto de caracteres Unicode ahora puede aparecer en símbolos e identificadores de Scheme , y hay otros cambios menores en las reglas léxicas. Los datos de caracteres también se especifican ahora en Unicode. Muchos procedimientos estándar se han trasladado a las nuevas bibliotecas estándar, que en sí mismas forman una gran expansión del estándar, que contiene 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 se han reemplazado 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 de macros. Ahora se requieren implementaciones compatibles para soportar la torre numérica completa de Scheme , y la semántica de los números se ha expandido, principalmente en la dirección del soporte para el estándar IEEE 754 para la 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 lenguaje de programación grande y moderno para programadores; y una versión pequeña, un subconjunto de la versión grande que conserva el minimalismo elogiado por los educadores e implementadores ocasionales. [14] Se crearon dos grupos de trabajo para trabajar en estas dos nuevas versiones de Scheme. El sitio Scheme Reports Process tiene enlaces a los estatutos de los grupos de trabajo, discusiones públicas y sistema de seguimiento de problemas.
El noveno borrador de R7RS (lenguaje reducido) se puso a disposición el 15 de abril de 2013. [15] La votación para ratificar este borrador se cerró el 20 de mayo de 2013, [16] y el informe final está disponible desde el 6 de agosto de 2013, describiendo "el lenguaje 'reducido' de ese esfuerzo: por lo tanto, no puede considerarse de manera 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 s-expresions , listas entre paréntesis en las que un operador de prefijo es seguido por sus argumentos. Los programas Scheme, por lo tanto, consisten en secuencias de listas anidadas. Las listas también son la principal estructura de datos en Scheme, lo que lleva a una equivalencia cercana 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.
La dependencia de las listas como estructuras de datos es compartida por todos los dialectos de Lisp. Scheme hereda un amplio conjunto de primitivas de procesamiento de listascons , como car
ycdr
de sus progenitores de Lisp. Scheme utiliza variables tipificadas de forma estricta pero dinámica y admite procedimientos de primera clase . Por lo tanto, los procedimientos se pueden asignar como valores a variables o pasar como argumentos a procedimientos.
Esta sección se centra principalmente en las características innovadoras del lenguaje, incluidas aquellas que distinguen a Scheme de otros lenguajes Lisp. 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, se utiliza la notación "===> resultado" para indicar el resultado de evaluar la expresión en la línea inmediatamente anterior. Esta es la misma convención que se utiliza 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 Scheme R5RS, 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 enlace de variables es la expresión lambda, porque todas las demás construcciones de enlace de variables pueden explicarse en términos de expresiones lambda". [4]
Ejemplo: una macro para implementar let
como expresión que se utiliza lambda
para realizar las vinculaciones de variables.
( define-sintaxis let ( reglas-sintaxis () (( let (( var expr ) ... ) cuerpo ... ) (( lambda ( var ... ) cuerpo ... ) expr ... ))))
Por lo tanto, al utilizar let
la definición anterior, 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 señalaron 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) podía servir como núcleo de un lenguaje de programación potente y expresivo". [7]
Al igual que la mayoría de los lenguajes de programación modernos y a diferencia de los Lisp 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 puede ser llamada. 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 los compiladores e intérpretes de la época. En esos Lisp, era perfectamente posible que una referencia a una variable libre dentro de un procedimiento hiciera referencia a vinculaciones bastante distintas externas 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 los años 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 de 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 1975 de Sussman y Steele, "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 AI Memo 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" en Lisp como palabra clave para introducir un procedimiento, así como el desarrollo de técnicas de programación funcional que implican el uso de funciones de orden superior en Lisp. Pero los primeros Lisp no eran expresiones adecuadas del cálculo lambda debido a su tratamiento de las 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 matemática y herramientas. 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 lógica matemática poderosa. Segundo, 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 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 podía utilizarse para derivar de manera elegante toda la semántica imperativa y declarativa de otros lenguajes de programación, incluidos ALGOL y Fortran , y el alcance dinámico de otros lenguajes Lisp, utilizando expresiones lambda no como simples instancias de procedimientos sino como "estructuras de control y modificadores del entorno". [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 documentos posteriores, procedieron a demostrar el poder puro de este uso práctico del cálculo lambda.
Scheme hereda su estructura de bloques de lenguajes estructurados en bloques anteriores, en particular ALGOL . En Scheme, los bloques se implementan mediante tres construcciones de enlace : let, let*
y letrec
. Por ejemplo, la siguiente construcción crea un bloque en el que un símbolo llamado var
está enlazado al número 10:
( define var "goose" ) ;; 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 "goose"
Los bloques se pueden anidar para crear estructuras de bloques de cualquier complejidad según las necesidades del programador. El uso de la estructuración de bloques para crear enlaces locales reduce el riesgo de colisión de espacios de nombres que, de lo contrario, puede ocurrir.
Una variante de let
, let*
, permite que los enlaces hagan referencia a variables definidas anteriormente en la misma construcción, de este modo:
( 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 procedimientos mutuamente recursivos se vinculen entre sí.
;; Cálculo de las secuencias masculina y femenina de Hofstadter como una lista de pares( define ( hofstadter-hombre-mujer n ) ( letrec (( mujer ( lambda ( n ) ( si ( = n 0 ) 1 ( - n ( hombre ( mujer ( - n 1 ))))))) ( hombre ( lambda ( n ) ( si ( = n 0 ) 0 ( - n ( mujer ( hombre ( - n 1 )))))))) ( let loop (( i 0 )) ( si ( > i n ) ' () ( cons ( cons ( mujer i ) ( hombre i )) ( loop ( + i 1 ))))))) ( hofstadter-hombre-mujer 8 ) ===> (( 1 . 0 ) ( 1 . 0 ) ( 2 . 1 ) ( 2 . 2 ) ( 3 . 2 ) ( 3 . 3 ) ( 4 . 4 ) ( 5 . 4 ) ( 5 . 5 ) )
(Véase las secuencias masculina y femenina de Hofstadter para las definiciones utilizadas en este ejemplo).
Todos los procedimientos enlazados en un solo procedimiento letrec
pueden referirse entre sí por su nombre, así como a valores de variables definidas anteriormente en el mismo procedimiento letrec
, pero no pueden referirse a valores definidos posteriormente en el mismo procedimiento letrec
.
Una variante de let
, la forma "let con nombre", 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 de la forma let. El cuerpo puede repetirse como se desee llamando al procedimiento. La forma let con nombre se usa ampliamente para implementar la iteración.
Ejemplo: un contador simple
( let loop (( n 1 )) ( if ( > n 10 ) ' () ( cons 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 la recursión de cola para expresar la iteración . Se requieren implementaciones de Scheme que cumplan con los estándares para optimizar las llamadas de cola de modo de admitir una cantidad ilimitada de llamadas de cola activas (R5RS sec. 3.5) [4] —una propiedad que el informe de Scheme describe como recursión de cola adecuada— , lo que hace que sea seguro para los programadores de Scheme escribir algoritmos iterativos utilizando estructuras recursivas, que a veces son más intuitivas. Los procedimientos recursivos de cola y la forma nombradalet
brindan soporte para la iteración utilizando la recursión de cola.
;; Creando una lista de cuadrados del 0 al 9: ;; Nota: loop es simplemente un símbolo arbitrario usado como etiqueta. Cualquier símbolo servirá.( define ( lista-de-cuadrados n ) ( deja bucle (( i n ) ( res ' ())) ( si ( < i 0 ) res ( bucle ( - i 1 ) ( cons ( * 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 retroceso .
Las continuaciones se pueden utilizar para emular el comportamiento de las instrucciones de retorno en lenguajes de programación imperativos. La siguiente función find-first
, dadas function func
y list lst
, devuelve el primer elemento x
en lst
tal que (func x)
devuelva true.
( define ( find-first func lst ) ( call-with-current-continuation ( lambda ( return-immediately ) ( for-each ( lambda ( x ) ( if ( func x ) ( return-immediately x ))) lst ) #f ))) ( ¿ 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ándolos a variables y pasándolos como argumentos a los procedimientos.
( let* (( yin (( lambda ( cc ) ( mostrar "@" ) cc ) ( llamada-con-continuación-actual ( lambda ( c ) c )))) ( yang (( lambda ( cc ) ( mostrar "*" ) cc ) ( llamada-con-continuación-actual ( lambda ( c ) c ))))) ( yin yang ))
Cuando se ejecuta este código, se muestra una secuencia de conteo:@*@**@***@****@*****@******@*******@********...
A diferencia de Common Lisp, todos los datos y procedimientos de 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 requiere una notación especial para referirse a una función como valor. Esto a veces se conoce como la distinción " Lisp-1 vs. Lisp-2 ", que hace 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 defun
las #'
primitivas de Common Lisp.
;; Variable ligada a un número: ( define f 10 ) f ===> 10 ;; Mutación (alteración del valor ligado) ( set! f ( + f f 6 )) f ===> 26 ;; Asignación de un procedimiento a la misma variable: ( set! f ( lambda ( n ) ( + n 12 ))) ( f 6 ) ===> 18 ;; Asignación del resultado de una expresión a la misma variable: ( set! f ( f 1 )) f ===> 13 ;; programación funcional: ( apply + ' ( 1 2 3 4 5 6 )) ===> 21 ( set! f ( lambda ( n ) ( + n 100 ))) ( map f ' ( 1 2 3 )) ===> ( 101 102 103 )
Esta subsección documenta 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.
Scheme especifica un conjunto comparativamente completo de tipos de datos numéricos, incluidos los tipos complejos y racionales , 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 en particular.
Los números pueden tener la cualidad de ser exactos. Un número exacto solo puede producirse mediante una secuencia de operaciones exactas que involucren otros números exactos; por lo tanto, la inexactitud es contagiosa. El estándar especifica que dos implementaciones cualesquiera deben producir resultados equivalentes para todas las operaciones que resulten en números exactos.
El estándar R5RS especifica los 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, no se exige que las implementaciones de Scheme implementen toda la torre numérica, pero deben implementar "un subconjunto coherente que sea consistente con los propósitos de la implementación y el espíritu del lenguaje Scheme" (R5RS sec. 6.2.3). [4] El nuevo estándar R6RS sí exige 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... de modo que siempre devuelvan resultados exactos cuando se les den 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 sí 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 ( defina 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 el R6RS porque no implementa la torre numérica completa.
El esquema apoya la evaluación diferida a través del delay
formulario y el procedimiento force
.
( define un 10 ) ( define eval-aplus2 ( delay ( + a 2 ))) ( establece! a 20 ) ( fuerza eval-aplus2 ) ===> 22 ( define eval-aplus50 ( delay ( + a 50 ))) ( deja (( a 8 )) ( fuerza eval-aplus50 )) ===> 70 ( establece! a 100 ) ( fuerza eval-aplus2 ) ===> 22
Se conserva el contexto léxico de la definición original de la promesa 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 perezosa, como streams . [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 sugiere una implementación de delay
y force
, implementando la promesa como un procedimiento sin argumentos (un thunk ) y usando memorización para garantizar que solo se evalúe una vez, independientemente de la cantidad 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 utilizando las funciones definidas en SRFI 41: [24]
;; Define la secuencia de Fibonacci: ( define fibs ( stream-cons 0 ( stream-cons 1 ( stream-map + fibs ( stream-cdr fibs ))))) ;; Calcula el número centésimo en la secuencia: ( stream-ref fibs 99 ) ===> 218922995834555169026
La mayoría de los lenguajes Lisp especifican un orden de evaluación para los argumentos de los procedimientos. Scheme no lo hace. El orden de evaluación (incluido el orden en el que se evalúa la expresión en la posición del operador) puede ser elegido por una implementación en cada llamada, y la única restricción es que "el efecto de cualquier evaluación concurrente de las expresiones de operador y operando está restringido a ser consistente con algún orden secuencial de evaluación". (R5RS sec. 4.1.3) [4]
( let (( ev ( lambda ( n ) ( display "Evaluando " ) ( display ( if ( procedimiento? n ) "procedimiento" n )) ( newline ) 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 Lisp, la aparición de una expresión en la posición del operador (el primer elemento) de una expresión de Scheme es completamente 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+), (ev1) y (ev2) 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 pueden mostrarse en cualquier orden mediante Scheme estándar cuando se ejecuta el código de ejemplo anterior, aunque el texto de una línea no puede intercalarse con el de 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 de macros higiénico que permite al programador añadir nuevas construcciones sintácticas al lenguaje utilizando un sublenguaje de coincidencia de patrones simple (R5RS sec 4.3). [4] Antes de esto, el sistema de macros higiénico había sido relegado a un apéndice del estándar R4RS, como un sistema de "alto nivel" junto con un sistema de macros de "bajo nivel", ambos tratados como extensiones de Scheme en lugar de una parte esencial del lenguaje. [25]
Las implementaciones del sistema de macros higiénico, también llamado syntax-rules
, deben respetar el alcance léxico del resto del lenguaje. Esto se garantiza mediante reglas especiales de denominación 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 del lenguaje de R5RS Scheme durante algún tiempo.
;; Define una macro para implementar una variante de "si" con una multiexpresión ;; rama verdadera y ninguna rama falsa. ( define-syntax when ( syntax-rules () (( when pred exp exps ... ) ( if pred ( begin exp exps ... )))))
Las invocaciones de macros y procedimientos tienen un gran parecido (ambas son expresiones s), pero se tratan de forma diferente. Cuando el compilador encuentra una expresión s en el programa, primero comprueba si el símbolo está definido como una palabra clave sintáctica dentro del ámbito 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 de forma recursiva hasta que no queden invocaciones de macros. Si no es una palabra clave sintáctica, el compilador compila 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 cabecera de la expresión s y llamarla como un procedimiento con las expresiones de cola evaluadas pasadas como argumentos.
La mayoría de las implementaciones de Scheme también proporcionan sistemas de macros 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 imposibilidad 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, ofrecen una posible solución. [26]
Antes de R5RS, Scheme no tenía un equivalente estándar del eval
procedimiento que es omnipresente en otros Lisp, aunque el primer documento de Lambda lo había descrito evaluate
como "similar a la función EVAL de LISP" [18] y el primer informe revisado en 1978 lo reemplazó por enclose
, que tomaba dos argumentos. El segundo, tercer y cuarto informes revisados 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 debería ser 5 o 6: [27]
( let (( nombre '+ )) ( let (( + * )) ( evaluar ( lista nombre 2 3 ))))
Si se evalúa en el entorno externo, donde name
se define , el resultado es la suma de los operandos. Si se evalúa en el entorno interno, donde se ha asociado el símbolo "+" 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 extiende esto proporcionando un procedimiento llamado environment
por 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í:
( define ( evalúa 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 el valor falso en una expresión booleana. En Scheme, desde la norma IEEE de 1991, [3] todos los valores excepto #f
, incluido NIL
el equivalente de en Scheme que se escribe como '()
, se evalúan como el valor verdadero en una expresión booleana. (R5RS sec. 6.3.1) [4]
Mientras que la constante que representa el valor booleano de verdadero es T
en la mayoría de los Lisp, en Scheme es #t
.
En Scheme, los tipos de datos primitivos son disjuntos. Solo uno de los siguientes predicados puede ser verdadero para cualquier objeto de Scheme: boolean?
, pair?
, symbol?
, number?
, char?
, string?
, vector?
, port?
, procedure?
. (R5RS sec 3.2) [4]
En cambio, dentro del tipo de datos numéricos, los valores numéricos se superponen. Por ejemplo, un valor entero satisface todos los predicados integer?
, rational?
, real?
y al mismo tiempo. (R5RS sec 6.2) complex?
[ 4]number?
El esquema 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?
evalúa a #f
menos que sus parámetros representen el mismo objeto de datos en la memoria;eqv?
es generalmente lo mismo que eq?
pero trata los objetos primitivos (por ejemplo, caracteres y números) de manera especial, de modo que los números que representan el mismo valor son eqv?
pares 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]En Scheme también existen operaciones de equivalencia dependientes de tipo: string=?
y string-ci=?
compara dos cadenas (la última realiza una comparación independiente de mayúsculas y minúsculas); char=?
y char-ci=?
compara caracteres; =
compara números. [4]
Hasta el estándar R5RS, el comentario estándar en Scheme era un punto y coma, 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 "comentarla") al anteponerla con #;
(introducido en SRFI 62 [28] ) y se puede producir un comentario de varias líneas o "comentario en bloque" al rodear el texto con #|
y |#
.
La entrada y salida de Scheme se basa 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á soportada en el estándar, por 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-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 el estricto esquema R5RS.
Ejemplo 1: Con salida predeterminada a (puerto de salida actual):
( let (( hola0 ( lambda () ( display "Hola mundo" ) ( nueva línea )))) ( hola0 ))
Ejemplo 2: Como 1, pero usando el argumento de puerto opcional para los procedimientos de salida
( 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 (( hello0 ( lambda () ( display "Hello world" ) ( newline )))) ( with-output-to-file "helloworldoutputfile" hello0 ))
Ejemplo 4: Como 2, pero con el archivo explícito abierto y el puerto cerrado para enviar la salida al archivo
( let (( hola1 ( lambda ( p ) ( display "Hola mundo" p ) ( newline p ))) ( puerto-de-salida ( abrir-archivo-de-salida "archivo-de-salida-holamundo" ))) ( hola1 puerto-de-salida ) ( cerrar-puerto-de-salida puerto -de-salida ))
Ejemplo 5: Como 2, pero usando call-with-output-file para enviar la salida a un archivo.
( let (( hola1 ( lambda ( p ) ( display "Hola mundo" p ) ( nueva línea p )))) ( llamada-con-archivo-de-salida "archivo-de-salida-holamundo" 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
se proporcionan read-char
, peek-char
y . Para escribir y leer expresiones de Scheme, Scheme proporciona y . 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 utilizando el predicado .char-ready?
read
write
eof-object?
Con el estándar SRFI 28 también se define un procedimiento de formato básico similar a format
la función de Common Lisp, que le da nombre. [30]
En Scheme, los procedimientos están vinculados a variables. En R5RS, el estándar de lenguaje exigió formalmente que los programas pudieran cambiar las vinculaciones de variables de los procedimientos integrados, redefiniéndolos de manera efectiva. (R5RS "Cambios de lenguaje") [4] Por ejemplo, +
se puede ampliar para aceptar cadenas y números redefiniéndolo:
( ¡establecer! + ( dejar (( original+ + )) ( lambda argumentos ( aplicar ( si ( o ( nulo? argumentos ) ( cadena? ( auto argumentos ))) cadena-agregar original+ ) argumentos )))) ( + 1 2 3 ) ===> 6 ( + "1" "2" "3" ) ===> "123"
En R6RS, cada enlace, incluidos los estándar, pertenece 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 por mutación está prohibida. En cambio, es posible importar un procedimiento diferente bajo el nombre de uno estándar, lo que en efecto es similar a la 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 un "?" y los procedimientos que cambian el valor de datos ya asignados terminan con un "!". Los programadores de Scheme suelen seguir estas convenciones.
En contextos formales como los estándares de Scheme, se utiliza la palabra "procedimiento" en lugar de "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. A veces, la aplicación de un procedimiento se denomina formalmente combinación .
Al igual que en otros Lisp, 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 de que realizan una optimización de llamadas de cola para admitir una cantidad indefinida de llamadas de cola activas .
La forma de los títulos de los documentos de estándares desde R3RS, "Informe 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 Resumen de R3RS está estrechamente modelada 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] . En ellos se describen "formas" estándar: palabras clave y sintaxis que las acompañan, que proporcionan la estructura de control del lenguaje, y procedimientos estándar que realizan tareas comunes.
En esta tabla se describen los formatos estándar de Scheme. Algunos formatos aparecen en más de una fila porque no se pueden clasificar fácilmente en una única función del lenguaje.
Las formas marcadas con una "L" en esta tabla se clasifican como formas de "biblioteca" derivadas 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 dos tablas siguientes describen los procedimientos estándar del esquema R5RS. El esquema 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 pueden clasificarse 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 de 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, muchos procedimientos y formas sintácticas comunes no están definidos por el estándar. Para mantener el lenguaje básico pequeño pero facilitar la estandarización de extensiones, la comunidad de Scheme tiene un proceso de "Solicitud de implementación de Scheme" (SRFI, por sus siglas en inglés) mediante el cual se definen bibliotecas de extensión a través de un análisis cuidadoso de las propuestas de extensión. Esto promueve la portabilidad del código. Muchas de las SRFI son compatibles con todas o la mayoría de las implementaciones de Scheme.
Las SRFI con un apoyo bastante amplio en diferentes implementaciones incluyen: [33]
El diseño elegante y minimalista ha hecho de Scheme 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 scripting . Esto ha dado lugar a decenas de implementaciones, [34] la mayoría de las cuales difieren entre sí tanto 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 Scheme estándar y portable 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-impresión al estilo Lisp tradicional para el desarrollo y la depuración. Muchas también compilan programas Scheme en binario ejecutable. El soporte para incrustar código Scheme 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 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 hace que la incrustación sea mucho más sencilla. 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 Java , y los compiladores de Scheme a C suelen facilitar el uso de bibliotecas externas escritas en C, hasta el punto de permitir la incorporació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.
Scheme es ampliamente utilizado por varias [35] escuelas; en particular, varios cursos introductorios de informática utilizan Scheme junto con el libro de texto Structure and Interpretation of Computer Programs (SICP). [36] Durante los últimos 12 años, PLT ha llevado a cabo 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 rudimentaria de Scheme. La antigua clase introductoria de programación 6.001 del MIT se enseñaba en Scheme, [37] Aunque 6.001 ha sido reemplazado por cursos más modernos, SICP continúa enseñándose en el MIT. [38] Del mismo modo, la clase introductoria en UC Berkeley , CS 61A, se enseñó hasta 2011 completamente en Scheme, salvo pequeñas desviaciones hacia Logo para demostrar el alcance dinámico. Hoy, 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 currículo antiguo y partes de la clase todavía se enseñan en Scheme. [39]
El libro de texto How to Design Programs de Matthias Felleisen, actualmente en la Northeastern University, es utilizado por algunos institutos de educación superior para sus cursos introductorios de informática. Tanto la Northeastern University como el Worcester Polytechnic Institute utilizan Scheme exclusivamente para sus cursos introductorios Fundamentals of Computer Science (CS2500) e Introduction to Program Design (CS1101), respectivamente. [40] [41] Rose-Hulman utiliza Scheme en su curso más avanzado Programming Language Concepts. [42] El curso principal de Brandeis University , Structure and Interpretations of Computer Programs (COSI121b), también se enseña exclusivamente en Scheme por el científico informático teórico Harry Mairson . [43] La clase introductoria de Indiana University , C211, se enseña completamente en Scheme. Una versión a tu propio ritmo del curso, CS 61AS, continúa utilizando Scheme. [44] Los cursos introductorios de informática en Yale y Grinnell College también se enseñan en Scheme. [45] Paradigmas de diseño de programación, [46] un curso obligatorio para los estudiantes de posgrado en informática de la Universidad Northeastern , también utiliza ampliamente Scheme. El antiguo curso introductorio de informática de la Universidad de Minnesota - Twin Cities, CSCI 1901, también utilizó Scheme como su lenguaje 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ó para lo siguiente:
{{cite web}}
: CS1 maint: varios nombres: lista de autores ( enlace )Una metáfora útil para la diferencia entre FUNCTION y QUOTE 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. FUNCTION actúa como una cubierta cerrada o no porosa (de ahí el término "cierre" utilizado por Landin). Por lo tanto, hablamos de expresiones Lambda "abiertas" (las funciones en LISP son generalmente expresiones Lambda) y expresiones Lambda "cerradas". [...] Mi interés en el problema del entorno comenzó cuando Landin, que tenía un profundo conocimiento del problema, visitó el MIT durante 1966-67. Entonces 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 .