0% encontró este documento útil (0 votos)
14 vistas

Empezando A Programar Haskell

El documento introduce conceptos básicos de Haskell como aritmética, lógica booleana, comparaciones, tipos de datos, funciones predefinidas y funciones definidas por el usuario. Se explican conceptos como la aplicación de funciones mediante espacios en lugar de paréntesis y el uso de la sentencia if/else.

Cargado por

cristina meza
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
14 vistas

Empezando A Programar Haskell

El documento introduce conceptos básicos de Haskell como aritmética, lógica booleana, comparaciones, tipos de datos, funciones predefinidas y funciones definidas por el usuario. Se explican conceptos como la aplicación de funciones mediante espacios en lugar de paréntesis y el uso de la sentencia if/else.

Cargado por

cristina meza
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 31

Empezando a programar Haskell

Muy bien ¡Vamos a empezar! Si eres esa clase de mala persona que no lee las
introducciones y te la has saltado, quizás debas leer la última sección de la introducción
porque explica lo que necesitas para seguir esta guía y como vamos a trabajar. Lo primero
que vamos a hacer es ejecutar GHC en modo interactivo y utilizar algunas funciones para ir
acostumbrándonos un poco. Abre una terminal y escribe ghci. Serás recibido con un saludo
como éste:
GHCi, version 7.2.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude>

¡Enhorabuena, entraste de GHCi! Aquí el apuntador (o prompt) es Prelude> pero como


éste se hace más largo a medida que cargamos módulos durante una sesión, vamos a
utilizar ghci>. Si quieres tener el mismo apuntador ejecuta :set prompt "ghci> ".
Aquí tenemos algo de aritmética simple.

ghci> 2 + 15
17
ghci> 49 * 100
4900
ghci> 1892 - 1472
420
ghci> 5 / 2
2.5
ghci>

Se explica por si solo. También podemos utilizar varias operaciones en una misma línea
de forma que se sigan todas las reglas de precedencia que todos conocemos. Podemos usar
paréntesis para utilizar una precedencia explícita.

ghci> (50 * 100) - 4999


1
ghci> 50 * 100 - 4999
1
ghci> 50 * (100 - 4999)
-244950

¿Muy interesante, eh? Sí, se que no, pero ten paciencia. Una pequeña dificultad a tener
en cuenta ocurre cuando negamos números, siempre será mejor rodear los números
negativos con paréntesis. Hacer algo como 5 * -3 hará que GHCi se enfade, sin
embargo 5 * (-3) funcionará.
La álgebra booleana es también bastante simple. Como seguramente
sabrás, && representa el Y lógico mientras que || representa
el O lógico. not niega True a False y viceversa.
ghci> True && False
False
ghci> True && True
True
ghci> False || True
True
ghci> not False
True
ghci> not (True && True)
False

La comprobación de igualdad se hace así:

ghci> 5 == 5
True
ghci> 1 == 0
False
ghci> 5 /= 5
False
ghci> 5 /= 4
True
ghci> "hola" == "hola"
True

¿Qué pasa si hacemos algo como 5 + "texto" o 5 == True? Bueno, si probamos con
el primero obtenemos este amigable mensaje de error:
No instance for (Num [Char])
arising from a use of `+' at <interactive>:1:0-9
Possible fix: add an instance declaration for (Num [Char])
In the expression: 5 + "texto"
In the definition of `it': it = 5 + "texto"

GHCi nos está diciendo es que "texto" no es un número y por lo tanto no sabe como
sumarlo a 5. Incluso si en lugar de "texto" fuera "cuatro", "four", o "4", Haskell no lo
consideraría como un número. + espera que su parte izquierda y derecha sean números. Si
intentamos realizar True == 5, GHCi nos diría que los tipos no coinciden. Mientras
que + funciona solo con cosas que son consideradas números, == funciona con cualquiera
cosa que pueda ser comparada. El truco está en que ambas deben ser comparables entre si.
No podemos comparar la velocidad con el tocino. Daremos un vistazo más detallado sobre los
tipos más adelante. Nota: podemos hacer 5 + 4.0 porque 5 no posee un tipo concreto y
puede actuar como un entero o como un número en coma flotante. 4.0 no puede actuar como
un entero, así que 5 es el único que se puede adaptar.
Puede que no lo sepas, pero hemos estado usando funciones durante todo este tiempo.
Por ejemplo, * es una función que toma dos números y los multiplica. Como ya has visto, lo
llamamos haciendo un sándwich sobre él. Esto lo llamamos funciones infijas. Muchas
funciones que no son usadas con números son prefijas. Vamos a ver alguna de ellas.

Las funciones normalmente son prefijas así que de ahora en adelante no vamos a decir
que una función está en forma prefija, simplemente lo asumiremos. En muchos lenguajes
imperativos las funciones son llamadas escribiendo su nombre y luego escribiendo sus
parámetros entre paréntesis, normalmente separados por comas. En Haskell, las funciones
son llamadas escribiendo su nombre, un espacio y sus parámetros, separados por espacios.
Para empezar, vamos a intentar llamar a una de las funciones más aburridas de Haskell.
ghci> succ 8
9

La función succ toma cualquier cosa que tenga definido un sucesor y devuelve ese
sucesor. Como puedes ver, simplemente hemos separado el nombre de la función y su
parámetro por un espacio. Llamar a una función con varios parámetros es igual de sencillo.
Las funciones min y max toman dos cosas que puedan ponerse en orden (¡cómo los números!)
y devuelven uno de ellos.
ghci> min 9 10
9
ghci> min 3.4 3.2
3.2
ghci> max 100 101
101

La aplicación de funciones (llamar a una función poniendo un espacio después de ella y


luego escribir sus parámetros) tiene la máxima prioridad. Dicho con un ejemplo, estas dos
sentencias son equivalentes:

ghci> succ 9 + max 5 4 + 1


16
ghci> (succ 9) + (max 5 4) + 1
16

Sin embargo, si hubiésemos querido obtener el sucesor del producto de los números 9 y
10, no podríamos haber escrito succ 9 * 10 porque hubiésemos obtenido el sucesor de 9, el
cual hubiese sido multiplicado por 10, obteniendo 100. Tenemos que
escribir succ (9 * 10) para obtener 91.
Si una función toma dos parámetros también podemos llamarla como una función infija
rodeándola con acentos abiertos. Por ejemplo, la función div toma dos enteros y realiza una
división entera entre ellos. Haciendo div 92 10 obtendríamos 9. Pero cuando la llamamos
así, puede haber alguna confusión como que número está haciendo la división y cual está
siendo dividido. De manera que nosotros la llamamos como una función infija
haciendo 92 `div` 10, quedando de esta forma más claro.
La gente que ya conoce algún lenguaje imperativo tiende a aferrarse a la idea de que los
paréntesis indican una aplicación de funciones. Por ejemplo, en C, usas los paréntesis para
llamar a las funciones como foo(), bar(1), o baz(3, "jaja"). Como hemos dicho, los
espacios son usados para la aplicación de funciones en Haskell. Así que estas funciones en
Haskell serían foo, bar 1 y baz 3 "jaja". Si ves algo como bar (bar 3) no significa
que bar es llamado con bar y 3 como parámetros. Significa que primero llamamos a la
función bar con 3 como parámetro para obtener un número y luego volver a llamar bar otra
vez con ese número. En C, esto sería algo como bar(bar(3)).

Las primeras pequeñas funciones


En la sección anterior obtuvimos una idea básica de como llamar a las funciones ¡Ahora
vamos a intentar hacer las nuestras! Abre tu editor de textos favorito y pega esta función que
toma un número y lo multiplica por dos.

doubleMe x = x + x
Las funciones son definidas de forma similar a como son llamadas. El nombre de la
función es seguido por los parámetros separados por espacios. Pero, cuando estamos
definiendo funciones, hay un = y luego definimos lo que hace la función. Guarda esto
como baby.hs o como tú quieras. Ahora navega hasta donde lo guardaste y
ejecuta ghci desde ahí. Una vez dentro de GHCi, escribe :l baby. Ahora que nuestro código
está cargado, podemos jugar con la función que hemos definido.
ghci> :l baby
[1 of 1] Compiling Main ( baby.hs, interpreted )
Ok, modules loaded: Main.
ghci> doubleMe 9
18
ghci> doubleMe 8.3
16.6

Como + funciona con los enteros igual de bien que con los número en coma flotante (en
realidad con cualquier cosa que pueda ser considerada un número), nuestra función también
funciona con cualquier número. Vamos a hacer una función que tome dos números,
multiplique por dos cada uno de ellos y luego sume ambos.
doubleUs x y = x*2 + y*2

Simple. La podríamos haber definido también como doubleUs x y = x + x + y + y.


Ambas formas producen resultados muy predecibles (recuerda añadir esta función en el
fichero baby.hs, guardarlo y luego ejecutar :l baby dentro de GHCi).
ghci> doubleUs 4 9
26
ghci> doubleUs 2.3 34.2
73.0
ghci> doubleUs 28 88 + doubleMe 123
478

Como podrás deducir, puedes llamar tus propias funciones dentro de las funciones que
hagas. Teniendo esto en cuenta, podríamos redefinir doubleUs como:
doubleUs x y = doubleMe x + doubleMe y

Esto es un simple ejemplo de un patrón normal que verás por todo Haskell. Crear
funciones pequeñas que son obviamente correctas y luego combinarlas en funciones más
complejas. De esta forma también evitarás repetirte. ¿Qué pasa si algunos matemáticos
descubren que 2 es en realidad 3 y tienes que cambiar tu programa? Puedes simplemente
redefinir doubleMe para que sea x + x + x y como doubleUs llama
a doubleMe automáticamente funcionara en este extraño mundo en el que 2 es 3.
Las funciones en Haskell no tienen que estar en ningún orden en particular, así que no
importa si defines antes doubleMe y luego doubleUs o si lo haces al revés.
Ahora vamos a crear una función que multiplique un número por 2 pero solo si ese número
es menor o igual que 100, porque los número mayores 100 ya son suficientemente grandes
por si solos.

