260 likes | 375 Views
Combinadores SK. Introducción. Examinaremos una técnica de reducción de grafos basada en un conjunto fijo de supercombinadores. Entre ellos, S y K son los combinadores más importantes.
E N D
Introducción • Examinaremos una técnica de reducción de grafos basada en un conjunto fijo de supercombinadores. Entre ellos, S y K son los combinadores más importantes. • Este método posibilita la definición de una muy simple máquina de reducción: maneja operadores primitivos y no necesita un mecanismo de instanciación. • La implementación de esta máquina puede llegar a ser más perezosa que una que incorpore los mecanismos descriptos hasta ahora, pero a un costo considerable.
Esquema de compilación SK • Nuestra estrategia será transformar un programa funcional en uno que sólo contenga constantes y operadores primitivos, además de los combinadores S, K e I. • Las reglas de reducción de estos combinadores es como sigue: • S f g x -> f x (g x) • K x y -> x • I x -> x • S, K e I son supercombinadores, pero usaremos el término más general de combinador para nombrarlos.
Introduciendo S, K e I • Consideremos las siguientes expresiones: • Fun = \ x -> e1 e2 Fun’ = S (\ x -> e1) (\ x -> e2) • Podemos demostrar que Fun y Fun’ son extensionalemente iguales. • Transformaci’on S: • \ x -> e1 e2 => S (\ x -> e1) (\ x -> e2) • Usamos => para denotar “compilación”.
Transformación S • Ejemplo: h = \ x -> Or x True • \ x -> Or x True • S => S (\ x -> Or x) (\ x -> True) • S=> S (S (\ x -> Or) (\ x -> x)) (\ x -> True) • Cada vez que aplicamos la transformación S el \ x es empujado un nivel hacia abajo, ya que mientras el cuerpo sea una aplicación este tipo de transformación puede ser efectuado. • Por otro lado, en cada aplicación de la transformación dos nuevas abstracciones son generadas. Al final los cuerpos de estas abstracciones serán atomicos y entonces tendremos que considerar los siguientes dos casos:
Transformación I • i) La expresión es \ x -> x. • Esta expresión es la función identidad, la que hemos definido como el combinador I. • La transformación I reemplaza \ x -> x con I, entonces • Transformación I: • \ x -> x => I • S (S (\ x -> Or) (\ x -> x)) (\ x -> True) • I => S (S (\ x -> Or) I) (\ x -> True)
Transformación K • ii) La expresión es de la forma \ x -> c, donde c es una constante o una variable distinta de x. Este tipo de funciones toman un argumento, lo descartan y retornan c, por lo tanto podemos reemplazarlas por espresiones de la forma (K c) • Transformación K : • \ x -> c => K c • Como en el caso de S, se puede fácilmente demostrar que estas dos expresiones son extensionalmente iguales. • S (S (\ x -> Or) I) (\ x -> True) • K => S (S (K Or) I) (K True)
Reglas de traducción y transformación • Para resumir, hemos presentado reglas de transformación y reducción para los combinadores S, K e I, las que desplegamos en conjunto a continuación: • reducción SS f g x -> f x (g x) • reducción KK x y -> x • reducción II x -> x • transformación S\ x -> e1 e2 => S (\ x -> e1) (\ x -> e2) • transformación K\ x -> c => K c (c x) • transformación I\ x -> x => I
Reglas de traducción y transformación (2) • Podemos usar las reglas de reducción para evaluar el programa resultado de aplicar las transformaciones: • h x • = S (S (K Or) I) (K True) x • S -> S (K Or) I x (K True x) • S -> K Or x (I x) (K True x) • K -> Or (I x) (K True x) • I -> Or x (K True x) • K -> Or x True
Compilación e implementación • Las transformaciones que hemos presentado constituyen un algortimo completo de compilación, el que transforma cualquier expresión lambda en una que sólo consiste de combinaciones de los combinadores y constantes. • El siguiente es el algoritmo de compilación SK para compilar una expresión e: • Mientras e contenga una abstracción hacer • (1) Elija cualquier abstracción innermost • (2) Si el cuerpo es una aplicación, aplique • la transformación S • (3) Si no, el cuerpo debe ser una variable o • una constante, aplique la transformación • K o I según corresponda.
Compilación e implementación (2) • Ejemplo: • (\ x -> + x x) 5 • S => S (\ x -> + x) (\ x -> x) 5 • S => S (S (\ x -> +) (\ x -> x)) (\ x -> x) 5 • I => S (S (\ x -> +) I) (\ x -> x) 5 • I => S (S (\ x -> +) I) I 5 • K => S (S (K +) I) I 5 S (S (K +) I) I 5 -> S (K +) I 5 (I 5) -> K + 5 (I 5) (I 5) -> + (I 5) (I 5) -> + 5 (I 5) -> + 5 5 -> 10
Compilación e implementación (3) • Nombre Objeto sintáctico • e, e1, e2 expresiones • f, f1, f2 expresiones que no contienen abstracciones • x variable • cv constante • C[ e ] compila e a un combinador SK • C [ e1 e2 ] = C [ e1 ] C [ e2 ] • C [ \ x -> e ] = A x [ C [ e ] ] • C [ cv ] = cv • A x [ f ] abstrae x de f • A x [ f1 f2 ] = S (A x [ f1 ]) (A x [ f2 ]) • A x [ x ] = I • A x [ cv ] = K cv
Implementaciones • Los combinadores S, K, I son ejemplos particulares de supercombinadores, por lo tanto, la máquina de reducción que ejecuta código SK es una simplificación de una máquina de reducción de supercombinadores. El método para buscar el próximo redex unwinding la espina, el uso de un spine stack, y el uso de nodos de indirección todos se aplican también en esta máquina particular. Las principales diferencias son: • i) los combinadores son implementados directamente como operadores primitivos, no mecanismo general de instanciación de cuerpo para compilar el supercombinador. • ii) la máquina de reducción no tiene que implementar el mecanismo de instanciación.
Instanciación perezosa • Un programa compilado a combinadores SK ejecuta aún más perezosamente que un programa supercombinador. • Consideremos: • $F x =If ec et ef , dondeet y ef pueden ser expresiones muy grandes • Cuando $F es aplicada nuevas instancias de et y ef son construídas, aún cuando una de las dos será descartada. • Sin embargo, veamos que pasa cuando esta expresión es compilada usando combinadores SK y posteriormente aplicada a un argumento
Instanciación perezosa (2) • \ x -> If ec et ef • S => S (\ x -> If ec et) (\ x -> ef) • S => S (S (\ x -> If ec) (\ x -> et)) (\ x -> ef) • S => S (S (S (\ x -> If) (\ x -> ec)) (\ x -> et)) (\ x -> ef) • ... => S (S (S (K If) cc) ct) cf • Mostrar segmento inicial de secuencia de reducción (aplicada a x) • Notar que no se ha construído instancia de et o ef. Esta se ha pospuesto al construír las expresiones (ct x) y (cf x). Sólo la condición del If será evaluada. • S empuja el argumento un nivel hacia abajo dentro del cuerpo de la función.
I no es necesario • En realidad sólo los combinadores S y K son suficientes para expresar el código combinatorio de una función, ya que el combinador I puede ser expresado en función de estos dos combinadores. • I = S K K • Sin embargo, toda implementación razonable incluye a I. • Transformación de expresiones lambda en combinadores [Curry58] • Técnica de implementación [Turner79]
Optimización de los esquemas SK • Los ejemplos vistos muestran que el algoritmo de compilación tiende a generar expresiones combinatorias relativamente grandes a partir de simples abstracciones. De hecho, así como está definido el algoritmo de compilación es virtualmente inusable, ya que las expresiones obtenidas son muy grandes y su reducción a forma normal requiere muchos pasos de reducción. • Sin embargo, se pueden introducir optimizaciones al algoritmo de tal forma que la técnica sea efectivamente utilizable. • Para eso necesitaremos introducir algunos combinadores nuevos.
Optimización K • Consideremos la expresión \ x -> + 1, cuando es compilada obtenemos la expresión S (K +) (K 1), lo que es poco razonable ya que x no ocurre en el cuerpo de la abstracción. • Un mejor resultado sería la expresión K (+ 1). Esta optimización puede ser obtenida al introducir la siguiente transformación: • S (K p) (K q) => K (p q) • Es trivial mostrar que las dos expresiones son extensionalmente iguales.Cuando K (p q) es aplicada a un argumento su reducción requiere sólo un paso, en vez de los tres necesarios para reducir S (K p) (K q). Entonces: • A x [ e ] = K e sii x no ocurre en e • Nota: Completamente perezoso.
El combinador B • Consideremos la siguiente abstracción: \ x -> - x. Esta expresión compila a S (K -) I, lo que es bastante ineficiente ya que el argumento es pasado a la rama izquierda (K -) que a su vez lo descartará. • Sería conveniente tener una versión de S que sólo pase x a la rama derecha. Llamemos esta versión B, con la siguiente regla de reducción: • B f g x = f (g x) • La correspondiente regla de optimización puede ser formulada entonces así: • S (K p) q => B p q • Optimizando nuestro ejemplo tenemos que S (K -) I => B - I
El combinador B (2) • Notar que la optimización introducida ahorra trabajo en tiempo de compilación (el programa resultante es menor) y en tiempo de ejecución, porque hay menos reducciones que efectuar. • De hecho el ejemplo puede aún ser optimizado un poco más, la expresión (B p I) es equivalente a p. Entonces podemos introducir otra regla de optimización: • B p I => p • lo que permite optimizar nuestro ejemplo de la siguiente forma: • B - I => - • Esta última es una muy buena traducción para la expresión, es equivalente a haber aplicado h-reducción.
El combinador C • Así como (B f g x) distribuye a x solamente hacia la rama derecha (g) es también conveniente tener un combinador, llamémosle C, que distribuye x sólo a la rama izquierda (f): • C f g x -> f x g • La regla de optimización para C es la siguiente: • S p (K q) => C p q • A continuación resumimos las nuevas reducciones y reglas de optimización: • B f g x -> f (g x) S (K p) (K q) => K (p q) • C f g x -> f x g S (K p) I => p • S (K p) q => B p q • S p (K q) => C p q
Modificaciones al compilador • Formalizaremos las optimizaciones introduciendo una nueva función Opt, la que optimiza a una expresión combinatoria. • A x [ f ] abstrae x de f • A x [ f1 f2 ] = Opt (S (A x [ f1 ]) (A x [ f2 ]) • A x [ x ] = I • A x [ cv ] = K cv • Opt [ e ] optimiza e • Opt [ S (K p) (K q) ] = K (p q) • Opt [ S (K p) I ] = p • Opt [ S (K p) q ] = B p q • Opt [ S p (K q) ] = C p q • Opt [ S p q ] = S p q
Combinadores S´, B´,C´ y B* • Existe un caso más en el que se puede optimizar el código generado por el compilador. Esto ocurre cuando se abstraen varias variables de una expresión. • Reglas de reducciónReglas de transformación • S’ c f g x -> c (f x) (g x) S (B x y) z => S’ x y z • B’ c f g x -> c f (g x) B (c f) g => B’ c f g • C’ c f g x -> c (f x) g C (B c f) g => C’ c f g • B* c f g x -> c (f (g x)) B c (B f g) => B* c f g
El tamaño de traducciones SK • Como hemos visto una característica común en todos los ejemplos es que el programa traducido es mucho más grande que en su correspondiente representación lambda. • Se ha demostrado que el tamaño de una expresión combinatoria puede ser, en el peor caso, proporcional al cuadrado del tamaño de la expresión lambda. • Otra observación a hacer es que el algoritmo de compilación a código SK reinspecciona código que ya ha sido parcialmente compilado. Esto puede ser visto en la regla de compilación: • C [ \ x -> e ] = A x [ C [ e ] ]
Ventajas de SK • i) Un conjunto fijo y pequeño de combinadores puede ser implementado en hardware, evitando así un paso de interpretación. • ii) La instanciación del cuerpo de una abstracción es hecho perezosamente, lo que evita instanciar secciones del grafo que luego serían descartadas. • iii) La técnica es completamente perezosa • iv) La máquina de reducción es relativamente simple de implementar.
Desventajas de SK • i) Pasos de ejecución muy pequeños. Se generan muchos nodos de aplicación intermedios que rápidamente son descartados. Mucho consumo de espacio. • ii) La traducción a combinadores es muy costosa comparada con técnicas de compilación de supercombinadores. Programas muy grandes.