0% encontró este documento útil (0 votos)
83 vistas15 páginas

Introducción Al Cálculo Lambda Usando Racket

Este documento introduce el cálculo lambda y su historia, descripción y aplicaciones. Brevemente: 1) El cálculo lambda fue introducido por Alonzo Church en 1936 y es el fundamento de la programación funcional. 2) Todo se basa en funciones anónimas que toman valores de entrada y devuelven valores de salida. 3) Las expresiones lambda se evalúan mediante la sustitución de variables por valores.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
83 vistas15 páginas

Introducción Al Cálculo Lambda Usando Racket

Este documento introduce el cálculo lambda y su historia, descripción y aplicaciones. Brevemente: 1) El cálculo lambda fue introducido por Alonzo Church en 1936 y es el fundamento de la programación funcional. 2) Todo se basa en funciones anónimas que toman valores de entrada y devuelven valores de salida. 3) Las expresiones lambda se evalúan mediante la sustitución de variables por valores.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 15

Introducción al cálculo lambda usando Racket∗

Camilo Chacón Sartori


[email protected]

Actualizado: April 8, 2022

El cálculo lambda es una notación for- alizar computación conocido como a-machines
mal que permite expresar funciones com- (automatic machines)2 en su artículo «On Com-
putables. El cual es el fundamento de putable Numbers» (Turing, 1936). Y el segundo,
la programación funcional. Se define con mostraría la equivalencia de los dos modelos en
la letra Griega lambda (λ) y se expresa
su trabajo sobre la teoría de la recursividad.
a través de expresiones lambda, y térmi-
nos lambda que son usados para represen- Dando paso a dos modelos distintos (pero equiv-
tar binding variables 1 dentro de una fun- alentes) de realizar la computación que actual-
ción. Este documento tiene como objetivo mente se suele llamar: «tesis de Church-Turing»,
ser una introducción básica a los aspectos y fundando así lo que hoy en día se conoce como
teóricos y prácticos del cálculo lambda, y ciencia de la computación.
a la programación funcional. Para esto úl-
timo usaremos el lenguaje de programación
Racket. 1.2 Aplicación
Años después, en la década de 1950 John Mc-
1 Introducción Carthy crearía Lisp que es uno de los primeros
lenguajes de programación. Que a pesar de no
1.1 Historia haber sido basado en el cálculo-λ, el mismo John
McCarthy dijo: «Lisp [...] no fue un intento de
El cálculo lambda (cálculo-λ) fue introducido llevar a la práctica el cálculo lambda, aunque si
por Alonzo Church en su artículo: «An un- alguien hubiera empezado con esa intención, po-
solvable problem of elementary number theory» dría haber terminado con algo como LISP.» (Sz-
(Church, 1936) en la década de 1930. Adicional- mulewicz; Wexelblat, 1978). Es así, como nace
mente fue supervisor de destacados alumnos el paradigma de programación funcional que tra-
como Alan Turing y Stephen Kleen en Prince- baja con máquinas de reducción (Barendregt and
ton. El primero propuso un modelo para re- Barendsen (1984)). Por otro lado, el trabajo

Este documento esta bajo licencia CC-BY-NC 4.0 In-
de Turing derivaría en el paradigma de progra-
ternational license n N O. mación imperativo.
1
Esto significaría «variables vinculantes» o «variables Este documento pretende ser una breve in-
enlazadas» pero prefiero mantener en este documento el
2
concepto en su idioma original para evitar confusiones. Actualmente se le denomina máquina de Turing.