doubleSmallNumber x = if x > 100


then x
else x*2

Acabamos de introducir la sentencia if de Haskell. Probablemente ya estés familiarizado


con la sentencia if de otros lenguajes. La diferencia entre la sentencia if de Haskell y la de
los lenguajes imperativos es que la parte else es obligatoria. En los lenguajes imperativos
podemos saltarnos unos cuantos pasos si una condición no se ha satisfecho pero en Haskell
cada expresión o función debe devolver un valor. También podríamos haber definido la
sentencia if en una sola línea pero así parece un poco mas legible. Otro asunto acerca de la
sentencia if en Haskell es que es una expresión. Básicamente una expresión es un trozo de
código que devuelve un valor. 5 es una expresión porque devuelve 5, 4 + 8 es una
expresión, x + y es una expresión porque devuelve la suma de x e y. Como la parte else es
obligatoria, una sentencia if siempre devolverá algo y por tanto es una expresión. Si
queremos sumar uno a cada número que es producido por la función anterior, podemos
escribir su cuerpo así.
doubleSmallNumber' x = (if x > 100 then x else x*2) + 1

Si hubiésemos omitido los paréntesis, sólo hubiera sumado uno si x no fuera mayor que
100. Fíjate en el ' al final del nombre de la función. Ese apóstrofe no tiene ningún significado
especial en la sintaxis de Haskell. Es un carácter válido para ser usado en el nombre de una
función. Normalmente usamos ' para denotar la versión estricta de una función (una que no
es perezosa) o una pequeña versión modificada de una función o variable. Como ' es un
carácter válido para la funciones, podemos hacer cosas como esta.
conanO'Brien = "¡Soy yo, Conan O'Brien!"

Hay dos cosas que nos quedan por destacar. La primera es que el nombre de esta función
no empieza con mayúsculas. Esto se debe a que las funciones no pueden empezar con una
letra en mayúsculas. Veremos el porqué un poco más tarde. La segunda es que esta función
no toma ningún parámetro, normalmente lo llamamos una definición (o un nombre). Como no
podemos cambiar las definiciones (y las funciones) después de que las hayamos
definido, conanO'Brien y la cadena "It's a-me, Conan O'Brien!" se pueden utilizar
indistintamente.
Una introducción a las listas
Al igual que las listas de la compra de la vida real, las listas en Haskell son muy útiles. Es
la estructura de datos más utilizada y pueden ser utilizadas de diferentes formas para modelar
y resolver un montón de problemas. Las listas son MUY importantes. En esta sección daremos
un vistazo a las bases sobre las listas, cadenas de texto (las cuales son listas) y listas
intensionales.

En Haskell, las listas son una estructura de datos homogénea. Almacena varios
elementos del mismo tipo. Esto significa que podemos crear una lista de enteros o una lista de
caracteres, pero no podemos crear una lista que tenga unos cuantos enteros y otros cuantos
caracteres. Y ahora, ¡una lista!
Nota
Podemos usar la palabra reservada let para definir un nombre en GHCi.
Hacer let a = 1 dentro de GHCi es equivalente ha escribir a = 1 en un fichero y luego
cargarlo.

ghci> let lostNumbers = [4,8,15,16,23,42]


ghci> lostNumbers
[4,8,15,16,23,42]

