Technical ReportPDF Available

Abstract

El cálculo lambda es una notación formal que permite expresar funciones computables. 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
www.camilochacon.com
@camilo_chacon_s
Actualizado: 3 de agosto de 2023
El cálculo lambda es una notación for-
mal que permite expresar funciones compu-
tables. El cual es el fundamento de la pro-
gramación funcional. Se define con la letra
Griega lambda (λ) y se expresa a través
de expresiones lambda, y términos lamb-
da que son usados para representar binding
variables1dentro de una función. Este do-
cumento tiene como objetivo ser una in-
troducción básica a los aspectos teóricos y
prácticos del cálculo lambda, y a la progra-
mación funcional. Para esto último usare-
mos el lenguaje de programación Racket.
1. Introducción
1.1. Historia
El cálculo lambda (cálculo-λ) fue introduci-
do por Alonzo Church en su artículo: «An un-
solvable problem of elementary number theory»
(Church, 1936) en la década de 1930. Adicio-
nalmente fue supervisor de destacados alumnos
como Alan Turing y Stephen Kleen en Prince-
ton. El primero propuso un modelo para realizar
*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.
computación conocido como a-machines (auto-
matic machines)2en su artículo «On Compu-
table 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 equi-
valentes) 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, s.f.; Wexelblat, 1978). Es así, como na-
ce el paradigma de programación funcional que
trabaja con máquinas de reducción (Barendregt
y Barendsen, 1984). Por otro lado, el trabajo de
Turing derivaría en el paradigma de programa-
ción imperativo.
2Actualmente se le denomina máquina de Turing.
1
Este documento pretende ser una breve intro-
ducción al cálculo lambda con un enfoque aplica-
do usando un lenguaje de programación funcio-
nal 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 función
frecibe un valor ay devuelve un b. Además a
ybpueden ser representados como conjuntos de
elementos. Así, la función es una relación entre
elementos de dos conjuntos. Para nuestro ejem-
plo el conjunto ase le llama dominio y el con-
junto bcodomino, que representan al conjunto
de entrada y salida respectivamente. En nota-
ció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. Entonces,
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 funcio-
nes matemáticas trata con funciones anónimas,
es decir, no existe la necesidad de asignarle un
nombre 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 va-
lor de salida.
Algo importante a mencionar es que el cálculo-
λsolo admite funciones con un solo valor de en-
trada. Cuando queremos tratar con múltiples ar-
gumentos entonces se debe utilizar una propie-
dad conocida como currying, el cual transforma
los argumentos de entrada en una cadena de fun-
ciones con un solo valor. En otras palabras, una
función f(x, y, z) = x+y+zque tiene tres argu-
mentos puede ser reescrita de la siguiente forma:
(x(y(z(x+y+z))), donde el operador
dar por supuesta una asociación a la derecha.
La función se puede evaluar usando aplicación
de funciones. Hace referencia a aplicar un con-
junto de argumentos desde el dominio para ob-
tener 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 reduc-
ció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 esta notación):
< Ex p r > : := 'λ'<Var-list > '.'
< Expr > | < A p p - term >
< A pp -t e rm > :: = < Ap p- t er m > < It em > |
< It e m >
< Item > ::= < var > | (< e xpr >)
< V ar - l is t > : = < v ar > | < va r > ','
<Var-list >
< va r > : = [ a - z ] +
Entonces según el lenguaje anteriormente de-
finido, 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 expresio-
nes válidas radica en verlas como una secuencia
de caracteres e ir clasificándolas con el lenguaje
3Puedes probar el cálculo-λen un intérprete
online: https://jacksongl.github.io/files/demo/lambda/
index.htm
anteriormente 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
DrRacket5. A continuación, podemos ver la lista
de expresiones válidas representada en código:
;; Expr. ( 3)
(define id
(lambda (x)
x))
;; Expr. ( 4)
(define k
(lambda (x y )
x))
;; Expr. ( 5)
(define k2
(lambda (x y )
4https://docs.racket-lang.org/ts-guide
5Toda la información al respecto lo puedes encontrar
en: https://download.racket-lang.org/.
3
y))
;; Expr. ( 6)
(define f
(lambda (x y )
x y) )
;; Expr. ( 7)
(define f
(lambda (x y z )
(x y z ) ) )
Una definición de una función en Racket co-
mienza con la palabra reservada define, poste-
riormente 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 ha-
brá 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óni-
mas); (2) la sintaxis en Racket se construye con
la notación expresión-S que está basada en pa-
ré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 rea-
liza 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 programa-
ción podría cambiar (aunque no puedo asegurar
que sea para bien, ¡no soy el culpable del resul-
tado!).
2.3.1. Operadores
Toda expresión lambda válida es le suele lla-
mar «término lambda». A través de estos térmi-
nos podemos derivar algunos importantes opera-
dores de cálculo-λ:
Abstracción: Es un operador de abstracción,
por ejemplo, si tes un término lambda y xes
una variable, entonces la siguiente 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 ma-
nera que se ejecuta una función en Rac-
ket.) Notación formal: Si tenemos un tér-
mino (λx.T )V=T[x:= V]donde [x:= V]
4
es una substitución de Vpor x. Por ejemplo:
(λx.x)1 = 1.
Entonces, podemos concluir que cada opera-
dor también es un término lambda.
Importante: Hasta aquí el lector se pudo per-
catar de algo que es fundamental en la programa-
ció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 realizarse no es po-
sible 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 toda-
ví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 argumen-
to, 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 funciones 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 cuen-
ta, por eso, vea con mucha atención el siguien-
te ejemplo. En el segundo caso, se define una
función que devuelve otra función y aplica, por
ejemplo:
(λ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 diferen-
te 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:
Si tenemos, por ejemplo (λx.x)yrepresenta
una función identidad aplicada ay. También
5
una función de aplicación tiene asociación
a la izquierda, es decir, si tenemos el tér-
mino «a b c» equivale a: «(a b)c».
Una función (λx.y)se le considera cons-
tante y es diferente a la anterior, dado que
siempre devolverá yindependiente del valor
a aplicarse a x. En consecuencia, xse des-
carta.
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 apli-
ca a xdonde Ves un argumento de entrada.
Es decir, las variables que no son bound (de-
finidas 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 abs-
tracción realiza bind a una bound ofree variables
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 in-
teresante, el argumento es una función constante
(cuando una función es argumento de otra, se le
llama: high-order function7), donde nes free, por
tanto, descarta a la variable anidada y. (Nues-
tros 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 di-
ferencias que tenemos tener en cuenta.
;; Expr. ( 8)
(define y 1)
(define f
(lambda (x)
x y) )
; ; A p li c ac i ó 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 compi-
lar. 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 estar
entre paréntesis, devuelve siempre la última
variable (a la derecha), por eso en este ca-
so 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
error.
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:
;; Expr. ( 9)
(define id
(lambda (x)
x))
(define c
(lambda (x)
( x 1 ) ) )
; ; A p li c ac i ó 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.
;; Expr. ( 1 0 )
(define n 0)
(define c1
(lambda (x)
1 0) )
(define f2
(lambda (x)
(( lambda (y)
(x y)) n)
)
)
; ; A p li c ac i ó n ( ej e cu c i ón )
> ( f 2 c 1 )
10
2.3.3. Reducción
Conversión α. Ya hemos visto como funcio-
na una substitución para hacer uso de variables
bound yfree. Pero debemos tener cuidado pa-
ra 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. Re-
nombrando 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 pa-
réntesis de cada scope (Ver imagen 1).
(a) xexterna. (b) xinterna.
Figura 1: Tratamiento de ambigüedades en Rac-
ket.
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 argumen-
tos devuelven lo mismo. Es una forma de exten-
der el uso de una función. Por ejemplo la fun-
ción λx.(z x) = Tes equivalente a la función
λz.T =Tsi xes bound.
Ejemplo en Racket. Si tenemos dos funcio-
ne: f1yf2, al aplicar conversión-ηde f1af2:
(define f1
(lambda (x)
( f2 x ) ) )
(define f2
(lambda (x)
( l is t x ) ) )
; ; A p li c ac i ó n ( ej e cu c i ón )
> ( e q ua l ? ( f1 2 ) ( f 2 2 ) )
#t ; ; T rue
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 estos en
nuestras funciones aritméticas. (Para los siguien-
tes ejemplos se recomienda utilizar el evaluador
8
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 te-
ner nfunciones, pero, siempre tiene una función
de inicio de «cadena» de funciones que es exclu-
siva). Las siguientes operaciones han sido ante-
riormente definidas en artículo de Wikipedia, s.f.
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í: (SU MA 1 2) 3. Podemos
definir dicha función así:
SU MA =λ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 defini-
dos 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 (¡pe-
ro lo intentaremos!), así si queremos hacer:
(P RED 10), la derivación debería ser la si-
guiente (están subrayadas las substituciones pa-
ra 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 R ED 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 ele-
mentos (sin acceso aleatorio ni índice) que acep-
tan cualquier tipo de dato y, a su vez, son in-
mutables (o sea no puede modificarse una vez
9https://es.wikipedia.org/wiki/notación_polaca
creadas). Por lo demás, es la estructura de datos
más popular en Racket.
( l is t ) ; L is t a va c í a s
( l is t 1 2 3 ) ; T re s n ú m er o s
(list "x" "y" "z"); T r e s c a r a cteres
( 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 pala-
bra 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 ar x )
"x"
> ( c dr x )
'("y" "z")
> (cdr ( cdr x))
'("z")
> (cdr ( cdr ( 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 on s 0 '(2 3 ) )
'(0 2 3 )
> ( c ar ( c o ns 5 2) )
5
> ( c dr ( c on s '(5) '( ) ) )
'()
> ( e qu a l ? ( cd r ( c o ns '(5) '(1 ) ) )
( c dr ( c on s '( 5) 1 ) ) )
#f
> ( e qu a l ? ( cd r ( c o ns '(5) '(1 ) ) )
( c dr ( c on s '(5) '( 1) ) ) )
#t
Tabla hash. Esta estructura de datos permite
asociar una clave con un valor, por ejemplo 1
10, cuando queramos acceder a la clave 1siempre
devolverá 10. En Racket existen varias variantes
de tabla hash: una mutable y otra inmutable.
Veremos ejemplos de cada una.
Mutable. Una vez creada está puede ser mo-
dificada: ya sea el valor de una clave o incluso
borrar toda la tabla hash.
(define h ( m a ke -h a sh ) ) ; Se crea la
tabla hash h.
( h as h -s et ! h 1 1 0)
( 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 estructu-
ra de datos inmutables, lo que es muy útil en
algunos casos.
(define hi (make-immutable-hash
'([ 1 . "b"][2 . "a"] ) ) ) ; Se
crea l a t a b l a h a s h h con d o s
elem e n t o s .
> ( h as h -r e f h i 1 )
"b"
> (ha s h - s e t h i 3 2) ; crea una
nueva tabla hash .
'# h as h ( (1 . "b") ( 2 . "a") ( 3 . 2) )
> ( h as h -s e t! h i 3 2)
. . h a s h - set!: c o n t r a c t violati o n
e xp e ct e d: ( a nd / c ha s h ? ( n ot /c
i mm u t a bl e ? ) )
given: '# h as h ( (1 . "b") ( 2 . "a") )
a rg u m en t p o s it i o n : 1 s t
other argu m e n t s . ..:
Antes mencionamos que una estructura inmu-
table no puede ser modificada después de su crea-
ció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 pa-
ra 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 utili-
zando Racket, espero que, hasta el momento, el
lector allá encontrado interesante aprender Rac-
ket, pero aún falta lo mejor, y lo mejor, espero
12
que pueda ser visible en este apartado.
Fibonacci. Una manera para adentrarse en
la recursividad es aprender a implementar fibo-
nacci. Recordemos las funciones de cómo ope-
ra 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) ) ) ) ; e l se i f
)
)
)
> ( f ib 9 )
34
La primera expresión en cond es ((= n0) 0),
de tal forma de que si se cumple (= n0), en-
tonces devolverá 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 word - c o u n t
(lambda ( l is t s ea rc h c o un t )
(cond
[( n ul l ? li s t ) c o un t ]
[( e q ? ( c ar l i st ) s ea r c h )
( w o rd - co u n t ( c dr l i st )
s ea r c h ( + c ou n t 1 ) ) ]
[( w o r d- c ou n t ( cd r l is t )
s ea r ch c o un t ) ]
)
)
)
> ( w or d -c o un t '(" a aa " " a " " bb b " " a "
" a aa " " y " )" aa a " 0)
2
> ( w or d -c o un t '(" a aa " " a " " bb b " " 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 l is t ) 1) l is t ]
[( l a s t- e l em e nt ( c d r l i st ) ) ]
)))
(define new-reverse
(lambda (old-list new-list)
(cond
[( n ul l ? o l d- l is t ) ne w -l i st ]
[(new-reverse
( t ak e o ld - li s t ( -
( l en g th o l d- l is t )
1) ) ( a p pe n d n ew - l is t
(last-element
o ld - li st ) ) ) ]
)
)
)
> (new-reverse '(" a a a " " b bb " " a"
"y")'() )
'(" y " " a" " b bb " " aa a " )
Algunas apreciaciones con respecto a este úl-
timo algoritmo:
Aplicamos descomposición a nuestro al-
goritmo, o sea, creamos una segunda función
13
last-element que aplica una funcionalidad en
específico. Esto nos ayuda a reutilizar dicha
función a futuro y que, no este ligada sola-
mente a nuestra función new-reverse (ade-
más de hacerlo más legible).
La función take toma los primeros n
elementos 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 pa-
radigma 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 lengua-
je aplicadas a algunos tipos de algoritmos, don-
de 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 es-
te tutorial podría pensar en adquirir mi libro:
«Computación y programación funcional», pu-
blicado por la editorial Marcombo, 2021. En el
cual, además de usar Racket, incorporo Python
para los ejemplos.
Referencias
Barendregt, H. ( & Barendsen, E. (1984). Intro-
duction to lambda calculus. Nieuw archief
voor wisenkunde,4, 337-372.
Church, A. (1936). An Unsolvable Problem
of Elementary Number Theory. Ameri-
can Journal of Mathematics,58 (2), pp.
345-363. Recuperado desde http://www.
jstor.org/stable/2371045
14
Rojas, R. (2015). A Tutorial Introduction to
the Lambda Calculus. arXiv: 1503. 09060
[cs.LO]
Szmulewicz, D. (s.f.). Recuperado desde https://
danielsz.github.io/blog/ 2019- 08- 05T21_
14.html
Turing, A. M. (1936). On Computable Numbers,
with an Application to the Entscheidungs-
problem. Proceedings of the London Mat-
hematical Society,2(42), 230-265.
Wexelblat, R. L. (Ed.). (1978). History of
Programming Languages. New York, NY,
USA: Association for Computing Machi-
nery.
Wikipedia. (s.f.). 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.
Lambda calculus -wikipedia, the free encyclopedia
  • Wikipedia
Wikipedia. Lambda calculus -wikipedia, the free encyclopedia.
Recuperado desde https:// danielsz
  • D Szmulewicz
Szmulewicz, D. (s.f.). Recuperado desde https:// danielsz.github.io/blog/2019-08-05T21_ 14.html