1
troducción al cálculo lambda con un enfoque Cada valor de entrada está asociado a su
aplicado usando un lenguaje de programación valor de salida.
funcional llamado Racket (un lenguaje que es Algo importante a mencionar es que el cálculo-
derivado de Lisp y, a su vez, de Scheme). λ solo admite funciones con un solo valor de
entrada. Cuando queremos tratar con múlti-
ples argumentos entonces se debe utilizar una
2 Descripción propiedad conocida como currying, el cual trans-
forma los argumentos de entrada en una cadena
2.1 Preliminar de funciones con un solo valor. En otras pal-
abras, una función f (x, y, z) = x + y + z que
Todo en cálculo-λ se basa en funciones. Por lo
tiene tres argumentos puede ser reescrita de la
tanto, primero debemos definir que es una fun-
siguiente forma: (x → (y → (z → (x + y + z))),
ción matemática. Una función se puede definir
donde el operador → dar por supuesta una aso-
de la siguiente manera: f : a → b, donde la fun-
ciación a la derecha.
ción f recibe un valor a y devuelve un b. Además
La función se puede evaluar usando aplicación
a y b pueden ser representados como conjuntos
de funciones. Hace referencia a aplicar un
de elementos. Así, la función es una relación
conjunto de argumentos desde el dominio para
entre elementos de dos conjuntos. Para nuestro
obtener el valor a devolver desde el codominio,
ejemplo el conjunto a se le llama dominio y el
en término de programación se diría ejecutar
conjunto b codomino, que representan al con-
una función. Entonces, la evaluación –usando
junto de entrada y salida respectivamente. En
reducción de expresiones– sería la siguiente:
notación conjuntista esta misma función puede
ser definida de la siguiente manera: f (a) = b. ((x, y, z) → x + y + z)(1, 2, 3)
Un ejemplo puede ser una función f que recibe = ((1, 2, 3) → x + y + z)
un número entero que incrementa en 1. En-
= ((1, 2, 3) → 1 + 2 + 3)
tonces, f (x) = x + 1, donde a x se le asigna
el valor 5, es decir: f (5) = 5 + 1 devolviendo 6. = (1 + 2 + 3)
=6

2.2 Motivación Su equivalente en currying:

El cálculo lambda a diferencia con las funciones


matemáticas trata con funciones anónimas, es ((x → (y → (z → x + y + z)))(1)(2))(3)
decir, no existe la necesidad de asignarle un nom- = (y → (z → 1 + y + z))(2))(3)
bre explícito a una función, por ejemplo: = (z → 1 + 2 + z)(3)
= (1 + 2 + 3)(3)
sumar_numeros(x, y) → x + y (1) = (1 + 2 + 3)
Se puede escribir de manera anónima: =6
Intuitivamente, nos podemos dar cuenta que
(x, y) → x + y (2) se va aplicando una substitución (= reemplazo)

2
de las variables a la izquierda (x, y, z) por los caracteres e ir clasificándolas con el lenguaje an-
valores a la derecha (1, 2, 3), que están dentro de teriormente descrito. Te darás cuenta de que
cada expresión3 . cumple con las reglas sintácticas.
Por el contrario, algunas expresiones inválidas
2.3 El cálculo lambda pueden ser: λλ, x.x, λ.., o λ.x. Ahora veremos
como podemos implementar dichas expresiones
El cálculo-λ se puede expresar en un simple en un lenguaje de programación.
lenguaje usando la notación Backus-Naur form Importante: La versión original de cálculo-λ
(que dicho sea de paso, cualquier lenguaje de pro- propuesta por Alonzo Church es libre de tipos
gramación se puede expresar con está notación): (free-type), esto significa que, no existe diferen-
cia entre tipos de datos como en algunos lengua-
hExpri ::= ’λ’ hVar-listi ‘.’ hExpri
jes de programación estáticamente tipado como
| hApp-termi
C++ que posee: int, double, char, string, etc.
hApp-termi ::= hApp-termi hItemi También es importante decir que, en Racket por
| hItemi defecto, es free-type pero puedes usar una ver-
sión modificada del lenguaje que si admite tipos
4 , lo mismo ocurre con el cálculo-λ que tiene una
hItemi ::= hvari
| (hexpri) versión con tipos. En este documento usaremos
la versión free-type en ambos casos.
hVar-listi := hvari Ejemplos en Racket. Las expresiones pre-
| hvari ‘,’ hVar-listi vias podemos probarlas usando el lenguaje de
programación Racket. Para eso, primero debe-
hvari := [a-z]+ mos tener instalado el entorno de desarrollo Dr-
Racket5 . A continuación, podemos ver la lista
Entonces según el lenguaje anteriormente de expresiones válidas representada en código:
definido, las siguientes expresiones lambda
son válidas:
; ; Expr. (3)
( define id
λx.x (3) ( lambda ( x )
λx, y.x (4) x))

λx, y.y (5) ; ; Expr. (4)


( define k
λx, y.x (y) (6) ( lambda ( x y )
λx, y, z.x (y (z)) (7) x))

Un ejercicio para entender que son expresiones ; ; Expr. (5)