Como puedes ver, las listas se definen mediante corchetes y sus valores se separan por
comas. Si intentáramos crear una lista como esta [1,2,'a',3,'b','c',4], Haskell nos
avisaría que los caracteres (que por cierto son declarados como un carácter entre comillas
simples) no son números. Hablando sobre caracteres, las cadenas son simplemente listas de
caracteres. "hello" es solo una alternativa sintáctica de ['h','e','l','l','o']. Como las
cadenas son listas, podemos usar las funciones que operan con listas sobre ellas, lo cual es
realmente útil.
Una tarea común es concatenar dos listas. Cosa que conseguimos con el operador ++.
ghci> [1,2,3,4] ++ [9,10,11,12]
[1,2,3,4,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world"
ghci> ['w','o'] ++ ['o','t']
"woot"

Hay que tener cuidado cuando utilizamos el operador ++ repetidas veces sobre cadenas
largas. Cuando concatenamos dos listas (incluso si añadimos una lista de un elemento a otra
lista, por ejemplo [1,2,3] ++ [4], internamente, Haskell tiene que recorrer la lista entera
desde la parte izquierda del operador ++. Esto no supone ningún problema cuando trabajamos
con listas que no son demasiado grandes. Pero concatenar algo al final de una lista que tiene
cincuenta millones de elementos llevará un rato. Sin embargo, concatenar algo al principio de
una lista utilizando el operador : (también llamado operador cons) es instantáneo.
ghci> 'U':"n gato negro"
"Un gato negro"
ghci> 5:[1,2,3,4,5]
[5,1,2,3,4,5]

Fíjate que : toma un número y una lista de números o un carácter y una lista de
caracteres, mientras que ++ toma dos listas. Incluso si añades un elemento al final de las lista
con ++, hay que rodearlo con corchetes para que se convierte en una lista de un solo
elemento.
ghci> [1,2] ++ 3
<interactive>:1:10:
No instance for (Num [a0])
arising from the literal `3'
[...]

ghci> [1,2] ++ [3]


[1,2,3]

[1,2,3] es una alternativa sintáctica de 1:2:3:[]. [] es una lista vacía. Si anteponemos


3 a ella con :, obtenemos [3], y si anteponemos 2 a esto obtenemos [2,3].
Nota
[], [[]] y [[],[],[]] son cosas diferentes entre si. La primera es una lista vacía, la
segunda es una lista que contiene un elemento (una lista vacía) y la tercera es una lista que
contiene tres elementos (tres listas vacías).

Si queremos obtener un elemento de la lista sabiendo su índice, utilizamos !!. Los índices
empiezan por 0.
ghci> "Steve Buscemi" !! 6
'B'
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1
33.2

Pero si intentamos obtener el sexto elemento de una lista que solo tiene cuatro elementos,
obtendremos un error, así que hay que ir con cuidado.
Las listas también pueden contener listas. Estas también pueden contener a su vez listas
que contengan listas, que contengan listas...

ghci> let b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]


ghci> b
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b ++ [[1,1,1,1]]
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
ghci> [6,6,6]:b
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b !! 2
[1,2,2,3,4]

Las listas dentro de las listas pueden tener diferentes tamaños pero no pueden tener
diferentes tipos. De la misma forma que no se puede contener caracteres y números en un
lista, tampoco se puede contener listas que contengan listas de caracteres y listas de
números.

Las listas pueden ser comparadas si los elementos que contienen pueden ser
comparados. Cuando usamos <, <=, >, y >= para comparar listas, son comparadas en orden
lexicográfico. Primero son comparadas las cabezas. Luego son comparados los segundos
elementos y así sucesivamente.
¿Qué mas podemos hacer con las listas? Aquí tienes algunas funciones básicas que
pueden operar con las listas.

 head toma una lista y devuelve su cabeza. La cabeza de una lista es básicamente
el primer elemento.
 ghci> head [5,4,3,2,1]
 5

 tail toma una lista y devuelve su cola. En otros palabras, corta la cabeza de la
lista.
 ghci> tail [5,4,3,2,1]
 [4,3,2,1]

 last toma una lista y devuelve su último elemento.


 ghci> last [5,4,3,2,1]
 1

 init toma una lista y devuelve toda la lista excepto su último elemento.
 ghci> init [5,4,3,2,1]
 [5,4,3,2]

¿Pero que pasa si intentamos obtener la cabeza de una lista vacía?

ghci> head []
*** Exception: Prelude.head: empty list

¡Oh, lo hemos roto! Si no hay monstruo, no hay cabeza. Cuando


usamos head, tail, last e init debemos tener precaución de no usar con ellas listas
vacías. Este error no puede ser capturado en tiempo de compilación así que siempre es una
buena práctica tomar precauciones antes de decir a Haskell que te devuelva algunos
elementos de una lista vacía.
 length toma una lista y obviamente devuelve su tamaño.
 ghci> length [5,4,3,2,1]
 5

 null comprueba si una lista está vacía. Si lo está, devuelve True, en caso
contrario devuelve False. Usa esta función en lugar de xs == [] (si tienes una lista
que se llame xs).
 ghci> null [1,2,3]
 False
 ghci> null []
 True

 reverse pone del revés una lista.


 ghci> reverse [5,4,3,2,1]
 [1,2,3,4,5]

 take toma un número y una lista y extrae dicho número de elementos de una
lista. Observa.
 ghci> take 3 [5,4,3,2,1]
 [5,4,3]
 ghci> take 1 [3,9,3]
 [3]
 ghci> take 5 [1,2]
 [1,2]
 ghci> take 0 [6,6,6]
 []

Fíjate que si intentamos tomar más elementos de los que hay en una lista,
simplemente devuelve la lista. Si tomamos 0 elementos, obtenemos una lista vacía.

 drop funciona de forma similar, solo que quita un número de elementos del
comienzo de la lista.
 ghci> drop 3 [8,4,2,1,5,6]
 [1,5,6]
 ghci> drop 0 [1,2,3,4]
 [1,2,3,4]
 ghci> drop 100 [1,2,3,4]
 []

 maximum toma una lista de cosas que se pueden poner en algún tipo de orden y
devuelve el elemento más grande.
 minimum devuelve el más pequeño.
 ghci> minimum [8,4,2,1,5,6]
 1
 ghci> maximum [1,9,2,3,4]
 9
 sum toma una lista de números y devuelve su suma.
 product toma una lista de números y devuelve su producto.
 ghci> sum [5,2,1,6,3,2,5,7]
 31
 ghci> product [6,2,1,2]
 24
 ghci> product [1,2,5,6,7,9,2,0]
 0

 elem toma una cosa y una lista de cosas y nos dice si dicha cosa es un elemento
de la lista. Normalmente, esta función es llamada de forma infija porque resulta más
fácil de leer.
 ghci> 4 `elem` [3,4,5,6]
 True
 ghci> 10 `elem` [3,4,5,6]
 False

Estas fueron unas cuantas funciones básicas que operan con listas. Veremos más
funciones que operan con listas más adelante.

Rangos
¿Qué pasa si queremos una lista con todos los números entre el 1 y el 20? Sí, podríamos
simplemente escribirlos todos pero obviamente esto no es una solución para los que buscan
buenos lenguajes de programación. En su lugar, usaremos rangos. Los rangos son una
manera de crear listas que contengan una secuencia aritmética de elementos enumerables.
Los números pueden ser enumerados. Uno, dos, tres, cuatro, etc. Los caracteres también
pueden ser enumerados. El alfabeto es una enumeración de caracteres desde la A hasta la Z.
Los nombres no son enumerables. ¿Qué viene después de “Juan”? Ni idea.

Para crear una lista que contenga todos los números naturales desde el 1 hasta el 20
simplemente escribimos [1..20]. Es equivalente a
escribir [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] y no hay ninguna
diferencia entre escribir uno u otro salvo que escribir manualmente una larga secuencia de
enumerables es bastante estúpido.
ghci> [1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> ['K'..'Z']
"KLMNOPQRSTUVWXYZ"

También podemos especificar el número de pasos entre elementos de un rango ¿Y si


queremos todos los números pares desde el 1 hasta el 20? ¿o cada tercer número?

ghci> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
ghci> [3,6..20]
[3,6,9,12,15,18]
Es cuestión de separar los primeros dos elementos con una coma y luego especificar el
límite superior. Aunque son inteligentes, los rangos con pasos no son tan inteligentes como
algunas personas esperan que sean. No puedes escribir [1,2,4,8,16..100] y esperar
obtener todas las potencias de 2. Primero porque solo se puede especificar un paso. Y
segundo porque las secuencias que no son aritméticas son ambiguas si solo damos unos
pocos elementos iniciales.
Para obtener una lista con todos los números desde el 20 hasta el 1 no podemos
usar [20..1], debemos utilizar [20,19..1].
¡Cuidado cuando uses números en coma flotante con los rangos! Éstos no son del todo
precisos (por definición), y su uso con los rangos puede dar algunos resultados no esperados.

ghci> [0.1, 0.3 .. 1]


[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]

Mi consejo es no utilizar rangos con números en coma flotante.

También podemos utilizar los rangos para crear listas infinitas simplemente no indicando
un límite superior. Más tarde nos centraremos más en las listas infinitas. Por ahora, vamos a
examinar como obtendríamos los primeros 24 múltiplos de 13. Sí, podemos
utilizar [13,26..24*13]. Pero hay una forma mejor: take 13 [13,26..]. Como Haskell es
perezoso, no intentará evaluar la lista infinita inmediatamente porque no terminaría nunca.
Esperará a ver que es lo que quieres obtener de la lista infinita. En este caso ve que solo
queremos los primeros 24 elementos y los evalúa con mucho gusto.
Ahora, un par de funciones que generan listas infinitas:

 cycle toma una lista y crea un ciclo de listas iguales infinito. Si intentáramos
mostrar el resultado nunca terminaría así que hay que cortarlo en alguna parte.
 ghci> take 10 (cycle [1,2,3])
 [1,2,3,1,2,3,1,2,3,1]
 ghci> take 12 (cycle "LOL ")
 "LOL LOL LOL "

 repeat toma un elemento y produce una lista infinita que contiene ese único
elemento repetido. Es como hacer un ciclo de una lista con un solo elemento.
 ghci> take 10 (repeat 5)
 [5,5,5,5,5,5,5,5,5,5]

Aunque aquí sería más simple usar la función replicate, ya que sabemos el
número de elementos de antemano. replicate 3 10 devuelve [10,10,10].
Soy una lista intensional
Si alguna vez tuviste clases de matemáticas, probablemente viste algún conjunto definido
de forma intensiva, definido a partir de otros conjuntos más generales. Un conjunto definido de
forma intensiva que contenga los diez primeros números naturales pares
sería . La parte anterior al separador se llama la función de
salida, es la variable, es el conjunto de entrada y es el predicado. Esto significa
que el conjunto contiene todos los dobles de los número naturales que cumplen el predicado.
Si quisiéramos escribir esto en Haskell, podríamos usar algo como take 10 [2,4..].
Pero, ¿y si no quisiéramos los dobles de los diez primeros número naturales, sino algo más
complejo? Para ello podemos utilizar listas intensionales. Las listas intensionales son muy
similares a los conjuntos definidos de forma intensiva. En este caso, la lista intensional que
deberíamos usar sería [x*2 | x <- [1..10]]. x es extraído de [1..10] y para cada
elemento de [1..10] (que hemos ligado a x) calculamos su doble. Su resultado es:
ghci> [x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

Como podemos ver, obtenemos el resultado deseado. Ahora vamos a añadir una
condición (o un predicado) a esta lista intensional. Los predicados van después de la parte
donde enlazamos las variables, separado por una coma. Digamos que solo queremos los
elementos que su doble sea mayor o igual a doce:

ghci> [x*2 | x <- [1..10], x*2 >= 12]


[12,14,16,18,20]

Bien, funciona. ¿Y si quisiéramos todos los números del 50 al 100 cuyo resto al dividir por
7 fuera 3? Fácil:

ghci> [ x | x <- [50..100], x `mod` 7 == 3]


[52,59,66,73,80,87,94]

¡Todo un éxito! Al hecho de eliminar elementos de la lista utilizando predicados también se


conoce como filtrado. Tomamos una lista de números y la filtramos usando predicados. Otro
ejemplo, digamos que queremos lista intensional que reemplace cada número impar mayor
que diez por “BANG!” y cada número impar menor que diez por “BOOM!”. Si un número no es
impar, lo dejamos fuera de la lista. Para mayor comodidad, vamos a poner la lista intensional
dentro de una función para que sea fácilmente reutilizable.
boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]

La última parte de la comprensión es el predicado. La función odd devuelve True si le


pasamos un número impar y False con uno par. El elemento es incluido en la lista solo si
todos los predicados se evalúan a True.
ghci> boomBangs [7..13]
["BOOM!","BOOM!","BANG!","BANG!"]

Podemos incluir varios predicados. Si quisiéramos todos los elementos del 10 al 20 que no
fueran 13, 15 ni 19, haríamos:

ghci> [x | x <- [10..20], x /= 13, x /= 15, x /= 19]


[10,11,12,14,16,17,18,20]

No solo podemos tener varios predicados en una lista intensional (un elemento debe
satisfacer todos los predicados para ser incluido en la lista), sino que también podemos
extraer los elementos de varias listas. Cuando extraemos elementos de varias listas, se
producen todas las combinaciones posibles de dichas listas y se unen según la función de
salida que suministremos. Una lista intensional que extrae elementos de dos listas cuyas
longitudes son de 4, tendrá una longitud de 16 elementos, siempre y cuando no los filtremos.
Si tenemos dos listas, [2,5,10] y [8,10,11] y queremos que el producto de todas las
combinaciones posibles entre ambas, podemos usar algo como:
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]

Como era de esperar, la longitud de la nueva lista es de 9 ¿Y si quisiéramos todos los


posibles productos cuyo valor sea mayor que 50?

ghci> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50]


[55,80,100,110]

¿Qué tal una lista intensional que combine una lista de adjetivos con una lista de
nombres? Solo para quedarnos tranquilos...

ghci> let noums = ["rana","zebra","cabra"]


ghci> let adjetives = ["perezosa","enfadada","intrigante"]
ghci> [noum ++ " " ++ adjetive | noum <- noums, adjetive <- adjetives]
["rana perezosa","rana enfadada","rana intrigante","zebra perezosa",
"zebra enfadada","zebra intrigante","cabra perezosa","cabra enfadada",
"cabra intrigante"]

¡Ya se! Vamos a escribir nuestra propia versión de length. La llamaremos length'.
length' xs = sum [1 | _ <- xs]

_ significa que no nos importa lo que vayamos a extraer de la lista, así que en vez de
escribir el nombre de una variable que nunca usaríamos, simplemente escribimos _. La
función reemplaza cada elemento de la lista original por 1 y luego los suma. Esto significa que
la suma resultante será el tamaño de nuestra lista.
Un recordatorio: como las cadenas son listas, podemos usar las listas intensionales para
procesar y producir cadenas. Por ejemplo, una función que toma cadenas y elimina de ellas
todo excepto las letras mayúsculas sería algo tal que así:

removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]

Unas pruebas rápidas:

ghci> removeNonUppercase "Jajaja! Ajajaja!"


"JA"
ghci> removeNonUppercase "noMEGUSTANLASRANAS"
"MEGUSTANLASRANAS"

En este caso el predicado hace todo el trabajo. Dice que el elemento será incluido en la
lista solo si es un elemento de [A..Z]. Es posible crear listas intensionales anidadas si
estamos trabajando con listas que contienen listas. Por ejemplo, dada una lista de listas de
números, vamos eliminar los números impares sin aplanar la lista:
ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],
[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

Podemos escribir las listas intensionales en varias líneas. Si no estamos usando GHCi es
mejor dividir las listas intensionales en varias líneas, especialmente si están anidadas.
Tuplas
De alguna forma, las tuplas son parecidas a las listas. Ambas son una forma de almacenar
varios valores en un solo valor. Sin embargo, hay unas cuantas diferencias fundamentales.
Una lista de números es una lista de números. Ese es su tipo y no importa si tiene un sólo
elemento o una cantidad infinita de ellos. Las tuplas sin embargo, son utilizadas cuando sabes
exactamente cuantos valores tienen que ser combinados y su tipo depende de cuantos
componentes tengan y del tipo de estos componentes. Las tuplas se denotan con paréntesis y
sus valores se separan con comas.

Otra diferencia clave es que no tienen que ser homogéneas. Al contrario que las listas, las
tuplas pueden contener una combinación de valores de distintos tipos.

Piensa en como representaríamos un vector bidimensional en Haskell. Una forma sería


utilizando listas. Podría funcionar. Entonces, ¿si quisiéramos poner varios vectores dentro de
una lista que representa los puntos de una figura bidimensional? Podríamos usar algo
como [[1,2],[8,11],[4,5]]. El problema con este método es que también podríamos
hacer cosas como [[1,2],[8,11,5],[4,5]] ya que Haskell no tiene problemas con ello,
sigue siendo una lista de listas de números pero no tiene ningún sentido. Pero una tupla de
tamaño 2 (también llamada dupla) tiene su propio tipo, lo que significa que no puedes tener
varias duplas y una tripla (una tupla de tamaño 3) en una lista, así que vamos a usar éstas. En
lugar de usar corchetes rodeando los vectores utilizamos paréntesis: [(1,2),(8,11),(4,5)].
¿Qué pasaría si intentamos crear una forma como [(1,2),(8,11,5),(4,5)]? Bueno,
obtendríamos este error:
Couldn't match expected type `(t, t1)'
against inferred type `(t2, t3, t4)'
In the expression: (8, 11, 5)
In the expression: [(1, 2), (8, 11, 5), (4, 5)]
In the definition of `it': it = [(1, 2), (8, 11, 5), (4, 5)]

Nos está diciendo que hemos intentado usar una dupla y una tripla en la misma lista, lo
cual no esta permitido ya que las listas son homogéneas y un dupla tiene un tipo diferente al
de una tripla (aunque contengan el mismo tipo de valores). Tampoco podemos hacer algo
como [(1,2),("uno",2)] ya que el primer elemento de la lista es una tupla de números y el
segundo es una tupla de una cadena y un número. Las tuplas pueden ser usadas para
representar una gran variedad de datos. Por ejemplo, si queremos representar el nombre y la
edad de alguien en Haskell, podemos utilizar la tripla: ("Christopher", "Walken", 55).
Como hemos visto en este ejemplo las tuplas también pueden contener listas.
Utilizamos la tuplas cuando sabemos de antemano cuantos componentes de algún dato
debemos tener. Las tuplas son mucho más rígidas que las listas ya que para cada tamaño
tienen su propio tipo, así que no podemos escribir una función general que añada un elemento
a una tupla: tenemos que escribir una función para añadir duplas, otra función para añadir
triplas, otra función para añadir cuádruplas, etc.

Mientras que existen listas unitarias, no existen tuplas unitarias. Realmente no tiene
mucho sentido si lo piensas. Una tupla unitaria sería simplemente el valor que contiene y no
nos aportaría nada útil.

Como las listas, las tuplas pueden ser comparadas si sus elementos pueden ser
comparados. Solo que no podemos comparar dos tuplas de diferentes tamaños mientras que
si podemos comparar dos listas de diferentes tamaños. Dos funciones útiles para operar con
duplas son:

 fst toma una dupla y devuelve su primer componente.


 ghci> fst (8,11)
 8
 ghci> fst ("Wow", False)
 "Wow"

 snd toma una dupla y devuelve su segundo componente. ¡Sorpresa!


 ghci> snd (8,11)
 11
 ghci> snd ("Wow", False)
 False

Nota
Estas funciones solo operan sobre duplas. No funcionaran sobre triplas, cuádruplas,
quíntuplas, etc. Veremos más formas de extraer datos de las tuplas un poco más tarde.

Ahora una función interesante que produce listas de duplas es zip. Esta función toma dos
listas y las une en un lista uniendo sus elementos en una dupla. Es una función realmente
simple pero tiene montones de usos. Es especialmente útil cuando queremos combinar dos
listas de alguna forma o recorrer dos listas simultáneamente. Aquí tienes una demostración:
ghci> zip [1,2,3,4,5] [5,5,5,5,5]
[(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci> zip [1 .. 5] ["uno","dos","tres","cuatro","cinco"]
[(1,"uno"),(2,"dos"),(3,"tres"),(4,"cuatro"),(5,"cinco")]

Como vemos, se emparejan los elementos produciendo una nueva lista. El primer
elemento va el primero, el segundo el segundo, etc. Ten en cuenta que como las duplas
pueden tener diferentes tipos, zip puede tomar dos listas que contengan diferentes tipos y
combinarlas. ¿Qué pasa si el tamaño de las listas no coincide?
ghci> zip [5,3,2,6,2,7,2,5,4,6,6] ["soy","una","tortuga"]
[(5,"soy"),(3,"una"),(2,"tortuga")]

Simplemente se recorta la lista más larga para que coincida con el tamaño de la más
corta. Como Haskell es perezoso, podemos usar zip usando listas finitas e infinitas:
ghci> zip [1..] ["manzana", "naranja", "cereza", "mango"]
[(1,"manzana"),(2,"naranja"),(3,"cereza"),(4,"mango")]

He aquí un problema que combina tuplas con listas intensionales: ¿Qué triángulo recto
cuyos lados miden enteros menores que 10 tienen un perímetro igual a 24? Primero, vamos a
intentar generar todos los triángulos con lados iguales o menores que 10:

ghci> let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <-


[1..10] ]

Simplemente estamos extrayendo valores de estas tres listas y nuestra función de salida
las esta combinando en una tripla. Si evaluamos esto escribiendo triangles en GHCi,
obtendremos una lista con todos los posibles triángulos cuyos lados son menores o iguales
que 10. Ahora, debemos añadir una condición que nos filtre únicamente los triángulos rectos.
Vamos a modificar esta función teniendo en consideración que el lado b no es mas largo que
la hipotenusa y que el lado a no es más largo que el lado b.
ghci> let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <-
[1..b], a^2 + b^2 == c^2]

Ya casi hemos acabado. Ahora, simplemente modificaremos la función diciendo que solo
queremos aquellos que su perímetro es 24.

ghci> let rightTriangles' = [ (a,b,c) | c <- [1..10], b <- [1..c], a


<- [1..b], a^2 + b^2 == c^2, a+b+c == 24]
ghci> rightTriangles'
[(6,8,10)]

Cargando...
¡Y ahí está nuestra respuesta! Este método de resolución de problemas es muy común en
la programación funcional. Empiezas tomando un conjunto de soluciones y vas aplicando
transformaciones para ir obteniendo soluciones, filtrándolas una y otra vez hasta obtener las
soluciones correctas.

Tipos y clases de tipos


Cree en el tipo
Anteriormente mencionamos que Haskell tiene un sistema de tipos estático. Se conoce el
tipo de cada expresión en tiempo de compilación, lo que produce código más seguro. Si
escribimos un programa que intenta dividir un valor del tipo booleano por un número, no
llegará a compilarse. Esto es bueno ya que es mejor capturar este tipo de errores en tiempo
de compilación en lugar de que el programa falle. Todo en Haskell tiene un tipo, de forma que
el compilador puede razonar sobre el programa antes de compilarlo.

Al contrario que Java o C, Haskell posee inferencia de tipos. Si escribimos un número, no


tenemos que especificar que eso es un número. Haskell puede deducirlo él solo, así que no
tenemos que escribir explícitamente los tipos de nuestras funciones o expresiones para
conseguir resultados. Ya hemos cubierto parte de las bases de Haskell con muy poco
conocimiento de los tipos. Sin embargo, entender el sistema de tipos es una parte muy
importante para dominar Haskell.

Un tipo es como una etiqueta que posee toda expresión. Esta etiqueta nos dice a que
categoría de cosas se ajusta la expresión. La expresión True es un booleano, "Hello" es una
cadena, etc.
Ahora vamos a usar GHCi para examinar los tipos de algunas expresiones. Lo haremos
gracias al comando :t, el cual, seguido de una expresión válida nos dice su tipo. Vamos a dar
un vistazo:
ghci> :t 'a'
'a' :: Char
ghci> :t True
True :: Bool
ghci> :t "HOLA!"
"HELLO!" :: [Char]
ghci> :t (True, 'a')
(True, 'a') :: (Bool, Char)
ghci> :t 4 == 5
4 == 5 :: Bool

Podemos ver que ejecutando el comando :t sobre una expresión se muestra esa misma
expresión seguida de :: y de su tipo. :: se puede leer como tiene el tipo. Los tipos explícitos
siempre se escriben con su primera letra en mayúsculas. 'a', como hemos visto, tiene el
tipo Char. El nombre de este tipo viene de “Character” (carácter en inglés). True tiene el
tipo Bool. Tiene sentido. Pero, ¿qué es esto? Examinando el tipo
de "HOLA!" obtenemos [Char]. Los corchetes definen una lista. Así que leemos esto como
una lista de caracteres. Al contrario que las listas, cada tamaño de tupla tiene su propio tipo.
Así que la expresión (True, 'a') tiene el tipo (Bool, Char), mientras que la
expresión ('a', 'b', 'c') tiene el tipo (Char, Char, Char). 4 == 5 siempre
devolverá False así que esta expresión tiene el tipo Bool.
Las funciones también tiene tipos. Cuando escribimos nuestras propias funciones
podemos darles un tipo explícito en su declaración. Generalmente está bien considerado
escribir los tipos explícitamente en la declaración de un función, excepto cuando éstas son
muy cortas. De aquí en adelante les daremos tipos explícitos a todas las funciones que
creemos. ¿Recuerdas la lista intensional que filtraba solo las mayúsculas de una cadena?
Aquí tienes como se vería con su declaración de tipo:

removeNonUppercase :: [Char] -> [Char]


removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]

