Technical ReportPDF Available

Introducción al cálculo lambda usando Racket

Authors:

Abstract

El cálculo lambda es un notación formal que permite expresar funciones computa-bles. El cual es el fundamento de la programación funcional. Se define con la letra Griega lambda (λ) y se expresa a través de expresiones lambda, y términos lambda que son usados para representar binding variables 1 dentro de una función. Este documento pretender ser una introducción básica a los aspectos teóricos y prácticos del cálculo lambda, y a la programación funcional. Para esto último usaremos el lenguaje de programación Racket.
Introducción al cálculo lambda usando Racket
Camilo Chacón Sartori
camilochs@gmail.com
Actualizado: April 9, 2022
El cálculo lambda es una notación for-
mal que permite expresar funciones com-
putables. El cual es el fundamento de
la programación funcional. Se define con
la letra Griega lambda (λ) y se expresa
a través de expresiones lambda, y térmi-
nos lambda que son usados para represen-
tar binding variables1dentro de una fun-
ción. Este documento tiene como objetivo
ser una introducción básica a los aspectos
teóricos y prácticos del cálculo lambda, y
a la programación funcional. Para esto úl-
timo usaremos el lenguaje de programación
Racket.
1 Introducción
1.1 Historia
El cálculo lambda (cálculo-λ) fue introducido
por Alonzo Church en su artículo: «An un-
solvable problem of elementary number theory»
(Church, 1936) en la década de 1930. Adicional-
mente fue supervisor de destacados alumnos
como Alan Turing y Stephen Kleen en Prince-
ton. El primero propuso un modelo para re-
Este documento esta bajo licencia CC-BY-NC 4.0 In-
ternational license nN O.
1Esto significaría «variables vinculantes» o «variables
enlazadas» pero prefiero mantener en este documento el
concepto en su idioma original para evitar confusiones.
alizar computación conocido como a-machines
(automatic machines)2en su artículo «On Com-
putable Numbers» (Turing, 1936). Y el segundo,
mostraría la equivalencia de los dos modelos en
su trabajo sobre la teoría de la recursividad.
Dando paso a dos modelos distintos (pero equiv-
alentes) de realizar la computación que actual-
mente se suele llamar: «tesis de Church-Turing»,
y fundando así lo que hoy en día se conoce como
ciencia de la computación.
1.2 Aplicación
Años después, en la década de 1950 John Mc-
Carthy crearía Lisp que es uno de los primeros
lenguajes de programación. Que a pesar de no
haber sido basado en el cálculo-λ, el mismo John
McCarthy dijo: «Lisp [...] no fue un intento de
llevar a la práctica el cálculo lambda, aunque si
alguien hubiera empezado con esa intención, po-
dría haber terminado con algo como LISP.» (Sz-
mulewicz; Wexelblat, 1978). Es así, como nace
el paradigma de programación funcional que tra-
baja con máquinas de reducción (Barendregt and
Barendsen (1984)). Por otro lado, el trabajo
de Turing derivaría en el paradigma de progra-
mación imperativo.
Este documento pretende ser una breve in-
2Actualmente se le denomina máquina de Turing.
1
troducción al cálculo lambda con un enfoque
aplicado usando un lenguaje de programación
funcional llamado Racket (un lenguaje que es
derivado de Lisp y, a su vez, de Scheme).
2 Descripción
2.1 Preliminar
Todo en cálculo-λse basa en funciones. Por lo
tanto, primero debemos definir que es una fun-
ción matemática. Una función se puede definir
de la siguiente manera: f:ab, donde la fun-
ción frecibe un valor ay devuelve un b. Además
aybpueden ser representados como conjuntos
de elementos. Así, la función es una relación
entre elementos de dos conjuntos. Para nuestro
ejemplo el conjunto ase le llama dominio y el
conjunto bcodomino, que representan al con-
junto de entrada y salida respectivamente. En
notación conjuntista esta misma función puede
ser definida de la siguiente manera: f(a) = b.
Un ejemplo puede ser una función fque recibe
un número entero que incrementa en 1. En-
tonces, f(x) = x+ 1, donde a xse le asigna
el valor 5, es decir: f(5) = 5 + 1 devolviendo 6.
2.2 Motivación
El cálculo lambda a diferencia con las funciones
matemáticas trata con funciones anónimas, es
decir, no existe la necesidad de asignarle un nom-
bre explícito a una función, por ejemplo:
sumar_numeros(x, y)x+y(1)
Se puede escribir de manera anónima:
(x, y)x+y(2)
Cada valor de entrada está asociado a su
valor de salida.
Algo importante a mencionar es que el cálculo-
λsolo admite funciones con un solo valor de
entrada. Cuando queremos tratar con múlti-
ples argumentos entonces se debe utilizar una
propiedad conocida como currying, el cual trans-
forma los argumentos de entrada en una cadena
de funciones con un solo valor. En otras pal-
abras, una función f(x, y, z) = x+y+zque
tiene tres argumentos puede ser reescrita de la
siguiente forma: (x(y(z(x+y+z))),
donde el operador dar por supuesta una aso-
ciación a la derecha.
La función se puede evaluar usando aplicación
de funciones. Hace referencia a aplicar un
conjunto de argumentos desde el dominio para
obtener el valor a devolver desde el codominio,
en término de programación se diría ejecutar
una función. Entonces, la evaluación –usando
reducción de expresiones– sería la siguiente:
((x, y, z)x+y+z)(1,2,3)
= ((1,2,3) x+y+z)
= ((1,2,3) 1 + 2 + 3)
= (1 + 2 + 3)
= 6
Su equivalente en currying:
((x(y(zx+y+z)))(1)(2))(3)
= (y(z1 + y+z))(2))(3)
= (z1 + 2 + z)(3)
= (1 + 2 + 3)(3)
= (1 + 2 + 3)
= 6
Intuitivamente, nos podemos dar cuenta que
se va aplicando una substitución (= reemplazo)
2
de las variables a la izquierda (x, y, z) por los
valores a la derecha (1,2,3), que están dentro de
cada expresión3.
2.3 El cálculo lambda
El cálculo-λse puede expresar en un simple
lenguaje usando la notación Backus-Naur form
(que dicho sea de paso, cualquier lenguaje de pro-
gramación se puede expresar con está notación):
hExpri::= ’λhVar-listi.hExpri
|hApp-termi
hApp-termi::= hApp-termi hItemi
|hItemi
hItemi::= hvar i
| (hexpri)
hVar-listi:= hvari
|hvari,hVar-listi
hvari:= [a-z]+
Entonces según el lenguaje anteriormente
definido, las siguientes expresiones lambda
son válidas:
λx.x (3)
λx, y.x (4)
λx, y.y (5)
λx, y.x (y)(6)
λx, y, z.x (y(z)) (7)
Un ejercicio para entender que son expresiones
válidas radica en verlas como una secuencia de
3Puedes probar el cálculo-λen un intérprete online:
https://jacksongl.github.io/files/demo/lambda/
index.htm
caracteres e ir clasificándolas con el lenguaje an-
teriormente descrito. Te darás cuenta de que
cumple con las reglas sintácticas.
Por el contrario, algunas expresiones inválidas
pueden ser: λλ,x.x,λ.., o λ.x. Ahora veremos
como podemos implementar dichas expresiones
en un lenguaje de programación.
Importante: La versión original de cálculo-λ
propuesta por Alonzo Church es libre de tipos
(free-type), esto significa que, no existe diferen-
cia entre tipos de datos como en algunos lengua-
jes de programación estáticamente tipado como
C++ que posee: int, double, char, string, etc.
También es importante decir que, en Racket por
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
versión con tipos. En este documento usaremos
la versión free-type en ambos casos.
Ejemplos en Racket. Las expresiones pre-
vias podemos probarlas usando el lenguaje de
programación Racket. Para eso, primero debe-
mos tener instalado el entorno de desarrollo Dr-
Racket5. A continuación, podemos ver la lista
de expresiones válidas representada en código:
;; Ex p r . (3 )
(define id
(lambda (x)
x))
;; Ex p r . (4 )
(define k
(lambda (x y )
x))
;; Ex p r . (5 )
(define k2
4https://docs.racket-lang.org/ts-guide
5Toda la información al respecto lo puedes encontrar
en: https://download.racket-lang.org/.
3
(lambda (x y )
y))
;; Ex p r . (6 )
(define f
(lambda (x y )
x y) )
;; Ex p r . (7 )
(define f
(lambda (x y z )
(x y z ) ) )
Una definición de una función en Racket
comienza con la palabra reservada define, pos-
teriormente se usa la palabra reservada lambda
que es equivalente a λ. Por lo tanto, como se
puede apreciar el código en Racket es similar al
cálculo-λ.
Sin embargo, –si el lector es cuidadoso– se
habrá dado cuenta de que existen dos diferencias
principales: (1) en nuestros ejemplos si debemos
definir un nombre a la función (aunque eso no
significa que Racket no acepte funciones anón-
imas); (2) la sintaxis en Racket se construye
con la notación expresión-S que está basada en
paréntesis (ver aquí6para más detalles).
Una vez definidas dichas funciones, podemos
ejecutarlas (evaluarlas) en el entorno de progra-
mación:
> ( i d 1 )
1
> (k 1 2)
1
> ( k 2 1 2)
2
> (f i d 1 )
1
6https://es.wikipedia.org/wiki/Expresión_S
> (f k 2 1 2 )
2
La ejecución de una función en Racket se re-
aliza a través de paréntesis seguido por los ar-
gumentos separados por espacios, por ejemplo:
(nombre_de_función a1a2... an). Puede, a
primera vista, parecer raro si vienes de lenguajes
imperativos como C++ o Python, pero créeme,
es algo muy divertido y tu visión de la progra-
mación podría cambiar (aunque no puedo ase-
gurar que sea para bien, ¡no soy el culpable del
resultado!).
2.3.1 Operadores
Toda expresión lambda válida es le suele lla-
mar «término lambda». A través de estos tér-
minos podemos derivar algunos importantes op-
eradores de cálculo-λ:
Abstracción: Es un operador de abstrac-
ción, por ejemplo, si tes un término lambda
yxes una variable, entonces la sigu-
iente función anónima (λx.t)es un término
lambda. Se le llama binding variables a la
variable xen el término t. Notación formal:
Se dice que un operador es de abstracción,
si TT[x]es un término que depende de
x. Entonces λx.T [x]equivale a xT[x].
Por ejemplo: (λx.1x+ 2)10 =110 + 2
= 12.
Aplicación: Es un operador de aplicación,
si por ejemplo, tykson términos lamb-
das, entonces (t k)es un término lambda.
Esto significa que tenemos una función t
con argumento k. (Podemos ver aquí que
el operador aplicación es similar a la man-
era que se ejecuta una función en Racket.)
4
Notación formal: Si tenemos un término
(λx.T )V=T[x:= V]donde [x:= V]es
una substitución de Vpor x. Por ejemplo:
(λx.x)1 = 1.
Entonces, podemos concluir que cada oper-
ador también es un término lambda.
Importante: Hasta aquí el lector se pudo
percatar de algo que es fundamental en la pro-
gramación funcional: la inmutabilidad, la cual
se refleja en los operadores de cálculo-λque,
por ejemplo, realizado una substitución esta es
una operación atómica, es decir, posterior a re-
alizarse no es posible modificar dicho valor.
Antes de continuar es importante aclarar el
concepto de «variable» en el cálculo-λ, –el cual
dicta mucho de ser equivalente a lo conocido en
un lenguaje de programación imperativo–, por
ejemplo, el siguiente término: λx.x +y, en el
cálculo-λtrata a ycomo una variable que to-
davía no ha sido definida, y es totalmente válido.
No así, sucede en lenguajes de programación im-
perativos donde si tenemos una variable zdentro
de una función, y esta variable no fue declarada
de manera global ni fue definida como argu-
mento, no es válida (ya que, de por sí, se supone
que no existe).
Por otro lado, se puede utilizar los paréntesis
para evitar ambigüedades al momento de definir
términos. Por ejemplo, si tenemos un término
(1) λx.((λx.x)x)y (2) (λx.(λx.x))x. No son
iguales. ¿Por qué? El primero define dos fun-
ciones que a su vez define a otra interna que
aplica y devuelve, por ejemplo:
λx.((λx.x)x)
=λx.((λx.x)10)
= (λx.10)
Ahora, podemos hacer uso de dicha función re-
sultante y entregarle el valor 2 como argumento
de entrada:
(λx.10)2
= 10
Este resultado (devuelve 10 y descarta el 2) es
curioso y es interesante mantenerlo en cuenta,
por eso, vea con mucha atención el siguiente
ejemplo. En el segundo caso, se define una fun-
ción que devuelve otra función y aplica, por ejem-
plo:
(λx.(λx.x))x
= (λx.(λx.x))10
= ((λx.(λx.x))10)
= (λx.x)
Vemos que devuelve una función identidad
(una función identidad es la que devuelve siem-
pre el mismo valor de entrada), o sea, ahora si
(a diferencia del primer ejemplo) va a devolver
el mismo valor de entrada:
(λx.x)2
= 2
Con esto, nos podemos percatar de que los tér-
minos lambda pueden operar de manera difer-
ente si utilizamos los paréntesis en un orden dis-
tinto. Por eso de su importancia, y que nos hace
pensar –de manera implícita– lo cuidadoso que
debemos ser a la hora de definir términos. Esta
precaución también es equivalente para cuando
escribamos algoritmos con Racket.
Algunas consideraciones que cabe señalar:
5
Si tenemos, por ejemplo (λx.x)yrepresenta
una función identidad aplicada ay. Tam-
bién una función de aplicación tiene aso-
ciación a la izquierda, es decir, si tenemos
el término «a b c» equivale a: «(a b)c».
Una función (λx.y)se le considera con-
stante y es diferente a la anterior, dado
que siempre devolverá yindependiente del
valor a aplicarse a x. En consecuencia, xse
descarta.
2.3.2 Free ybound variables
Son los dos tipos de variables en cálculo-λ;
por ejemplo: λx.x y, la variable xes bound
y la yes free, la substitución [x:= V]se
aplica a xdonde Ves un argumento de en-
trada. Es decir, las variables que no son bound
(definidas en la función) son free; otro ejemplo:
«λxy.x y z», donde xeyson bound yzes free.
(Además las variables puede ser un conjunto in-
finito λx1, ..., xn.(x1, ..., xn).)
Entonces decimos que una operación de ab-
stracción realiza bind a una bound ofree vari-
ables en un término lambda.
A continuación presentamos tres ejemplos de
variables bound yfree y después lo aplicaremos
en Racket.
(λx.x y)[x:= 1]
= (1 y)(8)
(λx.x 1)[x:= (λx.x)]
= (λ(λx.x).x 1)
= (λx.x)1
= 1
(9)
(λx.(λy.x y)n)[x:= (λx.10)]
= (λx.((λy.(x y))n))(λx.10)
= (λ(λx.10).((λy.(x y))n))
= ((λy.((λx.10) y))n)
= (λx.10)[x:= n]
= 10
(10)
Algunas aclaraciones, en (8) la variable xes
bound yyes free; en (9) el argumento es una
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
constante (cuando una función es argumento de
otra, se le llama: high-order function7), donde n
es free, por tanto, descarta a la variable anidada
y. (Nuestros amigos programadores imperativos
espero, solo espero, que no crean que esto sea
magia. Espero.)
Ejemplos en Racket. Ahora utilicemos el
conocimiento adquirido y llevémoslo a código.
También debemos recordar que Racket está in-
fluenciado por el cálculo-λ, lo que quiere decir
que, no es lo mismo. Por tanto, hay algunas
diferencias que tenemos tener en cuenta.
;; Ex p r . (8 )
(define y 1)
(define f
(lambda (x)
x y) )
; ; Ap l ic a ci ó n ( ej e cu c i ón )
> ( i d 2 )
1
En este ejemplo, ¿cuáles son las diferencias a
cálculo-λ?
7https://es.wikipedia.org/wiki/Función_de_
orden_superior
6
En Racket debemos definir previamente las
free variables, o si no no será posible com-
pilar. Aunque como se ve, no está definida
en el cuerpo de la función f.
El término final: x y, en Racket, al no es-
tar entre paréntesis, devuelve siempre la úl-
tima variable (a la derecha), por eso en este
caso descarta 2 y devuelve 1. En cambio, si
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
una función (= procedimiento en Racket),
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
cálculo-λcomo en Racket:
;; Ex p r . (9 )
(define id
(lambda (x)
x))
(define c
(lambda (x)
( x 1) ) )
; ; Ap l ic a ci ó n ( ej e cu c i ón )
> ( c i d )
1
Para finalizar, la expresión (10) que tiene la
función f2tiene una función anidada; donde n
es free yxal ser bound substituye la variable x
dentro de la función anidada; devolviendo (c1y);
y dado que c1es una función constante (no im-
porta el argumento de entrada porque siempre
devolverá lo mismo) yse descarta y devuelve 10.
;; Ex p r . (1 0 )
(define n 0)
(define c1
(lambda (x)
1 0) )
(define f2
(lambda (x)
(( lambda (y)
(x y)) n)
)
)
; ; Ap l ic a ci ó n ( ej e cu c i ón )
> ( f 2 c 1 )
10
2.3.3 Reducción
Conversión α. Ya hemos visto como funciona
una substitución para hacer uso de variables
bound yfree. Pero debemos tener cuidado para
no generar ambigüedades, por ejemplo, en la
siguiente expresión:
(λx.(λy.y x))y
= (λy.(λy.y y))
=ERROR
(11)
Esta substitución contiene un error porque el
yque se aplica pasa a ser la variable bound a la
izquierda, pero no es la misma yque se encuentra
a la derecha dentro de la función interna.
¿Cómo podemos solucionar esto? Fácil.
Renombrando las bound variables que crean am-
bigüedad. En este caso, se renombra la variable
yde la función interna por z.
La expresión quedaría así:
7
(λx.(λz.z x))y
= (λy.(λz.z y))
= (λz.z y)
=OK
(12)
Está confusión, en Racket no es un problema.
Porque lo soluciona a través del uso de sus parén-
tesis de cada scope (Ver imagen 1).
(a) xexterna. (b) xinterna.
Figure 1: Tratamiento de ambigüedades en
Racket.
Aunque eso no significa, en lo absoluto, que
sea correcto. Siempre se debe procurar escribir
nombre de variables idóneos, ya sea en Racket o
en cualquier lenguaje de programación.
Reducción β. La reducción beta es similar a
un paso de computación dentro de un algoritmo.
Esto se puede ver en la expresión anterior (12),
donde se va aplicando reducción hasta cuando ya
no es posible continuar, pero, en el cálculo-λfree-
type (que hemos estudiado en este documento)
la reducción podría ser infinita (= no terminar).
Por ejemplo:
(λx.x x) (λx.x x)
= (λx.(λx.x x) (λx.x x))
= (λx.(λx.(λx.x x) (λx.x x)) (λx.(λx.x x) (λx.x x)))
=recursión infinita
(13)
Conversión η. Es cuando dos funciones son
equivalente, si y solo si, dado todos los argu-
mentos devuelven lo mismo. Es una forma de
extender el uso de una función. Por ejemplo la
función λx.(z x) = Tes equivalente a la función
λz.T =Tsi xes bound.
Ejemplo en Racket. Si tenemos dos fun-
cione: f1yf2, al aplicar conversión-ηde f1a
f2:
(define f1
(lambda (x)
( f2 x ) ) )
(define f2
(lambda (x)
( l is t x )) )
; ; Ap l ic a ci ó n ( ej e cu c i ón )
> ( e q ua l ? ( f 1 2 ) ( f2 2 ) )
#t ; ; T r u e
Algunas cosas interesantes de este ejemplo:
La función list crea una lista en Racket.
La función equal? compara dos expresiones;
y si son iguales devuelve #t (= verdadero);
en caso contrario devolverá #f (= falso).
Dado similares argumentos a la función f1
yf2devuelve el mismo resultado. En este
caso devuelve la misma lista: 0(2). En con-
secuencia, la función f1extiende de f2.
2.3.4 Aritmética
Una de las características del cálculo-λes per-
mitir modelar desde operaciones lógicas pasando
por estructura de datos a operaciones aritméti-
cas. Para esto último, primero, debemos repre-
sentar los números usando la Church numerals,
que nos permite representar un número natural
dentro del cálculo-λen pos de operar con es-
tos en nuestras funciones aritméticas. (Para los
8
siguientes ejemplos se recomienda utilizar el eval-
uador online para cálculo-λ8.)
Church numerals. Cada número natural lo
podemos representar como funciones anidadas
de la siguiente forma:
0 = λf.λx.x
1 = λf.λx.f x
2 = λf.λx.f (f x)
3 = λf.λx.f (f(f x))
n=λf.λx.f1(f2(...(fnx)))
(14)
Como vimos en algunos ejemplos anteriores,
aquí hacemos uso de high-order functions o sea
de funciones que se utilizan como argumento. En
(14) hay dos cuestiones relevantes: (1) el argu-
mento fes una función y (2) siempre se devuelve
una única función (que internamente puede tener
nfunciones, pero, siempre tiene una función de
inicio de «cadena» de funciones que es exclu-
siva). Las siguientes operaciones han sido an-
teriormente definidas en artículo de Wikipedia
de cálculo-λen inglés y en (Rojas, 2015).
Suma. Con una composición podemos hacer
la suma entre dos números naturales, por ejem-
plo la idea sería tener una función SUMA que
al aplicar sea así: (SUM 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 mpor
su función correspondiente (numeral 1) sería:
«λf.λx.f x», lo mismo ocurre con mpara 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:
8https://jacksongl.github.io/files/demo/
lambda/index.htm
((n f)x)es equivalente a (n f x)dado que se
agrupa hacia la izquierda la aplicación.
Multiplicación. Es similar a la suma, dado
que una multiplicación entre mynsignifica la
suma de mpor nveces.
MU LT =λm.λn.λf.m (n f )
M U LT 2 = λm.λn.m (P LUS n) 0
MU LT M U LT 2
(16)
Predecesor. Dado que los numerales
definidos solo admiten valores enteros positivos,
en caso de la resta debemos definir previamente
la función predecesora (P RED n n1) que
se utiliza para cualquier valor superior a 0.
P RED =λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λu.u)
(17)
Esta función genera muchas derivaciones, por
lo cual lo hace difícil entender cada paso
(¡pero lo intentaremos!), así si queremos hacer:
(P RED 10), la derivación debería ser la sigu-
iente (están subrayadas las substituciones para
que sea más fácil de seguir):
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.(x1x2))))
= (λf.(λx.((((λx1.(λx2.(x1x2))) (λg.(λh.(h(g f ))))) (λu.x)) (λx0.x0))))
= (λf.(λx.(((λx2.((λg.(λh.(h(g f )))) x2)) (λu.x)) (λx0.x0))))
= (λ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
(18)
(Esta derivación fue obtenida usando: https://jacksongl.github.io/files/demo/lambda/
index.htm)
10
Resta. Con esto ya podemos definirla:
RES T A =λm.λn.n P RED m (19)
Entonces definidas las operaciones aritméti-
cas, ya podemos dar por entendido el mecanismo
de derivación del cálculo-λ, al menos a un nivel
inicial, cuestiones como la división queda a tra-
bajo del curioso lector. Por ahora, veamos como
estos operadores funcionan en Racket, que dicho
sea de paso, es mucho más simple.
Ejemplos en Racket. Ahora veamos la fa-
cilidad de utilizar los mismos operadores ya im-
plementados en Racket:
> (+ 1 2 )
3
> (- 3 1 )
2
> (* 4 ( * 4 1 0 ) )
160
> (+ 1 (/ 1 0 5 ) )
3
Podemos ver que Racket usa prefix notation (=
notación polaca) 9junto a la expresión-S.
2.4 Racket
En esta sección final nos adentraremos –y
relajaremos– con Racket, usando los conocimien-
tos ya adquiridos de cálculo-λ.
2.4.1 Estructuras de datos
Listas. Las listas son una secuencia de elemen-
tos (sin acceso aleatorio ni índice) que aceptan
cualquier tipo de dato y, a su vez, son inmutables
(o sea no puede modificarse una vez creadas).
9https://es.wikipedia.org/wiki/notación_polaca
Por lo demás, es la estructura de datos más pop-
ular en Racket.
( l is t ) ; L i s ta v a c í as
( l is t 1 2 3 ) ; T r es n ú m er os
(list "x" "y" "z"); T r e s car a c ter e s
( l is t 1 2 ( l is t ( li s t ) "x" "y"
( l is t 1 0 1 ) "z") ) ; Listas
anidadas
Una notación más simple es remover la pal-
abra list y anteponer unas comillas simples «’» al
paréntesis de la izquierda «(». A continuación se
presentan las declaraciones que son equivalentes
a las anteriores pero con la nueva notación:
'()
'(1 2 3 )
'("x" "y" "z")
'(1 2 ( '() "x" "y" '( 1 0 1) "z") )
Funciones car10 ycdr11. Son operaciones que
se pueden aplicar sobre listas. Primero, car se
utiliza para devolver el primer elemento de la
lista; Segundo, cdr devuelve la lista sin el primer
elemento. Por ejemplo:
> ( define x'("x" "y" "z") )
> ( c a r x )
"x"
> ( c d r x )
'("y" "z")
> (cdr ( c d r x ))
'("z")
> (cdr ( c d r ( cdr x ) ) )
'()
10car = «Contents of the Address part of Register num-
ber».
11cdr = «Contents of the Decrement part of Register
number».
11
Función cons. Permite concatenar dos valores
(independiente del tipo de valor).
> ( c o ns 0 '(2 3 ) )
'(0 2 3 )
> ( c a r ( co n s 5 2) )
5
> ( c d r ( c o ns '(5) '( ) ) )
'()
> ( e q ua l ? ( c d r ( c on s '(5) '(1 ) ) )
( c dr ( c on s '(5 ) 1 ) ))
#f
> ( e q ua l ? ( c d r ( c on s '(5) '(1 ) ) )
( c dr ( c on s '(5) '(1 ) ) ) )
#t
Tabla hash. Esta estructura de datos per-
mite asociar una clave con un valor, por ejemplo
110, cuando queramos acceder a la clave 1
siempre devolverá 10. En Racket existen varias
variantes de tabla hash: una mutable y otra in-
mutable. Veremos ejemplos de cada una.
Mutable. Una vez creada está puede ser mod-
ificada: ya sea el valor de una clave o incluso
borrar toda la tabla hash.
(define h ( ma k e- h as h ) ) ; Se c r e a l a
tabla h a s h h .
( h as h -s et ! h 1 10 )
( h as h -s et ! h 2 '( 1 2 3 ) )
( h as h -s et ! h '( 0 1 ) "binary")
> h
'# h as h ( (( 0 1 ) . "binary") (2 . (1 2
3) ) (1 . 10) )
La función hash-set nos permite agregar una
nueva clave-valor a la variable h, se agrega el
«!» al final cuando se trata de una estructura
mutable, en el caso opuesto, si omitimos el ! va
a devolver una nueva tabla hash con la nueva
clave-valor sin haber modificado la original. Con
la función hash-ref podemos acceder al valor de
una clave en particular: (hash-ref h 1) = 10.
Inmutable. Racket también soporta estruc-
tura de datos inmutables, lo que es muy útil en
algunos casos.
(define hi (make-immutable-hash
'([ 1 . "b"] [ 2 . "a"] ) ) ) ; Se
crea la tabla h a s h h con dos
ele m e nto s .
> ( h a sh - re f h i 1 )
"b"
> (h a s h -se t h i 3 2 ) ; c r e a un a
nueva t a b l a h a s h.
'# h as h ( (1 . "b") (2 . "a") ( 3 . 2) )
> ( h a sh - se t ! h i 3 2)
. . h a s h- s e t !: c ont r a c t v i o lat i o n
e xp e ct e d: ( a n d/ c h as h ? ( n o t/ c
i mm u t a bl e ? ) )
given: '# h as h ( (1 . "b") (2 . "a") )
a rg u m en t po s i t io n : 1 st
other a r g um e n t s. . . :
Antes mencionamos que una estructura in-
mutable no puede ser modificada después de su
creación, y como vemos en el ejemplo anterior
cuando hacemos uso de la función hash-set! el
intérprete de Racket arroja un error.
Con listas yhash ya tenemos suficiente para
comenzar a ver como construir algoritmos en
Racket, en donde aplicaremos constantemente
operaciones sobre listas utilizando recursividad.
(También existen otras estructuras como: pair,
vector y listas asociadas. Pero se lo dejamos al
interés y curiosidad del lector.)
2.4.2 Algoritmos
Es momento de ver algunos algoritmos utilizando
Racket, espero que, hasta el momento, el lec-
tor allá encontrado interesante aprender Racket,
pero aún falta lo mejor, y lo mejor, espero que
12
pueda ser visible en este apartado.
Fibonacci. Una manera para adentrarse en
la recursividad es aprender a implementar fi-
bonacci. Recordemos las funciones de cómo
opera el algoritmo: (1) f0= 0; (2) f1= 1; y
(3) fn=fn1+fn2. En Racket la función cond
es una condicional múltiple, es decir, if-else if.
(define fib
(lambda (n)
(cond
((= n 0) 0) ; i f
((= n 1) 1) ; e ls e if
(( > n 0) (+ ( f i b (- n 1) )
( f ib ( - n 2) ) ) ) ; el s e i f
)
)
)
> ( f i b 9)
34
La primera expresión en cond es ((= n0) 0),
de tal forma de que si se cumple (= n0), en-
tonces devolvera 0. De la misma manera ocurre
para las otras dos expresiones.
Contar palabras. La idea es contar las veces
que una palabra (entregada como argumento) se
repite dentro de una lista.
(define wor d - cou n t
(lambda ( l is t s ea r ch co u nt )
(cond
[( n u ll ? l is t ) c ou n t ]
[( e q ? ( ca r l is t ) s e a rc h )
( w o rd - co u n t ( cd r l is t )
s ea r c h (+ c o u nt 1 ) ) ]
[( w o r d- c o un t ( c dr l i st )
s ea r ch c o un t ) ]
)
)
)
> ( w o rd - co u nt '(" aa a " " a" " b bb " " a "
" a aa " " y" )" a aa " 0 )
2
> ( w o rd - co u nt '(" aa a " " a" " b bb " " a "
" a aa " " y" )"y" 0)
1
Lista inversa. ¿Qué tal si queremos revertir
el orden de una lista? Claro, en Racket por de-
fecto ya existe la función reverse que hace esto,
pero, en este caso, queremos implementarla por
nosotros mismos. Una, de las tantas formas de
hacerlo, es la siguiente:
(define last-element
(lambda ( l is t )
(cond
[ (= ( l en g th li s t ) 1 ) l is t ]
[( l as t - el e m en t ( c dr l i st ) ) ]
)))
(define new-reverse
(lambda (old-list new-list)
(cond
[( n u ll ? o ld - li s t ) n e w- l is t ]
[(new-reverse
( t ak e o ld - l is t ( -
( l en g th o l d- l is t )
1) ) ( a p p en d n e w -l i s t
(last-element
o ld - li st ) ) ) ]
)
)
)
> (new-reverse '(" a a a " " b bb " " a "
"y")'() )
'(" y " " a " " b b b " " a aa " )
Algunas apreciaciones con respecto a este úl-
timo algoritmo:
Aplicamos descomposición a nuestro al-
goritmo, o sea, creamos una segunda fun-
13
ción last-element que aplica una funcional-
idad en específico. Esto nos ayuda a re-
utilizar dicha función a futuro y que, no
este ligada solamente a nuestra función new-
reverse (además de hacerlo más legible).
La función take toma los primeros nel-
ementos de una lista. Por ejemplo:
(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 yfree variables.
La operación de reducción.
Modelado de operaciones aritméticas.
Por otro lado, cada operación en cálculo-λlo
explicamos con su equivalente en el lenguaje de
programación Racket, además de dar una intro-
ducción a las estructuras de datos del lenguaje
aplicadas a algunos tipos de algoritmos, donde
prevalece la inmutabilidad y la recursividad,
componentes principales en la programación fun-
cional.
Solo me queda por decir: De parvis grandis
acervus erit. («De las cosas pequeñas se nutren
las cosas grandes».)
4 Más información
Para una versión completa y detallada de este tu-
torial podría pensar en adquirir mi libro: «Com-
putación y programación funcional», publicado
por la editorial Marcombo, 2021. En el cual,
además de usar Racket, incorporo Python para
los ejemplos.
Referencias
H. H. Barendregt and E. Barendsen. Introduc-
tion to lambda calculus. Nieuw archief voor
wisenkunde, 4:337–372, 01 1984.
A. Church. An unsolvable problem of ele-
mentary number theory. American Jour-
nal of Mathematics, 58(2):pp. 345–363, 1936.
ISSN 00029327. URL http://www.jstor.
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
ResearchGate has not been able to resolve any citations for this publication.
Article
Full-text available
ion is said to bind the free variable x in M . E.g. we say that x:yxhas x as bound and y as free variable. Substitution [x := N ] is only performedin the free occurrences of x:yx(x:x)[x := N ] yN(x:x):In calculus there is a similar variable binding. InRbaf(x; y)dx the variable x isbound and y is free. It does not make sense to substitute 7 for x:Rbaf(7; y)d7;but substitution for y makes sense:Rbaf(x; 7)dx.For reasons of hygiene it will always be assumed that...
Article
This paper is a concise and painless introduction to the $\lambda$-calculus. This formalism was developed by Alonzo Church as a tool for studying the mathematical properties of effectively computable functions. The formalism became popular and has provided a strong theoretical foundation for the family of functional programming languages. This tutorial shows how to perform arithmetical and logical computations using the $\lambda$-calculus and how to define recursive functions, even though $\lambda$-calculus functions are unnamed and thus cannot refer explicitly to themselves.
On computable numbers, with an application to the Entscheidungsproblem
  • A M Turing
A. M. Turing. On computable numbers, with an application to the Entscheidungsproblem. Proceedings of the London Mathematical Society, 2(42):230-265, 1936.
Lambda calculus -wikipedia, the free encyclopedia
  • Wikipedia
Wikipedia. Lambda calculus -wikipedia, the free encyclopedia.