válidas radica en verlas como una secuencia de ( define k2
3 4
Puedes probar el cálculo-λ en un intérprete online: https://docs.racket-lang.org/ts-guide
5
https://jacksongl.github.io/files/demo/lambda/ Toda la información al respecto lo puedes encontrar
index.htm en: https://download.racket-lang.org/.

3
( lambda ( x y )
y)) > ( f k2 1 2)
2
; ; Expr. (6)
( define f La ejecución de una función en Racket se re-
( lambda ( x y ) aliza a través de paréntesis seguido por los ar-
x y)) gumentos separados por espacios, por ejemplo:
(nombre_de_función a1 a2 ... an ). Puede, a
; ; Expr. (7) primera vista, parecer raro si vienes de lenguajes
( define f imperativos como C++ o Python, pero créeme,
( lambda ( x y z )
es algo muy divertido y tu visión de la progra-
(x y z) ))
mación podría cambiar (aunque no puedo ase-
Una definición de una función en Racket gurar que sea para bien, ¡no soy el culpable del
comienza con la palabra reservada define, pos- resultado!).
teriormente se usa la palabra reservada lambda
que es equivalente a λ. Por lo tanto, como se
2.3.1 Operadores
puede apreciar el código en Racket es similar al
cálculo-λ. Toda expresión lambda válida es le suele lla-
Sin embargo, –si el lector es cuidadoso– se mar «término lambda». A través de estos tér-
habrá dado cuenta de que existen dos diferencias minos podemos derivar algunos importantes op-
principales: (1) en nuestros ejemplos si debemos eradores de cálculo-λ:
definir un nombre a la función (aunque eso no
significa que Racket no acepte funciones anón- • Abstracción: Es un operador de abstrac-
imas); (2) la sintaxis en Racket se construye ción, por ejemplo, si t es un término lambda
con la notación expresión-S que está basada en y x es una variable, entonces la sigu-
paréntesis (ver aquí6 para más detalles). iente función anónima (λx.t) es un término
Una vez definidas dichas funciones, podemos lambda. Se le llama binding variables a la
ejecutarlas (evaluarlas) en el entorno de progra- variable x en el término t. Notación formal:
mación: Se dice que un operador es de abstracción,
> ( id 1) si T ≡ T [x] es un término que depende de
1 x. Entonces λx.T [x] equivale a x → T [x].
Por ejemplo: (λx.1 ∗ x + 2)10 = 1 ∗ 10 + 2
> ( k 1 2) = 12.
1
• Aplicación: Es un operador de aplicación,
> ( k2 1 2) si por ejemplo, t y k son términos lamb-
2 das, entonces (t k) es un término lambda.
Esto significa que tenemos una función t
> ( f id 1)
1
con argumento k. (Podemos ver aquí que
el operador aplicación es similar a la man-
6
https://es.wikipedia.org/wiki/Expresión_S era que se ejecuta una función en Racket.)

4
Notación formal: Si tenemos un término de entrada:
(λx.T )V = T [x := V ] donde [x := V ] es
una substitución de V por x. Por ejemplo: (λx.10)2
(λx.x)1 = 1. = 10
Entonces, podemos concluir que cada oper-
ador también es un término lambda.
Importante: Hasta aquí el lector se pudo Este resultado (devuelve 10 y descarta el 2) es
percatar de algo que es fundamental en la pro- curioso y es interesante mantenerlo en cuenta,
gramación funcional: la inmutabilidad, la cual por eso, vea con mucha atención el siguiente
se refleja en los operadores de cálculo-λ que, ejemplo. En el segundo caso, se define una fun-
por ejemplo, realizado una substitución esta es ción que devuelve otra función y aplica, por ejem-
una operación atómica, es decir, posterior a re- plo:
alizarse no es posible modificar dicho valor.
Antes de continuar es importante aclarar el
concepto de «variable» en el cálculo-λ, –el cual (λx.(λx.x))x
dicta mucho de ser equivalente a lo conocido en = (λx.(λx.x))10
un lenguaje de programación imperativo–, por
= ((λx.(λx.x))10)
ejemplo, el siguiente término: λx.x + y, en el
cálculo-λ trata a y como una variable que to- = (λx.x)
davía no ha sido definida, y es totalmente válido.
No así, sucede en lenguajes de programación im- Vemos que devuelve una función identidad
perativos donde si tenemos una variable z dentro (una función identidad es la que devuelve siem-
de una función, y esta variable no fue declarada pre el mismo valor de entrada), o sea, ahora si
de manera global ni fue definida como argu- (a diferencia del primer ejemplo) va a devolver
mento, no es válida (ya que, de por sí, se supone el mismo valor de entrada:
que no existe).
Por otro lado, se puede utilizar los paréntesis
para evitar ambigüedades al momento de definir (λx.x)2
términos. Por ejemplo, si tenemos un término =2
(1) λx.((λx.x)x) y (2) (λx.(λx.x))x. No son
iguales. ¿Por qué? El primero define dos fun- Con esto, nos podemos percatar de que los tér-
ciones que a su vez define a otra interna que minos lambda pueden operar de manera difer-
aplica y devuelve, por ejemplo: ente si utilizamos los paréntesis en un orden dis-
λx.((λx.x)x) tinto. Por eso de su importancia, y que nos hace
= λx.((λx.x)10) pensar –de manera implícita– lo cuidadoso que
debemos ser a la hora de definir términos. Esta
= (λx.10)
precaución también es equivalente para cuando
Ahora, podemos hacer uso de dicha función re- escribamos algoritmos con Racket.
sultante y entregarle el valor 2 como argumento Algunas consideraciones que cabe señalar:

5
• Si tenemos, por ejemplo (λx.x)y representa
una función identidad aplicada a y. Tam- (λx.(λy.x y)n)[x := (λx.10)]
bién una función de aplicación tiene aso- = (λx.((λy.(x y))n))(λx.10)
ciación a la izquierda, es decir, si tenemos = (λ(λx.10).((λy.(x y))n))
el término «a b c» equivale a: «(a b) c». (10)
= ((λy.((λx.10) y))n)
= (λx.10)[x := n]
• Una función (λx.y) se le considera con-
= 10
stante y es diferente a la anterior, dado
que siempre devolverá y independiente del Algunas aclaraciones, en (8) la variable x es
valor a aplicarse a x. En consecuencia, x se bound y y es free; en (9) el argumento es una
descarta. función de identidad que hace una substitución
de x; y en (10) que, a su vez, es el ejemplo
más interesante, el argumento es una función
2.3.2 Free y bound variables constante (cuando una función es argumento de
otra, se le llama: high-order function 7 ), donde n
Son los dos tipos de variables en cálculo-λ; es free, por tanto, descarta a la variable anidada
por ejemplo: λx.x y, la variable x es bound y. (Nuestros amigos programadores imperativos
y la y es free, la substitución [x := V ] se espero, solo espero, que no crean que esto sea
aplica a x donde V es un argumento de en- magia. Espero.)
trada. Es decir, las variables que no son bound Ejemplos en Racket. Ahora utilicemos el
(definidas en la función) son free; otro ejemplo: conocimiento adquirido y llevémoslo a código.
«λxy.x y z», donde x e y son bound y z es free. También debemos recordar que Racket está in-
(Además las variables puede ser un conjunto in- fluenciado por el cálculo-λ, lo que quiere decir
finito λx1 , ..., xn .(x1 , ..., xn ).) que, no es lo mismo. Por tanto, hay algunas
Entonces decimos que una operación de ab- diferencias que tenemos tener en cuenta.
stracción realiza bind a una bound o free vari-
ables en un término lambda. ; ; Expr. (8)
A continuación presentamos tres ejemplos de
( define y 1)
variables bound y free y después lo aplicaremos ( define f
en Racket. ( lambda ( x )
(λx.x y)[x := 1] x y))
(8)
= (1 y) ; ; Aplicaci ó n ( ejecuci ó n )
> ( id 2)
1
(λx.x 1)[x := (λx.x)]
En este ejemplo, ¿cuáles son las diferencias a
= (λ(λx.x).x 1)
(9) cálculo-λ?
= (λx.x)1 7
https://es.wikipedia.org/wiki/Función_de_
=1 orden_superior

6
• En Racket debemos definir previamente las ( define n 0)
free variables, o si no no será posible com-
pilar. Aunque como se ve, no está definida ( define c1
en el cuerpo de la función f. ( lambda ( x )
10) )
• El término final: x y, en Racket, al no es-
tar entre paréntesis, devuelve siempre la úl- ( define f2
( lambda ( x )
tima variable (a la derecha), por eso en este
(( lambda ( y )
caso descarta 2 y devuelve 1. En cambio, si (x y)) n)
el término final hubiera sido: (x y), se da )
por supuesto un intento de usar operador )
de aplicación (ejecución de una función), lo
cual Racket interpretaría el valor 1 como ; ; Aplicaci ó n ( ejecuci ó n )
una función (= procedimiento en Racket), > ( f2 c1 )
10
pero como 1 no es una función, daría un er-
ror.

La expresión (9) es mucho más fácil de intuir


dado que el comportamiento es similar, tanto en 2.3.3 Reducción
cálculo-λ como en Racket:
Conversión α. Ya hemos visto como funciona
; ; Expr. (9)
una substitución para hacer uso de variables
( define id bound y free. Pero debemos tener cuidado para
( lambda ( x ) no generar ambigüedades, por ejemplo, en la
x)) siguiente expresión:

( define c
( lambda ( x ) (λx.(λy.y x))y
( x 1) ) ) = (λy.(λy.y y)) (11)
; ; Aplicaci ó n ( ejecuci ó n ) = ERROR
> ( c id )
1 Esta substitución contiene un error porque el
Para finalizar, la expresión (10) que tiene la y que se aplica pasa a ser la variable bound a la
función f 2 tiene una función anidada; donde n izquierda, pero no es la misma y que se encuentra
es free y x al ser bound substituye la variable x a la derecha dentro de la función interna.
dentro de la función anidada; devolviendo (c1 y); ¿Cómo podemos solucionar esto? Fácil.
y dado que c1 es una función constante (no im- Renombrando las bound variables que crean am-
porta el argumento de entrada porque siempre bigüedad. En este caso, se renombra la variable
devolverá lo mismo) y se descarta y devuelve 10. y de la función interna por z.
; ; Expr. (10) La expresión quedaría así:

7
extender el uso de una función. Por ejemplo la
(λx.(λz.z x))y función λx.(z x) = T es equivalente a la función
= (λy.(λz.z y)) λz.T = T si x es bound.
(12) Ejemplo en Racket. Si tenemos dos fun-
= (λz.z y)
cione: f1 y f2 , al aplicar conversión-η de f1 a
= OK
f2 :
Está confusión, en Racket no es un problema.
Porque lo soluciona a través del uso de sus parén-
( define f1
tesis de cada scope (Ver imagen 1).
( lambda ( x )
( f2 x ) ) )

( define f2
( lambda ( x )
( list x ) ) )
(a) x externa. (b) x interna.
; ; Aplicaci ó n ( ejecuci ó n )
Figure 1: Tratamiento de ambigüedades en > ( equal ? ( f1 2) ( f2 2) )
Racket. # t ; ; True

Aunque eso no significa, en lo absoluto, que Algunas cosas interesantes de este ejemplo:
sea correcto. Siempre se debe procurar escribir
nombre de variables idóneos, ya sea en Racket o • La función list crea una lista en Racket.
en cualquier lenguaje de programación.
• La función equal? compara dos expresiones;
Reducción β. La reducción beta es similar a
y si son iguales devuelve #t (= verdadero);
un paso de computación dentro de un algoritmo.
en caso contrario devolverá #f (= falso).
Esto se puede ver en la expresión anterior (12),
donde se va aplicando reducción hasta cuando ya • Dado similares argumentos a la función f1
no es posible continuar, pero, en el cálculo-λ free- y f2 devuelve el mismo resultado. En este
type (que hemos estudiado en este documento) caso devuelve la misma lista: 0 (2). En con-
la reducción podría ser infinita (= no terminar). secuencia, la función f1 extiende de f2 .
Por ejemplo:
2.3.4 Aritmética
(λx.x x) (λx.x x)
Una de las características del cálculo-λ es per-
= (λx.(λx.x x) (λx.x x))
mitir modelar desde operaciones lógicas pasando
= (λx.(λx.(λx.x x) (λx.x x)) (λx.(λx.x x) (λx.x x)))
por estructura de datos a operaciones aritméti-
= recursión infinita cas. Para esto último, primero, debemos repre-
(13) sentar los números usando la Church numerals,
Conversión η. Es cuando dos funciones son que nos permite representar un número natural
equivalente, si y solo si, dado todos los argu- dentro del cálculo-λ en pos de operar con es-
mentos devuelven lo mismo. Es una forma de tos en nuestras funciones aritméticas. (Para los

8
siguientes ejemplos se recomienda utilizar el eval- ((n f ) x) es equivalente a (n f x) dado que se
uador online para cálculo-λ8 .) agrupa hacia la izquierda la aplicación.
Church numerals. Cada número natural lo Multiplicación. Es similar a la suma, dado
podemos representar como funciones anidadas que una multiplicación entre m y n significa la
de la siguiente forma: suma de m por n veces.

0 = λf.λx.x
M U LT = λm.λn.λf.m (n f )
1 = λf.λx.f x
M U LT 2 = λm.λn.m (P LU S n) 0 (16)
2 = λf.λx.f (f x) (14)
M U LT ≡ M U LT 2
3 = λf.λx.f (f (f x))
n = λf.λx.f1 (f2 (...(fn x))) Predecesor. Dado que los numerales
Como vimos en algunos ejemplos anteriores, definidos solo admiten valores enteros positivos,
aquí hacemos uso de high-order functions o sea en caso de la resta debemos definir previamente
de funciones que se utilizan como argumento. En la función predecesora (P RED n → n − 1) que
(14) hay dos cuestiones relevantes: (1) el argu- se utiliza para cualquier valor superior a 0.
mento f es una función y (2) siempre se devuelve
una única función (que internamente puede tener
P RED = λn.λf.λx.n (λg.λh.h (g f )) (λu.x) (λu.u)
n funciones, pero, siempre tiene una función de
(17)
inicio de «cadena» de funciones que es exclu-
Esta función genera muchas derivaciones, por
siva). Las siguientes operaciones han sido an-
lo cual lo hace difícil entender cada paso
teriormente definidas en artículo de Wikipedia
(¡pero lo intentaremos!), así si queremos hacer:
de cálculo-λ en inglés y en (Rojas, 2015).
(P RED 1 → 0), la derivación debería ser la sigu-
Suma. Con una composición podemos hacer
iente (están subrayadas las substituciones para
la suma entre dos números naturales, por ejem-
que sea más fácil de seguir):
plo la idea sería tener una función SUMA que
al aplicar sea así: (SU M A 1 2) → 3. Podemos
definir dicha función así:

SU M A = λm.λn.λf.λx. m f ((n f ) x)) (15)