removeNonUppercase tiene el tipo [Char] -> [Char], que significa que es una función
que toma una cadena y devuelve otra cadena. El tipo [Char] es sinónimo de String así que
sería más elegante escribir el tipo como removeNonUppercase :: String -> String.
Anteriormente no le dimos un tipo a esta función ya que el compilador puede inferirlo por si
solo. Pero, ¿cómo escribimos el tipo de una función que toma varios parámetros? Aquí tienes
una función que toma tres enteros y los suma:
addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z

Los parámetros están separados por -> y no existe ninguna diferencia especial entre los
parámetros y el tipo que devuelve la función. El tipo que devuelve la función es el último
elemento de la declaración y los parámetros son los restantes. Más tarde veremos porque
simplemente están separados por -> en lugar de tener algún tipo de distinción más explícita
entre los tipos de parámetros y el tipo de retorno, algo como Int, Int, Int -> Int.
Si escribimos una función y no tenemos claro el tipo que debería tener, siempre podemos
escribir la función sin su tipo y ejecutar el comando :t sobre ella. Las funciones también son
expresiones así que no hay ningún problema en usar :t con ellas.
Aquí tienes una descripción de los tipos más comunes:

 Int representa enteros. Se utiliza para representar número enteros, por lo


que 7 puede ser un Int pero 7.2 no puede. Int está acotado, lo que significa que
tiene un valor máximo y un valor mínimo. Normalmente en máquinas de 32bits el valor
máximo de Int es 2147483647 y el mínimo -2147483648.
 Integer representa... esto... enteros también. La diferencia es que no están
