Technical ReportPDF Available

Abstract and Figures

Introducción a la programación funcional usando lenguajes con sistema de tipos estáticos.
Content may be subject to copyright.
Introducci´on a la programaci´on funcional usando Haskell y Agda*
Camilo Chac´on Sartori
camilochs@gmail.com
22 de septiembre de 2021
´
Indice
1. Introducci´on 2
2. Aspectos hist´oricos 4
3. alculo lambda 4
4. Teor´ıa de tipos 6
4.1. alculo lambda tipado (λ)..................................... 7
4.2. Correspondencia de Curry-Howard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4.3. Reglasdederivaci´on ......................................... 9
4.3.1. Regladeformaci´on...................................... 10
4.3.2. Regladeintroducci´on .................................... 10
*Este documento esta bajo licencia CC-BY-NC 4.0 International license LMN.
1
Introducci´on a la programaci´on funcional usando Haskell y Agda2
Camilo Chac´on Sartori
camilochs@gmail.com
4.3.3. Regladeeliminaci´on..................................... 10
4.3.4. Regladecomputaci´on .................................... 10
5. De la teor´ıa a la pr´actica 11
6. Haskell 11
6.1. Instalaci´on .............................................. 11
6.2. Caracter´ısticas ............................................ 12
6.2.1. Funcionespuras ....................................... 13
6.2.2. Funciones de primer orden (high-order functions)..................... 14
6.2.3. usqueda de patrones (pattern matching) ......................... 15
6.2.4. Evaluaci´on perezosa (lazy evalution ) ............................ 16
6.2.5. Transparencia referencial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
7. Agda 21
7.1. Instalaci´on .............................................. 21
7.2. Tipodedato ............................................. 22
7.3. Expresionesb´asicas.......................................... 23
7.4. Expresionesl´ogicas.......................................... 23
7.5. Expresionesaritm´eticas ....................................... 24
7.6. Expresiones ogicas aritm´eticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
7.7. Tipos dependientes e impl´ıcitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7.8. Operaciones con tipos dependientes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
7.8.1. Head yLast .......................................... 27
7.8.2. Tail .............................................. 28
7.8.3. Prepend yAppend ...................................... 28
8. Conclusi´on 28
9. Sugerencias finales 28
1. Introducci´on
Una caracter´ıstica que, al menos para m´ı, representa mejor a un inform´atico es su conocimiento en los
lenguajes formales. Como dijo Raymond Turner en su libro sobre filosof´ıa de la inform´atica 1:
La inform´atica es un ´area dominada por lenguajes. [Tur18]
Esto nos diferencia de un matem´atico o de un f´ısico, que, a pesar de estos preparados para alcanzar
un alto conocimiento algor´ıtmico, pueden carecer de una profundidad te´orica y pr´actica al momento de
aprovechar mejor las peculiaridades de los lenguajes que usan. Un inform´atico es, en cambio, un estudioso de
su herramienta principal, a saber: los lenguajes formales, es un ser curioso que intenta, cada cierto tiempo,
aprender nuevos lenguajes; pero no se queda solo con eso, tambi´en experimenta creando sus propios lenguajes
en pos de buscar un conjunto de caracter´ısticas que pueda hacer su lenguaje m´as expresivo, seguro y eficiente.
Manteniendo en su mente la idea de construir un mejor software.
Antes bien no todos los inform´aticos tienen conocimiento en dise˜nar –y menos en implementar– lenguajes.
Hay inform´aticos que son bastante buenos en una herramienta especifica. Maestros en el arte de hacer uso de
tecnolog´ıa, no tanto en crearlas. Es como el del escritor, el cual al alcanzar un mayor dominio en su lengua
puede abordar distintos tipos de proyectos literarios, ya sea ensayos, novelas, poes´ıa, cuentos, pues su amplio
conocimiento en su idioma les permite cambiar de contexto, r´apidamente.
1Turner usa el t´ermino computer science, lo traduje simplemente a inform´atica para mantener la coherencia con este art´ıculo.
Aunque tengo claro que existen diferencias prefiero tratarlas –en esta ocasi´on– como sin´onimos.
agina 2
Introducci´on a la programaci´on funcional usando Haskell y Agda4
Camilo Chac´on Sartori
camilochs@gmail.com
Aunque, claro, eso no significa que tenga el mismo nivel en todos los g´eneros, es normal que se logre
una mayor maestr´ıa en uno que en otro. Lo mismo ocurre con los lenguajes y sus paradigmas asociados,
algunos programadores aman la programaci´on funcional, otros la programaci´on orientada a objetos, y algunos
indecisos –como yo– nos gusta experimentar cada una sin tener uno predilecto. Solo nos interesa tener un
mayor conocimiento sobre la computaci´on, ((generalistas)) lo llaman algunos.
Existen varios lenguajes formales en la inform´atica. Algunos pertenecen a la programaci´on (Turing com-
pletos3: Python, Scratch, C++, etc.); de especificaci´on (los relacionados con la verificaci´on formal: TLA+,
Coq, Alloy, etc.); y otros para manipular y organizar la informaci´on (XML, JSON, RDF, etc.). Cada categor´ıa
de lenguajes formales (existen m´as) nos ayuda a dar respuesta a diferentes problemas, y no son excluyentes
entre s´ı (v´ease la Figura 1). Es normal usar varios, ya sea de una misma categor´ıa o de otras, en un mismo
proyecto aunque los usamos con diferentes objetivos.
Figura 1: Lenguajes formales de inform´atica.
El objetivo principal de este art´ıculo es el siguiente: aprender sobre la programaci´on funcional desde sus
or´ıgenes hasta nuestros d´ıas, presentando algunas de sus caracter´ısticas m´as relevantes y por qu´e estas son
sobresalientes.
Y los objetivos espec´ıficos se organizan de la siguiente forma: (1) ense˜nar el c´alculo lambda no tipado
(o libre de tipos) que es el fundamento de la programaci´on funcional; (2) luego pasar´e revista sobre algunos
lenguajes formales que nos adentraran a la teor´ıa de tipos, esencial para construir software confiable y seguro;
y (3), para finalizar, dar´e una breve presentaci´on de dos lenguajes de programaci´on funcionales: Haskell y
Agda; el primero es un lenguaje funcional puro, y el segundo es interesante pues conoceremos de qu´e trata
los tipos dependientes.
Espero que este art´ıculo le permita cambiar su forma de pensar sobre la programaci´on funcional y le
extienda su ´optica con respecto a la inform´atica. Muchas personas creen que la programaci´on funcional es
el paradigma por excelencia y rechazan a otros lenguajes, este art´ıculo busca todo lo contrario, presenta la
programaci´on funcional como una herramienta que puede ser ´util para algunas partes de un programa, donde
3Un lenguaje se considera Turing completo cuando posee estructuras de control (p. ej., ciclos iterativos) y permite la muta-
bilidad de variables. O sea, puede simular una m´aquina de Turing.
agina 3
Introducci´on a la programaci´on funcional usando Haskell y Agda6
Camilo Chac´on Sartori
camilochs@gmail.com
la fiabilidad, coherencia y seguridad sean variables que tengan mayor peso que la eficiencia o la velocidad de
desarrollo. Tenga presente esto.
Al final de este trabajo se encuentran algunas sugerencias, el repositorio con el c´odigo utilizado, notas y
referencias.
2. Aspectos hist´oricos
El origen de la programaci´on funcional se remonta a la d´ecada de 1930, cuando Alonzo Church creo el
alculo lambda. El primer lenguaje de programaci´on. Que, como usted puede darse cuenta, surgi´o antes de la
llegada del primer ordenador digital. Por aquellos a˜nos el alumno doctoral de Church, Alan Turing escribi´o
un art´ıculo donde presento la a-machines (hoy conocida como la m´aquina de Turing) para comprobar que el
problema de la decisi´on no tiene soluci´on en este sistema5.
Es decir, Turing realizo una demostraci´on negativa, dando cuenta que, en la m´aquina de Turing, este
problema es indecible, no computable, no recursivo. Ambos sistemas l´ogicos –o tambi´en llamados modelos de
computaci´on–, el c´alculo lambda y la m´aquina de Turing, demostraron ser equivalente. Y son el fundamento
te´orico de c´omo funciona un lenguaje de programaci´on y un ordenador. Nos ayudan a entender los procesos
computacionales y a conocer lo qu´e es computable o no.
La m´aquina de Turing influenci´o a los lenguajes de programaci´on imperativos (p. ej. C, Java, Fortran) que
son, ante todo, m´as permisivos con el manejo de sus estructuras y propiedades. Por ejemplo, la mutabilidad
de variables (locales y globales) est´a permitida gracia al amplio uso de ciclos iterativos, y es, esta cuesti´on lo
que los hace m´as proclive a errores (o tambi´en dicho, efectos colaterales [side-effect]), pues, gran parte de la
responsabilidad de hacer un programa seguro y confiable recae en el programador.
Esta libertad no es del todo negativa, en muchos casos, es m´as f´acil obtener mayor eficiencia, reducir el
tiempo de c´omputo, pues en estos lenguajes existe menos capas de abstracci´on para llegar al lenguaje que usa
todo ordenador cl´asico (no cu´antico): ensamblador (Assembly), que es, dicho sea de paso, imperativo. Esto
no ocurre con los lenguajes de programaci´on funcionales.
3. alculo lambda
En esta secci´on dar´e una breve introducci´on a la programaci´on funcional usando el c´alculo lambda; para
simplificar los ejemplos usaremos el c´alculo lambda no tipado o libre de tipos (tambi´en existe el tipado [lo
veremos en la siguiente secci´on]).
El c´alculo lambda no tipado (CLNT) es un peque˜no lenguaje formal, muy flexible, muy poderoso. Tambi´en
se le conoce como una m´aquina reductiva (¡pronto lo podr´a inferir!). Es ´util porque nos permite hacer uso de
algunas caracter´ısticas que se encuentra en todos los lengua jes funcionales: predominancia de la recursividad
por sobre los ciclos iterativos; la inmutabilidad de variables; la reducci´on de expresiones. Y se llama ((alculo
lambda)) porque realiza un c´alculo, en este caso, una reducci´on o derivaci´on (formalmente se conoce como
reducci´on ). La idea subyacente detr´as del CLNT es reducir una expresi´on lambda, en donde cada paso
representa la ((computaci´on)).
El CLNT se puede describir usando la notaci´on BNF (Backus-Naur-Form):
e::= x|(λx . e)|e e
Con esta simple definici´on ya tenemos cubierto todas las combinaciones para crear expresiones lambda
usando el CLNT. Si tiene alguna confusi´on de como ser´ıa su sintaxis, a continuaci´on lo ir´e explicando en
detalle.
5Para demostrar la indecidibilidad del problema de la decisi´on (un problema de la decisi´on es todo aquel que dado un enunciado
espera una respuesta ((verdadera)) o((falsa)), es decir, la respuesta es binaria, solo dos opciones), Turing propuso el problema de
la parada, que reza as´ı: ((Si existe un programa P que recibe como argumento un programa K con datos de entrada X; P debe
devolver 1 si K con la entrada X termina en un n´umero finito de pasos, mientras que devuelve un 0 si K con X cae en un bucle
infinito)).
agina 4
Introducci´on a la programaci´on funcional usando Haskell y Agda7
Camilo Chac´on Sartori
camilochs@gmail.com
Veamos un primer ejemplo:
(λ x . x)y
El ejemplo superior es una expresi´on lambda que tiene definida la funci´on lambda (λ x . x), con el argumento
de entrada y. El s´ımbolo lambda λsignifica que estamos definiendo una funci´on lambda, la cual recibe como
argumento x(su cabecera: ((λ x))); seguido por un punto ((.)), el cual significa que a la derecha se definir´a el
cuerpo de la funci´on. Toda funci´on lambda tiene una cabecera y un cuerpo.
El cuerpo contiene una expresi´on, la cual toda funci´on lambda debe devolver. En este ejemplo devuelve
el mismo argumento de entrada, entonces se trata de una funci´on lambda de identidad (dado cualquier
argumento devolver´a el mismo). Tenga presente que una funci´on lambda no se asigna un nombre, es entonces,
an´onima por definici´on (¡ahora ya sabe de donde proviene el concepto de funci´on an´onima o lambda utilizado
en muchos lenguajes populares!).
Ahora dada la expresi´on anterior, apliquemos la reducci´on sobre ella:
(λ x . x)y
y
Esta devuelve y. Comienza yreemplazando a la primera xque se encuentra en la cabecera, despu´es si
existe x(de la cabecera) existe en el cuerpo lo sustituye (xes una variable bound), entonces lo devuelve (v´ease
la Figura 2). A continuaci´on una figura que sirva como esquema visual de esta computaci´on:
Figura 2: Funci´on lambda de identidad en el c´alculo lambda no tipado (CLNT).
Reducci´on Comentarios
(λ x . h)y x es una variable free porque no aparece en su cabecera
h y, por tanto, se ((pierde))
Ahora podemos avanzar a un ejemplo un poco m´as complejo. (Cabe se˜nalar que cada expresi´on lambda,
cualquiera sea, se agrupan de izquierda a derecha, en pares). Para evitar confusiones usaremos par´entesis para
dejar esta cuesti´on en claro. As´ı, si tenemos la siguiente expresi´on lambda:
agina 5
Introducci´on a la programaci´on funcional usando Haskell y Agda8
Camilo Chac´on Sartori
camilochs@gmail.com
((λ x . x) (λ y .y)) (λ z . z)
Los pasos para su reducci´on son los siguientes (v´ease que las tres funciones lambdas son de identidad):
L´ınea Reducci´on
1 ((λ x . x) (λ y .y)) (λ z . z)
2 (λ y .y) (λ z . z)
3 (λ z . z)
Algo interesante del ejemplo superior es que en la l´ınea 3 no va a devolver z. ¿Por qu´e? Dado que no tiene
otra expresi´on a la derecha (argumento de entrada) no puede aplicar (ejecuci´on) ninguna operaci´on; as´ı, no
queda m´as que devolver la funci´on lambda: (λ z . z).
Terminamos esta breve introducci´on a CLNT con un ejemplo, un poco, solo un poco, m´as intrincado:
Reducci´on Comentarios
((λ x .(x x) 1) (λ y ,2)) (λ z . z) La expresi´on (x x) tiene dos veces la variable bound x
((((λ y ,2) (λ z . z))((λ y ,2) (λ z . z))) 1) Por ello se sustituye dos veces con la expresi´on ((λ y ,2) (λ z . z ))
((2 ((λ y ,2) (λ z . z))) 1)
((2 2) 1) Esta expresi´on no es reducible
ease que la ´ultima expresi´on ((2 2) 1) no puede ser reducida a ninguna otra expresi´on porque carece
de una funci´on lambda disponible. La reducci´on solo ocurre cuando hay una funci´on lambda disponible (a
la izquierda) y, adem´as, el argumento (a la derecha) existe. Si va realizando cada sustituci´on –de izquierda
a derecha–, teniendo en cuenta las variables bound, y luego fij´andose cu´ales son free, entonces no va a tener
problema con entender este ejemplo. Recomiendo hacerlo con un l´apiz y papel o en alg´un editor de texto.
Para finalizar con esta secci´on, dejo a usted la siguiente funci´on lambda. Intente aplicar la reducci´on:
(λ x .(x x)) (λ x . (x x))
¿Lo hizo? Bienvenido al problema de la parada :-)
Church se percat´o de este problema, y es por ello que, algunos a˜nos despu´es, introdujo el c´alculo lambda
tipado que veremos en la siguiente secci´on.
Nota: Tambi´en puede revisar mi introducci´on al CLNT usando Racket en el siguiente enlace. Si no
es suficiente, y necesita mayor profundidad sobre el CLNT, y a su vez, de la programaci´on funcional y
de la computaci´on en general, podr´ıa pensar en adquirir mi libro: Computaci´on y programaci´on funcional.
Una introducci´on al c´alculo lambda y la programaci´on funcional usando Racket y Python[Cha21]. En donde
presento este tema con mayor detalle y profundidad.
4. Teor´ıa de tipos
La teor´ıa de tipos fue inventada por Bertrand Russell un destacado fil´osofo anal´ıtico del siglo XX, en
respuesta a la paradoja que ´el mismo hab´ıa encontrado a˜nos antes en el sistema axiom´atico (teor´ıa informal
de conjuntos) presentado por el l´ogico Gottlob Frege. A esta paradoja –o mejor dicha antinomia– se la conoce
como la ((paradoja de Russell)). Su trabajo fue puesto a disposici´on del p´ublico en su obra magna: ((Principia
Mathematica)), junto a Alfred North Whitehead, publicado entre 1910 y 1913. Dicha obra fue un enorme
manantial de ideas para los l´ogicos que, posteriormente, comenzaron a crear los fundamentos te´oricos de la
inform´atica.
En la d´ecada de 1940, Alonzo Church que a˜nos antes hab´ıa inventado el c´alculo lambda no tipado (CLNT),
extendi´o su sistema a uno que incorporar´a la noci´on de sistema de tipos. Este ((lenguaje)) tendr´ıa una enorme
agina 6
Introducci´on a la programaci´on funcional usando Haskell y Agda9
Camilo Chac´on Sartori
camilochs@gmail.com
repercusi´on, en particular, en lo referente al surgimiento de una nueva ´area de investigaci´on: la teor´ıa de tipos,
que se present´o como una alternativa a la teor´ıa de conjuntos y, adem´as, demostr´o ser de gran provecho a la
inform´atica. La teor´ıa de tipos es entonces una intersecci´on entre la matem´atica y la inform´atica.
A continuaci´on dar´e una breve introducci´on al c´alculo lambda tipado para entender en qu´e se diferencia
del CLNT, y, conoceremos algunas reglas de derivaci´on.
4.1. alculo lambda tipado (λ)
El c´alculo lambda tipado o tambi´en llamado c´alculo lambda simplemente tipado (λ) es una forma de
teor´ıa de tipos que extiende al CLNT incorporando el ((formador de tipos)) con el s´ımbolo que construye
tipos de funciones.
El tipo funci´on στda cuenta que, dado un argumento de tipo σ, devuelve un valor de tipo τ. La
flecha significa que es una funci´on, y la asociaci´on, por convenci´on, siempre ocurre desde la derecha, a saber:
στψes lo mismo que σ(τψ). Y se puede leer como: ((el tipo funci´on admite un argumento
de tipo τy devuelve un valor de tipo ψ, luego la siguiente tipo funci´on admite un argumento de tipo σy
devuelve un valor de tipo ψ))
La sintaxis de λen BNF –que incorpora tipos– es la siguiente:
e::= x|(λx :τ . e)|e e
Entonces con esta sintaxis una funci´on lambda con tipos tiene esta forma: (λx :τ .x), donde la variable x
tiene asociado τque es un tipo (v´ease la Figura 3). Con respecto a esto, a continuaci´on doy algunos ejemplos
alidos sint´acticamente:
1. (λ x :τ . (λ y :τ . y)x)
2. (λ x :τ1.(λ y :τ2.(λ z :τ3. z)y)x)
Figura 3: Funci´on lambda de identidad con tipo en el c´alculo lambda tipado.
En el primer ejemplo las dos funciones lambdas tienen el mismo tipo τ, en cambio, el segundo ejemplo
cuenta con tres tipos diferentes: (τ1, τ2, τ3). Cuando se quiere pasar como argumento una expresi´on lambda
que tiene un tipo que no es el esperado en la cabecera de la funci´on lambda esta no es compatible, no puede
reducir, no puede computar. Como se ve a continuaci´on:
(λ x :τ . x) (e:ξ)
En el ejemplo anterior τ6=ξson tipos diferentes, por ello, la expresi´on lambda no es ((computable)), no
reducible. Cambiando a los tipos tradicionales que encontramos en los principales lenguajes de programaci´on
se ver´ıa as´ı:
agina 7
Introducci´on a la programaci´on funcional usando Haskell y Agda11
Camilo Chac´on Sartori
camilochs@gmail.com
(λ x : int . x) (e: string)
La variable xsolo puede aceptar como argumento expresiones lambdas que tenga asociado el tipo int,
y dado que el argumento ees del tipo string, no cabe posibilidad de reducci´on. Esto es una importante
diferencia con el CLNT que, por naturaleza, es polim´orfico (puede aceptar m´ultiples objetos, es flexible y no
tiene restricciones de tipos). V´ease la siguiente Figura 4 para un caso exitoso de reducci´on:
Figura 4: Reducci´on de una expresi´on lambda con tipos compatibles.
4.2. Correspondencia de Curry-Howard
La correspondencia de Curry-Howard10 hace referencia a que existe una relaci´on directa entre la l´ogica y
la inform´atica. Fue obra de los trabajos realizados por los l´ogicos-matem´aticos estadounidenses Haskell Curry
y William Alvin Howard a mediados del siglo XX, que, aunque no fue realizado en conjunto, m´as tarde,
demostraron ser compatibles con la idea fundamental de esta correspondencia, en la idea subyacente de que:
((Una demostraci´on l´ogica (proof) es un programa. Por ende, el sistema de tipos es equivalente a un sistema
ogico.))
¿Un programa es una demostraci´on? Esto se puede lograr mediando el uso de la teor´ıa de tipos; es m´as,
esta relaci´on nos ha alimentado de diversos lenguajes de programaci´on funcionales con tipos que nos ayudan
a tener una mayor confiabilidad y seguridad en los programas que desarrollamos.
Otra cuesti´on importante a aclarar y que es de suma importancia sobre los sistemas de tipos:
((Cuando una expresi´on, variable, estructura tiene asignado un tipo estos no pueden cambiar en tiempo
de ejecuci´on. A esto nos referimos con: inmutabilidad.))
¿Se imagina escribir un programa (demostraci´on en este contexto) donde cada variable cambia de tipo en
tiempo de ejecuci´on? Ser´ıa imposible demostrar nada a priori (en tiempo de compilaci´on); por ello, asegurar
que el programa no se cayera, tuviera errores, o dejara de funcionar inexplicablemente, ser´ıa una tarea inviable.
10Aqu´ı la palabra ((correspondencia)) se usa como se utiliza en matem´atica: dado una funci´on fcon un dominio Ay codominio
B,f:AB, entonces existe al menos una relaci´on binaria entre al menos un elemento de Aen B. As´ı, entonces llegamos al
nombre de dicha correspondencia, para indicar la relaci´on existente entre los trabajos de Curry y Howard.
Curry demostr´o que los tipos podr´ıan verse como un sistema de l´ogica intuicionista, y lo mismo hizo Howard pero conectado
la l´ogica intuicionista con el c´alculo lambda. Dicha relaci´on tambi´en se ha llamado isom´orfica por demostrarse ser equivalentes.
Hoy se ha ampliado para a˜nadir el trabajo de Joachim Lambek en teor´ıa de las categor´ıas, por ello, no es raro ver que algunos
la llaman: correspondencia de Curry-Howard-Lambek. (Como este art´ıculo no presenta la teor´ıa de las categor´ıas, hemos dejado
fuera a Lambek.)
agina 8
Introducci´on a la programaci´on funcional usando Haskell y Agda12
Camilo Chac´on Sartori
camilochs@gmail.com
Esto es uno de los problemas de algunos lenguajes imperativos que, aunque tengan un sistema de tipo, este es
ebil; y es que, aunque se pueda asignar tipos a las variables, est´as pueden convertirse a otro tipo compatible
en tiempo de ejecuci´on (casting). Mantienen una cierta permisividad; que los lenguajes que veremos en este
art´ıculo (Haskell y Agda) no cuentan.
4.3. Reglas de derivaci´on
En esta secci´on conocemos algunas caracter´ısticas te´oricas de la teor´ıa de tipo: las reglas de derivaci´on.
Ya vimos en el c´alculo lambda libre de tipos y con tipos, que, desde una expresi´on lambda, se puede reducir
hasta llegar a una expresi´on irreductible (forma normal).
En el apartado anterior mencion´e que seg´un la correspondencia de Curry-Howard una demostraci´on equi-
vale a un programa. En matem´atica se puede decir que:
pes una demostraci´on de la proposici´on P
En la teor´ıa de tipos la sentencia anterior es equivalente a:
pes miembro del tipo P
La cual se puede leer como p:P, y es, sin duda, muy importante para entender el contexto de la teor´ıa
de tipo y su equivalencia con un sistema l´ogico (p. ej. la l´ogica proposicional).
En la teor´ıa de tipos nos encontramos con varias reglas de derivaci´on (o reglas de inferencia), donde cada
una explica algo diferente; a continuaci´on se presenta una tabla explicativa:
Regla ¿Qu´e responde? ¿De qu´e trata?
Regla de formaci´on ¿Cu´ales son los tipos del siste-
ma?
Regla que permite describir
que cadena de s´ımbolos son
sint´acticamente v´alida en este
lenguaje, en este caso, para el
sistema de tipos
Regla de formaci´on ¿Cu´ales son los tipos del siste-
ma?
Regla que permite describir
que cadena de s´ımbolos son
sint´acticamente v´alida en este
lenguaje, en este caso, para el
sistema de tipos
Reglas de introducci´on y eliminaci´on ¿Qu´e expresiones son miem-
bros de qu´e tipo?
La regla de introducci´on incor-
pora el uso de un operador l´ogi-
co; en cambio, la regla de elimi-
naci´on lo elimina
Regla de computaci´on (o reducci´on β) ¿C´omo estos objetos pueden
ser reducidos a formas simples?
(es decir, c´omo se eval´ua una
expresi´on)
Una expresi´on puede ser reem-
plazada o sustituida, esto re-
presenta un paso dentro de la
computaci´on
Nota: Para efectos pr´acticos, veremos estas reglas de inferencia aplicadas al operador de conjunci´on ().
(Dejamos a usted la labor de profundizar en como se usan estas reglas con otros operadores, por ejemplo:
disyunci´on, negaci´on e implicaci´on).
agina 9
Introducci´on a la programaci´on funcional usando Haskell y Agda13
Camilo Chac´on Sartori
camilochs@gmail.com
4.3.1. Regla de formaci´on
Aes un tipo Bes un tipo
(AB)es un tipo (F)
Se lee como ((Si A es un tipo y B es un tipo, entonces ABtambi´en es un tipo)).
4.3.2. Regla de introducci´on
p:A q :B
(p, q) : (AB)(I)
Se lee: ((Si pdel tipo Ayqdel tipo B, entonces la tupla (p, q) tiene el tipo AB. El primer ptiene el
tipo A; y el segundo q, tiene el tipo B)).
4.3.3. Regla de eliminaci´on
r: (AB)
fst r:A(E1)r: (AB)
snd r:B(E2)
Para la regla E1se lee: ((Si rdel tipo AB, entonces la operaci´on fst devuelve el primer elemento rdel
tipo A))
Para la regla E2se lee: ((Si rdel tipo AB, entonces la operaci´on snd devuelve el segundo elemento rdel
tipo B))
4.3.4. Regla de computaci´on
fst(p, q)psnd(p, q)q
Se lee: ((Si se aplica la operaci´on fst a la tupla (p, q), entonces la expresi´on se reduce a p; Si se aplica la
operaci´on snd a la tupla (p, q), entonces la expresi´on se reduce a q))
Conclusi´on: La teor´ıa de tipos es un ´area amplia y profunda dentro de la inform´atica te´orica, o siendo
espec´ıfico, de la teor´ıa de lenguajes de programaci´on. Hay muchas teor´ıas de tipos que buscan resolver distintos
problemas. Algunas son extensiones de otras. Independiente de cu´al se estudie todas buscan entender c´omo
crear software confiable. As´ı pues, en este breve repaso solo he dado un resumen general por donde usted pueda
explorar este mundo que, a fin de cuenta, lo que busca es dise˜nar e implementar mejor software. Recomiendo
revisar las referencias al final de este documento para profundizar en este tema.
Nota: Por ejemplo, los lenguajes de programaci´on que veremos a continuaci´on: Haskell y Agda, se basan
en diferentes teor´ıas de tipos. Haskell en el sistema de tipos de Hindley–Milner; y Agda en la teor´ıa unificada
de tipos dependientes que es similar a la teor´ıa de tipos de Martin-L¨of. Ya explicaremos en qu´e se distinguen.
Por otro lado, para estudiar a fondo la teor´ıa de tipo lo remito al libro: ((Type Theory and Functional
Programming))[Tho91]. Recomiendo, en particular, los cap´ıtulos del 1 al 4. Da una introducci´on a la l´ogica
proposicional y de predicados, luego a la programaci´on funcional y a teor´ıa de tipos. Excelente. Totalmente
vigente.
agina 10
Introducci´on a la programaci´on funcional usando Haskell y Agda14
Camilo Chac´on Sartori
camilochs@gmail.com
5. De la teor´ıa a la pr´actica
la programaci´on funcional. Luego surgir´ıa la teor´ıa de tipos que le entrega los fundamentos te´oricos para
crear programas confiable, en base la correspondencia de Curry-Howard, para as´ı llegar a la concepci´on de
que es posible tener programas que sean equivalente a demostraciones matem´aticas.
Como dijo John Hughes en su influente art´ıculo: Why functional programming matters (¿Por qu´e es
importante la programaci´on funcional?).
La programaci´on funcional se llama as´ı porque su operaci´on fundamental es la aplicaci´on de fun-
ciones como argumentos. El propio programa principal se escribe como una funci´on que recibe
la entrada del programa como argumento y entrega la salida del programa como resultado. Nor-
malmente, la funci´on principal se define en t´erminos de otras funciones, que a su vez se definen
en t´erminos de m´as funciones a´un, hasta que en el nivel inferior las funciones son primitivas del
lenguaje. [Hug89]
A continuaci´on comenzaremos con la parte aplicada de esta entrada usando un lenguaje de programaci´on
puro como lo es Haskell, y luego dar´e paso a otro que pretende ser su evoluci´on: Agda.
6. Haskell
Tenemos lenguajes de programaci´on m´as presentes en la industria que a la acad´emica. Un caso ser´ıa, por
ejemplo, Python o PHP o JavaScript. Por otro lado, tenemos algunos que son m´as f´acil de encontrar en un
entorno acad´emico: Racket, Haskell, MATLAB. No obstante, la mayor´ıa de estos lenguajes se encuentran en
ambos mundos, en la industria y en la academia, pero con distintos grados de presencia.
Haskell es un lenguaje que cuenta con un sistema de tipo est´atico, y es, funcional puro.
Lo primero nos indica que cada variable y firma de una funci´on debe tener asociado un tipo de dato;
lenguajes como Python no cuenta con esta propiedad y son de tipado din´amico, es decir, no es necesario
al crear una variable o al definir la firma de una funci´on asignar un tipo de dato a la variable o a los
argumentos de la funci´on (o el tipo que devolver´a).
Lo segundo, lo diferencia de otros lenguajes funcionales, como por ejemplo los del dialecto o familia
Lisp (Scheme, Racket, Clojure, etc.) que son m´as permisivos con ciertas cuestiones (a saber, admiten
un tipado din´amico, estados mutables sobre las variables), Haskell en cambio, es m´as restrictivo dado a
su rico sistema de tipo (caracter´ısticas que permite producir menos errores en los programas).
6.1. Instalaci´on
Linux (Ubuntu). Para instalarlo debe ejecutar el siguiente comando:
1$su d o ap t - g et in s ta l l h as k el l - p la t fo r m
2$gh c - - v er s io n
Nota: GHC (Glasgow Haskell Compiler) es el nombre del compilador de Haskell. Para m´as detalles (y
sobre como instalarlo en Windows o Mac OS) puede visitar el sitio web oficial de Haskell: enlace.
Para compilar un fichero .hs (extensi´on de Haskell) puede seguir los siguientes pasos:
1$ec h o ’ m a i n = put S t r L n " ¡ H o l a M u ndo !" ’ > ho l a m u n d o . hs
2$gh c h o l a mu n d o . hs
3$./ h o la mu nd o
4$¡ Ho l a M un d o !
agina 11
Introducci´on a la programaci´on funcional usando Haskell y Agda15
Camilo Chac´on Sartori
camilochs@gmail.com
6.2. Caracter´ısticas
Las caracter´ısticas que, al menos una persona que se adentra la programaci´on funcional m´as repetidas
veces aparece, son, al mismo tiempo, caracter´ısticas propias de Haskell (v´ease la Figura 5):
1. Funciones puras.
2. Funciones de primer orden.
3. Evaluaci´on perezosa (lazy evalution).
4. B´usqueda de patrones.
5. Transparencia referencial (la m´as fundamental).
No son las ´unicas, pero si las que suelen ser las m´as representativas. Y es tambi´en cierto que algunas se
solapan entre s´ı, o mejor dicho, colaboran entre s´ı.
Antes de explicar estas caracter´ısticas es importante insistir en algo: en la programaci´on funcional las
funciones son el eje central de todo el c´odigo que escribimos, la estructura esencial, su naturaleza. La compu-
taci´on se forma a trav´es de funciones (como se menciona en ingl´es: first-class function [que en espa˜nol ser´ıa:
las funciones son ciudadanos de primera clase, esto para recalcar su categor´ıa]).
Similar a lo que ocurre con las variables que cambian de estado en la programaci´on imperativa, o, el
manejo de mensajes que se van enviando de un objeto a otro en el paradigma orientado a objetos, en la
programaci´on funcional las funciones son lo principal. Y como las funciones son tan importantes es que, en
las caracter´ısticas que veremos a continuaci´on, aparecen en cada ejemplo.
Figura 5: Cinco caracter´ısticas fundamentales de la programaci´on funcional. Haciendo ´enfasis en la transpa-
rencia referencial.
agina 12
Introducci´on a la programaci´on funcional usando Haskell y Agda16
Camilo Chac´on Sartori
camilochs@gmail.com
6.2.1. Funciones puras
Una funci´on pura es toda aquella que dado el mismo conjunto de argumentos, esta devuelve el mismo
valor; y que adem´as, est´a libre de efecto colateral: no se modifica el estado de una variable externa dentro de
la funci´on, es entonces, una funci´on pura una funci´on inmutable. Esto aduce que una funci´on como rand()
en C o input() en Python son imposible en este tipo de lenguajes. Pues con cada ejecuci´on devolver´ıa un
valor diferente (aunque siempre se proveen mecanismo para hacer factible incorporar este tipo de cuestiones
cubiertas de alguna estructura que lo haga m´as segura [un ejemplo son las Monad para utilizar I/O], pues, es
imposible omitir por completo la necesita de I/O, o, de hacer uso de la funci´on rand()).
Ejemplo 1. Comencemos con un ejemplo trivial: definir una funci´on que sume dos n´umeros.
1sum T w o N u m : : Int -> In t - > I nt
2sum T w o N u m x y = x + y
3Prelude > su m T w o N u m 1 2
43
Cada l´ınea hace lo siguiente:
1. La firma la funci´on, su definici´on. La funci´on tiene el nombre de sumTwoNum, y el s´ımbolo :: significa
que a la derecha se definir´a los tipos de argumentos de entrada junto al tipo que devuelve la funci´on (el
´ultimo). Para este ejemplo, los primeros dos Int significa que la funci´on acepta dos valores Integer, y,
devuelve un Integer. La composici´on se realiza usando la flecha (similar a como se usa en el c´alculo
lambda tipado).
2. Acepta dos argumentos, se puede a˜nadir cualquier nombre como alias, en este caso asigne la letra x
(para el primer argumento) e y (para el segundo). Despu´es del s´ımbolo =, a la derecha, es el ((cuerpo))
de la funci´on. Pues ah´ı se realiza la operaci´on de suma.
3. Se invoca a la funci´on sumTwoNum pasando dos argumentos: 1 y 2.
4. Devuelve 3.
Ejemplo 2. Un ejemplo de funci´on pura es una que recibe como argumento una lista de valores y devuelve
una nueva lista con los mismos valores, es decir, una funci´on que copia una lista:
1cL i st :: [ I nt ] - > [ Int]
2cL i s t [ ] = [ ]
3cL i s t [ x ] = [ x ]
4cL i s t ( x: x s ) = ( c Li s t [ x ]) ++ ( c Li s t x s )
5Prelude > c Li st [1 , 2 ,3]
6[1 , 2 ,3 ]
Para entender la sintaxis de Haskell explicar´e cada l´ınea:
1. Es la firma de funci´on, su definici´on. En este caso, el primer [Int] (a la izquierda) significa que la funci´on
cList solo acepta una lista de valores Integer; y el segundo [Int] significa que solo devolver´a una lista de
Integer.
2. Es el primer case base: si el argumento (lista) est´a vac´ıa [], entonces devuelve una lista vac´ıa [].
3. Segundo caso base: si la lista tiene solo un elemento [x], entonces devuelve la lista con ese elemento [x].
4. Aqu´ı pasa lo importante: si la lista tiene m´as de un elemento, entonces realiza una concatenaci´on de
lista ++ usando un doble invocaci´on recursiva. La primera, (cList [x]) se pasa una lista con un solo
elemento, donde x es la cabecera de la lista (head); y la segunda, (cList xs), donde xs es la cola de la
lista (tail). Importante: Haskell permite obtener a trav´es del pattern matching directamente la cabecera
y la cola de la lista, usando la sintaxis: (x:xs).
5. Se invoca a la funci´on.
agina 13
Introducci´on a la programaci´on funcional usando Haskell y Agda17
Camilo Chac´on Sartori
camilochs@gmail.com
6. Valor de retorno: una nueva lista con los elementos id´enticos.
Nota: Como ejemplo para aprender c´omo usar listas en Haskell, el ejemplo anterior est´a bien; pero, para
el ((mundo real)) carece de sentido, pues, en Haskell toda lista es inmutable, por ello, no es necesario copiarla.
6.2.2. Funciones de primer orden (high-order functions)
Una funci´on de primer orden indica que un programa admite que sus funciones reciban como argumento
otras funciones. En los lenguajes funcionales esto ocurre constantemente y es algo natural. Un ejemplo b´asico
ser´ıa la funci´on map.
Ejemplo 1.
1map C u s t o m : : ( a -> b ) - > [ a ] - > [ b ]
2map C u s t o m f [ ] = []
3m ap C u st o m f ( x : xs ) = [ ( f x ) ] + + m a pC u s t om f x s
Cada l´ınea hace lo siguiente:
1. Se define la funci´on mapCustom, su firma. El primer argumento es una funci´on (a b) y el segundo
una lista [a]; que, finalmente devuelve una lista [b].
2. ´
Unico caso base: si se recibe una funci´on y una lista vac´ıa [], entonces no hay nada que hacer, devuelve
una lista vac´ıa [].
3. Si se recibe una funci´on y una lista con elementos, entonces primero se invoca a la funci´on f con el
primer elemento de a lista: x. Luego, se concatena la lista con una invocaci´on recursiva de mapCustom,
donde se pasa como argumento la misma funci´on f y la cola de la lista: xs.
Y algunos ejemplos de invocaci´on podr´ıan ser los siguientes:
1> m ap C us to m ( \ x -> x + 1 ) [1 , 2 ,3 ] - - [ 2 ,3 , 4]
2> m ap C u st o m ( \ x - > x * ( - 1 ) ) [ 1 , 2 , 3] -- [ -1 , - 2 , -3 ]
3> map C u s t o m reverse ["ab"," cd " ]- - [" b a " ," d c "]
1. Aqu´ı se invoca dando como primer argumento una funci´on an´onima (tenga presente la sintaxis). El es el
alias del argumento que recibe la funci´on an´onima (recuerde que una funci´on an´onima por definici´on no
es necesario asignarle un nombre), y devuelve lo que se define en su cuerpo (despu´es del s´ımbolo (()))
que ser´ıa incrementar en 1 a cada uno de los elementos de la lista.
2. Lo mismo que la l´ınea anterior, excepto que en vez de sumar un 1 a cada elemento, se multiplica por -1.
otese que en Haskell no es posible hacer: x * -1, por ello, debe hacer uso de par´entesis para ((cubrir))
al n´umero negativo. Pues el s´ımbolo * opera en infix y el - en prefix, entonces el compilador deber´ıa
arrojar un error porque no sabe el orden en cu´al debe evaluar la expresi´on.
3. En este caso no definiremos una funci´on an´onima, en cambio, usar´e una funci´on est´andar: reverse. Que,
si la lista es de caracteres, entonces los invierte.
Ejemplo 2. Ahora modificaremos la funci´on mapCustom a otra con el nombre mapCustomWithRepeat,
en donde a˜nadiremos un argumento extra para repetir los elementos de una lista, a saber, lo siguiente deber´ıa
devolver:
1> m a pC u s t om W i t hR e p e a t ( \ x - > x + 1) 2 [1 , 2]
2[2 , 2 ,3 , 3]
3> mapCustomWithRepeat reverse 3 [ " a b " ," cd " ]
4["bd","b d " ,"bd","dc","d c " ," dc " ]
La definici´on de mapCustomWithRepeat ser´ıa:
agina 14
Introducci´on a la programaci´on funcional usando Haskell y Agda18
Camilo Chac´on Sartori
camilochs@gmail.com
1mapCustomWithRepeat :: (a -> b) -> In t - > [ a ] - > [ b ]
2mapCustomWithRepeat f _ [] = []
3mapCustomWithRepeat f r (x:xs) = take r ( repeat (f x) ++ mapCustomWithRepeat f r xs
La ´unica diferencia con mapCustom es la incorporaci´on de un argumento Int que nos permitir´a repetir
cada elemento, haciendo uso claro, de dos funciones del est´andar de Haskell: take y repeat. ¿Por qu´e debe ser
Int y no a o b? Pues porque take tiene la restricci´on de tipo Int (su declaraci´on de tipo es: ((take :: Int [a]
[b]))), por ello, si lo us´aramos con el argumento r (l´ınea 3) estamos obligados a definirlo como Int (l´ınea 1).
Nota: Revisaremos algunos otros ejemplos en la secci´on de transparencia referencial.
6.2.3. usqueda de patrones (pattern matching)
Hasta ahora hemos visto la b´usqueda de patrones de forma indirecta en Haskell, como por ejemplo el caso
de (x:xs) que encuentra autom´aticamente el siguiente patr´on: primer elemento de la lista junto a su cola.
Pero hay mucho m´as, revisemos algunos ejemplos. Pues es, sin duda, una de las caracter´ısticas m´as populares
en los lenguajes funcionales.
Ejemplo 1. Un divertido ejemplo para ver la flexibilidad de la b´usqueda de patrones en Haskell es, por
ejemplo, dado una funci´on que recibe como argumento una lista (llam´emosle x), esta debe devolver una nueva
lista y omitiendo el primer y ´ultimo elemento de la lista x. Como se puede apreciar en los ejemplos de abajo:
1> r em o ve F i rs t La s tE l em e n t [1 , 2 ,3 , 4 ,5 ]
2[2 , 3 ,4 ]
3> r em o ve F ir s tL a s tE l em e nt [1 , 2 ,3 ]
4[2 ]
5> r em o ve F ir s tL a st E le m en t [ 1 ,2 ]
6[]
7> remove F i r s t L a s t E l e m e n t [1]
8[]
9> remove F i r s t L a s t E l e m e n t []
10 []
La definici´on de la funci´on removeFirstLastElement es la siguiente:
1remove F i r s t L a s t E l e m e n t :: [ a ] - > [ a ]
2remove F i r s t L a s t E l e m e n t [] = []
3remove F i r s t L a s t E l e m e n t [ _ ] = []
4r em o v e Fi r s t L as t E l em e n t [ _ , _ ] = [ ]
5r em o v e Fi r s t L as t E l em e n t ( x : x s ) = [ head x s ] ++ r e m o v e F i r s t L a s t E l e m e n t xs
Se pueden ver cuatro expresiones que aplican la b´usqueda de patrones (l´ınea 2-5):
1. Definici´on de la funci´on removeFirstLastElement. Recibe una lista [a] y devuelve una nueva lista del
mismo tipo.
2. Primer caso base: si el patr´on es una lista vac´ıa [], entonces devuelve una lista vac´ıa [].
3. Segundo caso base: si la lista tiene un elemento (cualquiera) [ ], entonces devuelve una lista vac´ıa [].
4. Tercer caso base: si la lista tiene dos elementos (cualquiera) [ , ], entonces devuelve una lista vac´ıa [].
5. Si la lista tiene al menos tres elementos, entonces concatena la cabecera de la cola (head xs) con la
invocaci´on recursiva: removeFirstLastElement xs.
No s´e si se habr´a percatado, y es que, en cada nuevo patr´on que se define, es importante que est´e definido en
un orden. A saber, no es lo mismo:
1te s t : : [ a ] -> [ a ]
2te s t ( x : xs ) = x s
3te s t _ = [ ]
4> te s t [1 , 2 ,3 ]
5[2 , 3 ,]
agina 15
Introducci´on a la programaci´on funcional usando Haskell y Agda20
Camilo Chac´on Sartori
camilochs@gmail.com
Que el c´odigo de abajo que, como puede ver, devolver´a algo totalmente distinto:
1te s t : : [ a ] -> [ a ]
2te s t _ = [ ]
3te s t ( x : xs ) = x s
4> te s t [1 , 2 ,3 ]
5[]
Ejemplo 2. Ahora explicar´e una caracter´ıstica muy com´un en Haskell: los tipos de datos algebraicos
(TDA o en ingl´es: algebraic data types [ADT])19 . En resumen: es una forma de crear nuestros propios tipos
de datos –con sus operaciones si es necesario–, diferentes a los que ya nos proporciona Haskell.
Un ejemplo es tener un tipo de datos MultiItem que cuenta con tres constructores: un ´ıtem particular
(Item1); dos ´ıtems (Item2) y uno que acepte una colecci´on de Item2 o Item1 (ItemN). Se puede leer como:
((El tipo MultiItem puede tener el valor Item1 o Item2 o ItemN)).
1data Mul t i I t e m = I t em1 In t | Item 2 Int Int | I t em N [ Mu l ti I te m ]
Si revisamos la firma de cada tipo queda clara su definici´on:
1> :t I t em1
2It e m 1 : : Int - > M u l t i Item
3> :t I t em2
4It e m 2 : : Int - > I nt -> M u l t i I t e m
5> :t I t emN
6It e mN :: [ M ul ti I te m ] - > M ul ti I te m
Al ser cada uno un constructor del tipo MultiItem siempre debe devolver el mismo tipo. En consecuencia,
con la definici´on del tipo MultiItem ya completada es posible crear funciones que operen sobre esta ADT. Por
ejemplo la siguiente funci´on sumItems:
1su m I t e m s : : M u l t i I t e m - > I n t
2su m It e ms (I t em 1 x ) = x
3su m I t e m s ( It e m 2 x y ) = x + y
4su m I t e m s ( I t e m N ( x : xs ) ) = su m I t e m s x + su m [ s u mI t e ms e | e < - x s ]
La funci´on sumItems recibe un argumento del tipo MultiItem y suma sus valores (Item1 e Item2). Sabemos
que, un MultiItem tiene tres constructores para ´ıtem individuales, pares y superiores. El detalle de cada l´ınea
lo explico a continuaci´on:
1. La definici´on de la funci´on sumItems.
2. Primer caso base: se busca el patr´on (Item1 x) y si coincide, entonces devuelve su valor num´erico x que
ser´ıa un Int.
3. Segundo caso base: se busca el patr´on (Item2 x y) y si coincide, entonces suma x e y.
4. Si existe un ItemN, entonces suma todos sus elementos (ya sea Item1 o Item2) con la funci´on sum, usando
lista por compresi´on (¿se acuerda de Python?) en donde se invoca la funci´on sumItems recursivamente
por cada uno de los elementos de la lista.
6.2.4. Evaluaci´on perezosa (lazy evalution)
La evaluaci´on perezosa (EP) trata sobre el orden de evaluaci´on de las expresiones que programamos. El
orden de su computaci´on. Se encuentra dentro de las caracter´ısticas sem´anticas de Haskell. Adem´as tambi´en
nos permite suspender o aplazar la computaci´on hasta que realmente sea necesario realizar un
alculo. Dado esto es que, en Haskell, es posible trabajar con listas infinitas (con el l´ımite impuesto por la
cantidad de memoria RAM que tenga nuestro ordenador disponible).
19Los tipos de datos algebraicos fueron introducidos por Robin Milner en la definici´on del lenguaje de programaci´on ML. Estos
tipos poseen reglas polim´orficas, pudiendo tener ((diversas formas)) en su definici´on a trav´es del uso de la recursividad. Ha sido
adoptada en m´ultiples lengua jes de programaci´on, en particular, los de corriente funcional.
agina 16
Introducci´on a la programaci´on funcional usando Haskell y Agda21
Camilo Chac´on Sartori
camilochs@gmail.com
Como vimos en el ejemplo 2 de las funciones de primer orden donde hice uso de la funci´on repeat y take. La
primera, la funci´on repeat cuenta con la EP, y la funci´on take la evita porque obliga al int´erprete a ejecutarse
inmediatamente:
1>take 10 ( repeat 1 )
2[1,1 ,1,1,1 ,1 ,1,1 ,1 ,1]
Si vemos la definici´on de ambas funciones nos encontramos con lo siguiente:
1repeat :: a - > [ a ]
2repeat x = x : repeat x
3take :: I n t -> [a] - > [ a ]
4take n _ | n <= 0 = []
5take _ [] = []
6take n ( x : x s ) = x : take ( n - 1 ) x s
Con respecto al funcionamiento de la funci´on take: en la l´ınea 5, usa el s´ımbolo (()) para permitir un ((o)):
si el argumento n, y (wildcard, que significa que no es una lista), o, si n 0, entonces devuelve una lista
vac´ıa. As´ı, podemos limitar la computaci´on de la funci´on repeat.
Ejemplo 1. La funci´on repeat solo realiza una concatenaci´on del mismo elemento en pos de devolver una
lista infinita. De hecho si solo invocamos: (repeat 1) originara una recursi´on infinita (como lo puede ver en
su definici´on, l´ınea 2). Incluso algo m´as clarificador es que en Haskell est´a permitido esto:
1>le t r=(repeat 1 )
2> 1 + 1
32
En otros tipos de lenguajes que no tienen por defecto habilitada la EP por defecto esta l´ınea jam´as
terminar´ıa, estar´ıa dentro de un ciclo infinito, y por ello, nunca se ejecutar´ıa el 1 + 1. Pero dado que la
computaci´on se encuentra aplazada y suspendida, entonces hasta que no hagamos uso de r no se ejecutar´a
la computaci´on. Una forma de ejecutar ser´ıa hacer cualquiera de las siguientes expresiones (hay muchas m´as,
pero sirven de ejemplo):
1(lenght r)
2r == ( repeat 1 )
3(repeat r)
4r ++ [0]
Ejemplo 2. Este puede ser el ejemplo m´as claro de EP en Haskell. Pues hay una expresi´on que nunca es
evaluada, se omite, pues no se utiliza:
1> argumentNotEvaluated x y z = x + z
2> argumentNotEvaluated 1 [] 1
32
4> argumentNotEvaluated 1 ’a’ 1
52
6> argumentNotEvaluated 1 ( repeat 1) 1
72
Como podemos ver en el ejemplo superior el segundo argumento y nunca se va a ejecutar, y es, por esta
raz´on, que pas´emosle lo que le pasemos: una lista [], un valor Char, un (repeat 1), etc. el int´erprete de Haskell
no reclama. Pues si vemos el cuerpo de la funci´on argumentNotEvaluated solo se suma los argumento x y z,
nunca se utiliza el y. Si usted hace la prueba en un lenguaje como Python, por ejemplo, el int´erprete arrojar´a
una excepci´on, aunque el argumento y nunca lo use: ¡haga la prueba!
6.2.5. Transparencia referencial
Indica que una funci´on –o para generalizar, una estructura– puede ser sustituida por otra sin afectar el
comportamiento del programa. Esto es relevante e importante para comprender la programaci´on funcional
porque es, por as´ı decirlo, donde podemos llegar a conclusi´on de que, una funci´on se puede tratar como
un valor, y dado que es posible intercambiarla, sustituirla, entonces la programaci´on se hace m´as f´acil de
agina 17
Introducci´on a la programaci´on funcional usando Haskell y Agda22
Camilo Chac´on Sartori
camilochs@gmail.com
modificar, de mantener, de probar a trav´es de pruebas unitarias, por ejemplo. Y el hecho de tener funciones
puras ayuda a crear una transparencia en las estructuras que hacemos referencia.
Ejemplo. Un ejemplo de esta caracter´ıstica podr´ıa ser la funci´on sort, sabemos que existe m´ultiples de
algoritmos para ordenar una lista de n´umeros, as´ı podemos tener dos: bubbleSort (ordenamiento de burbuja,
ineficiente) y quickSort (ordenamiento r´apido, eficiente). En los siguientes ejemplos he utilizado cosas que
puede ser muy interesante para recien llegados a Haskell y que, espero lo encuentre divertido tanto como yo.
Para comenzar asumamos que tenemos una funci´on sort:
1sort f xs = ( f x s )
omo vemos la funci´on sort recibe dos argumentos: el algoritmo a utilizar para ordenar (que llamar´e f) y
la lista de n´umeros (que llamar´e xs). En su cuerpo se ejecuta la funci´on f que necesita de un argumento, en
este caso xs, que es la lista de n´umeros a ordenar. Entonces la idea ser´ıa invocar la funci´on sort de la siguiente
forma:
1> ( sort b u bb l eS o rt [ 6 ,2 , 1 ,5 , 3 ,4 ])
2[1 , 2 ,3 , 4 ,5 , 6]
3> ( sort q u ic k So rt [6 , 2 ,1 , 5 ,3 , 4] )
4[1 , 2 ,3 , 4 ,5 , 6]
5> ( sort b u bb l eS o rt [ 6 ,2 , 1 ,5 , 3 ,4 ]) == ( sort q ui ck S or t [ 6 ,2 , 1 ,5 , 3 ,4 ])
6True
Ahora desarrollaremos los dos algoritmos de ordenamiento que pasaran a ser el primer argumento de la
funci´on sort, para este ejemplo pueden ser dos: bubbleSort o quickSort.
Bubblesort. El ordenamiento de burbuja es uno de b´asicos algoritmos de ordenamiento que existen, pero
que llevado a un lenguaje funcional no es tan simple si se est´a habituado a lenguajes imperativos:
1bS o rt :: ( Ord a ) = > [ a ] -> [a]
2bS o r t [ ] = [ ]
3bS o r t [ x ] = [ x ]
4bS o r t ( x: x s )
5| x > ( head x s ) = head x s : b Sor t ( x : tail x s )
6|otherwise = x : b Sor t xs
7bub b l e S o r t x s = f o l dl (\ ac c _ - > b So r t a cc ) x s xs
A primera vista parece intrincado, pero ya veremos que solo hay que aclarar algunos conceptos b´asicos
que existen en Haskell (le recomiendo ver el c´odigo de este algoritmo escrito en un lenguaje imperativo para
hacer la analog´ıa.):
1. Se define la funci´on bSort. Se a˜nade la restricci´on de que la funci´on solo admitir´a objetos que sean del
tipo ordenable: (Ord a) (los par´entesis se puede omitir), donde Ord es un tipo de clase y a es el alias que
indica que acepta cualquier tipo a que a su vez es compatible con Ord. Por ejemplo Int es compatible
con Ord, pues posee las tres operaciones b´asicas de relaciones: transitividad (si x y y z, entonces
xz es verdadero), reflexividad (x x es verdadero) y antisim´etrica (si x y y x, entonces x ==
y es verdadero). Despu´es de la restricci´on se a˜nade el s´ımbolo . As´ı llegamos a la funci´on bSort que
acepta una lista de tipo [a] y devuelve una lista del mismo tipo.
2. Primer caso base: si la lista se encuentra vac´ıa [], entonces devuelve una lista vac´ıa [].
3. Segundo caso base: si la lista tiene un elemento [x], entonces devuelve la lista con ese elemento [x].
4. Se hace la b´usqueda de patrones (pattern matching) de la lista, donde x es la cabecera y xs la cola.
5. Se utiliza un guard que se representa con un s´ımbolo (()), es similar a un if-if else-else, en esta l´ınea
se valida si x es mayor a la cabecera de la cola (xs), es decir, se comparan los primeros dos elementos
contiguos (x ¿(head xs)). Si es as´ı, entonces concatena la lista con el s´ımbolo ((:)), la cabecera de la cola
junto a una llamada recursiva a bSort que le entrega como argumento la concatenaci´on entre la cabecera
x y la cola de xs (x : tail xs).
agina 18
Introducci´on a la programaci´on funcional usando Haskell y Agda23
Camilo Chac´on Sartori
camilochs@gmail.com
6. Si no se cumple la condici´on de la l´ınea 5, entonces con la palabra reservada otherwise (else) regresa la
concatenaci´on de una lista entre x y la llamada recursiva de bSort pas´andole xs como argumento.
7. Se define la funci´on bubbleSort que invoca a bSort. Usando la funci´on est´andar de Haskell foldl que
tiene la declaraci´on de tipo: (((a ba) a[b] a)) que opera sobre una lista de izquierda (foldl
= fold-left) a derecha (tambi´en existe el foldr = fold-right, que a diferencia del foldl tiene la declaraci´on
de tipo: (((a bb) b[a] b))).
Dado que la l´ınea 7 puede ser la m´as oscura y confusa por el uso de la funci´on est´andar foldl, veamos un
ejemplo simple: la divisi´on sobre una lista de elementos, ´util pues la divisi´on depende mucho del orden
en que se realiza la operaci´on de los dos elementos, porque no es lo mismo dividir 10/5 = 2 que 5/10 =
0.5.
Cada una de estas funciones: foldl y foldr necesita tres argumentos: una funci´on que reciba dos argumentos
y devuelve otro (v´ease la declaraci´on de tipos de cada funci´on [l´ınea 7] para m´as detalles); un valor inicial; y
una lista. Comencemos revisando la funci´on foldl.
1>fo l d l ( /) 5 [ 5 , 10 , 15 ]
2> 6.666666666666666e-3
3-- Detalle:
4-- 5 / 1 5 = 0 . 3 3 3 3 3 3 3 3 3 3 3 3 3 333
5-- 0 . 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 / 10 = 3 . 3 3 3 3 3 3 3 3 3 3 3 3 3 33 e - 2
6-- 3. 3 33 3 33 3 3 33 3 3 33 3 e - 2 / 5 = 6 . 6 66 6 6 66 6 6 66 6 6 66 e - 3
Es left porque el resultado (acumulador) de cada iteraci´on va cambiando el dividendo de la operaci´on
(izquierda) y no al divisor (v´ease la Figura 6).
Figura 6: Funcionamiento de la funci´on foldl.
Ahora veamos como nos devuelve un resultado totalmente distinto si cambiamos de foldl a foldr:
1>fo l d r ( /) 5 [ 5 , 10 , 15 ]
2> 1. 5
3-- Detalle:
4-- 5 / 5 = 1 . 0
5-- 1 0 / 1 . 0 = 1 0 .0
6-- 1 5 / 1 0 . 0 = 1 . 5
Es distinto porque el resultado (o acumulador) de cada iteraci´on hace cambiar al divisor y no al dividendo
(v´ease la Figura 7).
agina 19
Introducci´on a la programaci´on funcional usando Haskell y Agda25
Camilo Chac´on Sartori
camilochs@gmail.com
Figura 7: Funcionamiento de la funci´on foldr.
Nota: En Haskell la concatenaci´on entre lista se realiza usando el operador ((++)), pero cuando tenemos
un valor num´erico que no es una lista siempre debemos hacer esto: ([1] ++ [2,3,4]) == [1,2,3,4]. Convertir el 1
en una lista: [1]. Esto es algo tedioso. Por ello, Haskell nos provee una forma de simplificar esta concatenaci´on
usando el s´ımbolo ((:)): (1:[2,3,4]) == [1,2,3,4]. Es por este motivo que en la funci´on bSort preferimos usar el
((:)) y no el ++.
Quicksort. El ordenamiento r´apido (Quicksort) es uno de los algoritmos de ordenamiento m´as populares,
y ampliamente utilizado en m´ultiples lenguajes de programaci´on como el est´andar para la funci´on sort. Aunque
claro, no utilizan la misma versi´on que todos conocemos, son variaciones de Quicksort que a˜naden novedosas
optimizaciones (por ejemplo, el dual-pivot Quicksort propuesto por Vladimir Yaroslavskiy en 2009)24. Sin
embargo, para este ejemplo usaremos el Quicksort b´asico.
1q ui c kS o r t :: ( O r d a ) = > [ a ] - > [ a ]
2qui c k S o r t [ ] = [ ]
3q ui c k So r t ( p : xs ) = qu ic k S o rt le s se r + + [ p ] ++ qu i ck S o r t g r ea t e r
4wh e r e
5lesser = filter ( < p ) x s
6greater = filter ( >= p ) x s
El significado de cada l´ınea a continuaci´on:
1. Al igual que la funci´on bSort, aqu´ı se define la firma de la funci´on quickSort a˜nadiendo la restricci´on de
tipo de clase Ord para cualquier lista de tipo a (para m´as detalles v´ease la l´ınea 1 de la funci´on bSort,
porque su firma es equivalente).
2. ´
Unico caso de base: si la lista est´a vac´ıa [], entonces devuelve una lista vac´ıa [].
3. A la izquierda se aplica la b´usqueda de patrones (pattern matching) para obtener la cabecera (p) de la
lista y su cola (xs). He cambiado el nombre de x a p para la cabecera pues en quickSort este elemento
lo conocemos como pivote (v´ease su definici´on). A la derecha (despu´es del s´ımbolo ((=))), se realiza
una concatenaci´on de listas de tres elementos: (a) una invocaci´on recursiva a quickSort dando como
argumento lesser (ya veremos lo qu´e es), (b) seguida por b que es un elemento que se transforma a una
lista [b], (c) finalizando con otra invocaci´on recursiva de quickSort dando como argumento greater, en
esta ocasi´on.
24Puede ver el art´ıculo del Dual-Pivot Quicksort.
agina 20
Introducci´on a la programaci´on funcional usando Haskell y Agda26
Camilo Chac´on Sartori
camilochs@gmail.com
4. El where es una palabra reservada de Haskell que indica que a continuaci´on se realizara una construcci´on
desde una expresi´on previa circundante. ¿Qu´e significa? Por ejemplo que podemos definir variables
despu´es de where para usarla en la expresi´on circundante, as´ı, se puede decir que se ((enlaza)); recordemos
que Haskell es un lenguaje que no es imperativo donde todo se hace de arriba hacia abajo, l´ınea a l´ınea,
en cambio aqu´ı podemos hacer uso de este tipo de expresi´on que hacen el c´odigo mucho m´as funcional
y expresivo.
Entonces se define la variable lesser y greater para asignarle a cada una, una parte de la lista, los que
tienen los valores menores al pivote p (lesser), y los que tienen los valores mayores o igual a p (greater).
En ambos casos, a la funci´on est´andar filter se entrega una funci´on (a Bool) como primer argumento
((<p) y (p)) junto a su segundo argumento que ser´ıa la lista a filtrar (xs). La funci´on filter cuenta
con la siguiente declaraci´on de tipo: (a Bool) [a] [a].
Entonces podemos invocar la funci´on sort primero usando el algoritmo de ordenamiento de burbuja (bub-
bleSort) y luego el quickSort. En ambos casos, se devuelve la misma lista ordenada. Podemos entonces concluir
que la funci´on sort es transparente aunque tenga dos referencias distintas. El comportamiento de sort no
tuvo variaciones, ¡aun cuando al cambiar el primer argumento significa que el c´odigo a ejecutar
es totalmente diferente!
Nota: Aunque un lenguaje de programaci´on funcional evite los efectos colaterales a veces son necesarios,
pues, no podemos escapar de la necesidad de usar los datos de entrada y salida (I/O) o, de requerir, modificar
una lista sin tener que crear una nueva. Para superar esta encrucijada, al menos Haskell, nos proporciona las
monads que es un mecanismo que nos habilita la capacidad de simular efectos indeseados, no puros; sobre
esto, este art´ıculo no trata, pero lo menciono para su curiosidad.
Para una introducci´on completa de Haskell le recomiendo [Lip11].
7. Agda
Adga es un lenguaje de programaci´on con una sintaxis semejante a Haskell, que, incorpora nuevas pe-
culiaridades al lenguaje, asi´endolo a´un m´as seguro y robusto, m´as funcional a fin de cuentas. Entonces, si
te ha gustado o te ha llamado la atenci´on Haskell, es muy probable que tambi´en sientas una atracci´on por
Agda. (¡Que espero no muera cuando leas los siguientes p´arrafos!) Algunas de sus caracter´ısticas son genia-
les, y conf´ıo, pueda transmitirles ese gusto por entenderlas tanto como cuando yo las aprend´ı. Antes bien,
comencemos con algunos datos hist´oricos.
La primera versi´on de Agda fue desarrollada por Catarina Coquand en 1999. Sin embargo, en 2007,
Ulf Norell present´o en su tesis doctoral una nueva reimplementaci´on al lenguaje, reescribiendo totalmente,
por ello, la versi´on actual –y la que usaremos aqu´ı– se le conoce como Agda2, pero para simplificar solo le
llamaremos: Agda. (Cabe se˜nalar que Coquand y Norell son colegas y estudiaron en el mismo lugar: ((Chalmers
University of Technology)), as´ı pues, se puede decir que Agda mantiene una misma ((tradici´on)) de origen. Es
un lenguaje que surge desde la academia al igual que Haskell).
7.1. Instalaci´on
Para Linux primero debe tener las siguientes dependencias instaladas:
1su d o ap t - g et in s ta l l z li b 1g - d ev li bn c ur s es 5 - d ev
Adem´as de lo siguiente:
GHC. https://www.haskell.org/ghc/
cabal-install. https://www.haskell.org/cabal/
Alex. https://www.haskell.org/alex/
agina 21
Introducci´on a la programaci´on funcional usando Haskell y Agda28
Camilo Chac´on Sartori
camilochs@gmail.com
Happy. https://www.haskell.org/happy/
Una vez tenga todo instalado puede proceder a instalar Agda:
1ca b a l u p date
2ca b a l i n s tall A g d a
Nota: Para m´as detalles sobre la instalaci´on puede remitirse a este enlace, sobre todo, si tiene otro sistema
operativo u ocurre alg´un error.
7.2. Tipo de dato
Agda cuenta con una sintaxis similar a Haskell a la hora de crear nuestros propios tipos de datos, aunque
con algunas peculiaridades. En esta secci´on crear´e dos tipos b´asicos: Bool y Nat (n´umero natural); que los
usaremos en todos los ejemplos siguientes.
Nota: Tenga presente que estos tipos de datos ya existen y se pueden importar directamente desde alguna
biblioteca de Agda, no obstante, los crear´e manualmente a modo de ejemplo para ense˜nar algunas cuestiones
del lenguaje.
Comienzo con el tipo Bool:
1data Bool : Set wh e re
2true : Bool
3fa l s e : Bool
Aqu´ı podemos ver varias cosas nuevas con respecto a Haskell, como por ejemplo la palabra reservada Set,
y tambi´en en la sintaxis de la declaraci´on de valores del tipo Bool; a continuaci´on explico cada l´ınea:
1. La palabra reservada para crear un nuevo tipo, al igual que Haskell, es data. Luego viene el s´ımbolo
((:)), despu´es viene la palabra reservada Set que, es un peque˜no tipo en Agda (toda nueva definici´on de
un tipo en Agda requiere un tipo, por ello, se necesita Set que es un tipo simple). (¿Por qu´e Set? Es un
concepto utilizado en la teor´ıa de tipos de Martin-L¨of27, que, como antes mencione, es la teor´ıa de la
cual Agda se asienta.) La palabra reservada where significa que en las siguientes l´ıneas se deben definir
los constructores del tipo Bool.
2. El tipo Bool tiene el valor true, y es su primer constructor.
3. El tipo Bool tambi´en admite el valor false, y es su segundo constructor.
En Haskell ser´ıa equivalente a escribir: data Bool = true — false. En Agda nos vemos obligados a ser m´as
expl´ıcito al momento de definir nuevos tipos.
Nota: El nombre del tipo siempre debe comenzar con may´uscula, y los constructores deben comenzar en
min´uscula.
Ahora vamos por el Nat; este es un tipo de dato que crear´e para representar a los n´umeros naturales, que
nos ayudaran, por ejemplo, para hacer operaciones matem´aticas con los operadores: +,-,*.
1data Na t : S et w h ere
2zero : Na t
3succ : Na t -> Nat
La aclaraci´on de cada l´ınea:
1. Similar a Bool, aqu´ı se define el tipo Nat.
2. El tipo Nat tiene el valor zero, y es el primero constructor.
27Algo curioso sobre el tipo Set es que si definimos un nuevo tipo todos son tambi´en de tipo Set, entonces podr´ıamos pensar
que est´a permitido un tipo Set : Set, pues la respuesta es no. Si eso sucediera caemos en una paradoja, la computaci´on podr´ıa
nunca terminar, y la comprobaci´on de tipo se rompe. Para evitar este problema Agda provee de otros tipos, por ejemplo para el
tipo Set1es S et :Set1, para S et2es Set1:Set2, as´ı sucesivamente. Con ello, evitamos que un tipo Set sea –parad´ogicamente–
del mismo tipo Set.
agina 22
Introducci´on a la programaci´on funcional usando Haskell y Agda29
Camilo Chac´on Sartori
camilochs@gmail.com
3. El tipo Nat tambi´en admite el valor: Nat Nat. Esto quiere decir que el valor succ de tipo Nat es
una funci´on que acepta un argumento de tipo Nat y retorna un valor del mismo tipo. Entonces ser´ıa
alida las siguientes dos expresiones: (succ zero) o (succ (succ zero)); la primera representar´ıa al valor
siguiente de zero (=1) y la segunda al valor siguiente de succ zero (=2).
Figura 8: Representaci´on de los n´umeros naturales seg´un su tipo Nat.
En la superior Figura 8 se ve como se representar´ıa el tipo Nat con su equivalente n´umero natural. Para
evitar escribir siempre la sintaxis de los n´umeros en tipo Nat (succ ...), y solo usar n´umeros naturales, puede
nadir el siguiente pragma debajo de la definici´on de Nat. De esa forma ser´an equivalentes.
1{-# BUILTIN NATURAL Nat #-}
Nota: Tenga en cuenta que cada constructor –al momento de crear un tipo de dato– debe estar indentado
(tabulado o con un espacio) si no el compilador de Agda devolver´a un error. En el caso de la definici´on de
funciones –como veremos a continuaci´on– esta regla no es necesaria.
7.3. Expresiones b´asicas
Una vez ya tenemos creado los dos tipos b´asicos: Bool y Nat, es momento de crear algunas funciones para
operar con ellos. Comenzar´e con las operaciones l´ogicas tradicionales: conjunci´on (&& o AND), disyunci´on
(|| o OR), negaci´on (! o NOT) y disyunci´on exclusiva (o XOR) que har´an uso del tipo Bool. Y, despu´es,
continuar´e con las expresiones aritm´eticas como la suma (+), resta (-) y multiplicaci´on (*), que har´an uso del
tipo Nat.
7.4. Expresiones l´ogicas
Comenzar´e definiendo las funciones && (AND) y || (OR). Ambas en notaci´on infijo (infix).
1_ && _ : Bool - > Bool - > B ool
2true && true = true
3_ && _ = f a l se
Nota: Algo interesante y que hace diferente a Agda con respecto a Haskell es la comprobaci´on de la
totalidad (totality checking). ¿Qu´e significa? Por ejemplo para la definici´on de la funci´on && , si omito el
segundo patr´on: && = false, entonces el compilador me va a reclamar, no compilara. Esto no ocurre en
Haskell, es posible compilar el c´odigo y lo que ocurrir´a es que se lanzara una excepci´on para el patr´on no
agina 23
Introducci´on a la programaci´on funcional usando Haskell y Agda30
Camilo Chac´on Sartori
camilochs@gmail.com
cubierto (a saber, si en Haskell intentas evaluar: false && false, devolver´a una excepci´on, pero si compilara).
¡Esto es realmente genial! Pues el compilador es lo suficiente inteligente para verificar que combinaci´on de
tipos no esta cubierta.
1_ || _ : Bool - > Bool - > B ool
2fa l s e | | x = x
3tr u e | | _ = t r u e
Dada la firma de la funci´on && significa que el primer wildcard debe admitir un Bool y el segundo
tambi´en. Esto es interesante en Agda, pues, se hace una b´usqueda de patrones a nivel del nombre de la
funci´on. As´ı se podr´ıa admitir: true && true o false && true, etc. Lo mismo ocurre para la funci´on || .
Con respecto a las operaciones l´ogicas usted puede comprobar que ambas est´an bien; por ejemplo para
el operador || (OR), si el segundo patr´on: true || = true, quiere decir que da lo mismo el segundo wildcard
porque el primero ya es true, y siempre que se aplica una operaci´on de disyunci´on es suficiente que uno sea
verdadero para que retorne true. Puede verificar los dem´as.
Nota: Como dato curioso es que en Agda tambi´en se podr´ıa hacer las mismas funciones usando otra
notaci´on, como por ejemplo en postfijo ( &&) o prefijo (&& ).
A continuaci´on se define los operadores de negaci´on (!) y XOR ().
1!_ : B ool - > Bool
2! tr u e = f a l se
3! fa l s e = t rue
La negaci´on es bastante simple; la siguiente es la operaci´on de disyunci´on exclusiva que, como podemos ver,
Agda admite el unicode () en el nombre de la funci´on:
1_ _ : Bool - > Bool - > Boo l
2tr u e tr u e = f a lse
3fa l s e f a l s e = f alse
4_ _ = tr u e
Con estas operaciones ya definidas podemos evaluar las siguientes expresiones:
1> fa l s e & & t r u e
2fa l s e
3> fa l s e | | t r u e
4true
5> ! t r ue
6fa l s e
7>! f a lse
8true
9> true true
10 fa l s e
7.5. Expresiones aritm´eticas
Para las expresiones aritm´etica definir´e dos: suma y multiplicaci´on (la resta se la dejo a usted).
1_+_ : Na t -> Na t -> Nat
2zero +m=m
3succ n+m=succ ( n + m )
La funci´on + superior tiene definido el primer caso particular zero + m, que, significa que si hay un zero
a la izquierda, entonces retornara m. El segundo caso particular hace una b´usqueda de patrones succ n + m
directamente para realizar –a la derecha– una invocaci´on recursiva de la funci´on + : succ (n + m).
En la siguiente Figura 9 se puede ver cada paso para la expresi´on 2 + 1 = (succ (succ zero)) + (succ zero):
agina 24
Introducci´on a la programaci´on funcional usando Haskell y Agda31
Camilo Chac´on Sartori
camilochs@gmail.com
Figura 9: Cada paso de la computaci´on para la evaluaci´on: 2 + 1.
1_*_ : Na t -> Na t -> Nat
2zero *n=z ero
3succ n*m=n*m+m
Similar a la funci´on de suma, la multiplicaci´on en su segundo caso particular, aplica una invocaci´on
recursiva n * m y luego m se suma al resultado (claro, invocando a la funci´on + ).
7.6. Expresiones l´ogicas aritm´eticas
Ya tenemos expresiones l´ogicas y aritm´eticas; ahora crear´e algunos operadores que trabajan con ambos
tipos: Bool y Nat. Por ejemplo, el operador menor que (<) y mayor que (>).
1_ < _ : N a t - > Na t - > Bool
2zero <zero = fa l s e
3zero < ( succ _ ) = t rue
4(succ _) < zero = fa l s e
5(succ n ) < ( succ m ) = n < m
1_ > _ : N a t - > Na t - > Bool
2zero >zero = fa l s e
3zero > ( succ _ ) = f alse
4(succ _) > zero = true
5(succ n ) > ( succ m ) = n > m
Creo que con lo que hemos visto –y si a´un sigue aqu´ı– no es necesario entrar en detalles en c´omo funcionan
estas dos funciones. Pero si tiene dudas, haga el ejercicio en un papel (como la Figura anterior) y realice cada
una de las invocaciones recursivas; se dar´a cuenta de que funcionan correctamente, y ante todo, usted ganar´a
experiencia emp´ırica. Puede comenzar comprobando que estas expresiones son verdaderas: (succ zero) ¿zero
y (succ zero) <(succ (succ zero)). Como ejercicio defina el operador menor igual que () y el mayor igual
que ().
agina 25
Introducci´on a la programaci´on funcional usando Haskell y Agda32
Camilo Chac´on Sartori
camilochs@gmail.com
7.7. Tipos dependientes e impl´ıcitos
Los ejemplos anteriores nos sirvieron para adentrarnos a Agda, y vimos tambi´en lo similar que es a Haskell,
aunque algo que lo diferencia es que, incorpora la comprobaci´on de la totalidad, que es relevante para hacer
software m´as seguro. No obstante una de las caracter´ısticas nucleares y que inspiro su origen son los tipos
dependientes.
Los tipos dependientes significan que existen relaciones entre tipos, que, se pueden ir creando
tipos que depende de otros tipos, creando as´ı una cadena de restricciones en una jerarqu´ıa de
tipos. Esto puede ser de utilidad para verificar especificaciones, y dise˜nar implementaciones libres de errores.
Para ir aclarando esto ir´e presentando varios ejemplos en esta secci´on.
En su art´ıculo ((Programing and Proving in Agda)) (que lo recomiendo leer para aprender aspectos m´as
avanzados del lenguaje) Cockx dijo lo siguiente:
Los tipos dependientes son tipos que pueden referirse a –o depender de– partes de un programa.
Con tipos dependientes es posible escribir tipos m´as precisos que con otros tipos de lenguajes sin
sistema de tipos dependientes. [...] En lenguajes de tipos dependientes, el tipo que devuelve una
funci´on depende de los argumentos de entrada de la funci´on. [Coc21]
Veamos algunos ejemplos para aclarar esto:
1id e n t i t y : ( A : Set ) - > A - > A
2id e n t i t y A x = x
La funci´on de identidad superior (identity) tiene el tipo dependiente (A : Set) AA, lo que quiere
decir es que: ((Dado dos argumentos, el tipo dependiente (A : Set) y el valor de tipo A, entonces debe retornar
un tipo A)) Estableciendo as´ı una relaci´on entre el argumento de entrada y de salida, que deben ser A en
ambos casos. (Incluso, como veremos m´as adelante, puede existir un argumento de entrada de tipo X que
dependa de otro argumento de entrada de tipo Y. O sea estamos frente a tipos dependientes entre argumentos
de entrada, no solo entre argumento de entrada y salida.)
Entonces con la funci´on identity se puede evaluar as´ı: identity Bool true o identity Nat zero, pero si
intentamos evaluar: identity Bool zero, el compilador de Agda reclamara pues el tipo dependiente (A : Set)
no coincide con el valor que devolver´a, el argumento de entrada y salida son diferentes.
Agda tambi´en proporciona los tipos impl´ıcitos, como vimos antes en la funci´on identity es necesario a˜nadir
como primer argumento el tipo dependiente, empero esto no es necesario si el tipo aparece en algunos de los
otros argumentos, a esto se le conoce como tipos impl´ıcitos. Para ello debemos usar la sintaxis , si por ejemplo
tenemos (A : Set) puede ser convertido a impl´ıcito: A : Set si y solo si, aparece luego un argumento del tipo
A, quedando as´ı:
1id e n t i t y : { A : Set } - > A - > A
2id e n t i t y x = x
Con este cambio ya podemos evaluar algo como esto: identity true o identity zero sin problema.
Nota: En otros lenguajes funcionales tales como Haskell tambi´en es posible crear programas que hagan
uso de tipos dependientes (usando bibliotecas externas), pero Agda va m´as all´a, ya que su propio sistema de
comprobaci´on de tipos revisa que existe una dependencia, es algo natural, o sea por defecto.
No obstante este ejemplo es muy aburrido, a continuaci´on traba jaremos con listas (en nuestro caso le llama-
remos vectores) que nos permiten operaciones muy interesantes que hacen uso constante de tipos dependientes
e impl´ıcitos.
Definamos un tipo Vec, que vendr´ıa siendo un vector:
1data Ve c ( A : Set ) : Na t -> S et w h ere
2[] : V ec A zero
3_ :: _ : {n : Nat } -> A -> Vec A n -> V ec A ( succ n )
Algunas cosas a aclarar:
agina 26
Introducci´on a la programaci´on funcional usando Haskell y Agda33
Camilo Chac´on Sartori
camilochs@gmail.com
1. En esta l´ınea se agrega el Nat Set que significa que se debe a˜nadir un argumento adicional de tipo
Nat (que ser´a el largo del vector, su tama˜no). A este argumento –en Agda– se llama ´ındice (index) y,
por este motivo, el tipo Vec se llama un tipo de dato indexado (el concepto de ´ındice no tiene nada que
ver con una ubicaci´on de un elemento en un arreglo como sucede en otros lenguajes).
2. El primer constructor [] devuelve el tipo Vec A zero, donde A es el par´ametro, y zero es el tama˜no del
vector, en este caso 0.
3. El segundo constructor :: nade un elemento al vector. Donde el primer argumento es un elemento del
tipo A y el segundo el vector de tipo Vec A n que devuelve un tipo Vec A (succ n), donde (succ n) es el
largo de la lista (n). Debemos recordar que el largo de la lista se representa con un Nat, por ello el uso
del zero y succ.
Otra cuesti´on a tener en cuenta es que el tipo Vec puede ser polim´orfico, es decir, el par´ametro A permite
crear un vector de elementos Nat o Bool (homog´eneo, todos los elementos de un mismo tipo). Si por ejemplo
creamos un vector: zero :: [], entonces dicho vector es del tipo Vec Nat zero, por otro lado si tenemos un
vector: true :: true :: [], entonces el vector es del tipo Vec Bool (succ (succ zero)) o simplemente Vec Bool 2.
As´ı pues, el par´ametro A representa dicho polimorfismo. En consecuencia, nuestro tipo Vec lo podemos usar
para tener vectores de tipo Bool o Nat.
7.8. Operaciones con tipos dependientes
Es el momento de realizar algunas operaciones sobre nuestro tipo Vec, como ya puede haberse dado cuenta
las listas son el mejor ejemplo para tratar con este tipo de lenguajes, sucede con Haskell y tambi´en con Agda.
7.8.1. Head yLast
La funci´on head (devolver el primer elemento del vector) de abajo de tipo A : Setn : Nat Vec A (succ
n) A se le llama una funci´on de tipo dependiente, porque el tipo del argumento de salida (A) depende del
tipo de los argumentos de entrada, a saber, el tipo impl´ıcito: A : Set. Por otro lado, el argumento de entrada
Vec A (succ n) depende de ambos tipos impl´ıcitos: A : Set y n : Nat.
1head : {A : Set }{ n : Na t } -> Vec A (succ n ) - > A
2head ( x : : x s ) = x
La primera clausula o caso particular (x :: xs) es similar a Haskell, aunque con algunas diferencias: en la
funci´on head se espera que el vector tenga al menos un elemento. Entonces as´ı, se puede evitar la cl´ausula
((head [] = ...)), esto es algo realmente genial de Agda, y que, lo diferencia de Haskell donde ser´ıa necesario
definir ese caso particular.
Esto se puede lograr gracias al sistema de tipos de Agda que permite inferir a trav´es del tipo del argumento
de entrada Vec A (succ n), en particular de (succ n), que, el vector no est´a vac´ıo, y en consecuencia, esto nos
dice que los tipos en Agda nos proporcionan la suficiente informaci´on para evitar definir cl´ausulas innecesarias
e irrelevantes.
Nota: En Agda los tipos impl´ıcitos si vienen de manera consecutiva podemos prescindir del s´ımbolo (()).
Pues A : Setn : Nat es lo mismo que escribir A : Set n : Nat.
La funci´on last (devuelve el ´ultimo elemento del vector) tiene el mismo tipo de la funci´on head (firma),
aunque tiene un caso en particular extra:
1last : {A : Set }{ n : Na t } -> Vec A (succ n ) - > A
2last (x :: []) = x
3last (x :: x 1 :: x2 ) = last ( x 1 :: x2 )
As´ı entonces podemos evaluar lo siguiente:
1>last (zero :: zero : : ( succ zero) : : [ ])
2(succ zero)
3>last ( t rue : : f a lse : : [ ])
4fa l s e
agina 27
Introducci´on a la programaci´on funcional usando Haskell y Agda34
Camilo Chac´on Sartori
camilochs@gmail.com
La invocaci´on de la funci´on last se vuelve recursiva para ir ((iterando)) (l´ınea 3) por cada uno de los
elementos hasta llegar al final (l´ınea 2), que, como sabemos en la definici´on del tipo Vec, debe finalizar con [].
7.8.2. Tail
La funci´on tail (cola) es bastante simple de definir gracia a la b´usqueda de patrones, solo hay que tener
en cuenta que el tipo de argumento de salida es Vec A n, y no Vec A (succ n), pues el vector podr´ıa no
tener ning´un elemento, dicho caso, prohibir´ıa a˜nadir un (succ n) que asume que el vector tiene al menos un
elemento.
1tail : {A : Set }{ n : Na t } -> Vec A (succ n ) - > V e c A n
2tail (x :: x s ) = x s
¡Como puede ver el compilador de Agda infiere muchas cosas por nosotros gracias a la informaci´on que
nos proporciona su rico sistema de tipos!
7.8.3. Prepend yAppend
La funci´on prepend a˜nade un elemento al comienzo del vector, a diferencia de las funciones anteriores se
asume que el tipo Vec puede venir sin ning´un elemento Vec A n y debe retornar un tipo con al menos un
elemento Vec A (succ n). Esto es fundamental para entender el poder de Agda.
1pr e p e n d : { A : S e t } { n : N a t } - > A - > V e c A n - > V e c A ( su c c n )
2pr e p e n d e v s = e :: vs
Y la funci´on append a diferencia de prepend a˜nade un elemento al final de la lista. Por lo mismo, se
requiere una invocaci´on recursiva para llegar al final del vector, o sea, se crea una nueva lista con el nuevo
elemento al final.
1ap p e n d : { A : S e t } { n : N a t } - > V e c A n - > A - > V e c A ( succ n)
2ap p e n d ( x : : x s ) e = x : : ( ap p e n d x s e )
3ap p e n d [ ] e = e :: []
En el caso particular append [] e se a˜nade el elemento al final del vector junto al [], que significa el final
del vector.
Nota: Para ahondar en Agda le recomiendo revisar [Coc21; Nor08].
8. Conclusi´on
Este art´ıculo ha sido ((largo)), pero insuficiente para conocer los amplios aspectos de la programaci´on funcio-
nal; sin embargo, espero que usted pueda continuar con el viaje hac´ıa dentro de este paradigma, aprendiendo
nuevas herramientas, nuevos lenguajes y, sobre todo, nuevas t´ecnicas; sin olvidar, que lo m´as importante
para ser un buen programador es solucionar problemas de la manera m´as inteligente posible, con las mejores
herramientas posibles. Esta entrada present´o solo una de ellas.
Solo me queda a˜nadir que hay muchos m´as elementos que hacen de la programaci´on funcional al menos
interesante, y que, junto a algunos de sus lenguajes como Haskell y Agda nos dan las herramientas para
realizar mejores programas, que, como es de suponer, es imposible presentarlos todos en una entrada. ¿Desea
as? Revise los documentos de referencias que les dejo al final.
9. Sugerencias finales
La programaci´on funcional no es la panacea y nunca ha pretendido serlo; es m´as, debe verse como
una forma distinta de construir software que, puede ser ´util, en partes donde se requiera precisi´on y
fiabilidad. Verificaci´on despu´es de todo. No sea fan´atico de una herramienta ni menos de un paradigma.
Y desconf´ıe de todo aquel que presente alguna cuesti´on como algo superior; dude y sea cr´ıtico. Vea
agina 28
Introducci´on a la programaci´on funcional usando Haskell y Agda35
Camilo Chac´on Sartori
camilochs@gmail.com
todos los paradigmas y herramientas como sistemas que coexisten y nos ayudan a resolver diferentes
problemas.
Una cuesti´on importante de la programaci´on funcional radica que, como usted vio en esta entrada, para
crear generalizaciones se necesita de mucha abstracci´on, esto sucede de manera constante y permea todo
su c´odigo, y es ´util para formar la mente de un programador, pues la inform´atica –en general– trata de
abstracciones.
Intente aprender sobre otros paradigmas o modelo para representar la computaci´on, de esa forma usted
lograr´a una mayor visi´on de la propia programaci´on funcional.
Esta entrada es solo un peque˜no esbozo, la punta del iceberg, de todo el mundo que rodea a la pro-
gramaci´on funcional, procure entonces –si est´a interesado– revisar las referencias que dejo al final para
profundizar m´as a´un.
Nota: El c´odigo de Haskell y Agda presentado en este art´ıculo se encuentra disponible en este repositorio
de GitHub.
Referencias
[Hug89] John Hughes. ((Why Functional Programming Matters)). En: Comput. J. 32.2 (1989), p´ags. 98-107.
[Tho91] S. Thompson. Type Theory and Functional Programming. International computer science series.
Addison-Wesley, 1991. isbn: 9780201416671. url:https : / / books . google . es / books ? id =
jH9QAAAAMAAJ.
[Nor08] James Norell Ulf; Chapman. Dependently Typed Programming in Agda. 2008. url:http://www.
cse.chalmers.se/~ulfn/papers/afp08/tutorial.pdf.
[Lip11] M. Lipovaca. Learn You a Haskell for Great Good!: A Beginner’s Guide. No Starch Press, 2011.
isbn: 9781593272951. url:https://books.google.es/books?id=QesxXj%5C_ecD0C.
[Tur18] Raymond Turner. Computational Artifacts - Towards a Philosophy of Computer Science. Springer,
2018. isbn: 978-3-662-55564-4. doi:10.1007/978-3- 662- 55565- 1.url:https://doi.org/10.
1007/978-3-662-55565- 1.
[Cha21] Camilo Chac´on Sartori. Computaci´on y programaci´on funcional: introducci´on al c´alculo lambda y
la programaci´on funcional usando Racket y Python. Marcombo, 2021. isbn: 9788426732439. url:
https://books.google.es/books?id=CHtOzgEACAAJ.
[Coc21] Jesper Cockx. Programming and Proving in Agda. 2021. url:https://github.com/jespercockx/
agda-lecture-notes/blob/master/agda.pdf.
agina 29
ResearchGate has not been able to resolve any citations for this publication.
Article
Full-text available
As software becomes more and more complex, it is more and more important to structure it well. Well-structured software is easy to write, easy to debug, and provides a collection of modules that can be re-used to reduce future programming costs. Conventional languages place conceptual limits on the way problems can be modularised. Functional languages push those limits back. In this paper we show that two features of functional languages in particular, higher-order functions and lazy evaluation, can contribute greatly to modularity. As examples, we manipulate lists and trees, program several numerical algorithms, and implement the alphabeta heuristic (an algorithm from Artificial Intelligence used in game-playing programs). Since modularity is the key to successful programming, functional languages are vitally important to the real world. 1 Introduction This paper is an attempt to demonstrate to the "real world" that functional programming is vitally important, and also to h...
Book
The philosophy of computer science is concerned with issues that arise from reflection upon the nature and practice of the discipline of computer science. This book presents an approach to the subject that is centered upon the notion of computational artefact. It provides an analysis of the things of computer science as technical artefacts. Seeing them in this way enables the application of the analytical tools and concepts from the philosophy of technology to the technical artefacts of computer science. With this conceptual framework the author examines some of the central philosophical concerns of computer science including the foundations of semantics, the logical role of specification, the nature of correctness, computational ontology and abstraction, formal methods, computational epistemology and explanation, the methodology of computer science, and the nature of computation. The book will be of value to philosophers and computer scientists.
Donde el primer argumento es un elemento del tipo A y el segundo el vector de tipo Vec A n que devuelve un tipo Vec A (succ n), donde (succ n) es el largo de la lista (n)
  • Constructor El Segundo
El segundo constructor :: añade un elemento al vector. Donde el primer argumento es un elemento del tipo A y el segundo el vector de tipo Vec A n que devuelve un tipo Vec A (succ n), donde (succ n) es el largo de la lista (n). Debemos recordar que el largo de la lista se representa con un Nat, por ello el uso del zero y succ.
Type Theory and Functional Programming. International computer science series
  • S Thompson
S. Thompson. Type Theory and Functional Programming. International computer science series.
Dependently Typed Programming in Agda
  • Chapman
Chapman. Dependently Typed Programming in Agda. 2008. url: http://www. cse.chalmers.se/~ulfn/papers/afp08/tutorial.pdf.
Learn You a Haskell for Great Good!: A Beginner's Guide
  • M Lipovaca
M. Lipovaca. Learn You a Haskell for Great Good!: A Beginner's Guide. No Starch Press, 2011. isbn: 9781593272951. url: https://books.google.es/books?id=QesxXj%5C_ecD0C.
Computación y programación funcional: introducción al cálculo lambda y la programación funcional usando Racket y Python. Marcombo, 2021. isbn: 9788426732439
  • Sartori Camilo Chacón
Camilo Chacón Sartori. Computación y programación funcional: introducción al cálculo lambda y la programación funcional usando Racket y Python. Marcombo, 2021. isbn: 9788426732439. url: https://books.google.es/books?id=CHtOzgEACAAJ.
Programming and Proving in Agda
  • Jesper Cockx
Jesper Cockx. Programming and Proving in Agda. 2021. url: https://github.com/jespercockx/ agda-lecture-notes/blob/master/agda.pdf. página 29