Si reemplazamos la bound variable m por


su función correspondiente (numeral 1) sería:
«λf.λx.f x», lo mismo ocurre con m para el nu-
meral 2. Podemos ver que, el número total de
funciones anidadas que se generan son la suma
de los dos argumentos. Por otro lado, el término:
8
https://jacksongl.github.io/files/demo/
lambda/index.htm

9
(λf.λx.f x) = 1
(λn.λf.λx.((n (λg.λh.h(gf ))) (λu.x)) (λu.u)) (λf.λx.f x)
= ((λn.(λf.(λx.(((n (λg.(λh.(h (g f ))))) (λu.x))(λx0.x0))))) (λx1.(λx2.(x1 x2))))
= (λf.(λx.((((λx1.(λx2.(x1 x2))) (λg.(λh.(h (g f ))))) (λu.x)) (λx0.x0))))
= (λf.(λx.(((λx2.((λg.(λh.(h (g f )))) x2)) (λu.x)) (λx0.x0))))
(18)
= (λf.(λx.(((λg.(λh.(h (g f )))) (λu.x)) (λx0.x0))))
= (λf.(λx.((λh.(h ((λu.x) f ))) (λx0.x0))))
= (λf.(λx.((λx0.x0) ((λu.x) f ))))
= (λf.(λx.((λu.x) f )))
= (λf.(λx.x)) = 0