acotados así que pueden representar números muy grandes. Sin embargo, Int es
más eficiente.
 factorial :: Integer -> Integer
 factorial n = product [1..n]

 ghci> factorial 50
 3041409320171337804361260816606476884437764156896051200000
0000000

 Float es un número real en coma flotante de simple precisión.


 circumference :: Float -> Float
 circumference r = 2 * pi * r

 ghci> circumference 4.0


 25.132742

 Double es un número real en coma flotante de... ¡Doble precisión!.


 circumference' :: Double -> Double
 circumference' r = 2 * pi * r

 ghci> circumference' 4.0


 25.132741228718345

 Bool es el tipo booleano. Solo puede tener dos valores: True o False.
 Char representa un carácter. Se define rodeado por comillas simples. Una lista de
caracteres es una cadena.
Las tuplas también poseen tipos pero dependen de su longitud y del tipo de sus
componentes, así que teóricamente existe una infinidad de tipos de tuplas y eso son
demasiados tipos como para cubrirlos en esta guía. La tupla vacía es también un tipo () el
cual solo puede contener un valor: ().
Variables de tipo
¿Cual crees que es el tipo de la función head? Como head toma una lista de cualquier tipo
y devuelve su primer elemento... ¿Cual podrá ser? Vamos a verlo:
ghci> :t head
head :: [a] -> a

Hmmm... ¿Qué es a? ¿Es un tipo? Si recuerdas antes dijimos que los tipos deben
comenzar con mayúsculas, así que no puede ser exactamente un tipo. Como no comienza
con una mayúscula en realidad es una variable de tipo. Esto significa que a puede ser
cualquier tipo. Es parecido a los tipos genéricos de otros lenguajes, solo que en Haskell son
mucho más potentes ya que nos permite definir fácilmente funciones muy generales siempre
que no hagamos ningún uso especifico del tipo en cuestión. Las funciones que tienen
variables de tipos son llamadas funciones polimórficas. La declaración de
tipo head representa una función que toma una lista de cualquier tipo y devuelve un elemento
de ese mismo tipo.
Aunque las variables de tipo pueden tener nombres más largos de un solo carácter,
normalmente les damos nombres como a, b, c, d, etc.
¿Recuerdas fst? Devuelve el primer componente de una dupla. Vamos a examinar su
tipo.
ghci> :t fst
fst :: (a, b) -> a

Como vemos, fst toma una dupla que contiene dos tipos y devuelve un elemento del
mismo tipo que el primer componente de la dupla. Ese es el porqué de que podamos
usar fst con duplas que contengan cualquier combinación de tipos. Ten en cuenta que solo
porque a y b son diferentes variables de tipo no tienen porque ser diferentes tipos.
Simplemente representa que el primer componente y el valor que devuelve la función son del
mismo tipo.
Clases de tipos paso a paso (1ª parte)
Las clases de tipos son una especie de interfaz que define algún tipo de comportamiento.
Si un tipo es miembro de una clase de tipos, significa que ese tipo soporta e implementa el
comportamiento que define la clase de tipos. La gente que viene de lenguajes orientados a
objetos es propensa a confundir las clases de tipos porque piensan que son como las clases
en los lenguajes orientados a objetos. Bien, pues no lo son. Una aproximación más adecuada
sería pensar que son como las interfaces de Java, o los protocolos de Objective-C, pero
mejor.

¿Cuál es la declaración de tipo de la función ==?


ghci> :t (==)
(==) :: (Eq a) => a -> a -> Bool

Nota
El operador de igualdad == es una función. También lo son +, -, *, / y casi todos los
operadores. Si el nombre de una función está compuesta solo por caracteres especiales (no
alfanuméricos), es considerada una función infija por defecto. Si queremos examinar su tipo,
pasarla a otra función o llamarla en forma prefija debemos rodearla con paréntesis. Por
ejemplo: (+) 1 4 equivale a 1 + 4.

Interesante. Aquí vemos algo nuevo, el símbolo =>. Cualquier cosa antes del
símbolo => es una restricción de clase. Podemos leer la declaración de tipo anterior como: la
función de igualdad toma dos parámetros que son del mismo tipo y devuelve un Bool. El tipo
de estos dos parámetros debe ser miembro de la clase Eq (esto es la restricción de clase).
La clase de tipos Eq proporciona una interfaz para las comparaciones de igualdad.
Cualquier tipo que tenga sentido comparar dos valores de ese tipo por igualdad debe ser
miembro de la clase Eq. Todos los tipos estándar de Haskell excepto el tipo IO (un tipo para
manejar la entrada/salida) y las funciones forman parte de la clase Eq.
La función elem tiene el tipo (Eq a) => a -> [a] -> Bool porque usa == sobre los
elementos de la lista para saber si existe el elemento indicado dentro de la lista.
Algunas clases de tipos básicas son:

 Eq es utilizada por los tipos que soportan comparaciones por igualdad. Los
miembros de esta clase implementan las funciones == o /= en algún lugar de su
definición. Todos los tipos que mencionamos anteriormente forman parte de la
clase Eq exceptuando las funciones, así que podemos realizar comparaciones de
igualdad sobre ellos.
 ghci> 5 == 5
 True
 ghci> 5 /= 5
 False
 ghci> 'a' == 'a'
 True
 ghci> "Ho Ho" == "Ho Ho"
 True
 ghci> 3.432 == 3.432
 True

 Ord es para tipos que poseen algún orden.


 ghci> :t (>)
 (>) :: (Ord a) => a -> a -> Bool

Todos los tipos que hemos llegado a ver excepto las funciones son parte de la
clase Ord. Ord cubre todas las funciones de comparación como >, <, >= y <=. La
función compare toma dos miembros de la clase Ord del mismo tipo y devuelve su
orden. El orden está representado por el tipo Ordering que puede tener tres valores
distintos: GT, EQ y LT los cuales representan mayor que, igual que y menor que,
respectivamente.
Para ser miembro de Ord, primero un tipo debe ser socio del prestigioso y exclusivo
club Eq.
ghci> "Abrakadabra" < "Zebra"
True
ghci> "Abrakadabra" `compare` "Zebra"
LT
ghci> 5 >= 2
True
ghci> 5 `compare` 3
GT

 Los miembros de Show pueden ser representados por cadenas. Todos los tipos
que hemos visto excepto las funciones forman parte de Show. la función más utilizada
que trabaja con esta clase de tipos es la función show. Toma un valor de un tipo que
pertenezca a la clase Show y lo representa como una cadena de texto.
 ghci> show 3
 "3"
 ghci> show 5.334
 "5.334"
 ghci> show True
 "True"

 Read es como la clase de tipos opuesta a Show. La función read toma una
cadena y devuelve un valor del tipo que es miembro de Read.
 ghci> read "True" || False
 True
 ghci> read "8.2" + 3.8
 12.0
 ghci> read "5" - 2
 3
 ghci> read "[1,2,3,4]" ++ [3]
 [1,2,3,4,3]
Hasta aquí todo bien. Una vez más, todo los tipos que hemos visto excepto las
funciones forman parte de esta clase de tipos. Pero, ¿Qué pasa si simplemente
usamos read "4"?
ghci> read "4"
<interactive>:1:0:
Ambiguous type variable `a' in the constraint:
`Read a' arising from a use of `read' at
<interactive>:1:0-7
Probable fix: add a type signature that fixes these type
variable(s)

Lo que GHCi no está intentado decir es que no sabe que queremos que devuelva. Ten
en cuenta que cuando usamos anteriormente read lo hicimos haciendo algo luego con
el resultado. De esta forma, GHCi podía inferir el tipo del resultado de la función read.
Si usamos el resultado de aplicar la función como un booleano, Haskell sabe que tiene
que devolver un booleano. Pero ahora, lo único que sabe es que queremos un tipo de
la clase Read, pero no cual. Vamos a echar un vistazo a la declaración de tipo de la
función read.
ghci> :t read
read :: (Read a) => String -> a

¿Ves? Devuelve un tipo que es miembro de la clase Read, pero si luego no lo usamos
en ningún otro lugar, no hay forma de saber que tipo es. Por este motivo utilizamos
las anotaciones de tipo explícitas. Las anotación de tipo son una forma de decir
explícitamente el tipo que debe tener una expresión. Lo hacemos añadiendo :: al final
de la expresión y luego especificando el tipo. Observa:
ghci> read "5" :: Int
5
ghci> read "5" :: Float
5.0
ghci> (read "5" :: Float) * 4
20.0
ghci> read "[1,2,3,4]" :: [Int]
[1,2,3,4]
ghci> read "(3, 'a')" :: (Int, Char)
(3, 'a')

La mayoría de expresiones son del tipo que el compilador puede inferir por si solo.
Pero a veces, el compilador desconoce el tipo de valor que debe devolver una
expresión como read "5", que podría ser Int, Double, etc. Para saberlo, Haskell
debe en realidad evaluar read "5". Pero como Haskell es un lenguaje con tipos
estáticos, debe conocer todos los tipos antes de que el código sea compilado (o en
GHCi, evaluado). Así que con esto le estamos diciendo a Haskell: “Ey, esta expresión
debe ser de este tipo en caso de que no sepas cual es”.
 Los miembros de la clase Enum son tipos secuencialmente ordenados, es decir,
