Introducción al Cálculo Lambda Dr. Alejandro Guerra-Hernández Departamento de Inteligencia Artificial Universidad Veracruzana Facultad de Física e Inteligencia Artificial Sebastián Camacho No 5, Xalapa, Ver., México 91000 [email protected] www.uv.mx/aguerra 9 de marzo de 2007 ¿Puede uno resolver todos los problemas formulados en un lenguaje universal? Esta pregunta, conocida como el Entscheidungproblem, fue abordada de manera independiente (y resuelta de manera negativa) por Alan Turing y Alonzo Church. Para ello, Turing inventó una clase de máquinas (después llamadas máquinas de Turing) sobre las que definió el concepto de función computable. Church por su parte, inventó un sistema formal al que llamó cálculo lambda, con el cual definió el concepto de función computable. Las computadoras Von Neumann son conceptualmente máquinas de Turing con registros de acceso aleatorio. Los lenguajes imperativos como Fortran, Pascal, C y el código ensamblador se basan en la forma en que las máquinas de Turing son programadas: por medio de una secuencia de declaraciones (statements). Los lenguajes de programación funcional como ML, Miranda, están basados en el cálculo lambda. Lisp es un ejemplo temprano de estos lenguajes (aunque híbrido) e incluso se construyeron máquinas de reducción para este lenguaje. 1. Conceptos básicos Un programa funcional consiste de una expresión E que representa un algoritmo y su entrada. La expresión E es sujeta a una serie de reglas de reescritura. Una reducción consiste en remplazar una parte P de E por otra expresión P 0 de acuerdo a las reglas de reescritura. En notación esquemática, esto es: E[P ] → E[P 0 ] asumiendo que P → P 0 satisface las reglas de reescritura. El proceso de reducción se repite hasta que la expresión resultante no contiene más partes que puedan ser reescritas. Esta última forma, llamada forma normal E ∗ de la expresión E, constituye la salida del programa. 1 Ejemplo 1 Proceso de reducción en una expresión aritmética. Las reglas de reescritura correspondientes son las tablas de suma y multiplicación entre numerales. (7 + 4) × (8 + 5 × 3) → 11 × (8 + 5 × 3) → 11 × (8 + 15) → 11 × 23 → 253 Ejemplo 2 Proceso de reducción en una expresión simbólica. Las reglas de reescritura para append y sort pueden especificarse en pocas líneas. Las reglas como append se conocen como combinadores. → → → → firstOf firstOf firstOf firstOf conejo (sort (append (perro conejo) (sort (ratón gato)))) (sort (append (perro conejo) (gato ratón))) (sort (perro conejo gato ratón)) (conejo gato perro ratón) Los sistemas de reducción normalmente satisfacen la propiedad de ChurchRosser que indica que la forma normal obtenida es independiente de el orden de evaluación de los subtérminos. De hecho el ejemplo 1 puede reducirse como sigue: (7 + 4) × (8 + 5 × 3) → (7 + 4) × (8 + 15) → 11 × (8 + 15) → 11 × 23 → 253 o incluso evaluando varias expresiones al mismo tiempo: (7 + 4) × (8 + 5 × 3) → 1.1. 11 × (8 + 15) → 11 × 23 → 253 Aplicación y abstracción La primera operación básica del cálculo-λ es la aplicación. La expresión F.A o F A denotan al dato F considerado como algoritmo, aplicado al dato A considerado como su entrada. Esto tiene dos interpretaciones, tanto el proceso de computar F A, como la salida resultante del proceso. La primer interpretación captura el concepto de reducción, mientras la segunda el de modelo (semántica). Como se permiten expresiones del tipo F F , es decir F aplicada a sí misma, se dice que esta teoría es libre de tipos. Esto es útil para simular La segunda 2 operación básica es la abstracción. Si M ≡ M [x] es una expresión que contiene (depende de) x, entonces λx.M [x] denota la función x 7→ M [x]. La aplicación y la abstracción trabajan juntas en la siguiente fórmula intuitiva: (λx,2 × x + 1)3 = 2 × 3 + 1 = 7 esto es, λx. 2 × x + 1 denota la función x 7→ 2 × x + 1 aplicada al argumento 3, dando 2 × 3 + 1 o sea 7. En general tenemos que (λx.M [x])N = M [N ]. La última ecuación se escribe preferentemente como: (λx.M )N = M [x := N ] (1) donde x := N denota la substitución de x por N 1.2. Variables libres y acotadas Se dice que la abstracción acota la variable libre x en M , por ejemplo, en λx.yx tenemos a x acotada e y es libre. La substitución x := N se lleva a cabo únicamente en las ocurrencias libres de x: yx(λx.x)[x := N ] ≡ yN (λx.x) Por razones de higiene, se asume que siempre las variables acotadas que ocurren en una expresión son diferentes de las libres. Esto puede cumplirse renombrando las variables. Por ejemplo, λx.x se convierte en λy.y. De hecho, estas expresiones actúan de la misma forma: (λx.x)a = a = (λy.y)a y de hecho denotan al mismo algoritmo. Por lo tanto, expresiones que varían sólo en el nombre de las variables acotadas, son equivalentes. 1.3. Funciones de más argumentos Funciones de varios argumentos pueden obtenerse iterando la aplicación. Esta idea se debe a Schönfinkel, pero normalmente se conoce como currying, por H.B. Curry, quien lo introdujo de manera independiente. Intuitivamente, si f (x, y) depende de dos argumentos, uno puede definir: Fx F = λy.f (x, y), = λx.Fx . entonces: (F x)y = Fx y = f (x, y) 3 Esta última ecuación muestra que es conveniente usar la asociación a la izquierda cuando tenemos aplicaciones iteradas: F M1 . . . Mn denota (. . . ((F M1 )M2 ) . . . Mn ). Entonces la ecuación anterior se escribe F xy = f (x, y). La abstracción iterada, por dualidad, usa asociación a la derecha: (λx1 . . . xn f (x1 . . . xn ))x1 . . . xn = f (x1 , . . . , xn ) Usando n veces la aplicación β (Eq.??) esta última ecuación puede escribirse con notación de vector: → → → → (λ x .f [ x ]) N = f [N ]. 2. Conversión En esta sección introduciremos el cálculo-λ formalmente. Def 1 El conjunto de términos-λ (denotado por Λ) se construye a partir de un conjunto infinito de variables V = {v, v 0 , v 00 , . . . } usando aplicación y abstracción funcional: x∈V ⇒ x∈Λ M, N ∈ Λ ⇒ (M N ) ∈ Λ M ∈ Λ, x ∈ V ⇒ (λxM ) ∈ Λ Ejemplo 3 Las siguientes expresiones son términos-λ: v0 (v 0 v) (λv(v 0 v)) ((λv(v 0 v))v 00 ) (((λv(λv 0 (v 0 v)))v 00 )v 000 ) Por convención x, y, z, . . . denotan variables arbitrarias; M, N, L, . . . denotan términos-λ arbitrarios; Los paréntesis externos no se escriben. M ≡ N denota que M y N son el mismo término, o que pueden obtenerse uno a partir del otro renombrando las variables acotadas. Ejemplo 4 Equivalencia entre términos-λ: (λxy)z ≡ (λxy)z (λxx)z ≡ (λyy)z (λxx)z 6≡ z (λxx)z 6≡ (λxy)z 4 Usamos también las siguientes abreviaturas: F M1 . . . Mn ≡ (. . . ((F M1 )M2 ) . . . Mn ) y (λx1 . . . xn f (x1 . . . xn ))x1 . . . xn ≡ f (x1 , . . . , xn ). Ahora los términos del ejemplo ??, pueden escribirse como sigue: Ejemplo 5 Términos-λ escritos siguiendo nuestras convenciones: y yx λx.yx (λx.yx)z (λxy.yx)zw Def 2 El conjunto de variables libres de M , denotado por F V (M ), se define inductivamente como sigue: F V (x) F V (M N ) F V (λx.M ) = {x} = F V (M ) ∪ F V (N ) = F V (M ) − {x} Una variable en M está acotada, si no es libre. Observen que una variable está acotada si ocurre bajo el alcanze de un operador λ. M es un término cerrado (o combinador) si F V (M ) = ∅. El conjunto de términos-λ cerrados se denota como Λo . El resultado de substituir N por las ocurrencias libres de x en M , denotado por M [x := N ], se define como sigue: x[x := N ] = N y[x := N ] = y, si x 6≡ y (M1 M2 )[x := N ] = (M1 [x := N ]M2 [x := N ]) (λy.M1 )[x := N ] = λy.(M1 [x := N ]) Ejemplo 6 Variables libres y acotadas. Consideren el término λxy.xyz; las variables x e y están acotadas; la variable z es libre. El término λxy.xxy está cerrado. Por convención, si M1 y M2 ocurren en cierto contexto matemático (una definición o una prueba), entonces todas las variables acotadas en esos términos, se eligen para que sean diferentes de las variables libres. Por lo tanto, en la cláusula 2 de la definición de substitución, no es necesario especificar x 6≡ y. Ahora podemos introducir el cálculo lambda como una teoría formal. 5 Def 3 El esquema axiomático principal del cálculo-λ, conocido como axioma β, es: (λx.M )N = M [x := N ] (2) También se incluyen los axiomas de igualdad: M M =N = M ⇒ N =M M = N, N = L ⇒ M = L y reglas de compatibilidad: M = M0 ⇒ M Z = M 0Z 0 ⇒ ZM = ZM 0 M =M M = M0 ⇒ λx.M = λx.M 0 Si M = N es demostrable en el cálculo-λ, algunas veces escribimos λ ` M = N. Como consecuencia de las reglas de compatibilidad, uno puede remplazar (sub) términos por términos iguales en cualquier contexto: M = N ⇒ ...M ··· = ...N ... Por ejemplo, si λ ` (λy.yy)x = xx, entonces: λ ` λx.x((λy.yy)x)x = λx.x(xx)x Lema 1 λ ` (λx1 . . . xn .M )X1 . . . Xn = M [x1 := X1 ] . . . [xn := Xn ]. La demostración es sencilla. Por el axioma β (Ec. ??) tenemos: (λx1 .M )X1 = M [x1 = X1 ] luego, por inducción en n, obtenemos el resultado. 2 Ejemplo 7 Combinadores: I ≡ λx.x K K∗ S ≡ λxy.x ≡ λxy.y ≡ λxyz.zx(yz) 6 usando el lemma ??, obtenemos las siguientes ecuaciones: IM KM N = M = M K∗M N = N SM N L = M L(N L) ahora podemos resolver ecuaciones simples. Teorema 1 Teorema de punto fijo. ∀F ∃XF X = X. Existe un combinador de punto fijo: Y ≡ λf.(λx.f (xx))(λx.f (xx)) tal que: ∀F F (YF ) = YF La demostración pasa por definir W ≡ λx.F (xx) y X ≡ W W . Entonces tenemos que X ≡ W W ≡ (λx.F (xx))W = F (W W ) ≡ F X. 2 Podemos definir numerales y funciones numéricas en el cálculo-λ: Def 4 F n (M ) con F ∈ Λ y n ∈ ℵ se define inductivamente como sigue: F 0 (M ) ≡ F n+1 M ≡ (F (F n (M )) Los numerales de Church c0 , c1 , c2 , . . . se definen por: cn ≡ λf x.f n (x) Es posible definir los combinadores siguientes: A+ ≡ λxypq.xp(ypq) A∗ ≡ λxyz.x(yz) Aexp ≡ λxy.yx Observen que los números de Church están definidos como funciones parciales. Cada numeral espera dos argumentos f y x. Aunque las instancias de estos argumentos son irrelevantes, es más sencillo interpretar estos números si asumimos que f es la función sucesor y x es un símbolo representando el cero. Para ejemplificar esto, asumiendo que nuestro lenguaje Λ es más expresivo que lo que hemos definido hasta el momento, definamos las siguientes funciones: S = λr.ding y aplicar r 7 Z = λr.dong entoces c4 sería ding(ding(ding(ding(dong)))). Claro que si optamos por S como la función sucesor y Z por el símbolo para cero, tendríamos que c4 sería 1 + 1 + 1 + 1 + 0 = 4. Ahora, ¿Porqué definimos los combinadores para suma, multiplicación y exponente de esa forma? 8