(Esta derivación fue obtenida usando: https://jacksongl.github.io/files/demo/lambda/


index.htm)

10
Resta. Con esto ya podemos definirla: Por lo demás, es la estructura de datos más pop-
ular en Racket.
REST A = λm.λn.n P RED m (19)
Entonces definidas las operaciones aritméti- ( list ) ; Lista vac í as
( list 1 2 3) ; Tres n ú meros
cas, ya podemos dar por entendido el mecanismo
( list " x " " y " " z " ) ; Tres caracteres
de derivación del cálculo-λ, al menos a un nivel ( list 1 2 ( list ( list ) " x " " y "
inicial, cuestiones como la división queda a tra- ( list 1 0 1) " z " ) ) ; Listas
bajo del curioso lector. Por ahora, veamos como anidadas
estos operadores funcionan en Racket, que dicho
sea de paso, es mucho más simple. Una notación más simple es remover la pal-
abra list y anteponer unas comillas simples «’» al
Ejemplos en Racket. Ahora veamos la fa-
paréntesis de la izquierda «(». A continuación se
cilidad de utilizar los mismos operadores ya im-
presentan las declaraciones que son equivalentes
plementados en Racket:
a las anteriores pero con la nueva notación:
'()
'(1 2 3)
> (+ 1 2)
'( " x " " y " " z " )
3
'(1 2 ( '() " x " " y " '(1 0 1) " z " ) )
> (- 3 1)
2 Funciones car10 y cdr11 . Son operaciones que
> (* 4 (* 4 10) ) se pueden aplicar sobre listas. Primero, car se
160
utiliza para devolver el primer elemento de la
> (+ 1 (/ 10 5) )
3 lista; Segundo, cdr devuelve la lista sin el primer
elemento. Por ejemplo:
Podemos ver que Racket usa prefix notation (=
notación polaca) 9 junto a la expresión-S.
> ( define x '( " x " " y " " z " ) )
> ( car x )
2.4 Racket "x"

En esta sección final nos adentraremos –y > ( cdr x )


relajaremos– con Racket, usando los conocimien- '( " y " " z " )
tos ya adquiridos de cálculo-λ.
> ( cdr ( cdr x ) )
'( " z " )
2.4.1 Estructuras de datos
Listas. Las listas son una secuencia de elemen- > ( cdr ( cdr ( cdr x ) ) )
'()
tos (sin acceso aleatorio ni índice) que aceptan
cualquier tipo de dato y, a su vez, son inmutables 10
car = «Contents of the Address part of Register num-
(o sea no puede modificarse una vez creadas). ber».
11
cdr = «Contents of the Decrement part of Register
9
https://es.wikipedia.org/wiki/notación_polaca number».

11
Función cons. Permite concatenar dos valores clave-valor sin haber modificado la original. Con
(independiente del tipo de valor). la función hash-ref podemos acceder al valor de
una clave en particular: (hash-ref h 1) = 10.
> ( cons 0 '(2 3) ) Inmutable. Racket también soporta estruc-
'(0 2 3) tura de datos inmutables, lo que es muy útil en
algunos casos.
> ( car ( cons 5 2) )
( define hi ( m ak e - im m u ta b l e- h a sh
5
'([1 . " b " ][2 . " a " ]) ) ) ; Se
crea la tabla hash h con dos
> ( cdr ( cons '(5) '() ) )
elementos.
'()
> ( hash-ref hi 1)
"b"
> ( equal ? ( cdr ( cons '(5) '(1) ) )
> ( hash-set hi 3 2) ; crea una
( cdr ( cons '(5) 1) ) )
nueva tabla hash.
#f
'# hash ((1 . " b " ) (2 . " a " ) (3 . 2) )
> ( hash-set! hi 3 2)
> ( equal ? ( cdr ( cons '(5) '(1) ) )
. . hash-set!: contract violation
( cdr ( cons '(5) '(1) ) ) )
expected: ( and/c hash ? ( not/c
#t
immutable ?) )
Tabla hash. Esta estructura de datos per- given: '# hash ((1 . " b " ) (2 . " a " ) )
mite asociar una clave con un valor, por ejemplo argument position: 1 st
other arguments...:
1 → 10, cuando queramos acceder a la clave 1
siempre devolverá 10. En Racket existen varias Antes mencionamos que una estructura in-
variantes de tabla hash: una mutable y otra in- mutable no puede ser modificada después de su
mutable. Veremos ejemplos de cada una. creación, y como vemos en el ejemplo anterior
Mutable. Una vez creada está puede ser mod- cuando hacemos uso de la función hash-set! el
ificada: ya sea el valor de una clave o incluso intérprete de Racket arroja un error.
borrar toda la tabla hash. Con listas y hash ya tenemos suficiente para
( define h ( make-hash ) ) ; Se crea la comenzar a ver como construir algoritmos en
tabla hash h. Racket, en donde aplicaremos constantemente
( hash-set! h 1 10) operaciones sobre listas utilizando recursividad.
( hash-set! h 2 '(1 2 3) ) (También existen otras estructuras como: pair,
( hash-set! h '(0 1) " binary " ) vector y listas asociadas. Pero se lo dejamos al
> h
interés y curiosidad del lector.)
'# hash (((0 1) . " binary " ) (2 . (1 2
3) ) (1 . 10) )
2.4.2 Algoritmos
La función hash-set nos permite agregar una
nueva clave-valor a la variable h, se agrega el Es momento de ver algunos algoritmos utilizando
«!» al final cuando se trata de una estructura Racket, espero que, hasta el momento, el lec-
mutable, en el caso opuesto, si omitimos el ! va tor allá encontrado interesante aprender Racket,
a devolver una nueva tabla hash con la nueva pero aún falta lo mejor, y lo mejor, espero que

12
pueda ser visible en este apartado. > ( word-count '( " aaa " " a " " bbb " " a "
Fibonacci. Una manera para adentrarse en " aaa " " y " ) " aaa " 0)
la recursividad es aprender a implementar fi- 2
bonacci. Recordemos las funciones de cómo > ( word-count '( " aaa " " a " " bbb " " a "
" aaa " " y " ) " y " 0)
opera el algoritmo: (1) f0 = 0; (2) f1 = 1; y
1
(3) fn = fn−1 + fn−2 . En Racket la función cond
es una condicional múltiple, es decir, if-else if. Lista inversa. ¿Qué tal si queremos revertir
el orden de una lista? Claro, en Racket por de-
( define fib fecto ya existe la función reverse que hace esto,
( lambda ( n ) pero, en este caso queremos implementarla por
( cond nosotros mismos. Una, de las tantas formas de
((= n 0) 0) ; if hacerlo, es la siguiente:
((= n 1) 1) ; else if
(( > n 0) (+ ( fib (- n 1) )
( fib (- n 2) ) ) ) ; else if ( define last-element
) ( lambda ( list )
) ( cond
) [(= ( length list ) 1) list ]
[( last-element ( cdr list ) ) ]
> ( fib 9) )))
34
( define new-reverse
La primera expresión en cond es ((= n 0) 0), ( lambda ( old-list new-list )
de tal forma de que si se cumple (= n 0), en- ( cond
tonces devolvera 0. De la misma manera ocurre [( null ? old-list ) new-list ]
para las otras dos expresiones. [( new-reverse
Contar palabras. La idea es contar las veces ( take old-list (-
( length old-list )
que una palabra (entregada como argumento) se
1) ) ( append new-list
repite dentro de una lista. ( last-element
old-list ) ) ) ]
( define word-count )
( lambda ( list search count ) )
( cond )
[( null ? list ) count ]
[( eq ? ( car list ) search ) > ( new-reverse '( " aaa " " bbb " " a "
( word-count ( cdr list ) " y " ) '() )
search (+ count 1) ) ] '( " y " " a " " bbb " " aaa " )
[( word-count ( cdr list )
Algunas apreciaciones con respecto a este úl-
search count ) ]
) timo algoritmo:
)
) • Aplicamos descomposición a nuestro al-
goritmo, o sea, creamos una segunda fun-

13
ción last-element que aplica una funcional- 4 Más información
idad en específico. Esto nos ayuda a re-
utilizar dicha función a futuro y que, no Para una versión completa y detallada de este tu-
este ligada solamente a nuestra función new- torial podría pensar en adquirir mi libro: «Com-
reverse (además de hacerlo más legible). putación y programación funcional», publicado
por la editorial Marcombo, 2021. En el cual,
• La función take toma los primeros n el- además de usar Racket, incorporó Python para
ementos de una lista. Por ejemplo: los ejemplos.
(take (1 2 3) 2) = (1 2).
• La función length devuelve el tamaño de la
lista.
• La función append agrega un elemento a la
lista.

3 Conclusión
En este documento hicimos una introducción
al cálculo-λ libre de tipos (free-types) que, a
su vez, se puede ver como una introducción al
paradigma de programación funcional.
Revisamos los siguientes conceptos:
• Los operadores principales.
• Las bound y free variables.
• La operación de reducción.
• Modelado de operaciones aritméticas.
Por otro lado, cada operación en cálculo-λ lo Referencias
explicamos con su equivalente en el lenguaje de
programación Racket, además de dar una intro- H. H. Barendregt and E. Barendsen. Introduc-
ducción a las estructuras de datos del lenguaje tion to lambda calculus. Nieuw archief voor
aplicadas a algunos tipos de algoritmos, donde wisenkunde, 4:337–372, 01 1984.
prevalece la inmutabilidad y la recursividad,
componentes principales en la programación fun- A. Church. An unsolvable problem of ele-
cional. mentary number theory. American Jour-
Solo me queda por decir: De parvis grandis nal of Mathematics, 58(2):pp. 345–363, 1936.
acervus erit. («De las cosas pequeñas se nutren ISSN 00029327. URL http://www.jstor.
las cosas grandes».) org/stable/2371045.

14
R. Rojas. A tutorial introduction to the lambda
calculus, 2015.

D. Szmulewicz. URL https://danielsz.


github.io/blog/2019-08-05T21_14.html.

A. M. Turing. On computable numbers, with


an application to the Entscheidungsproblem.
Proceedings of the London Mathematical Soci-
ety, 2(42):230–265, 1936.

R. L. Wexelblat, editor. History of Program-


ming Languages. Association for Computing
Machinery, New York, NY, USA, 1978. ISBN
0127450408.

Wikipedia. Lambda calculus - wikipedia, the free


encyclopedia.

15

También podría gustarte