pueden ser enumerados. La principal ventaja de la clase de tipos Enum es que
podemos usar los miembros en las listas aritméticas. También tienen definidos los
sucesores y predecesores, por lo que podemos usar las funciones succ y pred. Los
tipos de esta clase son: (), Bool, Char, Ordering, Int, Integer, Float y Double.
 ghci> ['a'..'e']
 "abcde"
 ghci> [LT .. GT]
 [LT,EQ,GT]
 ghci> [3 .. 5]
 [3,4,5]
 ghci> succ 'B'
 'C'

 Los miembros de Bounded poseen límites inferiores y superiores, es decir están


acotados.
 ghci> minBound :: Int
 -2147483648
 ghci> maxBound :: Char
 '\1114111'
 ghci> maxBound :: Bool
 True
 ghci> minBound :: Bool
 False

minBound y maxBound son interesantes ya que tienen el tipo (Bounded a) => a. Es


decir, son constantes polimórficas.
Todas las tuplas son también Bounded si sus componentes los son también.
ghci> maxBound :: (Bool, Int, Char)
(True,2147483647,'\1114111')

 Num es la clase de tipos numéricos. Sus miembros tienen la propiedad de poder


comportarse como números. Vamos a examinar el tipo de un número.
 ghci> :t 20
 20 :: (Num t) => t

Parece que todos los números son también constantes polimórficas. Pueden actuar
como si fueran cualquier tipo de la clase Num.
ghci> 20 :: Int
20
ghci> 20 :: Integer
20
ghci> 20 :: Float
20.0
ghci> 20 :: Double
20.0

Estos son los tipo estándar de la clase Num. Si examinamos el tipo de * veremos que
puede aceptar cualquier tipo de número.
ghci> :t (*)
(*) :: (Num a) => a -> a -> a

Toma dos números del mismo tipo y devuelve un número del mismo tipo. Esa es la
razón por la que (5 :: Int) * (6 :: Integer) lanzará un error mientras
que 5 * (6 :: Integer) funcionará correctamente y producirá un Interger, ya
que 5 puede actuar como un Integer o un Int.
Para unirse a Num, un tipo debe ser amigo de Show y Eq.
 Integral es también un clase de tipos numérica. Num incluye todos los números,
incluyendo números reales y enteros. Integral únicamente incluye números
enteros. Int e Integer son miembros de esta clase.
 Floating incluye únicamente números en coma flotante, es
decir Float y Double.
Una función muy útil para trabajar con números es fromIntegral. Tiene el
tipo fromIntegral :: (Num b, Integral a) => a -> b. A partir de esta declaración
podemos decir que toma un número entero y lo convierte en un número más general. Esto es
útil cuando estas trabajando con números reales y enteros al mismo tiempo. Por ejemplo, la
función length tiene el tipo length :: [a] -> Int en vez de tener un tipo más general
como (Num b) => length :: [a] -> b. Creo que es por razones históricas o algo
parecido, en mi opinión, es absurdo. De cualquier modo, si queremos obtener el tamaño de
una lista y sumarle 3.2, obtendremos un error al intentar sumar un entero con uno en coma
flotante. Para solucionar esto, hacemos fromIntegral (length [1,2,3,4]) + 3.2.
Cargando...
Fíjate que en la declaración de tipo de fromIntegral hay varias restricciones de clase. Es
completamente válido como puedes ver, las restricciones de clase deben ir separadas por
comas y entre paréntesis.

La sintaxis de las funciones


Ajuste de patrones
En este capítulo cubriremos algunas de las construcciones sintácticas de Haskell más
interesantes, empezando con el ajuste de patrones (“pattern matching” en inglés). Un ajuste
de patrones consiste en una especificación de pautas que deben ser seguidas por los datos,
los cuales pueden ser deconstruidos permitiéndonos acceder a sus componentes.
Podemos separar el cuerpo que define el comportamiento de una función en varias partes,
de forma que el código quede mucho más elegante, limpio y fácil de leer. Podemos usar el
ajuste de patrones con cualquier tipo de dato: números, caracteres, listas, tuplas, etc. Vamos
a crear una función muy trivial que compruebe si el número que le pasamos es un siete o no.

lucky :: (Integral a) => a -> String


lucky 7 = "¡El siete de la suerte!"
lucky x = "Lo siento, ¡no es tu día de suerte!"
Cuando llamamos a lucky, los patrones son verificados de arriba a abajo y cuando un
patrón concuerda con el valor asociado, se utiliza el cuerpo de la función asociado. En este
caso, la única forma de que un número concuerde con el primer patrón es que dicho número
sea 7. Si no lo es, se evaluara el siguiente patrón, el cual coincide con cualquier valor y lo liga
a x. También se podría haber implementado utilizando una sentencia if. Pero, ¿qué pasaría
si quisiéramos una función que nombrara los número del 1 al 5,
o "No entre uno 1 y 5" para cualquier otro número? Si no tuviéramos el ajuste de
patrones deberíamos crear un enrevesado árbol if then else. Sin embargo con él:
sayMe :: (Integral a) => a -> String
sayMe 1 = "¡Uno!"
sayMe 2 = "¡Dos!"
sayMe 3 = "¡Tres!"
sayMe 4 = "¡Cuatro!"
sayMe 5 = "¡Cinco!"
sayMe x = "No entre uno 1 y 5"
Ten en cuenta que si movemos el último patrón (el más general) al inicio, siempre
obtendríamos "No entre uno 1 y 5" como respuesta, ya que el primer patrón encajaría
con cualquier número y no habría posibilidad de que se comprobaran los demás patrones.
¿Recuerdas la función factorial que creamos anteriormente? Definimos el factorial de un
número n como product [1..n]. También podemos implementar una función factorial
recursiva, de forma parecida a como lo haríamos en matemáticas. Empezamos diciendo que
el factorial de 0 es 1. Luego decimos que el factorial de cualquier otro número entero positivo
es ese entero multiplicado por el factorial de su predecesor.
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
Esta es la primera vez que definimos una función recursiva. La recursividad es muy
importante en Haskell, pero hablaremos de ello más adelante. Resumiendo, esto es lo que
pasa cuando intentamos obtener el factorial de, digamos 3. Primero intenta
calcular 3 * factorial 2. El factorial de 2 es 2 * factorial 1, así que ahora
tenemos 3 * (2 * factorial 1). factorial 1 es 1 * factorial 0, lo que nos lleva
a 3 * (2 * (1 * factorial 0)). Ahora viene el truco, hemos definido el factorial de 0
para que sea simplemente 1, y como se encuentra con ese patrón antes que el otro más
general obtenemos 1. Así que el resultado equivale a 3 * (2 * (1 * 1)). Si hubiésemos
escrito el segundo patrón al inicio, hubiese aceptado todos los números incluyendo el 0 y el
cálculo nunca terminaría. Por este motivo el orden es importante a la hora de definir los
patrones y siempre es mejor definir los patrones más específicos al principio dejando los más
generales al final.
Los patrones también pueden fallar. Si definimos una función como esta:

charName :: Char -> String


charName 'a' = "Albert"
charName 'b' = "Broseph"
charName 'c' = "Cecil"
E intentamos ejecutarla con un valor no esperado, esto es lo que pasa:

ghci> charName 'a'


"Albert"
ghci> charName 'b'
"Broseph"
ghci> charName 'h'
"*** Exception: tut.hs:(53,0)-(55,21): Non-exhaustive patterns in
function charName
Se queja porque tenemos un ajuste de patrones no exhaustivo y ciertamente así es.
Cuando utilizamos patrones siempre tenemos que incluir uno general para asegurarnos que
nuestro programa no fallará.

El ajuste de patrones también pueden ser usado con tuplas. ¿Cómo crearíamos una
función que tomara dos vectores 2D (representados con duplas) y que devolviera la suma de
ambos? Para sumar dos vectores sumamos primero sus componentes x y sus
componentes y de forma separada. Así es como lo haríamos si no existiese el ajuste de
patrones:
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors a b = (fst a + fst b, snd a + snd b)
Bien, funciona, pero hay mejores formas de hacerlo. Vamos a modificar la función para
que utilice un ajuste de patrones.

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)


addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
¡Ahí lo tienes! Mucho mejor. Ten en cuenta que es un patrón general, es decir, se
verificará para cualquier dupla. El tipo de addVectors es en ambos casos el
mismo: addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a), por lo que está
garantizado que tendremos dos duplas como parámetros.
fst y snd extraen componentes de las duplas. Pero, ¿qué pasa con las triplas? Bien,
como no tenemos funciones que hagan lo mismo con las triplas vamos a crearlas nosotros
mismos.
first :: (a, b, c) -> a
first (x, _, _) = x

second :: (a, b, c) -> b


second (_, y, _) = y

third :: (a, b, c) -> c


third (_, _, z) = z
_ tiene el mismo significado que con las listas intensionales. Denota que en realidad no
nos importa ese valor, ya que no lo vamos a utilizar.
También podemos utilizar ajuste de patrones con las listas intensionales. Fíjate:

ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]


ghci> [a+b | (a,b) <- xs]
[4,7,6,8,11,4]
En caso de que se produzca un fallo en el patrón, simplemente pasará al siguiente
elemento.

Las listas también pueden ser usadas en un ajuste de patrones. Puedes comparar contra
la lista vacía [] o contra cualquier patrón que involucre a : y la lista vacía. Como [1,2,3],
que solo es otra forma de expresar 1:2:3:[] (podemos utilizar ambas alternativas). Un patrón
como x:xs ligará la cabeza de la lista con x y el resto con xs, incluso cuando la lista tenga
solo un elemento, en cuyo caso xs acabará siendo la lista vacía.
Nota
El patrón x:xs es muy utilizado, especialmente con las funciones recursivas. Los patrones que
contengan un : solo aceptarán listas con algún elemento.
Si quisiéramos ligar, digamos, los tres primeros elementos de una lista a variables y el
resto a otra variable podemos usar algo como x:y:z:zs. Sin embargo esto solo aceptará
listas que tengan al menos 3 elementos.
Ahora que ya sabemos usar patrones con las listas vamos a implementar nuestra propia
función head.
head' :: [a] -> a
head' [] = error "¡Hey, no puedes utilizar head con una lista vacía!"
head' (x:_) = x
Comprobamos que funciona:

