En la semántica de los lenguajes de programación , la normalización por evaluación (NBE) es un método para obtener la forma normal de los términos en el cálculo λ recurriendo a su semántica denotacional . Un término se interpreta primero en un modelo denotacional de la estructura del término λ, y luego se extrae un representante canónico (β-normal y η-largo) mediante la reificación de la denotación. Este enfoque esencialmente semántico, sin reducción, difiere de la descripción sintáctica más tradicional, basada en la reducción, de la normalización como reducciones en un sistema de reescritura de términos donde se permiten β-reducciones en el interior de los términos λ.
NBE se describió por primera vez para el cálculo lambda simplemente tipado . [ 1 ] Desde entonces se ha extendido tanto a sistemas de tipos más débiles como el cálculo lambda sin tipado [ 2 ] utilizando un enfoque de teoría de dominios , como a sistemas de tipos más ricos como varias variantes de la teoría de tipos de Martin-Löf . [ 3 ] [ 4 ] [ 5 ] [ 6 ]
Describir
Consideremos el cálculo lambda simplemente tipado , donde los tipos τ pueden ser tipos básicos (α), tipos de función (→) o productos (×), dados por la siguiente gramática de formas de Backus-Naur (→ asociando a la derecha, como es habitual):
- (Tipos) τ ::= α | τ 1 → τ 2 | τ 1 × τ 2
Estos se pueden implementar como un tipo de dato en el metalenguaje; por ejemplo, para Standard ML , podríamos usar:
tipo de datos ty = Básico de cadena | Flecha de ty * ty | Producto de ty * tyLos términos se definen en dos niveles. [ 7 ] El nivel sintáctico inferior (a veces llamado nivel dinámico ) es la representación que se pretende normalizar.
- (Términos de sintaxis) s , t ,… ::= var x | lam ( x , t ) | app ( s , t ) | pair ( s , t ) | fst t | snd t
Aquí, lam / app (respectivamente, pair / fst , snd ) son las formas intro / elim para → (respectivamente, ×), y x son variables . Estos términos están destinados a ser implementados como un tipo de dato de primer orden en el metalenguaje:
tipo de dato tm = var de cadena | lam de cadena * tm | app de tm * tm | pair de tm * tm | fst de tm | snd de tmLa semántica denotacional de los términos (cerrados) en el metalenguaje interpreta las construcciones de la sintaxis en términos de características del metalenguaje; así, lam se interpreta como abstracción, app como aplicación, etc. Los objetos semánticos construidos son los siguientes:
- (Términos semánticos) S , T ,… ::= LAM (λ x . S x ) | PAIR ( S , T ) | SYN t
Nótese que en la semántica no hay variables ni formas de eliminación; se representan simplemente como sintaxis. Estos objetos semánticos se representan mediante el siguiente tipo de dato:
tipo de datos sem = LAM de ( sem -> sem ) | PAIR de sem * sem | SYN de tmHay un par de funciones indexadas por tipo que se mueven entre la capa sintáctica y la semántica. La primera función, generalmente escrita como ↑ τ , refleja la sintaxis del término en la semántica, mientras que la segunda reifica la semántica como un término sintáctico (escrito como ↓ τ ). Sus definiciones son mutuamente recursivas de la siguiente manera:
Estas definiciones se implementan fácilmente en el metalenguaje:
(* fresh_var : unidad -> cadena *) val variable_ctr = ref ~1 fun fresh_var () = ( variable_ctr := 1 + !variable_ctr ; "v" ^ Int . toString ( !variable_ctr ))(* reflect : ty -> tm -> sem *) fun reflect ( Arrow ( a , b )) t = LAM ( fn S => reflect b ( app ( t , ( reify a S )))) | reflect ( Prod ( a , b )) t = PAIR ( reflect a ( fst t ), reflect b ( snd t )) | reflect ( Basic _) t = SYN t(* reificar: ty -> sem -> tm *) y reificar ( Flecha ( a , b )) ( LAM S ) = dejar val x = fresh_var () en lam ( x , reificar b ( S ( reflejar a ( var x )))) fin | reificar ( Prod ( a , b )) ( PAIR ( S , T )) = pair ( reificar a S , reificar b T ) | reificar ( Básico _) ( SYN t ) = tPor inducción sobre la estructura de tipos, se deduce que si el objeto semántico S denota un término bien tipado s de tipo τ, entonces la reificación del objeto (es decir, ↓ τ S) produce la forma η-larga β-normal de s . Por lo tanto, solo queda construir la interpretación semántica inicial S a partir de un término sintáctico s . Esta operación, escrita ∥ s ∥ Γ , donde Γ es un contexto de ligaduras, procede por inducción únicamente sobre la estructura del término:
En la implementación:
tipo de dato ctx = vacío | agregar de ctx * ( cadena * sem )(* búsqueda : ctx -> cadena -> sem *) fun búsqueda ( agregar ( remdr , ( y , valor ))) x = si x = y entonces valor sino búsqueda remdr x(* significado: ctx -> tm -> sem *) fun significado G t = caso t de var x => búsqueda G x | lam ( x , s ) => LAM ( fn S => significado ( agregar ( G , ( x , S ))) s ) | app ( s , t ) => ( caso significado G s de LAM S => S ( significado G t )) | pair ( s , t ) => PAIR ( significado G s , significado G t ) | fst s => ( caso significado G s de PAIR ( S , T ) => S ) | snd t => ( caso significado G t de PAIR ( S , T ) => T )Cabe señalar que existen muchos casos no exhaustivos; sin embargo, si se aplica a un término cerrado bien tipificado, ninguno de estos casos faltantes se presenta. La operación NBE en términos cerrados es entonces:
(* nbe : ty -> tm -> tm *) fun nbe a t = reificar a ( significado vacío t )Como ejemplo de su uso, considérese el término sintáctico SKKque se define a continuación:
val K = lam ( "x" , lam ( "y" , var "x" )) val S = lam ( "x" , lam ( "y" , lam ( "z" , aplicación ( aplicación ( var "x" , var "z" ), aplicación ( var "y" , var "z" ))))) val SKK = aplicación ( aplicación ( S , K ), K )Esta es la codificación bien conocida de la función identidad en lógica combinatoria . Normalizarla en un tipo identidad produce:
- nbe ( Flecha ( Básico "a" , Básico "a" )) SKK ; val it = lam ( "v0" , var "v0" ) : tmEl resultado está en realidad en forma η-larga, como se puede ver fácilmente normalizándolo a un tipo de identidad diferente:
- nbe ( Flecha ( Flecha ( Básico "a" , Básico "b" ), Flecha ( Básico "a" , Básico "b" ))) SKK ; val it = lam ( "v1" , lam ( "v2" , app ( var "v1" , var "v2" ))) : tmVariantes
El uso de niveles de De Bruijn en lugar de nombres en la sintaxis residual crea reifyuna función pura en la que no hay necesidad de fresh_var. [ 8 ]
El tipo de datos de los términos residuales también puede ser el tipo de datos de los términos residuales en forma normal . El tipo de reify(y por lo tanto de nbe) deja claro que el resultado está normalizado. Y si el tipo de datos de las formas normales está tipado, el tipo de reify(y por lo tanto de nbe) deja claro que la normalización conserva el tipo. [ 9 ]
La normalización por evaluación también se escala al cálculo lambda tipado simple con sumas ( +), [ 7 ] utilizando los operadores de control delimitadosshift y reset. [ 10 ]
Véase también
- MINLOG , un asistente de pruebas que utiliza NBE como motor de reescritura.
Referencias
- ↑ Berger, Ulrich; Schwichtenberg, Helmut (1991). "Una inversa del funcional de evaluación para el cálculo λ tipado". LICS .
- ↑ Filinski, Andrzej; Rohde, Henning Korsholm (2005). "Una explicación denotacional de la normalización sin tipo mediante evaluación" . Foundations of Software Science and Computation Structures (FOSSACS) . Vol. 10. doi : 10.7146/brics.v12i4.21870 .
- ↑ Coquand, Thierry; Dybjer, Peter (1997). "Construcciones de modelos intuicionistas y pruebas de normalización". Estructura matemática en ciencias de la computación . 7 (1): 75– 94. doi : 10.1017/S0960129596002150 .
- ↑ Abel, Andreas; Aehlig, Klaus; Dybjer, Peter (2007). "Normalización por evaluación para la teoría de tipos de Martin-Löf con un universo" (PDF) . MFPS .
- ↑ Abel, Andreas; Coquand, Thierry; Dybjer, Peter (2007). "Normalización por evaluación para la teoría de tipos de Martin-Löf con juicios de igualdad tipificados" (PDF) . LICS .
- ↑ Gratzer, Daniel; Sterling, Jon; Birkedal, Lars (2019). "Implementación de una teoría de tipos dependiente modal" (PDF) . ICFP .
- 1 2 Danvy, Olivier (1996). "Evaluación parcial dirigida por tipo" ( PostScript comprimido con gzip ) . POPL ( FTP ). págs. 242–257 . (Para ver los documentos, consulte Ayuda:FTP )
- ↑ Filinski, Andrzej. "Una explicación semántica de la evaluación parcial dirigida por tipos" . Principios y práctica de la programación declarativa . doi : 10.7146/brics.v6i17.20074 .
- ↑ Danvy, Olivier; Rhiger, Morten; Rose, Kristoffer (2001). "Normalización por evaluación con sintaxis abstracta tipada" . Journal of Functional Programming . 11 (6): 673– 680. doi : 10.1017/S0956796801004166 .
- ↑ Danvy, Olivier; Filinski, Andrzej (1990). "Abstrayendo el control". LISP y programación funcional . págs. 151–160 . doi : 10.1145/91556.91622 . ISBN 0-89791-368-X. S2CID 6426191 .
- Cálculo lambda
- semántica de lenguajes de programación