ghci> head' [4,5,6]


4
ghci> head' "Hello"
'H'
¡Bien! Fíjate que si queremos ligar varias variables (incluso aunque alguna de ellas sea _ y
realmente no la queremos ligar) debemos rodearlas con paréntesis. Fíjate también en la
función error que acabamos de utilizar. Ésta toma una cadena y genera un error en tiempo
de ejecución usado la cadena que le pasemos como información acerca del error que ocurrió.
Provoca que el programa termine, lo cual no es bueno usar muy a menudo. De todas formas,
llamar a head con una lista vacía no tiene mucho sentido.
Vamos a crear una función que nos diga algunos de los primeros elementos que contiene
una lista.

tell :: (Show a) => [a] -> String


tell [] = "La lista está vacía"
tell (x:[]) = "La lista tiene un elemento: " ++ show x
tell (x:y:[]) = "La lista tiene dos elementos: " ++ show x ++ " y " ++
show y
tell (x:y:_) = "La lista es larga. Los primeros dos elementos son: "
++ show x ++ " y " ++ show y
Esta función es segura ya que tiene en cuenta la posibilidad de una lista vacía, una lista
con un elemento, una lista con dos elementos y una lista con más de dos elementos. Date
cuenta que podríamos escribir (x:[]) y (x:y:[]) como [x] y [x,y] sin usar paréntesis.
Pero no podemos escribir (x:y:_) usando corchetes ya que acepta listas con más de dos
elementos.
Ya implementamos la función length usando listas intensionales. Ahora vamos a
implementarla con una pizca de recursión.
length' :: (Num b) => [a] -> b
length' [] = 0
length' (_:xs) = 1 + length' xs
Es similar a la función factorial que escribimos antes. Primero definimos el resultado de
una entrada conocida, la lista vacía. Esto también es conocido como el caso base. Luego en
el segundo patrón dividimos la lista en su cabeza y el resto. Decimos que la longitud es 1 más
el tamaño del resto de la lista. Usamos _ para la cabeza de la lista ya que realmente no nos
interesa su contenido. Fíjate que también hemos tenido en cuenta todos los posibles casos de
listas. El primer patrón acepta la lista vacía, y el segundo todas las demás.
Vamos a ver que pasa si llamamos a length' con "ojo". Primero se comprobaría si es
una lista vacía, como no lo es continuaríamos al siguiente patrón. Éste es aceptado y nos dice
que la longitud es 1 + length' "jo", ya que hemos divido la cadena en cabeza y cola,
decapitando la lista. Vale. El tamaño de "jo" es, de forma similar, 1 + length' "o". Así que
ahora mismo
tenemos 1 + (1 + length' "o"). length' "o" es 1 + length' "" (también lo
podríamos escribir como 1 + length' []). Y como tenemos definido length' [] a 0, al
final tenemos 1 + (1 + (1 + 0)).
Ahora implementaremos sum. Sabemos que la suma de una lista vacía es 0, lo cual
escribimos con un patrón. También sabemos que la suma de una lista es la cabeza más la
suma del resto de la cola, y si lo escribimos obtenemos:
sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs
También existen los llamados patrones como, o patrones as (del inglés, as patterns). Son
útiles para descomponer algo usando un patrón, de forma que se ligue con las variables que
queramos y además podamos mantener una referencia a ese algo como un todo. Para ello
ponemos un @ delante del patrón. La mejor forma de entenderlo es con un
ejemplo: xs@(x:y:ys). Este patrón se ajustará exactamente a lo mismo que lo
haría x:y:ys pero además podríamos acceder fácilmente a la lista completa usando xs en
lugar de tener que repetirnos escribiendo x:y:ys en el cuerpo de la función. Un ejemplo
rápido:
capital :: String -> String
capital "" = "¡Una cadena vacía!"
capital all@(x:_) = "La primera letra de " ++ all ++ " es " ++ [x]
ghci> capital "Dracula"
"La primera letra de Dracula es D"
Normalmente usamos los patrones como para evitar repetirnos cuando estamos ajustando
un patrón más grande y tenemos que usarlo entero otra vez en algún lugar del cuerpo de la
función.
Una cosa más, no podemos usar ++ en los ajustes de patrones. Si intentamos usar un
patrón (xs ++ ys), ¿qué habría en la primera lista y qué en la segunda? No tiene mucho
sentido. Tendría más sentido ajustar patrones como (xs ++ [x,y,z]) o simplemente (xs +
+ [x]) pero dada la naturaleza de las listas no podemos hacer esto.
¡Guardas, Guardas!
Mientras que los patrones son una forma de asegurarnos que un valor tiene una
determinada forma y deconstruirlo, las guardas son una forma de comprobar si alguna
propiedad de una valor (o varios de ellos) es cierta o falsa. Suena muy parecido a una
sentencia if y de hecho es muy similar. La cuestión es que las guardas son mucho más
legibles cuando tienes varias condiciones y encajan muy bien con los patrones.
En lugar de explicar su sintaxis, simplemente vamos a crear una función que utilice
guardas. Crearemos una función simple que te regañará de forma diferente en función de
tu IMC (índice de masa corporal). Tu IMC es igual a tu altura dividida por tu peso al cuadrado.
Si tu IMC es menor que 18,5 tienes infrapeso. Si estas en algún lugar entre 18,5 y 25 eres del
montón. Si tienes entre 25 y 30 tienes sobrepeso y si tienes más de 30 eres obeso. Así que
aquí tienes la función (no estamos calculando nada ahora, simplemente obtiene un IMC y te
regaña)
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| bmi <= 18.5 = "Tienes infrapeso ¿Eres emo?"
| bmi <= 25.0 = "Supuestamente eres normal... Espero que seas
feo."
| bmi <= 30.0 = "¡Estás gordo! Pierde algo de peso gordito."
| otherwise = "¡Enhorabuena, eres una ballena!"
Las guardas se indican con barras verticales que siguen al nombre de la función y sus
parámetros. Normalmente tienen una sangría y están alineadas. Una guarda es básicamente
una expresión booleana. Si se evalúa a True, entonces el cuerpo de la función
correspondiente es utilizado. Si se evalúa a False, se comprueba la siguiente guarda y así
sucesivamente. Si llamamos a esta función con 24.3, primero comprobará si es menor o igual
que 18.5. Como no lo es, seguirá a la siguiente guarda. Se comprueba la segunda guarda y
como 24,3 es menor que 25, se devuelve la segunda cadena.
Recuerda a un gran árbol if then else de los lenguajes imperativos, solo que mucho
más claro. Generalmente los arboles if else muy grandes están mal vistos, pero hay
ocasiones en que un problema se define de forma discreta y no hay forma de solucionarlo.
Las guardas son una buena alternativa para esto.
Muchas veces la última guarda es otherwise. otherwise está definido simplemente
como otherwise = True y acepta todo. Es muy similar al ajuste de patrones, solo se
aceptan si la entrada satisface un patrón, pero las guardas comprueban condiciones
booleanas. Si todas las guardas de una función se evalúan a False (y no hemos dado otra
guarda otherwise), la evaluación falla y continuará hacia el siguiente patrón. Por esta razón
los patrones y las guardas encajan tan bien juntas. Si no existe ningún patrón ni ninguna
guarda aceptable se lanzará un error.
Por supuesto podemos usar guardas con funciones que tomen tantos parámetros como se
quieran. En lugar de dejar que el usuario tenga que calcular su propio IMC por su cuenta
antes de llamar a la función, vamos a modificar la función para que tome la altura y el peso y
lo calcule por nosotros.

bmiTell :: (RealFloat a) => a -> a -> String


bmiTell weight height
| weight / height ^ 2 <= 18.5 = "Tienes infrapeso ¿Eres emo?"
| weight / height ^ 2 <= 25.0 = "Supuestamente eres normal...
Espero que seas feo."
| weight / height ^ 2 <= 30.0 = "¡Estás gordo! Pierde algo de peso
gordito."
| otherwise = "¡Enhorabuena, eres una ballena!"
Vamos a ver si estoy gordo...

ghci> bmiTell 85 1.90


"Supuestamente eres normal... Espero que seas feo."
¡Sí! No estoy gordo, pero Haskell me acaba de llamar feo...

Fíjate que no hay un = después del nombre de la función y sus parámetros, antes de la
primera guarda. Muchos novatos obtienen un error sintáctico por poner un = ahí, y tú también
lo harás.
Otro ejemplo muy simple: vamos a implementar nuestra función max. Si recuerdas, puede
tomar dos cosas que puedan ser comparadas y devuelve la mayor.
max' :: (Ord a) => a -> a -> a
max' a b
| a > b = a
| otherwise = b
Las guardas también pueden ser escritas en una sola línea, aunque advierto que es mejor
no hacerlo ya que son mucho menos legibles, incluso con funciones cortas. Pero para
demostrarlo podemos definir max' como:
max' :: (Ord a) => a -> a -> a
max' a b | a > b = a | otherwise = b
¡Arg! No se lee fácilmente. Sigamos adelante. Vamos a implementar nuestro
propio compare usando guardas.
myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
| a > b = GT
| a == b = EQ
| otherwise = LT
ghci> 3 `myCompare` 2
GT
Nota
No solo podemos llamar a funciones de forma infija usando las comillas, sino que también
podemos definirlas de esta forma. A veces es más fácil leerlo así.
¿Dónde?
En la sección anterior definimos la función que calculaba el IMC así:

bmiTell :: (RealFloat a) => a -> a -> String


bmiTell weight height
| weight / height ^ 2 <= 18.5 = "Tienes infrapeso ¿Eres emo?"
| weight / height ^ 2 <= 25.0 = "Supuestamente eres normal...
Espero que seas feo."
| weight / height ^ 2 <= 30.0 = "¡Estás gordo! Pierde algo de peso
gordito."
| otherwise = "¡Enhorabuena, eres una ballena!"
Si te fijas notarás que nos repetimos tres veces. Nos repetimos tres veces. Repetirse (tres
veces) mientras estas programando es tan deseable como que te den una patada donde más
te duela. Ya que estamos repitiendo la misma expresión tres veces sería ideal si pudiésemos
calcularla una sola vez, ligarla a una variable y utilizarla en lugar de la expresión. Bien,
podemos modificar nuestra función de esta forma:

bmiTell :: (RealFloat a) => a -> a -> String


bmiTell weight height
| bmi <= 18.5 = "Tienes infrapeso ¿Eres emo?"
| bmi <= 25.0 = "Supuestamente eres normal... Espero que seas
feo."
| bmi <= 30.0 = "¡Estás gordo! Pierde algo de peso gordito."
| otherwise = "¡Enhorabuena, eres una ballena!"
where bmi = weight / height ^ 2
Hemos puesto la palabra reservada where después de las guardas (normalmente es mejor
alinearla con el resto de las barras verticales) y luego definimos varias variables. Estas
variables son visibles en las guardas y nos dan la ventaja de no tener que repetirnos. Si
decidimos que tenemos que calcular el IMC de otra forma solo tenemos que modificarlo en un
lugar. También mejora la legibilidad ya que da nombre a las cosas y hace que nuestros
programas sean más rápidos ya que cosas como bmi solo deben calcularse una vez.
Podríamos pasarnos un poco y presentar una función como esta:
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "Tienes infrapeso ¿Eres emo?"
| bmi <= normal = "Supuestamente eres normal... Espero que seas
feo."
| bmi <= fat = "¡Estás gordo! Pierde algo de peso gordito."
| otherwise = "¡Enhorabuena, eres una ballena!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
Las variables que definamos en la sección where de una función son solo visibles desde
esa función, así que no nos tenemos que preocupar de ellas a la hora de crear más variables
en otras funciones. Si no alineamos la sección where bien y de forma correcta, Haskell se
confundirá porque no sabrá a que grupo pertenece.
Las variables definidas con where no se comparten entre los cuerpos de diferentes
patrones de una función. Si queremos que varios patrones accedan a la misma variable
debemos definirla de forma global.
También podemos usar el ajuste de patrones con las secciones where. Podríamos
reescribir la sección where de nuestra función anterior como:
...
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)
Vamos a crear otra función trivial en el que dado un nombre y un apellido devuelva sus
iniciales.
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
Podríamos haber realizado el ajuste de patrones directamente en los parámetros de la
función (en realidad hubiese sido más corto y elegante) pero así podemos ver lo que es
posible hacer con las secciones where.
De la misma forma que hemos definido constantes en los bloques where también
podemos definir funciones. Manteniéndonos fieles a nuestro programa de salud vamos a
hacer una función que tome una lista de duplas de pesos y estaturas y devuelva una lista de
IMCs.
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
¡Ahí lo tienes! La razón por la que hemos creado la función bmi en este ejemplo es que no
podemos calcular simplemente un IMC desde los parámetros de nuestra función. Tenemos
que examinar todos los elementos de la lista y calcular su IMC para cada dupla.
Las secciones where también pueden estar anidadas. Es muy común crear una función y
definir algunas funciones auxiliares en la sección where y luego definir otras funciones
auxiliares dentro de cada uno de ellas.
Let it be
Muy similar a las secciones where son las expresiones let. Las secciones where son una
construcción sintáctica que te dejan ligar variables al final de una función de forma que toda la
función pueda acceder a ella, incluyendo todas las guardas. Las expresiones let sirven para
ligar variables en cualquier lugar y son expresiones en si mismas, pero son muy locales, así
que no pueden extenderse entre las guardas. Tal y como todas las construcciones de Haskell
que te permiten ligar valores a variables, las expresiones let permiten usar el ajuste de
patrones. ¡Vamos a verlo en acción! Así es como podríamos definir una función que nos diera
el área de un cilindro basándose en su altura y su radio.
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea
Su forma es let <definición> in <expresión>. Las variables que definamos en la
expresión let son accesibles en la parte in. Como podemos ver, también podríamos haber
definido esto con una sección where. Fíjate también que los nombres están alineados en la
misma columna. Así que, ¿cuál es la diferencia entre ellos? Por ahora parece que let pone
las definiciones primero y luego la expresión que las utiliza mientras que where lo hace en el
orden inverso.
La diferencia es que las expresiones let son expresiones por si mismas. Las
secciones where son simplemente construcciones sintácticas. ¿Recuerdas cuando explicamos
las sentencias if y se explicó que como son una expresión pueden ser usadas en casi
cualquier lugar?
ghci> [if 5 > 3 then "Woo" else "Boo", if 'a' > 'b' then "Foo" else
"Bar"]
["Woo", "Bar"]
ghci> 4 * (if 10 > 5 then 10 else 0) + 2
42
También puedes hacer lo mismo con las expresiones let.
ghci> 4 * (let a = 9 in a + 1) + 2
42
También pueden ser utilizadas para definir funciones en un ámbito local:

ghci> [let square x = x * x in (square 5, square 3, square 2)]


[(25,9,4)]
Si queremos ligar varias variables en una solo línea, obviamente no podemos alinear las
definiciones en la misma columna. Por este motivo podemos separarlas con puntos y comas.

ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar =
"there!" in foo ++ bar)
(6000000,"Hey there!")
No tenemos porque poner el último punto y coma pero podemos hacerlo si queremos.
Como ya hemos dicho, podemos utilizar ajustes de patrones con las expresiones let. Son
muy útiles para desmantelar tuplas en sus componentes y ligarlos a varias variables.
ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100
600
También podemos usar las secciones let dentro de las listas intensionales. Vamos a
reescribir nuestro ejemplo anterior que calculaba una lista de duplas de alturas y pesos para
que use un let dentro de una lista intensional en lugar de definir una función auxiliar con
un where.
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
Incluimos un let dentro de la lista intensional como si fuera un predicado, solo que no
filtra los elementos, únicamente liga variables. Las variables definidas en una
expresión let dentro de una lista intensional son visibles desde la función de salida (la parte
anterior a |) y todos los predicados y secciones que vienen después de su definición.
Podríamos hacer que nuestra función devolviera el IMC solo para la gente obesa así:
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
No podemos usar el nombre bmi dentro de la parte (w, h) <- xs ya que está definida
antes que la expresión let.
Omitimos la parte in de las secciones let dentro de las lista intensionales porque la
visibilidad de los nombres está predefinida en estos casos. Sin embargo, podemos usar una
sección let in en un predicado y las variables definidas solo serán visibles en este
predicado. La parte in también puede ser omitida cuando definimos funciones y constantes
dentro del intérprete GHCi. Si lo hacemos, las variables serán visibles durante toda la sesión.
ghci> let zoot x y z = x * y + z
ghci> zoot 3 9 2
29
ghci> let boot x y z = x * y + z in boot 3 4 2
14
ghci> boot
<interactive>:1:0: Not in scope: `boot'
Si las expresiones let son tan interesantes, ¿por qué no usarlas siempre en lugar de las
secciones where? Bueno, como las expresiones let son expresiones y son bastante locales
en su ámbito, no pueden ser usadas entre guardas. Hay gente que prefiere las
secciones where porque las variables vienen después de la función que los utiliza. De esta
forma, el cuerpo de la función esta más cerca de su nombre y declaración de tipo y algunos
piensan que es más legible.
Expresiones case
Muchos lenguajes imperativos (como C, C++, Java, etc.) tienen construcciones
sintácticas case y si alguna vez has programado en ellos, probablemente sepas acerca de
que va esto. Se trata de tomar una variable y luego ejecutar bloques de código para ciertos
valores específicos de esa variable y luego incluir quizá algún bloque que siempre se ejecute
en caso de que la variable tenga algún valor que no se ajuste con ninguno de los anteriores.
Haskell toma este concepto y lo lleva un paso más allá. Como su nombre indica las
expresiones case son, bueno, expresiones, como las expresiones if else o las
expresiones let. No solo podemos evaluar expresiones basándonos en los posibles valores
de un variable sino que podemos realizar un ajuste de patrones. Mmmm... tomar un valor,
realizar un ajuste de patrones sobre él, evaluar trozos de código basados en su valor, ¿dónde
hemos oído esto antes? Oh sí, en los ajuste de patrones de los parámetros de una función.
Bueno, en realidad es una alternativa sintáctica para las expresiones case. Estos dos trozos
de código hacen lo mismo y son intercambiables:
head' :: [a] -> a
head' [] = error "¡head no funciona con listas vacías!"
head' (x:_) = x
head' :: [a] -> a
head' xs = case xs of [] -> error "¡head no funciona con listas
vacías!"
(x:_) -> x
Como puedes ver la sintaxis para las expresiones case es muy simple.
case expresion of patron -> resultado
patron -> resultado
patron -> resultado
...
La expresión es ajustada contra los patrones. La acción de ajuste de patrones se
comporta como se espera: el primer patrón que se ajuste es el que se utiliza. Si no se puede
ajustar a ningún patrón de la expresión case se lanzará un error de ejecución.
Mientras que el ajuste de patrones de los parámetros de una función puede ser realizado
únicamente al definir una función, las expresiones case pueden ser utilizadas casi en
cualquier lugar. Por ejemplo:
describeList :: [a] -> String
describeList xs = "La lista es" ++ case xs of [] -> "una lista
vacía."
[x] -> "una lista
unitaria."
xs -> "una lista
larga."
Son útiles para realizar un ajuste de patrones en medio de una expresión. Como el ajuste
de patrones que se realiza en la definición de una función es una alternativa sintáctica a las
expresiones case, también podríamos utilizar algo como esto:
describeList :: [a] -> String
describeList xs = "The list is " ++ what xs
where what [] = "empty."
what [x] = "a singleton list."
what xs = "a longer list."

También podría gustarte