Aprende Haskell Por El Bien de Todos

August 27, 2022 | Author: Anonymous | Category: N/A
Share Embed Donate


Short Description

Download Aprende Haskell Por El Bien de Todos...

Description

 

siguiente

anterior |

Índice »

Sobre esta guía Bienvenido a ¡Aprende Haskell por el bien de todos!  Si  Si estás est ás leyendo esto es to probablemente probablemente quieras aprender a prender Haskell. Pues bien, has venido al sitio adecuado, pero primero vamos a hablar un poco sobre esta guía. Decidí escribir esc ribir esta guía porque quería quería arraigar ar raigar mi propio conocimiento conoc imiento de Haskell y porqu porquee pensé que podía ayudar a la gente que empezaba empezaba con Haskell. Haskell. Existen bastantes basta ntes manuales y guías sobre so bre Haskell por la red. Cuando empecé con Haskell no lo leí un único documento. La forma en la que aprendí Haskell fue leyendo vario varioss artí artículos culos y guías, porque explicaban el mismo mismo concepto de diferentes formas. Así, yendo a través de varios documentos, fui capaz de juntar todas las piezas y entonces todo encajó. De modo que, esto es e s un intento más de añadir otro útil docum documento ento para aprender a prender Haskell de forma que tengas más oportunidades de encontrar uno que te guste. Esta guía está dirigida a personas que tengan experiencia en lenguajes de programación imperativ imperativaa (C, C++, Java, Jav a, Python...) pero que no hayan programado antes en ningún lenguaje funcional (Haskell, ML, OCaml...). Aunque apuesto que incluso si no tienes experiencia e xperiencia como programador, un tipo tipo inteligente como tú podrá seguir adelante y aaprender prender Haskell. Haskell. El canal #Haskell  de  de la red freenode  es  es un buen buen lugar para preguntar dudas si te sientes estancado y sabes inglés. La gente es bastante amable, paciente y comprensiblee con los que empiezan. comprensibl empiezan. Intenté aprender Haskell, dos veces, antes de conseguir entenderlo. Al principio todo parecía extraño. Pero una vez que se iluminó ilum inó el camino y tras saltar el primer obstáculo, fue un cómodo paseo. Creo que lo que trato de decir es que Haskell Haskell es genial y si estás interesado en la programación deberías aprenderlo incluso aunque te sea totalmente extraño. Aprender Aprender Haskell es como aprender a programar por primera vez ¡Es divertido! Te Te fuerza a que pienses diferente, dife rente, lo cual nos lleva a la siguiente sección...

Entonces, ¿qué ¿qué es Haskell? Haskell es un lenguaje de programación puramente funcional. En los lenguajes imperativos obtenemos resultados dándole al computador una secuencia de tareas que luego éste ejecutará. Mientras las ejecuta, puede cambiar de estado. Por ejemplo, establecemos la variable 󰁡 a 5, realizamos realizam os algunas tareas y luego cambiamos cambiamos el valor va lor de la variable varia ble anterior. Estos lenguajes poseen estructuras de control de flujo para realizar ciertas acciones varias veces ( 󰁦󰁯󰁲 , 󰁷󰁨󰁩󰁬󰁥...). Con la programación puramente puramente funcional no dec decimos imos al computador lo que tiene que hace hacer, r, sino más bien, decimos como son las cosas. co sas. El factorial fa ctorial de un número número es e s el producto de todos los números números desde el 1 hasta ese número, número, la ssum umaa de una lista de números es el primer número número más más la sum s umaa del resto de la lista, etc. etc . Expresamos la forma de las funciones. Además no podemos establecer una variable a algo y luego establecerla a otra cosa. Si decimos que 󰁡 es 5, luego no podemos decir que es otra cosa porque acabamos de decir que es 5 ¿Acaso somos unos mentirosos? De este modo, en los lenguajes puramente funcionales, una función no tiene efectos secundarios. Lo único que

1 de 3  

puede hacer una función es calcular y devolver algo como resultado. Al principio esto puede parecer una limitación pero en realidad tiene algunas buenas consecuencias: consecuencias : si una función es llamada llamada dos veces ve ces con c on los mismos mismos parámetros, pará metros, obtendremos siempre el mismo resultado. A esto lo llamamos transparencia referencial  y  y no solo permite al compilador razonar acerca de el comportamiento comportam iento de un programa, programa, sino si no que también nos permite deducir fácilm fáci lmente ente (e incluso demostrar) que una función es correcta y así poder construir funciones más complejas uniendo funciones simples. Haskell es perezoso . Es decir, a menos que le indiquemos indiquemos lo contrario, Haskell no no ejecutará funciones ni calculará resultados hasta que se vea realmente forzado a hacerlo. Esto funciona muy muy bien junto con la transparencia referencial referenci al y permite que veamos los programas como una serie de transformaciones de datos. Incluso nos permite hacer cosas interesantes como estructuras de datos infinitas. Digamos que tenemos una lista de números inmutables inmutables 󰁸󰁳 󰀽 󰁛󰀱󰀬󰀲󰀬󰀳󰀬󰀴󰀬󰀵󰀬󰀶󰀬󰀷󰀬󰀸󰁝  y una función 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥  que multiplica multiplica cada elemento por 2 y

devuelve una nueva lista. Si

quisiéramos multiplicar multiplicar nuestra lista por 8 en un lenguaje imperativo imperativo he hiciéramos hiciéra mos 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥󰀨󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥󰀨󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥󰀨󰁸󰁳󰀩󰀩󰀩 , probablemente probablemente el e l computador computador recorrería re correría

la

lista, haría una copia y devolvería el valor. Luego, recorrería otras dos veces más la lista y devolvería dev olvería el e l valor final. En un lenguaje lenguaje perezoso, llamar llamar a 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥  con una lista sin s in forzar que muestre muestre el valor v alor acaba aca ba con un programa programa diciéndote “Claro “ Claro claro, ¡luego lo hago!”. Pero cuando c uando quieres ver eell resultado, el primer primer 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥  dice al segundo que que quiere el resultado, re sultado, ¡ahora! El segundo segundo dice al tercero eso mismo y éste a regañadientes devuelve un 1 duplicado, duplicado, lo cual es un 2. El segundo lo recibe y devuelve un 4 al primero. El primero ve el resultado y dice que el primer elemento de la lista es un 8. De este modo, el computador solo hace un recorrido a través de la lista y solo cuando lo necesitamos. Cuando queremos calcular algo a partir de unos datos iniciales en un lenguaje perezoso, solo tenemos que tomar estos datos e ir transformándolos y moldeándolos moldeándolos hasta que se parezcan al resultado que deseamos. Haskell es un lenguaje tipifi tipificado cado estáticamente. Cuando compilamos un programa, el compilador sabe que trozos del código son enteros, cuales son cadenas de texto, etc. Gracias a esto un montón de posibles errores son capturados en tie tiemp mpoo de comp co mpilación. ilación. Si intentam i ntentamos os sumar un número número y una cadena de texto, te xto, el compilador nos regañará. Haskell usa un fantástico sistema s istema de tipos que posee inferencia de tipos. Esto significa que no tenemos que etiquetar cada trozo de código explícitamente con un tipo porque el sistema de tipos lo puede deducir de forma inteligente. La inferencia de tipos también permite que nuestro código sea más general, si hemos creado una función que toma toma dos números y los suma suma y no establecemos e stablecemos explícitamente sus tipos, la función ace aceptará ptará cualquier par de parámetros que actúen como c omo números. números. Haskell es elegante y conciso. conciso . Se debe a que utiliza utiliza conceptos conce ptos de alto nivel. nive l. Los programas Haskell son normalm normalmente ente más cortos que los equivalentes imperativos. Y los programas cortos son más fáciles de mantener que los largos, además de que poseen menos errores. Haskell fue creado por unos tipos muy inteligentes (todos ellos con sus respectivos doctorados). El proyecto de crear Haskell comenzó comenzó en 1987 19 87 cuando un comité de investigadores inves tigadores se pusieron de acuerdo para diseñar un lenguaje lenguaje revolucionario. revo lucionario. En el 2003 el informe Haskell Haskell fue publicado, publicado, definiendo así una versión estable es table del lenguaje. lenguaje.

Qué necesitas para comen comenzar zar Un editor editor de texto y un compilador de Haskell. Probablemente ya tienes iinstalado nstalado tu editor de texto te xto favorito favori to así que no vamos a perder el e l tiempo con esto. es to. Ahora mismo, los dos principales compiladores compiladores de Haskell son GHC GHC (Glasgow Haskell Compiler) Compiler) y Hugs. Hugs. Para los propósitos de esta guía usaremos usare mos GHC. GHC. No voy a cubrir muchos muchos detalles de la instalación. instalac ión. En Windows es cuestión de descargarse el instalador, pulsar “siguiente” un par de veces y luego reiniciar el ordenador. En las distribuciones de Linux basadas en Debian se puede instalar con 󰁡󰁰󰁴‐󰁧󰁥󰁴  o instalando un paquete 󰁤󰁥󰁢 . En MacOS es cuestión de instalar un

2 de 3  

󰁤󰁭󰁧  o

utilizar utilizar 󰁭󰁡󰁣󰁰󰁯󰁲󰁴󰁳 . Sea cual sea tu plataforma, aquí tienes aquí tienes más información.

GHC toma un script de Haskell (normalmente tienen la extensión .hs ) y lo comp c ompila, ila, pero tamb ta mbién ién tiene un modo modo interactivo interac tivo el el cual nos permite interactuar con dichos scripts. Podemos llamar a las funciones de los scripts que hayamos cargado y los resultados serán será n mostrados mostrados de forma inmediata. inmediata. Para Pa ra aprender es mucho mucho más fácil y rápido rá pido en lugar de tener que compilar y ejecutar los programas una y otra vez. El modo interactivo se ejecuta tecleando 󰁧󰁨󰁣󰁩 desde tu terminal te rminal.. Si hemos definido algunas algun as funciones en un fichero llamado, llamado, digamos, 󰁭󰁩󰁳󰁆󰁵󰁮󰁣󰁩󰁯󰁮󰁥󰁳󰀮󰁨󰁳 , podemos cargar esas funciones tecleando 󰀺󰁬 󰁭󰁩󰁳󰁆󰁵󰁮󰁣󰁩󰁯󰁮󰁥󰁳 , siempre y cuando 󰁭󰁩󰁳󰁆󰁵󰁮󰁣󰁩󰁯󰁮󰁥󰁳󰀮󰁨󰁳  esté en el

mismo directorio en el que fue invocado 󰁧󰁨󰁣󰁩. Si modificamos

el script .hs  y  y queremos observar los cambios tenemos que volver a ejecutar 󰀺󰁬  󰁭󰁩󰁳󰁆󰁵󰁮󰁣󰁩󰁯󰁮󰁥󰁳  o ejecutar 󰀺󰁲  que es equivalente ya que recarga rec arga el script scri pt actual. Trabajaremos Trabajaremos definiendo algunas funciones en un fichero .hs , las cargamos y pasamos el rato jugando con ellas, luego modificaremos el fichero .hs  volviendo  volviendo a cargarlo y así sucesivamente. Seguiremos este proceso durante toda la guía.

siguiente

anterior |

Índice »

3 de 3  

siguiente

anterior |

Índice »

¡Preparados, listos, ya! Muy bien ¡Vamos ¡Vamos a empezar! empezar! Si eres e res esa es a clase de mala mala persona perso na 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 ejec utar GHC en modo modo interactivo interacti vo y utilizar utilizar algunas funciones para ir acostumb aco stumbrándonos rándonos un poco. Abre una terminal terminal y escribe es cribe 󰁧󰁨󰁣󰁩. Serás recibido con un saludo como éste: GHCi, version 7.2.1: http://www.haskell http://www.haskell.org/ghc/ .org/ghc/ :? for help version 7.2.1: 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, ¡Enh orabuena, entraste e ntraste de GHCi! GHCi! Aquí el apuntador (o

prompt )

es 󰁐󰁲󰁥󰁬󰁵󰁤󰁥> pero como éste se hace más largo a medida

que cargamos módulos módulos durante una sesión, se sión, vamos va mos a utilizar 󰁧󰁨󰁣󰁩>. Si quieres tener el mismo apuntador apuntador ejecuta :󰁳󰁥󰁴 󰁰󰁲󰁯󰁭󰁰󰁴 "󰁧󰁨󰁣󰁩>  ".

Aquíí tenemos algo Aqu a lgo de aritmética ar itmética simp si mple. le. 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 emb e mbargo argo 5 * (‐3) funcionará.

1 de 15  

La álgebra booleana es también bastante simple. Como seguram seguramente ente sabrás, && representa el Y lógico mientras que 󰁼󰁼 representa el O lógico. 󰁮󰁯󰁴 niega 󰁔󰁲󰁵󰁥 a 󰁆󰁡󰁬󰁳󰁥 y viceversa. ghci> False ghci> True ghci> True ghci> True ghci>

True && False True && True False || True not False not (True && True)

False

La comprobación de igualdad se hace así: ghci> True ghci> False ghci> False ghci> True ghci> True

5 == 5 1 == 0 5 /= 5 5 /= 4 "hola" == "hola"

¿Qué pasa si hacemos algo como 5 + "󰁴󰁥󰁸󰁴󰁯"  o 5 == 󰁔󰁲󰁵󰁥? Bueno, si probamos con el primero obtenemos este amigable mensaje de error: e rror: No instance for (Num [Char]) arising from a use of `+' at :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 "󰁴󰁥󰁸󰁴󰁯"  no es un número número y por lo tanto no sabe s abe como sumarlo a 5. Incluso si en lugar lugar de "󰁴󰁥󰁸󰁴󰁯"  fuera "󰁣󰁵󰁡󰁴󰁲󰁯" , "󰁦󰁯󰁵󰁲" , o "4", Haskell no lo consideraría como un número. número. + espera que su parte izquierda izquierda y

derecha sean números. Si intentamos realizar 󰁔󰁲󰁵󰁥 == 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 ambas deben ser comparables entre si. No podemos comparar la velocidad con c on el tocino. Daremos Dar emos 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 e l único que se puede adaptar. Puede que no lo sepas, pero hemos estado usando funciones durante todo este tiempo. Por ejemplo, ejemplo, * es una función que toma dos números y los multiplica. ultiplica. Como ya has visto, lo llamamos llamamos haciendo un sándwich sobre él. Esto lo llamamos llamamos funciones infijas. Muchas funciones que no son usadas con números números son s on prefijas. Vamos Vamos a ver ve r alguna de ellas. Las funciones f unciones normalmente normalmente son prefijas prefi jas así que de ahora ahor a en adelante no vamos v amos a decir que una función está en forma prefija, simp s implemen lemente te lo asumirem a sumiremos. os. En muchos lenguajes imperativo imperativoss las funciones son llamadas escribiendo su nombre y luego escribiendo sus parámetros entre paréntesis, normalmente normalmente separados separa dos 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 llam ar a una de las funciones más aburridas de Haskell.

2 de 15  

ghci> succ 8 9

La función 󰁳󰁵󰁣󰁣 toma cualquier cosa que tenga te nga definido un sucesor y devuelve ese sucesor. s ucesor. Como puedes puedes ver, ve r, simplem simplemente ente hemos separado el nombre de la función y su parámetro por un espacio. espacio . Llamar a una función con varios va rios parámetros es igual de sencillo. Las funciones 󰁭󰁩󰁮 y 󰁭󰁡󰁸 toman dos cosas cosa s que puedan ponerse en orden (¡cómo los núm números!) eros!) 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 números 9 y 10, 1 0, no podríamos haber escrito esc rito

󰁳󰁵󰁣󰁣 9 * 10 porque hubiése hubiésemos mos obtenido el sucesor de 9, el cual hubiese sido multipl multiplicado icado por 10, obteniendo 100. 10 0. Tenemos Tenemos

que escribir 󰁳󰁵󰁣󰁣 (9 * 10) para obtener 91. Si una función toma dos parámetros también podemos podemos llamarla como un unaa función infija rodeándola rodeá ndola con acentos abiertos. a biertos. Por ejemplo, la función 󰁤󰁩󰁶 toma dos enteros y realiza una división entera entre ellos. Haciendo 󰁤󰁩󰁶 92 10 obtendríamos 9. Pero cuando la llamamos llamamos así, puede haber alguna confusión como que núm número ero está e stá haciendo hacie ndo la división y cual c ual está siendo s iendo dividido. De manera que nosotros nosotro s la llamam llamamos os como c omo una función infija haciendo 92 󰁠󰁤󰁩󰁶󰁠 10, quedando quedando de esta es ta 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 ejemp e jemplo, lo, en C, usas los paréntesis para llamar llamar a las funciones como 󰁦󰁯󰁯(), 󰁢󰁡󰁲(1), o 󰁢󰁡󰁺(3, "󰁪󰁡󰁪󰁡") . Como hemos hemos dicho, los espacios son usados para la aplicación de funciones en Haskell Haskell.. Así que estas funciones en Haskell serían 󰁦󰁯󰁯, 󰁢󰁡󰁲 1 y 󰁢󰁡󰁺 3 "󰁪󰁡󰁪󰁡". Si ves algo como 󰁢󰁡󰁲 (󰁢󰁡󰁲 3) no significa que 󰁢󰁡󰁲 es llamado con 󰁢󰁡󰁲 y 3 como parámetros. Significa que prim primero ero llamamos llamamos a la función 󰁢󰁡󰁲 con 3 como parámetro para obtener o btener un número número y luego volver a llamar 󰁢󰁡󰁲 otra vez con ese número. En C, esto sería algo como 󰁢󰁡󰁲(󰁢󰁡󰁲(3)) .

Las primeras pequeñ pequeñas as funcion funciones es En la sección anterior obtuvimos una idea básica de como llamar a las funciones ¡Ahora vamos a intentar hacer las nuestras! Ab Abre re tu editor de textos favorito fa vorito y pega esta función que toma un núm número ero y lo multipl multiplica ica por dos. doubleMe doubleMe x  x = x + x

Las funciones f unciones son definidas de forma f orma similar a como son llamad llamadas. as. El nombre de la función es se seguido guido 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 󰁢󰁡󰁢󰁹.󰁨󰁳 o como tú quieras. Ahora navega hasta donde lo guardaste y ejecuta 󰁧󰁨󰁣󰁩 desde ahí. a hí. Una Una vez ve z dentro de GHCi, escribe :󰁬 󰁢󰁡󰁢󰁹. Ahora que nuestro código está es tá cargado, ca rgado, podemos podemos jugar con la función que hemos definido.

3 de 15  

ghci> :l baby [1 of 1] Compiling Main Ok, modules loaded: Main. ghci> doubleMe 9 18 ghci> doubleMe 8.3 16.6

( baby.hs, interprete d )

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

Simple. Simp le. La podríamos haber definido también como 󰁤󰁯󰁵󰁢󰁬󰁥󰁕󰁳  󰁸 󰁹 = 󰁸 + 󰁸 + 󰁹 + 󰁹. Ambas Ambas formas producen resultados muy predecibles (recuerda añadir esta función en el fichero 󰁢󰁡󰁢󰁹.󰁨󰁳, guardarlo y luego ejecutar :󰁬 󰁢󰁡󰁢󰁹 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 llamar tus propias propia s funciones dentro de las funciones f unciones que hagas. Teniendo Teniendo esto en cuenta, podríamos redefinir 󰁤󰁯󰁵󰁢󰁬󰁥󰁕󰁳  como: doubleUs x doubleUs  x y = doubleMe x + doubleMe y

Esto es e s un simple ejemplo ejemplo de un patrón normal que que ve verás rás por todo Haskell. Crear funciones pequeñas que son obvia obviamen mente te correctas y luego combinarlas en funciones más complejas. De esta forma también evitarás repetirte. ¿Qué pasa si algunos matemáticos matem áticos descubren que 2 es en e n realidad 3 y tienes ti enes que cambiar tu programa? Puedes simplem simplemente ente redefinir 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥 para que sea 󰁸 + 󰁸 + 󰁸 y como 󰁤󰁯󰁵󰁢󰁬󰁥󰁕󰁳  llama a 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥  automáticamente funcionara en este extraño mundo mundo en el que 2 es 3. 3. Las funciones f unciones en Haskell no no tienen que esta estarr en ningún orden en particular, particular, así que no importa si defines antes 󰁤󰁯󰁵󰁢󰁬󰁥󰁍󰁥  y luego 󰁤󰁯󰁵󰁢󰁬󰁥󰁕󰁳  o si lo haces al revés. Ahora vamos a crear cre ar una función que multipliq multiplique ue un número 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 doubleSmallNumber x if x  x > 100   then x then  x   else x else  x* *2

Acabamos de introducir la sentencia 󰁩󰁦 de Haskell. Probablemente Probablemente ya estés familiarizado familiarizado con co n la sentencia 󰁩󰁦 de otros lenguajes. La diferencia entre la sentencia 󰁩󰁦 de Haskell y la de los lenguajes imperativos es que la parte 󰁥󰁬󰁳󰁥 es obligatoria. o bligatoria. En los lenguajes imperativ imperativos os 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. v alor. También También podríamos podríamos haber definido la sentencia 󰁩󰁦 en una sola línea pero así parece un poco mas legible. Otro asunto acerca de la sentencia 󰁩󰁦 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

4 de 15  

devuelve 5, 4 + 8 es una expresión, 󰁸 + 󰁹 es una expresión porque devuelve la suma de 󰁸 e 󰁹. Como la parte 󰁥󰁬󰁳󰁥 es obligatoria, una sentencia 󰁩󰁦 siempre devolverá algo y por tanto es una expresión. Si queremos queremos sum s umar ar uno a cada número que es producido por la función anterior, podemos escribir su cuerpo así. doubleSmallNumber' x  x = (  (if if x  x > 100 100   then then x  x else else x  x* *2 ) +  1 doubleSmallNumber'

Si hubiésemos omitido los paréntesis, sólo hubiera sumado uno si 󰁸 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 versió n modificada de una función o varia variable. ble. Como ' es un carácter válido para la funciones, podemos hacer cosas como esta. conanO'Brien  conanO'Brien  = "¡Soy yo, Conan O'Brien!"

Hay dos cosas cosa s que nos quedan por destacar. La primera primera es e s que el nombre nombre de esta es ta función no empieza con mayúsculas. mayúsculas. Esto se debe a que las funciones no pueden empez empezar ar con c on una letra en mayúsculas. Veremos el porqué un poco más tar tarde. de. La segunda es que esta función no toma to ma ningún ningún parámetro, normalmente normalmente lo llamamos llamamos una definición definició n (o un nombre). nombre). Como Co mo no podemos cambiar las definiciones (y las funciones) después de que las hayamos definido, 󰁣󰁯󰁮󰁡󰁮󰁏'B󰁲󰁩󰁥󰁮  y la cadena "󰁉󰁴'󰁳 utilizar indistintamente. 󰁡‐󰁭󰁥,  󰁃󰁯󰁮󰁡󰁮  󰁏'B󰁲󰁩󰁥󰁮!"  se pueden utilizar

Una introducción a las listas Al igual igual que las listas de la compra de la vida real, las listas en Haskell son muy muy útiles. Es la estructura de datos más utilizada y pueden ser utilizadas utilizadas de diferentes difer entes formas para modelar modelar y resolver un montón montón de problemas. problemas. Las La s listas son MUY importantes. importantes. En esta sección 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 dato s 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 󰁬󰁥󰁴 para definir un nombre nombre en GHCi. GHCi. Hacer 󰁬󰁥󰁴 󰁡 = 1 dentro de GHCi es equivalente ha escribir 󰁡 = 1 en un fichero y luego cargarlo.

ghci> let lostNumbers = [4,8,15,16,23,4 [4,8,15,16,23,42] 2] 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,'󰁡',3,'󰁢','󰁣',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. "󰁨󰁥󰁬󰁬󰁯"  es solo una alternativa sintáctica de 󰁛'󰁨','󰁥','󰁬','󰁬','󰁯'󰁝 . 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 ++.

5 de 15  

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 utilizamos el operador ++ repetidas veces sobre cadenas largas. Cuando concatenamos dos listas (incluso si añadimos una lista de un elemento elemento a otra o tra lista, por ejemplo ejemplo 󰁛1,2,3󰁝  ++ 󰁛4󰁝, internamente, Haskell tiene que recorrer la lista entera desde la parte izquierda del operador ++. Esto no supone ningún problem problemaa cuando trabajam tra bajamos os con c on listas que no son demasiado grandes. Pero concatenar c oncatenar algo al final de una lista que tiene cincuenta millones de elementos llllevará evará 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 número y una lista de números o un carácter carác ter y una lista de caracteres cara cteres,, mientras que ++ toma dos listas. Incluso si añades un elemento al final de las lista con c on ++, hay que rodearlo con corchetes para que se convierte en una lista de un solo elemento.

ghci> [1,2] ++ 3 :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 sa biendo su índice, utilizamos utilizamos !!. Los índices empiezan por 0. ghci> "Steve Buscemi" !! 6 'B' ghci> [9.4,33.2,96.2,11.2,23.25] [9.4,33.2,96.2,11.2,23.25] !! 1 33.2

Pero si s i intentamos obtener el sexto elemento de una lista que solo tiene ccuatro uatro elementos, obtendremos un error, así que hay que ir con cuidado. Las listas lista s también pueden contener listas. Estas también pueden pueden contener a su s u vez listas que contengan listas, que contengan listas... ghci> let b = [[1,2,3,4] [[1,2,3,4],[5,3,3,3],[1,2 ,[5,3,3,3],[1,2,2,3,4],[1,2,3] ,2,3,4],[1,2,3]] ] ghci> b

6 de 15  

[[1,2,3,4],[5,3,3,3],[1, 5,3,3,3],[1,2,2,3,4],[1,2,3 2,2,3,4],[1,2,3]] ]] [[1,2,3,4],[ ghci> b ++ [[1,1,1,1]] [[1,2,3,4],[5,3,3,3],[1, [[1,2,3,4],[ 5,3,3,3],[1,2,2,3,4],[1,2,3 2,2,3,4],[1,2,3],[1,1,1,1]] ],[1,1,1,1]] ghci> [6,6,6]:b [[6,6,6],[1,2,3,4],[5,3, [[6,6,6],[1, 2,3,4],[5,3,3,3],[1,2,2,3,4 3,3],[1,2,2,3,4],[1,2,3]] ],[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 lista s pueden ser comp co mparadas aradas si los elementos que contienen pueden ser comp c omparados. arados. Cuando usamos = 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. 󰁨󰁥󰁡󰁤  toma una lista y devuelve su cabeza. La ca cabez bezaa de una lista es e s básicamen básica mente te el primer elemento.

ghci> head [5,4,3,2,1] 5

󰁴󰁡󰁩󰁬  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]

último elemento. 󰁬󰁡󰁳󰁴  toma una lista y devuelve su último ghci> last [5,4,3,2,1] 1

󰁩󰁮󰁩󰁴  toma una lista y devuelve toda la lista e excepto xcepto su último elemento. elemento.

ghci> init [5,4,3,2,1] [5,4,3,2]

Si imaginamos imaginamos las listas lista s como monstruos, serian seria n algo como:

7 de 15  

¿Pero que pasa si intentamos obtener la cabeza de una lista vacía? ghci> head [] *** Exception: Prelude.head: empty list

¡Oh, lo hemos hemos roto! ro to! Si no hay monstruo, no hay cabeza. Cuando usamos 󰁨󰁥󰁡󰁤, 󰁴󰁡󰁩󰁬, 󰁬󰁡󰁳󰁴 e 󰁩󰁮󰁩󰁴 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. o bviamente devuelve su tamaño. 󰁬󰁥󰁮󰁧󰁴󰁨  toma una lista y obviamente ghci> length [5,4,3,2,1] 5

󰁮󰁵󰁬󰁬  comprueba si una lista está vacía. Si lo está, devuelve 󰁔󰁲󰁵󰁥 , en caso contrario devuelve 󰁆󰁡󰁬󰁳󰁥 . Usa esta

función en lugar de 󰁸󰁳 == 󰁛󰁝 (si tienes ti enes una lista que se llam llamee xs). ghci> null [1,2,3] False ghci> null [] True

󰁲󰁥󰁶󰁥󰁲󰁳󰁥  pone del revés una lista.

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

󰁴󰁡󰁫󰁥  toma un número 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, ssimp implemen lemente te devuelve la lista. Si tomamos tomamos 0 elementos, obtenemos una lista vacía. 󰁤󰁲󰁯󰁰  funciona de forma f orma similar, solo que quita un número número de elementos del comienzo 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] []

cos as que se pueden poner poner en algún tipo de orden y devuelve el elemento más más grande. 󰁭󰁡󰁸󰁩󰁭󰁵󰁭  toma una lista de cosas

8 de 15  

󰁭󰁩󰁮󰁩󰁭󰁵󰁭  devuelve el e l más pequeño.

ghci> minimum [8,4,2,1,5,6] 1 ghci> maximum [1,9,2,3,4] 9

󰁳󰁵󰁭  toma una lista de números y devuelve su suma. 󰁰󰁲󰁯󰁤󰁵󰁣󰁴  toma una lista de números y devuelve su producto.

ghci> sum [5,2,1,6,3, [5,2,1,6,3,2,5,7] 2,5,7] 31 ghci> product [6,2,1,2] 24 ghci> product [1,2,5,6,7,9,2 [1,2,5,6,7,9,2,0] ,0] 0

󰁥󰁬󰁥󰁭  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 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.

Texas 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 lenguajes de programación. En E n su lugar lugar,, usaremos rangos. ra ngos. Los rangos son so n una manera de crear listas lista s que contengan una secuencia aaritmética ritmética de elementos enumerables. Los números pueden ser enumerados. Uno, dos, tres, cuatro, etc. Los caracteres también pueden ser enumerados. enumerados. El alfabeto es una enumeración de caractere car acteress desde la A hasta la Z. Los nombres nombres no son s on enumerables. enumerables. ¿Qué viene vi ene después de “Juan”? Ni idea. Para crear una lista que contenga todos los números naturales desde el 1 hasta el 20 simplemente simpl emente escribimos e scribimos 󰁛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 [1,2,3,4,5,6 ,7,8,9,10,11,12,13,14,15,16 ,12,13,14,15,16,17,18,19,20] ,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?

9 de 15  

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 cuestió n de separar los primeros dos elementos con una coma y luego espec especificar ificar el límite límite superior. superio r. Aunq Aunque ue 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.89999 [0.1,0.3,0.5 ,0.7,0.8999999999999999,1.0 99999999999,1.0999999999999999 999999999999999] ]

Mi consejo es e s no utilizar rangos con números números en coma flotante. También podemos podemos utilizar los rangos para crear cre ar listas lista s infinitas simplemente simplemente no indicando un límite límite superior. superio r. 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: mejor: 󰁴󰁡󰁫󰁥 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: 󰁣󰁹󰁣󰁬󰁥  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 cor tarlo 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 "

󰁲󰁥󰁰󰁥󰁡󰁴  toma un elemento elemento y produce una lista infinita que contiene eese se único eelem lemento ento repetido. Es como hacer un

ciclo de una lista con c on un solo elemento. ghci> take 10 (repeat 5) [5,5,5,5,5,5,5,5,5,5]

Au Aunqu nquee aaquí quí sería más simple usar la función 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥 , ya que sabemos el núm número ero de elementos de antemano. 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  3 10 devuelve 󰁛10,10,10󰁝 .

Soy una lista intensional Si alguna vez tuviste clases c lases de matemáticas matemáticas,, probablemente probablemente viste vi ste algú a lgúnn conjunto definido de forma intensiva, definido a partir de otros otro s conju co njuntos ntos más generales. Un conjunto conjunto definido de forma intensiva que contenga los diez die z pprimeros rimeros números números naturales pares sería se ría

10 de 15  

. 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 número naturales que cumplen cumplen el predicado. Si quisiéramos escribir esto en Haskell, podríamos usar algo como 󰁴󰁡󰁫󰁥 10 󰁛2,4..󰁝. Pero, ¿y si no quisiéramos los dobles de los diez primeros primeros número número naturales, sino algo más complejo? complejo? Para ello podemos podemos utilizar listas intensionales. intensio nales. 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 󰁛󰁸*2 󰁼 󰁸  [x*2 | x [x*2 | x = 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 :t head head :: [a] -> a

Hmmm... ¿Qué es 󰁡? ¿Es un tipo? Si recuerdas rec uerdas antes dijimos que los tipos deben comenzar con mayúsculas, mayúscul as, así a sí que no puede ser se r exactamente exacta mente un tipo. Como no comienza comienza con co n una mayúscula mayúscula eenn realidad es una variable de tipo. Esto significa que 󰁡 puede ser cualquier tipo. Es parecido a los tipos genéricos de otros lenguajes, solo que en Haskell son mu mucho cho más potentes ya que nos permite definir fácilmente funciones muy muy generales siempre s iempre que no hagamos hagamos ningún uso uso especifico es pecifico del tipo en cuestión. Las funciones que tienen variables de tipos son llamadas funciones polimórficas polimórficas. La declaración de tipo 󰁨󰁥󰁡󰁤 representa una función que toma toma una lista de cualquier cualquier tipo y devuelve un elemento elemento de ese es e mismo tipo. Aunque Aun que las variables var iables de tipo ti po pueden tener nombres más largos de un solo carácter, caráct er, normalmen normalmente te les damos nombres nombres como a, b, c, d, etc. ¿Recuerdas 󰁦󰁳󰁴 ? Devuelve el primer componente componente de una dupla. dupla. Vamos Vamos a examinar su tipo. ti po. ghci> :t fst fst :: (a, b) -> a

Como vemos, 󰁦󰁳󰁴  toma una dupla dupla que contiene co ntiene dos tipos t ipos y devuelve un elemento del mismo mismo tipo que el primer componente componente de la dupla. dupla. Ese es el porqué de que podamos usar 󰁦󰁳󰁴  con duplas que contengan cualquier combinación de tipos. tipos . T Ten en en cuenta que solo porque 󰁡 y 󰁢 son diferentes variables de tipo no tienen porque ser diferentes tipos. Simplemente representa que el primer componente componente y el valor que devuelve la función son s on del mismo 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 implementa el comportamiento que define la clase de titipos. pos. 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 lenguajes orientados ori entados a objetos. Bien, pues no lo son. s on. Un Unaa aproximación más adecuada sería pensar que son como las interfaces de Java, o los

3 de 7  

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 querem os examinar su tipo, pasarla pasa rla a otra función o llamarla llamarla en forma prefija debemos rodearla con paréntesis. paréntesis . Por ejemplo: (+)  1 4 equivale a 1 + 4.

Interesante. Aquí Aquí vemos algo a lgo nuevo, el símbolo => . Cualquier Cualquier cosa co sa antes del símbolo símbolo => es una restricción de clase. Podemos leer la declaración declaració n de tipo anterior anterio r como: la función de igualdad toma dos parámetros que son del mism mismoo tipo y devuelve un 󰁂󰁯󰁯󰁬. El tipo de estos dos parámetros debe ser miembro de la clase 󰁅󰁱  (esto es la restricción de clase). clase). La clase de tipos 󰁅󰁱 proporciona una interfaz para las comparaciones de igualdad. igualdad. Cualquier Cualquier tipo que tenga sentido comparar dos valores de ese tipo por igualdad debe ser miembro de la clase 󰁅󰁱. T Todos odos los tipos ti pos estándar está ndar de Haskell excepto el tipo IO (un tipo para manejar la entrada/salida) y las funciones forman parte de la clase clas e 󰁅󰁱 . La función 󰁥󰁬󰁥󰁭 tiene el tipo (󰁅󰁱  󰁡) => 󰁡 ‐>  󰁛󰁡󰁝  ‐> 󰁂󰁯󰁯󰁬 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: miembros de esta es ta clase implementan implementan las las 󰁅󰁱  es utilizada por los tipos que soportan comparaciones por igualdad. Los miembros funciones == o /=  en algún lugar lugar de su definición. Todos los tipos que mencionamos mencionamos anteriormente a nteriormente forman parte de la clase 󰁅󰁱  exceptuando las funciones, así que podemos podemos realiz rea lizar ar comparaciones comparacio nes 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

󰁏󰁲󰁤  es para tipos que poseen algún orden.

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

Todos los tipos ti pos que hemos llegado llegado a ver v er excepto las funciones son parte de la clase 󰁏󰁲󰁤 . 󰁏󰁲󰁤  cubre todas las funciones de comparación como >, =  y True ghci> LT ghci> True ghci> GT

"Abrakadabra" < "Zebra" "Abrakadabra" `compare` "Zebra" 5 >= 2 5 `compare` 3

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

󰁒󰁥󰁡󰁤  es como la clase de tipos opuesta a 󰁓󰁨󰁯󰁷. La función 󰁲󰁥󰁡󰁤  toma una cadena y devuelve un valor del tipo

que es miembro de 󰁒󰁥󰁡󰁤. 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, más, todo los tipos que hemos visto excepto las funciones forman parte de esta clase de tipos. Pero, ¿Qué pasa si simplemente usamos 󰁲󰁥󰁡󰁤 "4" ? ghci> read "4" :1:0:      

Ambiguous type variable `a' in the constraint: `Read a' arising from a use of `read' at :1:0-7 Probable fix: add a type signature that fixes these type variable(s)

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

¿V ¿Ves? es? Devuelve un tipo que eess miembro miembro de la clase 󰁒󰁥󰁡󰁤, pero si luego no lo usamos en ningún otro lugar, 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 L o hacemos añadiendo ::  al final de la expresión y luego especificando el tipo. Observa:

5 de 7  

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 󰁲󰁥󰁡󰁤 "5" , que podría ser 󰁉󰁮󰁴 , 󰁄󰁯󰁵󰁢󰁬󰁥 , etc. Para saberlo, Haskell debe debe en realidad rea lidad evaluar 󰁲󰁥󰁡󰁤 "5" . Pero como c omo Haskell Haskell es un lenguaje lenguaje con tipos estáticos, estáti cos, 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 󰁅󰁮󰁵󰁭  son tipos secuencialmente ordenados, es decir, pueden ser enumerados. La principal ventaja de la clase de tipos 󰁅󰁮󰁵󰁭 es que podemos podemos usar los miembros miembros en e n las listas aritméticas. También ambién tienen definidos los sucesores y predecesores, por lo que podemos usar las funciones 󰁳󰁵󰁣󰁣 y 󰁰󰁲󰁥󰁤. Los tipos de esta clase son: () , 󰁂󰁯󰁯󰁬, 󰁃󰁨󰁡󰁲, 󰁏󰁲󰁤󰁥󰁲󰁩󰁮󰁧 , 󰁉󰁮󰁴 , 󰁉󰁮󰁴󰁥󰁧󰁥󰁲 , 󰁆󰁬󰁯󰁡󰁴 y 󰁄󰁯󰁵󰁢󰁬󰁥 . ghci> ['a'..'e'] "abcde" ghci> [LT .. GT] [LT,EQ,GT] ghci> [3 .. 5] [3,4,5] ghci> succ 'B' 'C'

Los miembros de 󰁂󰁯󰁵󰁮󰁤󰁥󰁤  poseen límites inferiores y superiores, es decir están acotados. ghci> minBound -2147483648 ghci> maxBound '\1114111' ghci> maxBound True ghci> minBound

:: Int :: Char :: Bool :: Bool

False

󰁭󰁩󰁮󰁂󰁯󰁵󰁮󰁤  y 󰁭󰁡󰁸󰁂󰁯󰁵󰁮󰁤  son

interesantes ya que tienen el tipo (󰁂󰁯󰁵󰁮󰁤󰁥󰁤  󰁡) => 󰁡. Es decir, son constantes

polimórficas. Todas las tuplas son también 󰁂󰁯󰁵󰁮󰁤󰁥󰁤  si sus comp c omponentes onentes los son s on también. ghci> maxBound :: (Bool, Int, Char) (True,2147483647,'\1114111')

c lase de tipos numéricos numéricos.. Sus miembros miembros tienen tie nen la propiedad de poder comportarse como núm números. eros. 󰁎󰁵󰁭  es la clase Vamos a examinar el tipo de un número. ghci> :t 20 20 :: (Num t) => t

6 de 7  

Parece que todos los números son también constantes polimórficas. Pueden actuar como si fueran cualquier tipo de la clase 󰁎󰁵󰁭 . ghci> 20 ghci> 20 ghci> 20.0 ghci> 20.0

20 :: Int 20 :: Integer 20 :: Float 20 :: Double

Estos son los tipo estándar de la clase 󰁎󰁵󰁭 . 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  ::  󰁉󰁮󰁴) * (6 :: 󰁉󰁮󰁴󰁥󰁧󰁥󰁲)  lanz  lanzará ará 󰁉󰁮󰁴󰁥󰁲󰁧󰁥󰁲 ,

un error mientras que 5 * (6  ::  󰁉󰁮󰁴󰁥󰁧󰁥󰁲)  funcionará correctamente y producirá un

ya que 5 puede actuar como co mo un 󰁉󰁮󰁴󰁥󰁧󰁥󰁲  o un 󰁉󰁮󰁴 .

Para unirse a 󰁎󰁵󰁭 , un tipo debe ser amigo a migo de 󰁓󰁨󰁯󰁷 y 󰁅󰁱 . 󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬  es tamb ta mbién ién un clase de tipos num numérica. érica. 󰁎󰁵󰁭  incluye todos los números, números, incluyendo números números reales y

enteros. 󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬  únicamente incluye números números enteros. 󰁉󰁮󰁴  e 󰁉󰁮󰁴󰁥󰁧󰁥󰁲  son miembros de esta clase. 󰁆󰁬󰁯󰁡󰁴󰁩󰁮󰁧  incluye únicamente números en coma flotante, es decir 󰁆󰁬󰁯󰁡󰁴  y 󰁄󰁯󰁵󰁢󰁬󰁥 .

Una función función muy útil para trabajar con números números es 󰁦󰁲󰁯󰁭󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬 . Tiene el tipo 󰁦󰁲󰁯󰁭󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬  :: (󰁎󰁵󰁭 󰁢,  󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬 󰁡) =>  󰁡 ‐>  󰁢. A partir de esta

declaración podemos decir que toma un núm número ero entero y lo convierte convie rte en un número número más

general. Esto es útil cuando estas trabajando tra bajando con núm números eros reales re ales y enteros e nteros al mismo tiempo. tiempo. Por eejemp jemplo, lo, la función 󰁬󰁥󰁮󰁧󰁴󰁨 tiene el tipo 󰁬󰁥󰁮󰁧󰁴󰁨  :: 󰁛󰁡󰁝  ‐>  󰁉󰁮󰁴  en vez de tener un tipo más general como (󰁎󰁵󰁭 󰁢)  => 󰁬󰁥󰁮󰁧󰁴󰁨  :: 󰁛󰁡󰁝  ‐> 󰁢. Creo que es por razones históricas histór icas o algo parecido, en mi opinión, es absurdo. De cualquier modo, si queremos obtener el tamaño de una lista y sumarle sumarle 3.2 , obtendremos un error al intentar sumar un entero con uno en coma flotante. Para solucionar esto, hacemos 󰁦󰁲󰁯󰁭󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬  (󰁬󰁥󰁮󰁧󰁴󰁨  󰁛1,2,3,4󰁝)  + 3.2 . Fíjate que en la declaración de tipo de 󰁦󰁲󰁯󰁭󰁉󰁮󰁴󰁥󰁧󰁲󰁡󰁬  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.

siguiente

anterior |

Índice »

7 de 7  

siguiente

anterior |

Índice »

Ajuste de patrones

En este capítul ca pítuloo cubriremos algunas de las construcciones construcci ones sintácticas sintác ticas de Haskell más más

interesantes, empezando empezando con el e l ajuste de patrones (“patt pattern ern matching ” en ingl i nglés). é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 fáci l de leer. Podemos usar el e l 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 :: lucky  :: (  (Integral Integral a)  a) => => a  a -> ->   String lucky 7 = "¡El siete de la suerte!" lucky  lucky x lucky  x = "Lo siento, ¡no es tu día de suerte!"

Cuando llamamos a 󰁬󰁵󰁣󰁫󰁹, 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 󰁸. También se podría haber implementado utilizando una sentencia 󰁩󰁦. Pero, ¿qué pasaría si quisiéramos una función que nombrara nomb rara los número número del 1 al a l 5, o "󰁎󰁯 󰁥󰁮󰁴󰁲󰁥 󰁵󰁮󰁯 1 󰁹 5" para cualquier otro número? Si no tuviéramos el ajuste de patrones deberíamos crear un enrevesado árbol 󰁩󰁦 󰁴󰁨󰁥󰁮 󰁥󰁬󰁳󰁥. Sin embargo con él: sayMe :: sayMe  :: (  (Integral Integral a)  a) => => a  a -> ->   String sayMe  sayMe  1 = "¡Uno!" sayMe  sayMe  2 = "¡Dos!" sayMe 3 = "¡Tres!" sayMe  sayMe  sayMe  4 = "¡Cuatro!" sayMe  sayMe  5 = "¡Cinco!" sayMe x sayMe  x = "No entre uno 1 y 5"

Ten en cuenta que si movemos el último patrón (el más general) aall inicio, ssiempre iempre obtendríamos obtendríamos "󰁎󰁯 󰁥󰁮󰁴󰁲󰁥 󰁵󰁮󰁯 1 󰁹 5" como respuesta, ya y a que el primer patrón encajaría con co n cualquier cualquier número y no habría posibilidad de que se ccompr omprobaran obaran los demás patrones. ¿Recuerdas la función factorial que creamos anteriormente? Definimos el factorial de un número 󰁮 como 󰁰󰁲󰁯󰁤󰁵󰁣󰁴 󰁛1..󰁮󰁝. También ambién podemos implementar implementar una función factorial factori al recursiv recursiva, a, de forma fo rma 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 multiplicado por el factorial fac torial de su predecesor. predece sor. factorial  :: factorial  :: (  (Integral Integral a)  a) => => a  a -> a -> a factorial  factorial   0  =   1 factorial n factorial  n = n * factorial (n - 1)

1 de 10  

Esta es la primera primera vez v ez que que definimos una función recursiva. La recursividad re cursividad es muy muy importante en Haskell, Haskell, pero hablaremos de ello más adelante. Resumiendo, Resumiendo, esto es lo que pasa cuando intentamos obtener el factorial factor ial de, digamos 3. Primero intenta calcular 3 * 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  2. El factorial de 2 es 2 * 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  1, así que ahora tenemos 3 * (2 * 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  1). 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  1 es 1 * 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  0, lo que nos lleva a 3 * (2 * (1 * 󰁦󰁡󰁣󰁴󰁯󰁲󰁩󰁡󰁬  0)). Ahora viene el e l 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 patro nes también pueden fallar. fallar. Si definimos una función función co como mo esta: charName :: charName  ::   Char Char   -> ->   String charName  charName  'a' 'a'   = "Albert" charName  charName  'b' 'b'   = "Broseph" charName  charName  'c' '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 Non-exhaustive patterns in function charName

Se queja porque tenemos un ajuste ajuste de patro patrones nes no exhaustivo y ciertamente así es. Cuando utilizamos utilizamos patrones siemp si empre re tenemos que incluir uno uno general para as asegurarnos egurarnos que nuestro programa no fallará. El ajuste de patrones también pueden ser usado con tuplas. ¿Cómo crearíamos crear íamos una función que que tomara dos vectores vectore s 2D (representados con duplas) duplas) y que devolviera la sum s umaa de ambos? Para sumar dos vectores vectore s sumamos sumamos primero sus comp c omponentes onentes 󰁸 y sus componentes 󰁹 de forma separada. Así es como lo haríamos si no existiese el ajuste de patrones:

addVectors  :: addVectors  :: (  (Num Num a)  a) => (a, => (a, a) -> -> (a,  (a, a) -> -> (a,  (a, a) addVectors a addVectors  a b = (fst a + fst b, snd a + snd b)

Bien, funciona, pero hay mejores mejores formas de hacerlo. Vamos Vamos a modificar la función para que utilice un ajuste de patrones. addVectors  :: addVectors  :: (  (Num Num a)  a) => => (a,  (a, a) -> -> (a,  (a, a) -> -> (a,  (a, a) addVectors (x1, addVectors  (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

¡Ahí ¡Ahí lo tienes! Mucho mejor. mejor. Ten Ten en cuenta que es un patrón general, es e s decir, se s e verificará ve rificará para cualquier dupla. El tipo de 󰁡󰁤󰁤󰁖󰁥󰁣󰁴󰁯󰁲󰁳  es en ambos casos el mismo: 󰁡󰁤󰁤󰁖󰁥󰁣󰁴󰁯󰁲󰁳  󰀺󰀺 (󰁎󰁵󰁭 󰁡) 󰀽󰀾 (󰁡, 󰁡) ‐󰀾 (󰁡, 󰁡) ‐󰀾 (󰁡, 󰁡), por lo que está

garantizadoo que tendremos garantizad tendremos dos duplas como parámetros. pará metros. 󰁦󰁳󰁴 y 󰁳󰁮󰁤 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 :: first  :: (a,  (a, b, c) -> -> a  a first (x, first  (x, _   _  , _  ) = x second  :: second  :: (a,  (a, b, c) -> -> b  b second ( second  (  _   _  , y, _  y,  _  ) = y

2 de 10  

third   :: :: (a,  (a, b, c) -> -> c  c third third ( 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 podemos utilizar ajuste de patrones patro nes con las listas intensionales. Fíjate: Fíjate : ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)] ghci> [a+b | (a,b) head' [4,5,6] 4 ghci> head' "Hello" 'H'

¡Bien! Fíjate que si queremos ligar varias varia s variables vari ables (incluso aunque alguna alguna de ellas sea 󰁟 y rrealmen ealmente te no la queremos ligar) debemos debem os rodearlas rodea rlas con paréntesis. paréntesi s. Fíjate tamb ta mbién ién en la función 󰁥󰁲󰁲󰁯󰁲 que acabamos de utilizar. utilizar. Ésta toma to ma una 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 programa termine, lo cual no es bueno usar muy muy a menudo. De todas formas, llamar a 󰁨󰁥󰁡󰁤 con una lista vacía no tiene mucho sentido. Vamos a crear crea r una función que nos diga algunos algunos de los primeros elementos que contiene una lista.

3 de 10  

tell tell   :: :: (  (Show Show a)  a) => => [a]  [a] -> ->   String tell  tell  [] []    = "La lista está vacía" tell (x tell  (x:[] :[]) ) = "La lista tiene un elemento: "  "  ++ ++ show  show x tell (x tell  (x: :y:[] :[]) ) = "La lista tiene dos elementos: "  "  ++ ++ show  show x ++ ++   " y "  "  ++ ++ show  show y tell (x tell  (x: :y:_  ) = "La lista es larga. Los primeros dos elementos son: "  "  ++ ++ show  show x ++ ++   "

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 escri escribir bir (󰁸󰀺󰁛󰁝) y (󰁸󰀺󰁹󰀺󰁛󰁝)  como 󰁛󰁸󰁝 y 󰁛󰁸,󰁹󰁝 sin usar paréntesis. Pero no podemos escribir (󰁸󰀺󰁹󰀺󰁟)  usando corchetes ya que acepta listas con más de dos elementos. Ya implementamos la función 󰁬󰁥󰁮󰁧󰁴󰁨 usando listas intensionales. intensio nales. Ahora vamos a implementarla implementarla con una pizca de recursión. length' :: length'  :: (  (Num Num b)  b) => => [a]  [a] -> -> b  b length'  length'  [] []    =  0 length' ( length'  (  _: xs) = 1 + length' xs  _: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 longitud es 1 más el e l tamaño 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 󰁬󰁥󰁮󰁧󰁴󰁨' con "󰁯󰁪󰁯". 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 + 󰁬󰁥󰁮󰁧󰁴󰁨' "󰁪󰁯", ya que hemos divido la cadena en cabeza y cola, decapitando la lista. Vale. El tamaño de "󰁪󰁯" es, de forma similar, similar, 1 + 󰁬󰁥󰁮󰁧󰁴󰁨' "󰁯". Así que ahora mismo tenemos 1 + (1 + 󰁬󰁥󰁮󰁧󰁴󰁨'  "󰁯"). 󰁬󰁥󰁮󰁧󰁴󰁨'  "󰁯" es 1 + 󰁬󰁥󰁮󰁧󰁴󰁨' "" (también lo podríamos escribir como 1 + 󰁬󰁥󰁮󰁧󰁴󰁨'  󰁛󰁝). Y como tenemos definido 󰁬󰁥󰁮󰁧󰁴󰁨'  󰁛󰁝 a 0, al final tenemos 1 + (1 + (1 + 0)) .

Ahora implementaremos 󰁳󰁵󰁭. Sabemos que la suma de una lista vacía es 0, lo cual escribimos con un patrón. También También sabemos que la suma suma de una lista es e s la cabeza más la suma del resto de la co cola, la, y si lo escribimos obtenemos: sum'  :: sum'  :: (  (Num Num a)  a) => => [a]  [a] -> -> a  a sum'  sum'  [] []   = 0 sum' (x sum'  (x: :xs) = x + sum' xs

También existen los llamados patrones como , o patrones as  (del  (del inglés, as patt patterns  erns ). ). Son útiles para descompon des componer er algo usando un patrón, de forma que que se ligue con las variables varia bles que queramos queramos y además a demás podamos podamos mantener una referencia a ese algo como un todo. Para ello ponemos ponemos un 󰁀 delante del patrón. La mejor mejor forma for ma de entenderlo es con un ejemploo:: 󰁸󰁳󰁀(󰁸󰀺󰁹󰀺󰁹󰁳) . Este patrón se ajustará exactamente a lo mismo que lo haría 󰁸󰀺󰁹󰀺󰁹󰁳 pero además podríamos acceder fácilmente a la lista completa compl eta usando 󰁸󰁳 en lugar de tener que repetirnos escribiendo 󰁸󰀺󰁹󰀺󰁹󰁳 en el cuerpo c uerpo de la función. Un ejemplo ejemplo rápido: capital :: capital  ::   String String   -> ->   String capital  capital  "" ""   = "¡Una cadena vacía!" capital all capital  all@ @(x (x:_  :_  ) = "La primera letra de "  "  ++ ++ all  all ++ ++   " es "  "  ++ ++ [x]  [x]

ghci> capital "Dracula" "La primera letra de Dracula es D"

Normalmente usamos los patrones como  para  para evitar repetirnos cuando estamos ajustando un patrón más grande y tenemos que usarlo entero otra vez en algún lugar lugar del cuerpo de la función.

4 de 10  

Una cosa más, no podemos usar ++ en los ajustes de patrones. Si intentamos usar un patrón (󰁸󰁳 ++ 󰁹󰁳), ¿qué habría en la primera lista y qué en la segund se gunda? a? No tiene tie ne mucho sentido. Tendría Tendría más sentido ajustar a justar patrones como (󰁸󰁳 ++ 󰁛󰁸,󰁹,󰁺󰁝)  o simplemente (󰁸󰁳 ++ 󰁛󰁸󰁝) 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 titiene ene una determinada forma y deconstruirlo, las guardas son una forma de comp co mprobar robar si alguna alguna propiedad de una valor (o varios de ellos) es cierta o falsa. Suena muy parecido a una sentencia 󰁩󰁦 y de hecho es e s muy similar. similar. La cuestión cues tión es que las guardas son mucho mucho más legibles cuando tienes varias va rias condiciones co ndiciones y encajan e ncajan muy muy bien con los patrones. En lugar lugar de explicar su sintaxis, s intaxis, simplemente simplemente vamos a crear una función que que utilice guardas. Crearemos Crea remos una IMC (índice  (índice de masa corporal). Tu IMC es función simple simple que te regañará de forma diferente en función de tu IMC 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 es tamos calculando nada ahora, simplem simplemente ente obtiene un IMC y te regaña) bmiTell  bmiTell :: :: (  (RealFloat RealFloat a)  a) => => a  a -> ->   String bmiTell bmi bmiTell  bmi   | bmi    String bmiTell weight bmiTell  weight height   | weight / height ^ 2  -> a  a max' a max'  a b   | a > b = a   | otherwise = b

Las guardas tamb ta mbién ién pueden ser escritas esc ritas en e n una sola línea, aunque advierto que es mejor mejor no hacerlo hacer lo ya que son mucho menos legibles, incluso con funciones cortas. Pero para demostrarlo podemos definir 󰁭󰁡󰁸' como: max'  :: max'  :: (  (Ord Ord a)  a) => => a  a -> -> a  a -> -> a  a max' a max'  a b | a > b = a | otherwise = b

¡Arg! ¡Arg! No se lee fácilm fác ilmente. ente. Sigamos adelante. a delante. Vamos Vamos a implementar implementar nuestro propio 󰁣󰁯󰁭󰁰󰁡󰁲󰁥  usando guardas. myCompare  :: myCompare  :: (  (Ord Ord a)  a) => => a  a -> -> a  a -> ->   Ordering a `myCompare` b   | a > b = GT   | a == == b  b = EQ   | otherwise = LT

ghci> 3 `myCompare` 2 GT

Nota

No solo podemos llamar llamar a funciones de forma fo rma infija usando las comil co millas, las, sino que también también podemos definirlas de esta forma. A veces es más fácil leerlo así.

¿Dónde? En la sección secci ón anterior definimos la función que que calculaba el IMC así: bmiTell  :: (  (RealFloat RealFloat a)  a) => => a  a -> -> a  a -> ->   String bmiTell :: bmiTell weight bmiTell  weight height   | weight / height ^ 2  => [(a,  [(a, a)] -> -> [a]  [a] calcBmis xs calcBmis  xs = [bmi w h | (w, h)  a  a -> -> a  a -> -> a  a cylinder :: cylinder r cylinder  r h =   let sideArea let  sideArea = 2 * pi * r * h   topArea = pi * r ^2   in  in   sideArea + 2 * topArea

Su forma es 󰁬󰁥󰁴 󰀼󰁤󰁥󰁦󰁩󰁮󰁩󰁣󰁩󰃳󰁮󰀾  󰁩󰁮 󰀼󰁥󰁸󰁰󰁲󰁥󰁳󰁩󰃳󰁮󰀾 . Las variables que definamos en la expresión 󰁬󰁥󰁴 son accesibles en la parte 󰁩󰁮. Como podemos ver, ve r, también podríamos podríamos haber definido esto con una sección 󰁷󰁨󰁥󰁲󰁥. Fíjate también que los nombres están alineados en la misma columna. columna. Así que, ¿cuál es la difere diferencia ncia entre ellos? e llos? Por ahora parece que 󰁬󰁥󰁴 pone las definiciones definicio nes primero y luego la expresión que las utiliza m mientras ientras que 󰁷󰁨󰁥󰁲󰁥 lo hace en el orden inverso. La diferencia es que las expresiones 󰁬󰁥󰁴 son expresiones por si mismas. Las secciones 󰁷󰁨󰁥󰁲󰁥 son simplemente construcciones sintácticas. ¿Recuerdas cuando explicamos las sentencias 󰁩󰁦 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 puedes hacer lo mismo mismo ccon on las expresiones 󰁬󰁥󰁴. ghci> 4 * (let a = 9 in a + 1) + 2 42

También pueden pueden ser utilizadas utilizadas para definir funciones en un ámbito local: ghci> [let square x = x * x in (square 5, square 3, square 2)] [(25,9,4)]

8 de 10  

Si queremos ligar varias variables en e n una solo línea, obviamente no podemos alinear las definiciones definici ones en la misma columna. 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 (6000000,"Hey (6000000,"He y there!")

No tenemos porque poner el último punto punto y coma pero podemos hace hacerlo rlo si queremos. Como Co mo ya hemos dicho, podemos utilizar ajustes de patrones con las expresiones 󰁬󰁥󰁴. Son muy útiles para desmantelar tuplas tuplas en sus s us componentes componentes y ligarlos a varias variables. variables. ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100 600

También podemos podemos usar usa r las secciones secci ones 󰁬󰁥󰁴 dentro de las listas lista s intensionales. Vamos Vamos a reescribir reescri bir nuestro ejemplo ejemplo anterior anteri or que calculaba una lista de duplas de alturas y pesos para que use un 󰁬󰁥󰁴 dentro de una lista intensional intensio nal en lugar lugar de definir una función auxiliar con un 󰁷󰁨󰁥󰁲󰁥. calcBmis :: calcBmis  :: (  (RealFloat RealFloat a)  a) => => [(a,  [(a, a)] -> -> [a]  [a] calcBmis xs calcBmis  xs = [bmi | (w, h)  [(a,  [(a, a)] -> -> [a]  [a] calcBmis xs calcBmis  xs = [bmi | (w, h) =   25.0 25.0] ]

No podemos usar el nombre 󰁢󰁭󰁩 dentro de la parte (󰁷, 󰁨) 󰀼‐ 󰁸󰁳 ya que está definida antes que la expresión 󰁬󰁥󰁴. Omitimos Om itimos la parte 󰁩󰁮 de las secciones 󰁬󰁥󰁴 dentro de las lista intensionales porque la visibilidad de los nombres está predefinida en estos casos. Sin embargo, podemos usar una sección 󰁬󰁥󰁴 󰁩󰁮 en un predicado y las variables definidas solo serán visibles en este predicado. La parte 󰁩󰁮 también puede puede ser omitida cuando definimos definimos funciones y constantes dentro del intérprete 󰁇󰁈󰁃󰁩. 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 :1:0: :1:0: Not in scope: `boot'

Si las expresiones 󰁬󰁥󰁴 son tan interesantes, ¿por qué no usarlas siempre en lugar de las secciones 󰁷󰁨󰁥󰁲󰁥? Bueno, como las expresiones 󰁬󰁥󰁴 son expresiones y son bastante locales en su ámbito, no pueden ser usadas entre guardas. Hay gente que prefiere las secciones 󰁷󰁨󰁥󰁲󰁥 porque las variables varia bles vienen después de la función que los utiliza. De esta forma, el cuerpo de la función esta más cerca cer ca de su nombre y declaración declaraci ón de tipo y algunos algunos piensan que es más legible. legible.

Expresiones case Muchos lenguajes imperativos (como C, C++, Java, etc.) tienen construcciones sintácticas

9 de 10  

󰁣󰁡󰁳󰁥 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íficoss de esa variable y luego incluir específico incluir quizá quizá algún bloque bloque que siempre se ejecute en caso de que la variable tenga algún valor que no se ajuste a juste con ningun ningunoo de los anteriores. anter iores. Haskell toma este concepto c oncepto y lo lleva un paso más allá. Como Como su nombre indica las expresiones 󰁣󰁡󰁳󰁥 son, bueno, expresiones, como las expresiones 󰁩󰁦 󰁥󰁬󰁳󰁥 o las expresiones 󰁬󰁥󰁴. No solo podemos evaluar expresiones basándonos basá ndonos en los posibles valores valore s de un variable varia ble sino que podemos realizar un ajuste ajuste de patrones. Mmmm Mmmm... ... tomar un valor, realizar realizar un ajuste de patrones sobre él, evaluar ev aluar trozos de código basados basa dos 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 󰁣󰁡󰁳󰁥. Estos dos trozos de código có digo hacen lo mismo mismo y son s on intercambiables: head'   :: :: [a]  [a] -> -> a  a head' head'  head'  [] []   = error error   "¡head no funciona con listas vacías!" head' (x head'  (x:_  :_  ) = x

head' :: head'  :: [a]  [a] -> -> a  a head' xs head'  xs = case case xs  xs of  of [] []   -> ->   error error   "¡head no funciona con listas vacías!"   (x:_  (x:_  ) -> -> x  x

Como puedes ver la sintaxis para las expresiones 󰁣󰁡󰁳󰁥 es muy simple. case  expresion of patron case expresion of patron -> -> resultado  resultado   patron -> -> resultado  resultado   patron -> resultado -> 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 e s el que se utiliza. Si no se puede ajustar a ningún ppatrón atrón de la expresió expresiónn 󰁣󰁡󰁳󰁥 se lanzará un error de ejecución. Mientras que el ajuste de patrones patro nes de los parámetros de una función puede ser realizado únicamen únicamente te al definir una función, las expresiones 󰁣󰁡󰁳󰁥 pueden ser utilizadas casi en cualquier lugar. lugar. Por ejemplo: ejemplo:

describeList :: describeList  :: [a]  [a] -> ->   String describeList xs describeList  xs = "La lista es"  es" ++ ++   case case xs  xs of  of  [] []    -> ->   "una lista vacía."   [x] -> ->   "una lista unitaria."   xs ->  -> "una lista larga."

Son útiles para realizar re alizar un ajuste 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 󰁣󰁡󰁳󰁥, también podríamos podríamos utilizar algo como esto: es to: describeList :: describeList  :: [a]  [a] -> ->   String describeList xs describeList  xs = "The list is "  " ++ ++ what  what xs   where what where  what [] []   = "empty."   what [x] = "a singleton list."   what xs = "a longer list."

siguiente

anterior |

Índice »

10 de 10  

siguiente

anterior |

Índice »

¡Hola recursión! En el capítulo anterior ya mencionamos la recursión. En este capítulo veremos más detenidamente detenidamente este e ste tema, te ma, el porqué es importante en Haskell y como co mo podemos podemos crear soluciones a problemas de forma elegante y concisa. Si aún no sabes que es la recursión, lee esta frase: La recursión es en realidad una forma de definir funciones en la que dicha función es utiliza utiliza en la propia definición de la función. Las La s definicio definiciones nes matemáticas normalmente normalmente están está n definidas de forma recursiva. Por ejemplo, la serie de Fibonacci se define recursivamente. Primero, definimos los dos primeros números números de Fibonacci de forma f orma no recursiva. recursiva . Decimos que 󰁆(0) 󰀽 0 y 󰁆(󰀱)  󰀽 󰀱, que significa que el 1º y el 2º número de Fibonacci es 0 y 1, respectivamente. Luego, para cualquier otro índice, el número de Fibonacci es la suma de los dos números de Fibonacci anteriores. Así que 󰁆(󰁮)  󰀽 󰁆(󰁮‐󰀱)  + 󰁆(󰁮‐󰀲) . De esta forma, 󰁆(󰀳) 󰀽 󰁆(󰀲) + 󰁆(󰀱) que es 󰁆(󰀳) 󰀽 (󰁆(󰀱) + 󰁆(0)) + 󰁆(󰀱) . Como hemos hemos bajado hasta

los únicos números definidos no

recursivamente de la serie de Fibonacci, podemos asegurar que 󰁆(󰀳) 󰀽 󰀲. Los elementos definidos no recursivamente, como 󰁆(0) o 󰁆(󰀱),

se llaman casos base, y si tenemos solo casos base en una definición como en 󰁆(󰀳) 󰀽 (󰁆(󰀱) + 󰁆(0)) +

󰁆(󰀱) se denomina condición límite, la cual es

muy muy importante si quieres que tu función termine. te rmine. Si no hubiéramos definido

󰁆(0) y 󰁆(󰀱) no recursivamente, re cursivamente, nunca obtendríamos un resultado para un número número cualqu c ualquiera, iera,

ya que alcanz alca nzaríamos aríamos 0 y

continuaríamos con los número negativos. De repente, encontraríamos un 󰁆(‐󰀲000)  󰀽 󰁆(‐󰀲00󰀱)  + 󰁆(‐󰀲00󰀲)  y seguiríamos sin ver el final. La recursión re cursión es muy muy importante en Haskell ya que, al contrario que en los lenguajes imperativos, realizam r ealizamos os cálculos declarando como es algo, en lugar de declarar como obtener algo. Por este motivo no hay bucles 󰁷󰁨󰁩󰁬󰁥 o bucles 󰁦󰁯󰁲  en Haskell y en su lugar tenemos que usar la recursión recursi ón para declarar ccomo omo es algo.

El impresionante maximum La función 󰁭󰁡󰁸󰁩󰁭󰁵󰁭  toma una lista de cosas que pueden ser ordenadas (es decir instancias de la clase de tipos 󰁏󰁲󰁤 ) y devuelve la más grande. Piensa en como co mo implem implementaríamos entaríamos esto de forma imperativ imperativa. a. Probablemente crearíamos una variable varia ble para mantener el valor máximo máximo hasta el momento momento y luego recorreríamos rec orreríamos los elem e lementos entos de la lista de forma que si un elemento es mayor que el valor máximo máximo actual, ac tual, lo rempl remplazaríamos. azaríamos. El máximo máximo valor que se mantenga mantenga aall final es el e l resultado. ¡Wau! son muchas muchas palabras para definir un algoritmo tan simple. Ahora vamos a ver como definiríamos esto de forma recursiva. Primero podríamos establecer un caso base diciendo que el máximo máx imo de una lista unitaria es el único elemento que contiene la lista. Luego podríamos decir que el máxim máximoo de una lista más larga es la cabeza de esa lista si es mayor que el máximo de la cola, o el máximo de la cola en caso de que no lo sea. ¡Eso es! Vamos a implementarlo en Haskell. maximum'  :: (  (Ord Ord a)  a) => => [a]  [a] -> -> a  a maximum' :: maximum'  maximum'  [] []   = error error   "Máximo de una lista vacía" maximum' [x] maximum'  [x] = x maximum' (x maximum'  (x: :xs)

1 de 6  

     

| x > maxTail = x | otherwise = maxTail where maxTail where  maxTail = maximum' xs

Como puedes puedes ver ve r el ajuste de patrones funcionan genial junto junto con la recursión. recursi ón. Muchos lenguajes lenguajes imperativos no tienen patrones así as í que hay que utilizar muchos muchos 󰁩󰁦 / 󰁥󰁬󰁳󰁥 󰁥󰁬󰁳󰁥  para implementar los casos base. El primer caso base dice que si una lista está vacía, v acía, ¡Error! Tiene sentido se ntido porque, ¿cuál es el máximo máximo de una lista vacía? v acía? Ni idea. El segundo patrón también también representa represe nta un caso base. base . Dice que si nos dan una lista unitaria simp si mplem lemente ente devolvemos el único elem e lemento. ento. En el tercer patrón es donde está la acción. Usamos un patrón para dividir la lista en cabeza y cola. Esto es algo muy común cuando usamos una recursión con listas, así que ve acostumbrándote. Usamos una sección 󰁷󰁨󰁥󰁲󰁥 para definir 󰁭󰁡󰁸󰁔󰁡󰁩󰁬  como el máximo máximo del resto de la lista. Luego compr c omprobamos obamos si la cabez ca bezaa es mayor que el resto de la cola. Si lo es, e s, devolvemos la cabeza, si no, el máximo máximo del resto de la lista. Vamos a tomar to mar una lista de números de ejemplo y comprobar como funcionaria: 󰁛󰀲,󰀵,󰀱󰁝 . Si llamamos 󰁭󰁡󰁸󰁩󰁭󰁵󰁭'  con esta lista, los primeros dos patrones no ajustarían. El tercero si lo haría y la lista se dividiría en 󰀲 y 󰁛󰀵,󰀱󰁝. La sección 󰁷󰁨󰁥󰁲󰁥 requiere saber sa ber el máximo máximo de 󰁛󰀵,󰀱󰁝 así que nos vamos por ahí. Se ajustaría con el tercer patrón otra vez y 󰁛󰀵,󰀱󰁝 sería dividido en 󰀵 y 󰁛󰀱󰁝 . Otra vez, la sección 󰁷󰁨󰁥󰁲󰁥 requiere saber el máximo de 󰁛󰀱󰁝 . Como esto es un caso base, devuelve 1 ¡Por fin! Así que subimos subimos un paso, comparam c omparamos os 󰀵 con el máximo de 󰁛󰀱󰁝  (que es 󰀱) y sorprendentem so rprendentemente ente obtenemos 5. Así que ahora sabemos que el máximo de 󰁛󰀵,󰀱󰁝  es 󰀵. Subimos otro paso y tenemos 󰀲 y 󰁛󰀵,󰀱󰁝. Comparamos 󰀲 con el máximo de 󰁛󰀵,󰀱󰁝, que es 󰀵 y elegimos 󰀵 . Una forma más clara de escribir la función 󰁭󰁡󰁸󰁩󰁭󰁵󰁭'  es usando usa ndo la función 󰁭󰁡󰁸 . Si recuerdas, la función 󰁭󰁡󰁸  toma dos cosas que puedan puedan ser ordenadas o rdenadas y devuelve la mayor de ellas. Así es como co mo podríamos podríamos reescribir re escribir la función utilizando utilizando 󰁭󰁡󰁸 : maximum' :: maximum'  :: (  (Ord Ord a)  a) => => [a]  [a] -> -> a  a maximum'  maximum'  [] []    = error error   "maximum of empty list" maximum' [x] maximum'  [x] = x maximum' (x maximum'  (x: :xs) = x `max` (maximum' xs)

¿A que es elegante? e legante? Resumiendo, Resumiendo, el e l máximo máximo de una lista es e s el máximo entre su primer elemento y eell máximo máximo del resto res to de sus elementos.

Unas cuantas funciones recursivas más Ahora que sabemos cómo pensar de forma recursiva en general, vamos a implem implementar entar unas cuantas funciones de forma recursiva. En primer lugar, lugar, vamos va mos a imp i mplementar lementar 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥 . 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  toma un 󰁉󰁮󰁴  y algún a lgún elemento elemento y devuelve una lista que contiene varias repeticiones de ese mismo elemento. Por ejemplo, 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  󰀳 󰀵 devuelve 󰁛󰀵,󰀵,󰀵󰁝 . V Vamos amos a pensar en el caso base. Mi intuición me dice que el caso base es 0 o menos. Si intentamos replicar algo 0 o menos veces, debemos

2 de 6  

devolver una lista vacía. v acía. También ambién para números negativos ya que no tie tiene ne sentido. replicate'   :: :: (  (Num Num i,  i, Ord Ord i)  i) => => i  i -> -> a  a -> -> [a]  [a] replicate' replicate' n replicate'  n x   | n -> [a]  [a] reverse'  reverse'  [] []   = [] reverse' (x reverse'  (x: :xs) = reverse' xs ++ ++ [x]  [x]

¡Ahí ¡Ahí lo tie tienes! nes! Como Haskell soporta listas infinitas, en realidad nuestra recursión no tiene porque tener casos base. Pero si no los tiene, seguiremos calculando algo infinitamente infinitamente o bien produciendo una estructura iinfinita. nfinita. Sin embargo, lo bueno de estas lista listass infinitas es que podemos cortarlas por donde queramos. 󰁲󰁥󰁰󰁥󰁡󰁴  toma un elemento y devuelve una lista infinita que simplem simplemente ente tiene ese e se elem e lemento. ento. Una implementación implementación recursiva extremadamente extremadamente simple es:

3 de 6  

repeat'   :: :: a  a -> -> [a]  [a] repeat' repeat' x repeat'  x = x : repeat' x

Llamandoo a 󰁲󰁥󰁰󰁥󰁡󰁴  󰀳 nos daría una lista que tiene un 󰀳 en su cabeza y luego tendría una lista infinita de treses en su cola. Llamand Así que 󰁲󰁥󰁰󰁥󰁡󰁴  󰀳 se evaluaría a algo como 󰀳󰀺(󰁲󰁥󰁰󰁥󰁡󰁴  󰀳) , que es 󰀳󰀺(󰀳󰀺(󰁲󰁥󰁰󰁥󰁡󰁴  󰀳)) , que es 󰀳󰀺(󰀳󰀺(󰀳󰀺(󰁲󰁥󰁰󰁥󰁡󰁴  󰀳))) , etc. 󰁲󰁥󰁰󰁥󰁡󰁴  󰀳 nunca terminará su evaluación, mientras que 󰁴󰁡󰁫󰁥  󰀵  (󰁲󰁥󰁰󰁥󰁡󰁴  󰀳) nos devolverá un lista con cinco treses. Es igual que que hacer hace r 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  󰀵 󰀳. 󰁺󰁩󰁰  toma dos listas

y las comb co mbina ina en una. 󰁺󰁩󰁰  󰁛󰀱,󰀲,󰀳󰁝  󰁛󰀲,󰀳󰁝  devuelve 󰁛(󰀱,󰀲),(󰀲,󰀳)󰁝  ya que trunca la lista más larga

para que coincida con la más corta. ¿Qué pasa si combinamos algo con la lista vacía? Bueno, obtendríamos un una lista vacía. Así que es este es nuestro caso base. Sin embargo, 󰁺󰁩󰁰  toma dos listas como parámetros, así que en realidad tenemos dos casos base. zip'  :: zip'  :: [a]  [a] -> -> [b]  [b] -> -> [(a,b)]  [(a,b)] zip'  zip'  _   _   [] []   = [] zip'  zip'  [] []   _   _   = [] zip' (x zip'  (x: :xs) (y: (y:ys) = (x,y)  (x,y): :zip' xs ys

Los dos primeros patrones dicen que si la primera o la segunda lista están vacías entonces obtenemos una lista vacía. Combinar 󰁛󰀱,󰀲,󰀳󰁝  y 󰁛'󰁡','󰁢'󰁝  finalizará intentando combinar 󰁛󰀳󰁝  y 󰁛󰁝. El caso base aparecerá en escena y el resultado será (󰀱,'󰁡')󰀺(󰀲,'󰁢')󰀺󰁛󰁝  que exactamente e xactamente lo mismo que 󰁛(󰀱,'󰁡'),(󰀲,'󰁢')󰁝 . Vamos a implementar una función más de la biblioteca estándar, 󰁥󰁬󰁥󰁭, que toma un elemento y una lista y busca si dicho elemento está en esa lista. El caso base, como la mayoría de las veces con las listas, es la lista vacía. Sabemos que una lista vacía no contiene elementos, así que lo más más seguro se guro es que no contenga el elemento que estamos buscando... elem' :: elem'  :: (  (Eq Eq a)  a) => => a  a -> -> [a]  [a] -> ->   Bool elem' a elem'  a [] []   = False elem' a elem'  a (x: (x :xs)   | a == == x  x = True   | otherwise = a `elem'` xs

Bastante simp s imple le y previsible. previs ible. Si la cabeza no es elem e lemento ento que estam esta mos buscando entonces buscamos en la cola. Si llegamos a una lista vacía, el resultado es falso.

¡Quicksort! Tenemos una lista de elementos que pueden ser ordenados. S Suu tipo es miembro miembro de la clase c lase de tipos 󰁏󰁲󰁤 . Y ahora, queremos queremos ordenarlos. Existe un algoritmo muy muy interesante interesa nte para ordenarlos llamado llamado Quicksort. Es una forma muy muy inteligente de ordenar elem e lementos. entos. Mientras en algunos algun os lenguajes imperativos puede tomar hasta 10 líneas de código có digo para imp i mplementar lementar Quicksort, en Haskell la implementación implementación es mucho más corta y elegante. Quicksort se ha convertido en e n usa especie especi e de pieza de muestra de Haskell. Haskell. Por lo tanto, ta nto, vamos a implementarlo, implementarlo, a pesar de que la implem implementación entación de Quicksort en Haskell se considera muy cursi ya que todo el mundo lo hace en las presentaciones para que veamos los bonito que es. Bueno, la declaración de tipo será 󰁱󰁵󰁩󰁣󰁫󰁳󰁯󰁲󰁴  󰀺󰀺  (󰁏󰁲󰁤 󰁡)  󰀽󰀾  󰁛󰁡󰁝  ‐󰀾  󰁛󰁡󰁝 . Ninguna sorpresa. ¿Caso base? La lista vacía, como era de esperar. Ahora viene el algoritmo principal: una lista ordenada es una lista que tiene todos los elementos menores (o iguales) que la cabeza al principio (y esos valores están ordenados), luego viene la cabeza de la lista que estará en e n el medio y luego vienen los elementos que son mayores que la cabeza (que también también estarán estará n ordenados). Hemos Hemos dicho dos veces “ordenados”, así as í que probablemente probablemente tendremos que hacer

4 de 6  

dos llamadas recursivas. También hemos usado dos veces el verbo “es” para definir el algoritmo en lugar de “hace esto”, “hace aquello”, “entonces hace”... ¡Esa es la belleza de la program programación ación funcional! ¿Cómo vamos a conseguir filtrar los eelem lementos entos que son mayores y menores que la cabeza de la lista? Con listas intensionales. Así que empecemos y definamos esta función: quicksort   :: :: (  (Ord Ord a)  a) => => [a]  [a] -> -> [a]  [a] quicksort quicksort  quicksort  [] []   = [] quicksort (x quicksort  (x: :xs) =   let smallerSorted let  smallerSorted = quicksort [a | a  󰁡 ‐> 󰁡 ‐> 󰁡. Esto también puede ser escrito como 󰁭󰁡󰁸 :: (󰁏󰁲󰁤 󰁡) =>  󰁡  ‐> (󰁡 ‐> 󰁡) . Y también puede leerse como: 󰁭󰁡󰁸  toma un 󰁡 y devuelve (eso es ‐>) una

función que toma un 󰁡 y devuelve un 󰁡. Ese es el porqué el tipo devuelto y los parámetros de la función están separados solamente por flechas. ¿Y cómo nos beneficia esto? En pocas palabras, si llamamos a una función con parámetros de menos obtenemos una función parcialmente aplicada, es decir una función que toma tantos parámetros como le falte. Utilizar la aplicación parcial parcia l de funciones (o llamar a las funciones con c on menos parámetros) es una forma for ma sencilla de crear funciones al vuelo de forma que podam podamos os pasarlas como parámetros a otras funciones o dotarlas con algunos datos. Échale un vistazo a esta función ofensivamente simple. multThree  :: multThree  :: (  (Num Num a)  a) => => a  a -> -> a  a -> -> a  a -> -> a  a multThree x multThree  x y z = x * y * z

1 de 16  

¿Qué es lo que realm rea lmente ente pasa cuando realizamos realizamos 󰁭󰁵󰁬󰁴󰁔󰁨󰁲󰁥󰁥  3 5 9 o ((󰁭󰁵󰁬󰁴󰁔󰁨󰁲󰁥󰁥  3) 5) 9? Primero, 3 es aplicado a 󰁭󰁵󰁬󰁴󰁔󰁨󰁲󰁥󰁥  ya que está separado por un espacio. Esto crea una función que toma un parámetro y devuelve una función. Luego

5 es aplicado a está, de forma que se creará una función que toma un parámetro y lo multiplica por 15. 9 es aplicado a esa función y el resultado es 135 o algo similar. Recuerda que el tipo de esta función también podría escribirse como 󰁭󰁵󰁬󰁴󰁔󰁨󰁲󰁥󰁥 e l parámetro que toma la función función y lo que hay :: (󰁎󰁵󰁭 󰁡) => 󰁡 ‐> (󰁡 ‐> (󰁡 ‐> 󰁡)). Lo que está antes del ‐> es el después es lo que devuelve. Así qu quee nuestra función toma un 󰁡 y devuelve una función con co n un tipo (󰁎󰁵󰁭 󰁡) => 󰁡 ‐> (󰁡 ‐> 󰁡). De forma fo rma similar, similar, esta función toma una 󰁡 y devuelve una función del tipo (󰁎󰁵󰁭 󰁡) => 󰁡 ‐> 󰁡. Y finalmente, esta función

toma una 󰁡 y devuelve una 󰁡. Mira esto: ghci> ghci> 54 ghci> ghci> 180

let multTwoWithNine = multThree 9 multTwoWithNine 2 3 let multWithEighteen = multTwoWithNine 2 multWithEighteen 10

Al llamar a funciones con menos parámetros de los necesarios, hablando claro, creamos funciones al vuelo ¿Qué pasa si queremos querem os crear cre ar una función que tome un número número y lo compare con 100? 1 00? Podríamos hacer algo como esto: esto : compareWithHundred :: compareWithHundred  :: (  (Num Num a,  a, Ord Ord a)  a) => => a  a -> ->   Ordering compareWithHundred x compareWithHundred  x = compare 100 100 x  x

Si la llamamos llamamos con 99 nos devuelve G󰁔. Bastante simp s imple. le. Fíjate en la 󰁸 del lado derecho de la ecuación. Ahora vamos a pensar que devuelve` 󰁠󰁣󰁯󰁭󰁰󰁡󰁲󰁥  100. Devuelve una función que toma un número número y lo compara con 1100. 00. ¡Wau! ¿No es eso e so lo que buscábamos? Podemos reescribirlo como: compareWithHundred :: compareWithHundred  :: (  (Num Num a,  a, Ord Ord a)  a) => => a  a -> ->   Ordering compareWithHundred  compareWithHundred  = compare 100

La declaración de tipo permanece igual ya que 󰁣󰁯󰁭󰁰󰁡󰁲󰁥  100 devuelve una función. 󰁣󰁯󰁭󰁰󰁡󰁲󰁥 tiene el tipo (󰁏󰁲󰁤 󰁡) => 󰁡 res tricción de clase adicional ‐> (󰁡 ‐> 󰁏󰁲󰁤󰁥󰁲󰁩󰁮󰁧)  y llamarla con 100 devuelve (󰁎󰁵󰁭  󰁡, 󰁏󰁲󰁤 󰁡) => 󰁡 ‐> 󰁏󰁲󰁤󰁥󰁲󰁩󰁮󰁧 . La restricción se añade porque 100 es parte también de la clase de tipos 󰁎󰁵󰁭.

Nota

¡Asegúrate de que realmente sabes como funcionan las funciones currificadas y la aplicación parcial de funciones ya que son muy muy importantes! Las funciones infijas también pueden ser aplicadas parcialmente usando secciones. Para seccionar una función infija simplemente simpl emente hay que rodearla con paréntesis paréntes is y sum s uministrar inistrar un solo so lo parámetro en un lado. Esto crea una función que que toma un parámetro y lo aplica en e n el lado que falta un operando. Un Unaa función extremadam extremadamente ente trivi trivial al sería: divideByTen :: divideByTen  :: (  (Floating Floating a)  a) => => a  a -> -> a  a divideByTen  divideByTen  = (  (/ /10 10) )

Llamar a, digamos, 󰁤󰁩󰁶󰁩󰁤󰁥B󰁹󰁔󰁥󰁮  200 es equivalente a hacer 200/10  o (/10) 200. Una función que comprueba comprueba si un carácter está en mayúsculas sería: isUpperAlphanum :: isUpperAlphanum  ::   Char Char   -> ->   Bool isUpperAlphanum  isUpperAlphanum  = (`elem` ['A' ['A'.. ..'Z' 'Z']) ])

2 de 16  

Lo único especial de las secciones es el uso de ‐. Por definición, (‐4) sería una función que que toma un número número y le restase 4. Sin embargo, por conveniencia, (‐4) significa menos cuatro. cuatro . Así que si quieres una función que reste 4 a un número número puedes usar (󰁳󰁵󰁢󰁴󰁲󰁡󰁣󰁴  4) o ((‐) 4). ¿Qué pasa si intentamos hacer 󰁭󰁵󰁬󰁴󰁔󰁨󰁲󰁥󰁥  3 4󰁠󰁠󰁥󰁮 G󰁈C󰁩 󰁥󰁮 󰁬󰁵󰁧󰁡󰁲 󰁤󰁥 󰁤󰁡󰁲󰁬󰁥 󰁵󰁮 󰁮󰁯󰁭󰁢󰁲󰁥 󰁣󰁯󰁮 󰁵󰁮 󰁠󰁠󰁬󰁥󰁴 o pasarlo a otra función? ghci> multThree 3 4 :1:0:          

No instance for (Show (t -> t)) arising from a use of `print' at :1 :1:0-12 :0-12 Possible fix: add an instance declaration for (Show (t -> t)) In the expression: print it In a 'do' expression: print it

GHCii nos está GHC es tá diciendo que expresión producida es una función del tipo 󰁡 ‐> 󰁡 pero no sabe como mostrarlo por pantalla. Las funciones no son miembros de la clase de tipos 󰁓󰁨󰁯󰁷, así que no podemos obtener una cadena con la representación de una función. Si hacemos algo como 1 + 1 en GHCi, primero primero calcula ca lcula que que eso es 2, y luego llama a 󰁳󰁨󰁯󰁷 en 2 para tener una representación textual de ese número. Y una representación textual de 2 es simplemente simplemente "2", que es lo que obtenemos por pantalla.

Orden superior en su orden Las funciones pueden tomar funciones como parámetros y también devolver funciones. Para ilustrar esto vamos a crear una función que tome tome una función y la aplique dos veces vec es a algo. applyTwice  :: (a  (a -> -> a)  a) -> -> a  a -> -> a  a applyTwice  :: applyTwice f applyTwice  f x = f (f x)

Primero fíjate en su declaración de tipo. Antes, no necesitábamos usar paréntesis ya que ‐> es naturalmente asociativo por la derecha. Sin embargo, aquí está la excepción. Esto indica que el primer parámetro parámetro es una función que toma algo y devuelve algo del mismo tipo. El segundo parámetro es algo a lgo de ese mismo tipo y también devuelve algo de ese tipo. También También podríamos podríamos leer esta declaración de tipo de forma currificada, pero para salvarnos de un buen dolor de cabeza diremos simplemente simplemente que esta función toma dos parámetros y devuelve una sola co cosa. sa. El primer parámetro es una función (del tipo 󰁡 ‐> 󰁡) y el e l segundo es del mismo tipo 󰁡. La función puede ser del tipo 󰁉󰁮󰁴 ‐> 󰁉󰁮󰁴 o del tipo 󰁓󰁴󰁲󰁩󰁮󰁧 ‐> 󰁓󰁴󰁲󰁩󰁮󰁧 o cualquier otra cosa. Pero entonces, el segundo parámetro debe ser del mismo mismo tipo.

Nota

De ahora en e n adelante diremos que una función función toma varios vari os parámetros en lugar lugar de decir deci r que en realidad una función toma un parámetro y devuleve una función parcialm parcia lmente ente aplicada hasta que alcance una función que devuleva un valor sólido. Así que para simplificar diremos que 󰁡 ‐> 󰁡 ‐> 󰁡 toma dos parámetros, incluso aunque nosotros sepamos lo que realmente está pasando. El cuerpo de la función f unción es muy simple. simple. Usamos el parámetro 󰁦 como una función, aplicando 󰁸 a ella separándolas con un espacio y luego aplicando el resultado a 󰁦 otra vez. De todas formas, for mas, juega juega un poco con la función: ghci> applyTwice (+3) 10 16 ghci> applyTwice (++ " HAHA") "HEY"

3 de 16  

"HEY HAHA HAHA" ghci> applyTwice ("HAHA " ++) "HEY" "HAHA HAHA HEY" ghci> applyTwice (multThree 2 2) 9 144 ghci> applyTwice (3:) [1] [3,3,1]

Lo increíble incre íble y útil de la aplicación parcial parci al es evidente. e vidente. Si nuestra función requiere que le pasemos una función que tome un solo parámetro, podemos podemos simp si mplem lemente ente aplicar parcialmente una función hasta el e l que tome un solo parámetro y luego pasarla. Ahora vamos a usar la programación de orden superior para iimp mplementar lementar una una útil función que está en la librería estándar. Se llama 󰁺󰁩󰁰󰁗󰁩󰁴󰁨. Toma Toma una función función y dos listas y las une aplicando la función entre los correspondientes cor respondientes parámetros. Aquí tienes como la implementaríamos: zipWith' :: zipWith'  :: (a  (a -> -> b  b -> -> c)  c) -> -> [a]  [a] -> -> [b]  [b] -> -> [c]  [c] zipWith'  zipWith'    _  _   [] []     _  _   = [] zipWith'  zipWith'    _  _     _  _   [] []   = [] zipWith' f zipWith'  f (x: (x :xs) (y: (y:ys) = f x y : zipWith' f xs ys

Mira la declaración de tipo. El primer elemento es una función que toma dos cosas y produce una tercera. No tienen que ser del mismo mismo tipo, aunque pueden pueden serlo. El segun se gundo do y el tercer terc er parámetro son so n listas. La primera tiene que ser una lista de 󰁡 ya que la función de unión toma 󰁡 como primer parámetro. La segun se gunda da es una lista de 󰁢. El resultado es una lista de 󰁣. Si la declaración de tipo de una función dice que acepta una función 󰁡 ‐> 󰁢 ‐> 󰁣 como parámetro, también aceptará una función del tipo 󰁡 ‐> 󰁡 ‐> 󰁡. Recuerda que cuando estas creando una función, especialmente de orden superior, y no estas seguro de su tipo,

simplemente simpl emente puedes omitir la declaración declaraci ón de tipo y luego mirar mirar el e l tipo que infiere Haskell usando usando :󰁴. La acción de la función es muy similar a la de 󰁺󰁩󰁰. El caso base es el mismo, mismo, solo so lo que hay un parámetro extra, la función de unión, pero este parámetro no tiene importancia en el caso base así que usamos 󰁟 con él. El cuerpo de la función f unción para el último último patrón es también muy muy similar al de 󰁺󰁩󰁰, solo que no hace (󰁸, 󰁹) sino 󰁦 󰁸 󰁹. Una sola función de orden superior puede ser utilizada utiliz ada para realizar una multitud multitud de tareas diferentes si es e s suficientemente sufici entemente general. Aquí Aquí tienes una pequeña muestra muestra de las cosas que puede hacer 󰁺󰁩󰁰󰁗󰁩󰁴󰁨' : ghci> zipWith' (+) [4,2,5,6] [2,6,2,3] [6,8,7,9] ghci> zipWith' max [6,3,2,1] [7,3,1,5] [7,3,2,5] ghci> zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"] ["foo fighters","bar hoppers","baz aldrin"] ghci> zipWith' (*) (replicate 5 2) [1..] [2,4,6,8,10] ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5 [[1,2,3],[3,5,6],[2,3,4]] ,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]] [[3,2,2],[3,4,5],[5,4,3]] [[3,4,6],[9,20,30],[10,12,12]]

Como puedes puedes ver, ve r, una sola función de orden superior puede ser usada de forma mu muyy versátil. ve rsátil. Los Lo s lenguajes imperativos imperativos usan normalmente normalmente cosas co sas como bucles 󰁷󰁨󰁩󰁬󰁥, estableciendo alguna variable, comprobando su estado, etc. para conseguir un comportamiento comportam iento simil si milar ar y luego envolverlo con co n una interfaz, una función. La programación funcional utiliza las funciones de orden superior para abstraer los patrones comunes, como examinar dos listas por pares y hacer algo con esos pares o tomar un conjunto de soluciones y eliminar aquellas que no necesites.

Vamos a implementar implementar otra otr a función que ya está en la librería estándar e stándar llamada llamada 󰁦󰁬󰁩󰁰. 󰁦󰁬󰁩󰁰 toma una función y devuelve dev uelve una función que es como nuestra función original, ori ginal, solo que los dos primeros parámetros eestán stán intercambiados. Podemos implemen imp lementarla tarla así:

4 de 16  

flip'   :: :: (a  (a -> b ->  b -> -> c)  c) -> -> (b  (b -> -> a  a -> -> c)  c) flip' flip' f flip'  f = g   where g where  g x y = f y x

Aquí,í, nos aprovechamos Aqu aprov echamos del hecho de que las funciones es estén tén currificadas. currifica das. Cuando llamamos llamamos a 󰁦󰁬󰁩󰁰' sin los parámetros 󰁸 e 󰁹, devolverá una función que tome esos parámetros pero los llamará al revés. Incluso aunque las funciones a las que se les ha aplicado 󰁦󰁬󰁩󰁰 son normalmente pasadas a otras funciones, podemos tomar ventaja de la currificación cuando creemos funciones de orden superior pensando de antemano y escribir su resultado final como si fuese fuesenn llamadas llamadas totalm tota lmente ente aplicadas.

ghci> flip' zip [1,2,3,4,5] "hello" [('h',1),('e',2),('l',3) [('h',1),('e ',2),('l',3),('l',4),('o',5 ,('l',4),('o',5)] )] ghci> zipWith (flip' div) [2,2..] [10,8,6,4,2] [5,4,3,2,1]

Asociaciones y filtros 󰁭󰁡󰁰 toma una función y una lista y aplica a plica esa función a cada elem e lemento ento de esa es a lista, produciendo una nueva lista. Vamos a

ver su definición de tipo y como se define. map  :: (a  (a -> -> b)  b) -> -> [a]  [a] -> -> [b]  [b] map :: map  map    _  _   [] []   = [] map f map  f (x: (x :xs) = f x : map f xs

La definición definici ón de tipo dice que toma una una función y que a su vez esta toma to ma un 󰁡 y devuelve un 󰁢, una lista de 󰁡 y devuelve una lista de 󰁢. Es interesante intere sante que simplemente simplemente mirando la definición de tipo de una función, a veces podemos decir que hace la función. 󰁭󰁡󰁰 es una de esas funciones de orden superior que son realmente versátiles y que pueden ser usadas de millones formas diferentes. Aquí lo tienes en acción: ghci > map (+ ghci> ( +3) [1 [ 1 ,5 , 3, 1,6 ] [4,8,6,4,9] ghci> ghci > map (++ ( ++   "!" "!") ) ["BIFF" [ "BIFF", , "BANG" "BANG", , "POW" "POW"] ] ["BIFF!" "BIFF!", ,"BANG!" "BANG!", ,"POW!" "POW!"] ] ghci> ghci > map (replicate 3) [3 [3.. ..6 6] [[3 [[ 3,3,3],[ ],[4 4,4,4],[ ],[5 5,5,5],[ ],[6 6,6,6]] ghci> ghci > map (map (^ ( ^2)) [[1 [[1,2],[ ],[3 3,4,5,6],[ ],[7 7,8]] [[1 [[ 1,4],[ ],[9 9,16 16, ,25 25, ,36 36],[ ],[49 49, ,64 64]] ]] ghci> ghci > map fst [(1 [(1,2),( ),(3 3,5),( ),(6 6,3),( ),(2 2,6),( ),(2 2,5)] [1,3,6,2,2]

Probablemente te hayas dado cuenta de cada una de estas sentencias se puede conseguir usando listas por comprensión. 󰁭󰁡󰁰 (+3)  󰁛1,5,3,1,6󰁝  es lo mismo que escribir 󰁛󰁸+3 󰁼 󰁸  ->   Bool Bool) ) -> -> [a]  [a] -> -> [a]  [a] filter  filter    _  _   [] []   = [] filter  p (x: filter p (x :xs)   | p x = x : filter p xs   | otherwise = filter p xs

Bastante simple. Si 󰁰 󰁸 se evalúa a 󰁔󰁲󰁵󰁥 entonces el e l elemento elemento es incluido en la nueva lista. Si no, se s e queda fuera. Algun Algunos os

5 de 16  

ejemplos: ghci> > filter (> ( >3) [1 [ 1 ,5 ,3, 2 ,1 , 6,4 ,3 , 2, 1 ] ghci [5, 6 ,4 ] ghci> ghci > filter (== ( ==3 3) [1 [ 1, 2,3 , 4, 5 ] [3] ghci> ghci > filter even [1 [1.. ..10 10] ] [2,4,6,8,10 10] ] ghci> ghci > let let notNull  notNull x = not (null x) in in filter  filter notNull [[1 [[1,2,3], ],[] [],[ ,[3 3,4,5],[ ],[2 2,2], ],[] [], ,[] [], ,[ [[1 [[ 1,2,3],[ ],[3 3,4,5],[ ],[2 2,2]] ghci> ghci > filter (`elem` ['a' [ 'a'.. ..'z' 'z']) ]) "u LaUgH aT mE BeCaUsE I aM diFfeRent" "uagameasadifeent" ghci > filter (`elem` ['A' ghci> [ 'A'.. ..'Z' 'Z']) ]) "i lauGh At You BecAuse u r aLL the Same" "GAYBALLS"

Todo esto es to podría haberse logrado también con listas por comprensión que usaran predica predicados. dos. No hay ninguna regla que diga cuando usar 󰁭󰁡󰁰 o 󰁦󰁩󰁬󰁴󰁥󰁲  en lugar de listas por comprensión, simplemente simplemente debes decidir que es más legible dependiendo dependiendo del contexto. El filtro equivalente de aplicar varios predicados en una lista por comprensión es el mismo que aplicar varios filtrados o unir los predicados usando la función lógica &&. ¿Recuerdas nuestra función quicksort  del capítulo anterior? Usamos listas por comprensión para filtrar los eelem lementos entos que eran menores o iguales i guales y mayores que el pivote. pivote . Podemos conseguir lo mismo de forma m más ás legible usando 󰁦󰁩󰁬󰁴󰁥󰁲. quicksort  :: quicksort  :: (  (Ord Ord a)  a) => => [a]  [a] -> -> [a]  [a] quicksort  quicksort  [] []   = [] quicksort (x quicksort  (x: :xs) =   let smallerSorted let  smallerSorted = quicksort (filter (x) xs)   in  in   smallerSorted ++ ++ [x]  [x] ++ ++ biggerSorted  biggerSorted

Mapear y filtrar fi ltrar son el pan de cada día de todas las herramientas de un programador programador funcional. No importa importa si s i utilizas las funciones 󰁭󰁡󰁰 y 󰁦󰁩󰁬󰁴󰁥󰁲 o listas por comprensión. Recuerda como resolvimos res olvimos el problema problema de encontrar enco ntrar triángulos rectos con una determinada circunferencia. En programación programación imperativa, deberíamos haber solucionado el problema anidando tres bucles y luego comprobar si la combinación actual satisface las propiedades de un triángulo recto. En ese caso, lo habríamos mostrado por pantalla o algo parecido. Con la programación funcional este patrón se consigue con el mapeado y filtrado. Creas una función que tome un valor y produzca produz ca un resultado. Mapeamos esa función sobre todos los elementos de la lista y luego filtramos la lista resultante para que satisfaga nuestra búsqueda. Gracias a la evaluación perezosa de Haskell, incluso si mapeas algo sobre una lista varias veces o la filtras varias veces, solo se recorrerá la lista una vez. Vamos a buscar el número más grande por debajo de 100.000 que sea divisible simplemente divisible por 3829. Para lograrlo, simplemente filtramos un conjunto de posibilidades en el cual sabemos que está la solución. largestDivisible :: largestDivisible  :: (  (Integral Integral a)  a) => => a  a largestDivisible  largestDivisible  = head (filter p [100000 [ 100000, ,99999 99999.. ..]) ])   where p where  p x = x `mod` 3829 3829   == ==   0

Primero creamos una lista de números menores que 100.000 de forma descendiente. Luego la filtramos con nuestro predicado y como c omo los número número están es tán ordenados de forma for ma descendiente, el número número más grande que satisface satisfa ce nuestro predicado predica do es el primer elemento de la lista filtrada. Ni siquiera tenemos que usar una lista finita para nuestro conjunto de partida. La evaluación perezosa aparece otra vez. Como al final solo acabamos usando la cabeza de la lista, no importa si la lista es finita o infinita. La evaluación se para cuando se encuentre la primera solución adecuada.

6 de 16  

A continuación, vamos a buscar la suma de todos los cuadrados impares que son menores de 10.000. Pero primero, como vamos a usarla en nuestra solución, vamos a introducir la función 󰁴󰁡󰁫󰁥󰁗󰁨󰁩󰁬󰁥. Toma un predicado y una lista y recorre la lista desde el principio y devuelve estos elementos mientras mientras el e l predicado se mantenga cierto. Una Una vez encuentre un predicado que no se evalúe a cierto para. Si queremos obtener la primera palabra de "󰁌󰁯󰁳 󰁥󰁬󰁥󰁦󰁡󰁮󰁴󰁥󰁳  󰁳󰁡󰁢󰁥󰁮 󰁣󰁯󰁭󰁯 󰁭󰁯󰁮󰁴󰁡󰁲 󰁵󰁮󰁡 󰁦󰁩󰁥󰁳󰁴󰁡" , podríamos hacer 󰁴󰁡󰁫󰁥󰁗󰁨󰁩󰁬󰁥  (/='  ') "󰁌󰁯󰁳 󰁥󰁬󰁥󰁦󰁡󰁮󰁴󰁥󰁳  󰁳󰁡󰁢󰁥󰁮  󰁣󰁯󰁭󰁯 󰁭󰁯󰁮󰁴󰁡󰁲  󰁵󰁮󰁡 󰁦󰁩󰁥󰁳󰁴󰁡"  y obtendríam o btendríamos os

Vale, ahora a por la suma de todos los cuadrados impares menores que 10.00 10.000. 0. Primero empezaremos empezaremos mapeado la "󰁌󰁯󰁳" . Vale, función (󰁞2) a la lista infinita 󰁛1..󰁝. Luego filtramos la lista para quedarnos solo con los impares. impares. Después De spués tomamos tomamos los elementos mientras mientras sean s ean menores que 10.000. 10.00 0. Finalmente, obtenemos la sum sumaa de todos estos elementos. Ni siquiera tenemos que crear una función para obtener el resultado, podemos hacerlo en una línea en GHCi: ghci ghci> > sum (takeWhile (< (  sum (takeWhile (< ghci> ( -> [a]  [a] chain  chain  1 = [  [1 1] chain n chain  n   | even n =  n:chain (n `div` 2)   | odd n =  n:chain (n* (n*3 + 1)

Como la secuencia termina en 1, ese es el caso base. Es una función típica recursiva. ghci > chain 10 ghci> [10 10, ,5,16 16, , 8 , 4, 2 ,1] ghci> ghci > chain 1 [1] ghci> ghci > chain 30 [30 30, ,15 15, ,46 46, ,23 23, ,70 70, ,35 35, ,106 106, ,53 53, ,160 160, ,80 80, ,40 40, ,20 20, ,10 10, ,5,16 16, ,8 ,4 , 2, 1]

¡Bien! Parece que funciona correctamente. Y ahora, la función que nos da la respuesta a nuestro problema: numLongChains :: numLongChains  ::   Int numLongChains  numLongChains  = length (filter isLong (map chain [1 [ 1.. ..100 100])) ]))

7 de 16  

 

where isLong where  isLong xs = length xs > 15

Mapeamos con la función 󰁣󰁨󰁡󰁩󰁮 la lista 󰁛1..100󰁝  para obtener la lista de las secuencias. Luego filtramos la lista con un predicado que simplemente simplemente nos dice si una lista tiene tie ne un tamaño mayor que 15. Una Una vez ve z hemos hemos realizado re alizado el filtrado, vemos cuantas secuencias han quedado en la lista resultante.

Nota

Esta función tiene el tipo 󰁮󰁵󰁭󰁌󰁯󰁮󰁧C󰁨󰁡󰁩󰁮󰁳  :: 󰁉󰁮󰁴 porque length devuelve el tipo 󰁉󰁮󰁴 en lugar de un 󰁎󰁵󰁭 por razones históricas. También podemos podemos hacer hac er cosas c osas como 󰁭󰁡󰁰 (*) 󰁛0..󰁝, con el único motivo de ilustrar como funciona la currificación y como la funciones (parcialmente aplicadas) son valores reales que pueden ser pasadas como parámetros en otras funciones o como pueden ser incluidas en listas (solo (so lo que no puedes mostrarlas por pantalla). Hasta ahora solo so lo hemos hemos mapeado sobre listas funciones que toman un solo parámetro, como c omo 󰁭󰁡󰁰 (*2) 󰁛0..󰁝 para obtener una lista del tipo (󰁎󰁵󰁭 󰁡) => 󰁛󰁡󰁝, pero también podemos podem os usar 󰁭󰁡󰁰 (*) 󰁛0..󰁝 sin ningún problem problema. a. Lo que sucede es que cada c ada número número de la lista es aplicado a * que tiene el tipo (󰁎󰁵󰁭 󰁡) => 󰁡 ‐> 󰁡 ‐> 󰁡. Aplicar Aplicar un solo parámetro a una función que tiene dos parámetros obtenemos una función que solo toma un parámetro, así que tendríamos una lista de funciones (󰁎󰁵󰁭 󰁡) => 󰁛󰁡 ‐> 󰁡󰁝. 󰁭󰁡󰁰 (*) 󰁛0..󰁝 󰁠󰁠 󰁰󰁲󰁯󰁤󰁵󰁣󰁥 󰁵󰁮󰁡 󰁬󰁩󰁳󰁴󰁡 󰁱󰁵󰁥  󰁰󰁯󰁤󰁲󰃭󰁡󰁭󰁯󰁳  󰁥󰁳󰁣󰁲󰁩󰁢󰁩󰁲  󰁣󰁯󰁭󰁯 󰁠󰁠󰁛(0*),(1*),(2*),(3*),(4*),(5*)...

ghci> ghci > let let listOfFuns  listOfFuns = map (* ( *) [0 [0.. ..] ] ghci > (listOfFuns !! ghci> !!   4 ) 5 20

Al obtener el 4º elemento de nuestra lista obtenemos una función equivalente a (4*). Y luego aplicamos 5 a esa e sa función. Así que en realidad es como si escribiéramos (4*) 5 o simplemente 4 * 5.

Lambdas Las lambdas son funciones anónimas que suelen ser usadas cuando necesitam necesita mos una función una sola vez. ve z. Normalm Normalmente ente creamos cre amos funciones lambda lambda co conn el único propósito de pasarlas a funciones de orden superior. Para crear una lambda escribimos un 󰁜 (Porque tiene un cierto parecido con c on la letra griega lambda lambda si le echas mucha imaginación) y luego los parámetros separados por espacios. Luego escribimos una ‐> y luego el cuerpo de la función. Normalm Normalmente ente las envolvemos envo lvemos con paréntesis ya que de otra forma se extenderían al resto de la línea. Si miras 10 cm arriba verás que usamos una sección 󰁷󰁨󰁥󰁲󰁥 en nuestra función 󰁮󰁵󰁭󰁌󰁯󰁮󰁧C󰁨󰁡󰁩󰁮󰁳  para crear la función 󰁩󰁳󰁌󰁯󰁮󰁧  con el único propósito de usarla en un filtro.

Bien, en lugar de hacer hace r eso podemos usar una lambda: lambda: numLongChains :: numLongChains  ::   Int numLongChains  numLongChains  = length (filter (\ ( \xs -> -> length  length xs > 15 15) ) (map chain [1 [1.. ..100 100])) ]))

Las lambdas son expresiones, ese es el porqué podemos simplemente pasarlas así. La expresión (󰁜󰁸󰁳 ‐> 󰁬󰁥󰁮󰁧󰁴󰁨  󰁸󰁳 > 15) devuelve una función que nos dice si el tamaño de una lista es mayor que 15.

Es muy común que que la gente que no está muy muy acostumb ac ostumbrada rada a como funciona la currificación currificac ión y la aplicación a plicación parcial usen lambdas cuando no deben. Por ejemplo, ejemplo, la expresión 󰁭󰁡󰁰  (+3) 󰁛1,6,3,2󰁝  y 󰁭󰁡󰁰 (󰁜󰁸  ‐>  󰁸 + 3) 󰁛1,6,3,2󰁝  son equivalentes ya que

8 de 16  

ambas expresiones, (+3) y (󰁜󰁸 ‐> 󰁸 + 3) son funciones que toman un número y le suman 3. Nada Nada más que decir, crear una lambda lambda en este e ste caso cas o es algo estúpido ya que la aplicación parcial parc ial es mucho mucho más legible. Al igual que que las funciones normales, las lambdas lambdas pueden tomar cualquier número número de parámetros. ghci ghci> > zipWith (\ (\a b -> -> (a  (a * 30 30   + 3) / b) [5 [ 5,4,3,2,1] [1 [ 1, 2 ,3 , 4,5 ] [153.0 153.0, ,61.5 61.5, ,31.0 31.0, ,15.75 15.75, ,6.6 6.6] ]

Y al igual que la funciones normales, normales, las lambdas pueden usar el ajuste de patrones. La L a única diferencia diferenci a es que no puedes definir varios patrones para un parámetro, como crear 󰁛󰁝 y (󰁸:󰁸󰁳) para el mismo parámetro de forma que las variables se ajusten a uno u a otro. Si el ajuste de patrones patro nes falla en una lambda, lambda, se lanzará un error de eejecución, jecución, así que ten te n cuidado cuando los uses. ghci > map (\ ghci> ( \(a,b) -> -> a  a + b) [(1 [(1,2),( ),(3 3,5),( ),(6 6,3),( ),(2 2,6),( ),(2 2,5)] [3,8,9,8,7]

Normalm Norm almente ente rodeamos rodea mos las lambdas lambdas con co n paréntesis a no ser s er que queramos que se extiendan hasta el final fi nal de la línea. Aquí tienes algo interesante, debido a que las funciones se currifican por defecto, estas dos definiciones son iguales: addThree :: addThree  :: (  (Num Num a)  a) => => a  a -> -> a  a -> -> a  a -> -> a  a addThree x addThree  x y z = x + y + z

addThree :: addThree  :: (  (Num Num a)  a) => a => a -> -> a  a -> -> a  a -> -> a  a addThree  addThree  = \x -> ->   \y -> ->   \z -> -> x  x + y + z

Si definimos funciones de esta forma es obvio el motivo por el cual las definiciones de tipo son como son. Hay tres ‐> tanto en la declaración declaració n de tipo como en la ecuación. ecuac ión. Pero por supuesto, la primera forma de escribir funciones es mucho más legibl legible, e, y la segundo sirve únicamente para ilustrar la currificación. Sin embargo hay veces que es más interesante usar esta notación. Creo que la función 󰁦󰁬󰁩󰁰 es mucho más legible si la definimos así: flip' :: flip'  :: (a  (a -> -> b  b -> -> c)  c) -> -> b  b -> -> a  a -> -> c  c flip' f flip'  f = \x y -> -> f  f y x

Aunque Aun que es lo mismo que escribir escri bir 󰁦󰁬󰁩󰁰' 󰁦 󰁸 󰁹 = 󰁦 󰁹 󰁸, hacemos obvio que la mayor parte del tipo la usare usaremos mos para producir una nueva función. El caso de uso más común ddee 󰁦󰁬󰁩󰁰 es llamarla con solo la función parámetro y luego pasar la función resultante como parámetro a 󰁭󰁡󰁰󰁠󰁠󰁯 󰁠󰁠󰁦󰁩󰁬󰁴󰁥󰁲. Así que usa las lambdas lambdas cuando quieras hacer explícito que tu función esta principalmente pensada para se parcialmente aplicada y se pasada como a una función como parámetro.

Pliegues y papiroflexia Volviendo a cuando tratábamos tra tábamos con la recursión, recursió n, nos dimos cuenta de que muchas muchas funciones operaban con listas. Solíamos tener un caso base que era la lista vacía. Debíamos usar un patrón 󰁸:󰁸󰁳 y hacíamos hacía mos alguna alguna operación operaci ón con un solo elemento de la lista. Esto sugiere que es un patrón muy común, así que unas cuantas funciones muy útiles fueron creadas para encapsular este comportamiento. Estas funciones son llamadas llam adas pliegues (o folds  en  en ingles). Son una especie de función 󰁭󰁡󰁰, solo que reducen

9 de 16  

la lista a un solo valor. Un pliegue pliegue toma una función binaria, un valor inicial (a mi me me gusta llamarlo el acumulador) acumu lador) y una lista que plegar. La función binaria toma dos parámetros por si misma. La función binaria es llamada con el acumulador acumu lador y el e l primer primer (o último) último) elemento y produce un nuevo acumulador. acumulador. Luego, la función binaria se vuelve v uelve a llamar junto al nuevo acumulador acumulador y al a l nuevo primer (o último) último) elemento de la lista, y así sucesivamente. suces ivamente. Cuando se ha recorrido reco rrido la lista completa, compl eta, solo so lo permanece un acumulador acumulador,, que es el valor va lor al que se ha reducido la lista. lista . Primero vamos a ver la función 󰁦󰁯󰁬󰁤󰁬, también llamada pliegue por la izquierda. Esta pliega la lista empezando desde la izquierda. izquierda. La función binaria es aplicada junto a el e l valor inicial inicia l y la cabeza de la lista. Esto Est o produce un nuevo acumulador acumulador y la función binaria es vuelta a llamar con ese nuevo valor y el siguiente elemento, etc. Vamos a volver a implementar 󰁳󰁵󰁭, solo que esta es ta vez, vamos a usar usa r un pliegue pliegue en lugar de una recursión expl e xplícita. ícita. sum' sum'   :: :: (  (Num Num a)  a) => [a] => [a] -> -> a  a sum' xs sum'  xs = foldl (\ ( \acc x -> -> acc  acc + x) 0 xs

Probando, un, dos, tres: ghci > sum' [3 ghci> [3 , 5,2 ,1 ] 11

Vamos a dar un vistazo vista zo a como funciona este pliegu pliegue. e. 󰁜󰁡󰁣󰁣 󰁸 ‐> 󰁡󰁣󰁣 + 󰁸 es la función binaria. 0 es el valor inicial y 󰁸󰁳 es la lista que debe ser plegada. Primero, 0 se utiliza como el parámetro 󰁡󰁣󰁣 en la función binaria y 3 es utilizado utilizado como el parámetro pará metro 󰁸 (o el valor actual).` 󰁠0 + acumulador. Luego, 3 es usado como acumulador y 5 3 produce un 3 que pasa a ser el nuevo acumulador. como el elemento actual y por tanto 8 se convierte c onvierte en el nuevo acumulador. acumulador. Seguimos Seguimos adelante y 8 es el acumulador, 2 el elemento actual, así que el nuevo acumulador acumulador es 10. Para terminar ese 10 es usado usa do como acumulador acumulador y 1 como el elem e lemento ento actual, ac tual, produciendo un 1. ¡Enhorabuena, ¡Enhorabuena,

has hecho un pliegue! pliegue! A la izquierda izquierda tienes un diagrama diagrama profesional profes ional que ilustra como co mo funciona un pliegue pliegue paso a paso. Los números verdes (si los ves amarillos quizás seas daltónico) son los acumuladores. Puedes ver como la lista es e s consum co nsumida ida por el e l acumulador acumulador de arriba arri ba a abajo. a bajo. Ñam, Ñam, ñam, ñam... ñam... Si tenemos en cuenta que las funciones están currificadas, podemos escribir esta implementación de forma más bonita como: sum'  :: sum'  :: (  (Num Num a)  a) => => [a]  [a] -> -> a  a sum'  sum'  = foldl (+ ( +) 0

La función lamb lambda da (󰁜󰁡󰁣󰁣 󰁸 ‐> 󰁡󰁣󰁣 + 󰁸) es lo mismo mismo que (+). Podemos omitir el parámetro 󰁸󰁳 ya que al llamar a 󰁦󰁯󰁬󰁤󰁬 (+) 0 nos devuelve una función que toma una lista. Generalmente, si tienes una función del tipo 󰁦󰁯󰁯  󰁡 = 󰁢󰁡󰁲 󰁢 󰁡 la puedes

escribir como 󰁦󰁯󰁯 = 󰁢󰁡󰁲 󰁢 gracias a la currificación.

Vamos a implementar implementar otra otr a función con co n un pliegue pliegue por la izquierda izquierda antes de continuar con co n los pliegues por la derecha. Estoy Esto y seguro de que sabes que 󰁥󰁬󰁥󰁭 comprueba si un elemento elemento es parte de una lista así a sí que no lo explicaré de nuevo (mmm... (mmm... creo que ya lo hice). Vamos a implementarla.

10 de 16  

elem' elem'   :: :: (  (Eq Eq a)  a) => => a  a -> -> [a]  [a] -> ->   Bool elem' y elem'  y ys = foldl (\ (\acc x -> ->   if if x  x == == y  y then then   True True   else else acc)  acc) False False ys  ys

Bueno, bueno, bueno... bueno... ¿Qué estamos e stamos haciendo aquí? El valor de inicio y el acumulador acumulador son ambos del tipo booleano. Cuando hablamos hablamos de pliegues tanto el tipo del acum ac umulad ulador or como c omo el tipo del resultado res ultado final son el mismo. Empezam Empezamos os con c on el valor va lor inicial F󰁡󰁬󰁳󰁥. Tiene sentido ya y a que asumimos que el elemento no está en la lista. También porque porque si llamamos llamamos a un pliegue con una lista vacía el resultado será simplemente simplemente el valor inicial. inicia l. Luego comprobamos comprobamos si el elemento elemento actual act ual es el que estamos buscando. Si lo es, es , ponemos el acumulador a 󰁔󰁲󰁵󰁥. Si no lo es, dejamos el acumulador como estaba. Si ya estaba a F󰁡󰁬󰁳󰁥, permanece en ese estado ya que el elemento actual no es el que buscamos. Si era 󰁔󰁲󰁵󰁥, se queda como co mo estaba también. Ahora los pliegues por la derecha dere cha funcionan igual que los pliegues por la izquierda, izquierda, solo que el acum a cumulador ulador consume elemento elemento por la derecha. La L a función binaria de los pliegues por la izq izquierda uierda como primer parámetro el acumu acumulador lador y el valor actual como segundo parámetro (tal que así: 󰁜󰁡󰁣󰁣 󰁸 ‐> ...), la función binaria de los pliegues por la derecha tiene el valor actual como primer parámetro parámetro y el acum ac umulador ulador después (así: 󰁜󰁸 󰁡󰁣󰁣 ‐> ...). Tiene sentido ya que el pliegue por la derecha tiene el acumulador acumu lador a la derecha. der echa. El acumulador acumulador (y por tanto del resultado) res ultado) de un pliegue pliegue puede ser de cualquier tipo. Puede ser s er un número, número, un booleano e incluso una nueva lista. Vamos Vamos a iimp mplem lementar entar la función 󰁭󰁡󰁰 con un pliegue por la derecha. El acumulador acumulador será una lista, en la que iremos acumulando los elemento de la lista ya mapeados. Es obvio que el valor inicial será una lista vacía. map'  :: map'  :: (a  (a -> -> b)  b) -> -> [a]  [a] -> -> [b]  [b] map' f map'  f xs = foldr (\ (\x acc -> -> f  f x : acc) [] [] xs  xs

Si estamos esta mos mapeando mapeando (+3) a 󰁛1,2,3󰁝, recorremos reco rremos la lista desde el lado derecho. dere cho. Tomam Tomamos os el e l último último elemento, el cual es 3 y le aplicamos la función a él, de forma que acaba sie siendo ndo un 6. Luego lo añadimos añadimos al a l acumulador acumulador que es 󰁛󰁝. 6:󰁛󰁝 es 󰁛6󰁝 que pasa a ser el e l nuevo acumul acumulador. ador. Aplicamos Aplicamos (+3) a 2 , que es 5 y es añadido (:) al acumulador, de forma que nos queda 󰁛5,6󰁝. Hacemos Hacem os lo mismo mismo con co n el últim últimoo elemento y acabamos a cabamos obteniendo 󰁛4,5,6󰁝. Por supuesto, también podríamos podríamos haber implementad implementadoo esta es ta función usando un pliegue por la izquierda. Sería algo como 󰁭󰁡󰁰' 󰁦 󰁸󰁳 = 󰁦󰁯󰁬󰁤󰁬 (󰁜󰁡󰁣󰁣 󰁸 ‐> 󰁡󰁣󰁣  ++ 󰁛󰁦 󰁸󰁝) 󰁛󰁝 󰁸󰁳, pero la cuestión es que la función ++ es bastante menos eficiente que :, así que normalmente normalmente usamos pliegues por la derecha cuando co construim nstruimos os listas a partir de una lista.

Si pones del revés una lista, puedes hacer un pliegue por la derecha como si fuera un pliegue por la izquierda y viceversa. A veces ni siquiera tienes que hacerlo. La función 󰁳󰁵󰁭 por ejemplo puede ser implementada tanto con un pliegue por la izquierda izquierda como por la derecha. Una Una gran diferencia es que los pliegues por la derecha funcionan con listas infinitas, mientras que los pliegues por la izquierda no. Para aclarar las cosas, si tomas una lista infinita en algún lugar y le aplicas un pliegue por la derecha, en algún momento momento alcanzará alcanzará el inicio de la lista. Si embargo, si tomas una lista infinita en algú a lgúnn punto y le aplicas un pliegue pliegue por la izquierda izquierda nunca alcanzará alcanz ará el final. Los pliegues se pueden utilizar para implementar cualquier función que recorra una lista, elemento a elemento, y luego devuelvan un valor. Siempre que quieras recorrer una lista y devolver un valor, hay posibilidades de utilizar un pliegue. Esta es e s la razón por la que los pliegues, junto junto a los mapeos y los filtros, filtros , son unas de las funciones más útiles de la

programación funcional.

Las funciones 󰁦󰁯󰁬󰁤󰁬1 y 󰁦󰁯󰁬󰁤󰁲1 son muy parecidas a 󰁦󰁯󰁬󰁤󰁬 y 󰁦󰁯󰁬󰁤󰁲, solo que en lugar que no necesitas necesita s indicar un valor de

11 de 16  

inicio. Asumen Asumen que el primer (o el último) último) elemento de la lista list a es valor de inicio, luego emp e mpiezan iezan a plegar la lista por el elemento siguiente. Esto me recuerda que la función 󰁳󰁵󰁭 puede ser implementada como: 󰁳󰁵󰁭 = 󰁦󰁯󰁬󰁤󰁬1 (+). Ya que estas funciones dependen de de que la listas que van a plegar tengan al menos menos un elemento, pueden causar errores errore s en tiemp tie mpoo de ejecución ejecució n si son so n llamadas con listas vacías. Por otra parte, tanto 󰁦󰁯󰁬󰁤󰁬 como 󰁦󰁯󰁬󰁤󰁲 funcionan bien con listas vacías. vacías . Cuando hagas un pliegue pliegue piensa bien en como actuar ante una lista vacía. Si la función no tiene sentido al ser llamada con listas vacías probablemente puedas utilizar 󰁦󰁯󰁬󰁤󰁬1󰁠󰁠󰁹  󰁠󰁠󰁦󰁯󰁬󰁤󰁲1  para implementarla. Con el único motivo de mostrarte lo potente que estas funciones son, vamos va mos a implementar implementar un puñado puñado de funciones estándar usando pliegues: maximum' maximum'   :: :: (  (Ord Ord a)  a) => => [a]  [a] -> -> a  a maximum'  maximum'  = foldr1 (\ (\x acc -> ->   if if x  x > acc then then x  x else else acc)  acc) reverse' :: reverse'  :: [a]  [a] -> -> [a]  [a] reverse'  reverse'  = foldl (\ (\acc x -> -> x  x : acc) [] product' :: product'  :: (  (Num Num a)  a) => => [a]  [a] -> -> a  a product'  product'  = foldr1 (* (*) filter' :: filter'  :: (a  (a -> ->   Bool Bool) ) -> -> [a]  [a] -> -> [a]  [a] filter' p filter'  p = foldr (\ (\x acc -> ->   if if p  p x then then x  x : acc else else acc)  acc) [] head' :: head'  :: [a]  [a] -> -> a  a head'  head'  = foldr1 (\ (\x _   -> -> x)  x) last'  last'  :: :: [a]  [a] -> -> a  a last'  last'  = foldl1 (\ (\  _   _   x ->  x -> x)  x)

implementarla con ajuste de patrones, patro nes, pero de esta e sta forma for ma puedes puedes ver ve r que incluso se puede implementar implementar con 󰁨󰁥󰁡󰁤 es mejor implementarla pliegues. Nuestra Nuestra función 󰁲󰁥󰁶󰁥󰁲󰁳󰁥'  está bastante clara, creo. Tomamos como valor de inicio la lista vacía y luego recorremos la lista desde la izquierda izquierda y simplemente simplemente vamos añadiendo elementos a nuestro acum ac umulador ulador.. Al final tenemos la lista al revés. revés . 󰁜󰁡󰁣󰁣 󰁸 ‐> 󰁸 : 󰁡󰁣󰁣 se parece a la función : solo que los parámetros están al revés. Por esta razón también podíamos haber

escrito esto: 󰁦󰁯󰁬󰁤󰁬 (󰁦󰁬󰁩󰁰 (:)) 󰁛󰁝. Existe otra forma de representar los pliegues por la izquierda izquierda y por la derecha. dere cha. Digamos que tenemos tenemos un pliegue por la derecha, una función 󰁦 y un valor de inicio 󰁺. Si hacemos el pliegue sobre la lista 󰁛3,4,5,6󰁝 , básicamente es como si hiciésemos 󰁦 3 (󰁦 4 (󰁦 5 (󰁦 6 󰁺))). 󰁦 es llamada con el último último elemento de la lista y el acumulador, acumulador, ese valor es e s dado como acumulador ador de la siguiente llamada y así sucesivamente. sucesiva mente. Si tomamos + como 󰁦 y un valor de inicio 0, tenemos 3 + (4 + (5 + (6 + 0))). Representado de forma prefija sería (+) 3 ((+) 4 ((+) 5 ((+)  6 0))). De forma similar si hacemos

un pliegue por la izquierda, tomamos 󰁧 como función binaria y 󰁺 como acumulador, acumulador, sería equivalente a hacer 󰁧 (󰁧 (󰁧 (󰁧 󰁺 3) 4) 5) 6. Si tomam to mamos os 󰁦󰁬󰁩󰁰 (:) como función binaria y 󰁛󰁝 como el acumulador acumulador (de forma for ma que que estamos esta mos poniendo al reverso la

lista), entonces sería equivalente a 󰁦󰁬󰁩󰁰 (:) (󰁦󰁬󰁩󰁰 (:) (󰁦󰁬󰁩󰁰 (:) (󰁦󰁬󰁩󰁰 (:) 󰁛󰁝 3) 4) 5) 6. Y estoy casi seguro seguro que si evalúas esta expresión obtendrás 󰁛6,5,4,3󰁝 . 󰁳󰁣󰁡󰁮󰁬  y 󰁳󰁣󰁡󰁮󰁲 son como 󰁦󰁯󰁬󰁤󰁬 y 󰁦󰁯󰁬󰁤󰁲 , solo que devuelven todos los acumuladores adores intermedios en forma de lista. Existen

también 󰁳󰁣󰁡󰁮󰁬1 y 󰁳󰁣󰁡󰁮󰁲1, que son similares a 󰁦󰁯󰁬󰁤󰁬1 y 󰁦󰁯󰁬󰁤󰁲1 . ghci > scanl (+ ghci> ( +) 0 [  [3 3 ,5 ,2, 1 ] [0,3,8,10 10, ,11 11] ] ghci> ghci > scanr (+ ( +) 0 [3  [3,5,2,1] [11 11, ,8,3,1,0] ghci> ghci > scanl1 (\ ( \acc x -> ->   if if x  x > acc then then x  x else else acc)  acc) [3 [ 3,4 , 5, 3 ,7, 9, 2 ,1 ] [3,4,5,5,7,9,9,9] ghci> ghci > scanl (flip (: (:)) [] [] [  [3 3 , 2,1 ] [[] [],[ ,[3 3],[ ],[2 2,3],[ ],[1 1,2,3]]

12 de 16  

Cuando usamos 󰁳󰁣󰁡󰁮󰁬, el resultado final será el últim últimoo elemento de la lista resultante re sultante mientras mientras que con co n 󰁳󰁣󰁡󰁮󰁲 estará al principio. Estas funciones son so n utilizadas utilizadas para monitorizar monitorizar la progresión progres ión de una función que puede ser se r implementada implementada con un pliegue. Vamos a contestar contes tar a la siguiente cuestión cuestió n ¿Cuántos elemento toma toma la suma de todos las raíces r aíces de todos los números naturales exceder 1000? Para obtener las raíces de todos los número naturales simplemente hacemos 󰁭󰁡󰁰 󰁳󰁱󰁲󰁴 󰁛1..󰁝. Ahora, para obtener la suma podría utilizar un pliegue, pliegue, pero como estamos interesados i nteresados en la progresión progresi ón de la suma, utilizaremos utilizaremos 󰁳󰁣󰁡󰁮󰁬. Cuando obtengamos obtengamos la lista resultante, res ultante, simplem simplemente ente contamos cuantas sumas están por debajo de 1000. 100 0. La primera primera sum s umaa de la lista será 1. La segunda será 1 más la raíz de 2. La tercera será lo mismo que la anterior más la raíz de 3. Si hay X sumas menores de 1000, entonces tomará X + 1 elementos para que la suma exceda 1000. sqrtSums sqrtSums   :: ::   Int sqrtSums  sqrtSums  = length (takeWhile (< ( sqrtSums ghci> 131 ghci> ghci > sum (map sqrt [ [1 1.. ..131 131]) ]) 1005.0942035344083 ghci> ghci > sum (map sqrt [1 [ 1..130 ..130]) ]) 993.6486803921487

Utilizamos 󰁴󰁡󰁫󰁥󰁗󰁨󰁩󰁬󰁥  en lugar de 󰁦󰁩󰁬󰁴󰁥󰁲 porque éste no funciona con listas infinitas. Incluso aunque nosotros sepamos que la lista es ascendente, 󰁦󰁩󰁬󰁴󰁥󰁲 no lo sabe, así que usamos 󰁴󰁡󰁫󰁥󰁗󰁨󰁩󰁬󰁥 para cortar la lista por la primera ocurrencia de una suma que supere 1000.

Aplicación de funciones con $ Esta bien, ahora vamos a ver la función $, también llaamada mada aplicación de función. Antes de nada vamos a ver como co mo está definida: ($) :: :: (a  (a -> -> b)  b) -> -> a  a -> -> b  b f $ x = f x

¿Pero qué...? ¿Para qué queremos un operador tan inútil? ¡Es simpl simplemente emente la aplicación de una función! Bueno, casi, pero no solo eso. Mientras que la aplicación de funciones normal (un espacio entre dos cosas) tiene un alto orden de precedencia, la función $ tiene el orden o rden de precedencia más bajo. La aplicación de funciones con el espacio es asociativa a izquierdas (así que 󰁦 󰁡 󰁢 󰁣 es lo mismo mismo que ((󰁦 󰁡) 󰁢) 󰁣), la aplicación de funciones con $ es asociativa a derechas. Eso está muy bien, pero ¿De qué nos sirve esto? Básicamente es una función de conveniencia que utilizamos para no tener que escribir muchos paréntesis. Considera la expresión sum (󰁭󰁡󰁰 󰁳󰁱󰁲󰁴 󰁛1..130󰁝) . Gracias a que $ tiene un bajo orden de precedencia podemos escribir es misma expresión como 󰁳󰁵󰁭 $ 󰁭󰁡󰁰 󰁳󰁱󰁲󰁴 󰁛1..130󰁝 , ahorrándonos que nuestros dedos pulsen pulsen esas esa s molestas teclas. tec las. Cuando se encuentra un $, la expresión a la derecha es aplicada como parámetro a la función de la izquierda. ¿Qué pasa con 󰁳󰁱󰁲󰁴 3 + 4 + 9? Esta expresión suma 4 más 9 más la raíz de 3. Si lo que querem queremos os es la raíz de 3 + 4 + 9 tenemos que escribir 󰁳󰁱󰁲󰁴 (3 + 4 + 9) o si usamos $ podemos escribirlo como 󰁳󰁱󰁲󰁴 $ 3 + 4 + 9 ya que $

tiene menor orden de precedencia que cualquier otro operador. Por este motivo podemos imaginar a $ como una especie de paréntesis abierto que de forma automática añade un cierre al final de la expresión. ¿Qué pasaría con 󰁳󰁵󰁭 (󰁦󰁩󰁬󰁴󰁥󰁲  (> 10) (󰁭󰁡󰁰 (*2) 󰁛2..10󰁝))? Bueno, como $ es asociativo por la derecha, 󰁦 (󰁧 (󰁺

13 de 16  

󰁸)) sería igual que 󰁦 $ 󰁧 $  󰁺 󰁸. Seguimos adelante y 󰁳󰁵󰁭 (󰁦󰁩󰁬󰁴󰁥󰁲  (> 10) (󰁭󰁡󰁰 (*2)  󰁛2..10󰁝))  puede ser escrito

como 󰁳󰁵󰁭 $ 󰁦󰁩󰁬󰁴󰁥󰁲 (> 10) $ 󰁭󰁡󰁰 (*2) 󰁛2..10󰁝. Pero aparte de eliminar los paréntesis, la existencia del operador $ también supone supone que podemos tratar la aplicación a plicación de funciones como una función más. De esta forma, podemos, podemos, por ejem e jemplo, plo, mapear mapear una lista de funciones: ghci ghci> > map ($ ( $ 3) [(4 [(4+), (10 (10* *), (^ ( ^2), sqrt] [7.0 7.0, ,30.0 30.0, ,9.0 9.0, ,1.7320508075688772 ]

Composición de funciones En matemáticas matemáticas la composición de funciones está definida como:

, que significa que al componer

dos funciones se crea una nueva que, cuando se llama llama con un parámetro, digamos x , es equivalente a llamar a g  con  con x  y  y luego llamar a f  con  con el resultado anterior. En Haskell la composición de funciones es prácticamente práctic amente lo mismo. mismo. Realizamos la composici composición ón de funciones con la función ., que está definida como:

(.) :: :: (b  (b -> -> c)  c) -> (a -> (a -> -> b)  b) -> -> a  a -> -> c  c f . g = \x -> -> f  f (g x)

Fíjate en la declaración de tipo. 󰁦 debe tener como parámetro un valor con c on el mismo mismo tipo que el valor devuelto por 󰁧. Así que la función resultante res ultante toma un parámetro del mismo tipo que toma 󰁧 y devuelve un valor del mismo tipo que devuelve 󰁦. La expresión expresión to ma un número, número, lo multiplica multiplica por tres tre s y 󰁮󰁥󰁧󰁡󰁴󰁥  . (‐3)  devuelve una función que toma luego lo niega. Uno de los usos de la composición de funciones es el de crear funciones al vuelo para ser pasadas a otras funciones. Claro, puedes usar lambdas pero muchas veces la composición de funciones es más clara y concisa. Digamos que tenemos una lista de números núm eros y queremos convertirlos todos en negativo negativos. s. Una forma de hacerlo sería obteniendo primero el núm número ero absoluto abso luto y luego negándolo, negándol o, algo así: a sí: ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3, [5,-3,-6,7,-3,2,-19,24] 2,-19,24] [-5,-3,-6,-7,-3,-2,-19,-24]

Fíjate que la función lambda lambda se pare parece ce a la definición de comp c omposició osiciónn de funciones. Usando la composición de funciones quedaría así: ghci> map (negate . abs) [5,-3,-6,7,-3, [5,-3,-6,7,-3,2,-19,24] 2,-19,24] [-5,-3,-6,-7,-3,-2,-19,-24]

¡Genial! La composición de funciones es asociativa a derechas, así que podemos componer varias funciones al mismo tiempo. La expresión 󰁦 (󰁧 (󰁺 󰁸)) es equivalente a (󰁦 . 󰁧 . 󰁺) 󰁸. T Teniendo eniendo esto en cuenta, podemos convertir: ghci> map (\xs -> negate (sum (tail xs))) [[1..5],[3.. [[1..5],[3..6],[1..7]] 6],[1..7]] [-14,-15,-27]

En esto:

14 de 16  

ghci> map (negate . sum . tail) [[1..5],[3..6] [[1..5],[3..6],[1..7]] ,[1..7]] [-14,-15,-27]

¿Y qué pasa con las funciones que toman varios parámetros? Bueno, si queremos usarlas en la composición de funciones, tenemos que aplicarlas parcialmente de forma que cada función tome un solo parámetro. 󰁳󰁵󰁭 (󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  5󰁠 󰁠(󰁭󰁡󰁸 6.7 8.9))  se puede escribir como (󰁳󰁵󰁭 . 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  5 . 󰁭󰁡󰁸 6.7) 8.9  o como 󰁳󰁵󰁭  . 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  5 . 󰁭󰁡󰁸  6.7 $ 8.9 .

Lo que sucede aquí es: se crea una función que toma 󰁭󰁡󰁸 6.7 y aplica 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  5 a ella. Luego se crea otra función que toma el resultado de lo anterior y realiz rea lizaa una suma. Finalmente, Finalmente, la función anterior anterio r es llamada con 8.9. Normalmente se lee como: Aplica 8.9 a 󰁭󰁡󰁸 6.7, luego aplica 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  5 y luego aplica 󰁳󰁵󰁭 al resultado anterior. Si quieres reescribir una expresión con un montón de paréntesis usando la composición de funciones, puedes empezar empezar poniendo el último parámetro de la función más externa después de $ y luego empezar a componer todas las demás funciones, escribiéndolas sin el último parámetro y poniendo . entre ellas. Si tienes 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  100 (󰁰󰁲󰁯󰁤󰁵󰁣󰁴  (󰁭󰁡󰁰 (*3) (󰁺󰁩󰁰󰁗󰁩󰁴󰁨 󰁭󰁡󰁸 󰁛1,2,3,4,5󰁝󰁠 󰁠󰁛4,5,6,7,8󰁝)))  puedes escribirlo también como 󰁲󰁥󰁰󰁬󰁩󰁣󰁡󰁴󰁥  100 . 󰁰󰁲󰁯󰁤󰁵󰁣󰁴  . 󰁭󰁡󰁰 (*3)  . 󰁺󰁩󰁰󰁗󰁩󰁴󰁨  󰁭󰁡󰁸 󰁛1,2,3,4,5󰁝  $ 󰁛4,5,6,7,8󰁝 . Si una expresión termina con 3 paréntesis, existen posibilidades de escribir la misma

expresión usando 3 composiciones de funciones. Otro uso com co mún de la composición de funciones es e s la definición definició n de funciones en el llamado llamado estilo esti lo libre de puntos. Echa un vistazo a esta función que escribimos anteriormente: sum'  :: sum'  :: (  (Num Num a)  a) => [a] => [a] -> -> a  a sum' xs sum'  xs = foldl (+ ( +) 0 xs

Nota

El término estilo libre de puntos  (  (point-free style   oo pointless style  en  en inglés) se originó en topología, topología, una rama de las matemáticas que trabaja con espacios compuestos de puntos y funciones entre estos espacios. Así que una función en estilo libre de puntos es una función que no menciona explícitamente los puntos (valores) del espacio sobre los que actua. Este término té rmino puede puede confun co nfundir dir a la gente ya que normalmente normalmente el estilo libre de puntos puntos imp i mplica lica utilizar el operador de composición de funciones, el e l cual se represe representa nta con un punto en Haskell. Haskell. 󰁸󰁳 está expuesta en ambos lados de la ecuación. ecuaci ón. Podemos eliminar eliminar 󰁸󰁳 de ambos lados gracias a la currificación, ya que 󰁦󰁯󰁬󰁤󰁬  (+) 0 es una función que toma una lista. Escribir Escri bir la función anterior como 󰁳󰁵󰁭' = 󰁦󰁯󰁬󰁤󰁬 (+) 0 se llama estilo libre

de puntos. ¿Cómo ¿Cómo escribimos es cribimos esto en estilo libre de punto? fn x fn  x = ceiling (negate (tan (cos (max 50 50 x))))  x))))

No podemos eliminar simplemente simplemente x de amb a mbos os lados. La 󰁸 en el cuerpo de la función tiene ti ene un paréntesis después de ella. 󰁣󰁯󰁳 (󰁭󰁡󰁸  50)  no tiene mucho sentido. No puedes calcular el coseno de una función. Lo que hacemos es expresar 󰁦󰁮 como

una composici composición ón de funciones. fn  fn  = ceiling . negate . tan . cos . max 50

¡Excelente! Muchas veces una composición de funciones es mucho más concisa y legible, ya que te hace pensar en funciones y como se pasan los parámetros entre ellas en lugar de pensar en los datos y como estos son transformados. Puedes utilizar funciones simples utilizar simples con co n la composición de funciones para crear funciones mucho más compleejas. jas. Sin embargo, embargo, muchas veces,, escribir veces escri bir una función en estilo libre de puntos pude ser menos legible si la función es muy compleja. compleja. Es por eso que se desaconseja el uso de la composición de funciones para cadenas de funciones muy largas. El estilo recomendable para estos casos es usar secciones 󰁬󰁥󰁴 para dar nombres nombres a resultados intermedios, dividiendo divi diendo el problema problema en sub-problemas sub-problemas y luego

15 de 16  

realizar una composici composición ón con todo ellos de forma fo rma que si alguien lo lee le encuentre el sentido. En la sección secci ón de mapeos y filtros, solventamos el problema problema de encontrar la suma de todos los cuadrados imp i mpares ares menores que 10.000. Aquí tienes como se vería la solución si la ponemos en una función: oddSquareSum oddSquareSum   :: ::   Integer oddSquareSum  oddSquareSum  = sum (takeWhile (< ( ->   Float Float   -> ->   Float Float   -> ->   Shape ghci> ghci > :t Rectangle Rectangle  Rectangle  :: ::   Float Float   -> ->   Float Float   -> ->   Float Float   -> ->   Float Float   -> ->   Shape

Bien, los constructores co nstructores de datos dato s son funciones como todo lo demás ¿Quíen lo hubiera pensado? V Vamos amos a hace hacerr una función que tome una figura y devuleva su superficie o área: surface :: surface  ::   Shape Shape   -> ->   Float surface ( surface  (Circle Circle   _   _   _   _   r) = pi * r ^ 2  r) surface ( surface  (Rectangle Rectangle x1  x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)

La primera cosa destacable aquí es la declaración de tipo. Dice que toma una figura y devuelve un valor en coma flotante. No podemos podemos escribir es cribir una declaración declaració n de tipo como C󰁩󰁲󰁣󰁬󰁥 ‐> F󰁬󰁯󰁡󰁴 ya que C󰁩󰁲󰁣󰁬󰁥  no es un tipo, 󰁓󰁨󰁡󰁰󰁥 si lo es. Del mismo modo no podemos declarar una función cuya declaración de tipo sea 󰁔󰁲󰁵󰁥 ‐> I󰁮󰁴. La siguiente cosa que podemos destacar es que podemos usar el ajuste de patrones con los constructores. Ya hemos utilizado el ajuste de patrones con constructores anteriormente (en realidad rea lidad todo el tiempo) cuando ajustam ajustamos os valores va lores como co mo 󰁛󰁝, F󰁡󰁬󰁳󰁥, 5, solo que esos valores no tienen campos. Simplemen Simplemente te escribimos esc ribimos el constructor y luego ligamos ligamos sus campos a nombres. Como estamos interesa interesados dos en el radio, realmente no nos importan los dos primeros valores que nos dicen donde está el círculo. ghci > surface $ Circle ghci> Circle   10 10   20 20   10 314.15927 ghci> ghci > surface $ Rectangle Rectangle   0 0 100 100   100 10000.0

Bien ¡Funciona! Pero si intentamos mostrar por pantalla C󰁩󰁲󰁣󰁬󰁥 10 20 5 en una sesión se sión de GHCi obtendremos un error. Esto sucede porque Haskell Haskell aún no sabe como representar represe ntar nuestro tipo ccon on una cadena. Recuerda que cuando intentamos mostrar un valor por pantalla, primero primero Haskell ejecuta la función 󰁳󰁨󰁯󰁷 para obtener la representación en texto de un dato y luego lo muestra muestra en la terminal. Para hacer que nuestro tipo 󰁓󰁨󰁡󰁰󰁥 forme parte de la clase de tipo 󰁓󰁨󰁯󰁷 hacemos esto:

data  data  Shape Shape   = Circle Circle   Float Float   Float Float   Float Float   | Rectangle Rectangle   Float Float   Float Float   Float Float   Float Float   deriving deriving (  (S S

No vamos a preocuparnos ahora a hora mismo acerca de derivar. Simplemente Simplemente diremos que que si añadimos añadimos 󰁤󰁥󰁲󰁩󰁶󰁩󰁮󰁧  (󰁓󰁨󰁯󰁷) al final de una declaración de tipo, automáticamente Haskell hace que ese tipo forme parte de la clase de tipos 󰁓󰁨󰁯󰁷. Así que ahora ya podemos hacer esto: ghci > Circle ghci> Circle   10 10   20 20   5 Circle  Circle  10.0 10.0   20.0 20.0   5.0 ghci> ghci > Rectangle Rectangle   50 50   230 230   60 60   90 Rectangle  Rectangle  50.0 50.0   230.0 230.0   60.0 60.0   90.0

Los constructores de datos son funciones, así que podemos mapearlos, aplicarlos parcialmente o cualquier otra cosa. Si queremos una lista de círculos concéntricos con diferente radio podemos escribir esto: ghci > map (Circle ghci> ( Circle   10 10   20 20) ) [4 [ 4, 5 ,6, 6] [Circle Circle   10.0 10.0   20.0 20.0   4.0 4.0, ,Circle Circle   10.0 10.0   20.0 20.0   5.0 5.0, ,Circle Circle   10.0 10.0   20.0 20.0   6.0 6.0, ,Circle Circle   10.0 10.0   20.0 20.0   6.0 6.0] ]

2 de 32  

Nuestro tipo de dato es e s bueno, pero podría se mejor. mejor. Vamos Vamos a crear cre ar un tipo de dato intermed inter medio io que defina un punto en espacio bidimensional. Luego lo usaremos para hacer nuestro tipo más evidente. data data   Point Point   = Point Point   Float Float   Float Float   deriving deriving (  (Show Show) ) data  data  Shape Shape   = Circle Circle   Point Point   Float Float   | Rectangle Rectangle   Point Point   Point Point   deriving deriving (  (Show Show) )

Te habrás dado cuenta de que hemos hemos usado el el m mismo ismo nombre nombre para el e l tipo que para el constructor constructo r de datos. No tiene tie ne nada de especial, es e s algo común usar el mismo mismo nombre que el del tipo si solo hay un constructor de datos. Así que ahora C󰁩󰁲󰁣󰁬󰁥 tiene dos campos, uno es el del tipo 󰁐󰁯󰁩󰁮󰁴 y el otro del tipo F󰁬󰁯󰁡󰁴. De esta forma es más fácil entender que es cada cosa. Lo mismo sucede para el rectángul rectá ngulo. o. Tenemos Tenemos que modificar nuestra función funció n 󰁳󰁵󰁲󰁦󰁡󰁣󰁥 para que refleje estos cambios. surface :: surface  ::   Shape Shape   -> ->   Float surface ( surface  (Circle Circle   _   _   r) = pi * r ^ 2  r) surface ( surface  (Rectangle Rectangle (  (Point Point x1  x1 y1) (Point ( Point x2  x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

Lo único que hemos cambiado han sido los patrones. patro nes. Hem Hemos os descartado desca rtado comp co mpletamente letamente el punto punto en el patrón del círculo. círc ulo. Por otra parte, parte , en el patrón del rectángu rectá ngulo, lo, simplemente simplemente hemos hemos usado un ajuste de patrones patro nes anidado para obtener o btener las coordenadas coo rdenadas de los puntos. Si hubiésemos querido hacer una referencia refer encia directamente direc tamente a los puntos por cualquier motivo podríamos haber utilizado un patrón como . ghci> ghci > surface (Rectangle (Rectangle (  (Point Point   0 0) (Point ( Point   100 100   100 100)) )) 10000.0 ghci> ghci > surface (Circle (Circle (  (Point Point   0 0) 24 24) ) 1809.5574

¿Cómo sería una función que desplaza desplaza una figura? Tomaría una figura, la cantidad c antidad que se debe desplazar desplazar en el e l eje x , la cantidad que se debe desplazar en el eje y  y  y devolvería devolver ía una nueva figura con las mismas mismas dimensiones pero desplaz des plazada. ada. nudge :: nudge  ::   Shape Shape   -> ->   Float Float   -> ->   Float Float   -> ->   Shape nudge ( nudge  (Circle Circle (  (Point Point x  x y) r) a b = Circle Circle (  (Point Point (x  (x+ +a) (y+ (y+b)) r nudge ( nudge  (Rectangle Rectangle (  (Point Point x1  x1 y1) (Point ( Point x2  x2 y2)) a b = Rectangle Rectangle (  (Point Point (x1  (x1+ +a) (y1+ (y1+b)) (

Bastante sencillo. s encillo. Añadimos Añadimos las cantidades a desplazar desplazar a los puntos que representan la posición posició n de las figuras.

ghci > nudge (Circle ghci> ( Circle (  (Point Point   34 34   34 34) ) 10 10) ) 5 10 Circle ( Circle  (Point Point   39.0 39.0   44.0 44.0) ) 10.0

Si no queremos trabajar directamente directa mente con puntos, puntos, podemos crear crea r funciones auxiliares que creen figuras de algú a lgúnn tamaño en el centro del eje e je de coordenadas coo rdenadas de modo que luego las podamos desplazar desplazar.. baseCircle  :: baseCircle  ::   Float Float   -> ->   Shape baseCircle r baseCircle  r = Circle Circle (  (Point Point    0 0 ) r baseRect :: baseRect  ::   Float Float   -> ->   Float Float   -> ->   Shape baseRect width baseRect  width height = Rectangle Rectangle (  (Point Point   0 0) (Point (Point width  width height)

ghci > nudge (baseRect 40 ghci> 40   100 100) ) 60 60   23 Rectangle ( Rectangle  (Point Point   60.0 60.0   23.0 23.0) ) (Point ( Point   100.0 100.0   123.0 123.0) )

Como es lógico, podemos exportar nuestros datos en los módulos. módulos. Para hacerlo, hace rlo, solo tenemos que escribir esc ribir el nombre del tipo juntos a las funciones exportadas, y luego añadirles unos paréntesis que contengan los constructores de datos que queramos que se exporten, separados por comas. Si queremos que se exporten todos los constructores de datos para un cierto

3 de 32  

tipo podemos usar ... Si quisiéramos exportar las funciones y tipos que acabamos de crear en un módulo, módulo, podríamos podríamos empezar con esto: module Shapes  Shapes module ( Point Point( (.. ..) ) , Shape Shape( (.. ..) ) , surface , nudge , baseCircle , baseRect ) where

Haciendo 󰁓󰁨󰁡󰁰󰁥 (..) estamos exportando todos los constructores de datos de 󰁓󰁨󰁡󰁰󰁥, lo que significa que cualquiera que importe nuestro módulo puede crear figuras usando los constructores C󰁩󰁲󰁣󰁬󰁥 y 󰁒󰁥󰁣󰁴󰁡󰁮󰁧󰁬󰁥 . Sería lo mismo que escribir 󰁓󰁨󰁡󰁰󰁥 (󰁒󰁥󰁣󰁴󰁡󰁮󰁧󰁬󰁥,  C󰁩󰁲󰁣󰁬󰁥) .

También podríamos podríamos optar o ptar por no exportar ningún constructor de datos dato s para 󰁓󰁨󰁡󰁰󰁥 si  simp mplem lemente ente escribiendo es cribiendo 󰁓󰁨󰁡󰁰󰁥 en dicha sentencia. De esta forma, fo rma, quien quien importe nuestro módulo módulo solo podrá crear figuras utilizando utilizando las funciones auxiliares 󰁢󰁡󰁳󰁥C󰁩󰁲󰁣󰁬󰁥 y 󰁢󰁡󰁳󰁥󰁒󰁥󰁣󰁴. D󰁡󰁴󰁡.󰁍󰁡󰁰  utiliz  utilizaa este método. No No puedes crear cre ar un diccionario utilizando utilizando 󰁍󰁡󰁰.󰁍󰁡󰁰 󰁛(1,2),(3,4)󰁝  ya que no se exporta el constructor de datos. Sin emb e mbargo, argo, podemos crear un diccionario utilizando funciones funciones auxiliares como c onstructores de datos son so n simples es funciones que toman los los camp ca mpos os del tipo como co mo parámetros parámetros y 󰁍󰁡󰁰.󰁦󰁲󰁯󰁭󰁌󰁩󰁳󰁴 . Recuerda, los constructores devuelven un valor de un cierto tipo (como 󰁓󰁨󰁡󰁰󰁥) como resultado. Así que cuando elegimos no exportarlos, estamos esta mos previniendo que la gente que importa importa nuestro módulo módulo pueda utilizar utilizar esas es as funciones, pero si alguna otra función devuelve devuelve el tipo que estamos exportando, las podemos utilizar para crear nuestros propios valores de ese tipo. No exportar los constructores de datos de un tipo de dato lo hace más abstracto en el sentido de que oculta su implemen imp lementació tación. n. Sin embargo, embargo, los usuarios usuario s del módulo módulo no podrán usar el ajuste de patrones patro nes sobre ese e se tipo. ti po.

Sintaxis de registro Bien, se nos ha dado la tarea de crear un tipo que describa a una persona. La información que queremos queremos almacenar de ca cada da persona es: es : nombre, nombre, apellidos, edad, altura, número de teléfono y el sabor de su helado favorito. No se nada acerca de ti, pero para mi es todo lo que necesito saber de una persona. ¡V ¡Vamos amos allá! data  Person   = Person Person   String String   String String   Int Int   Float Float   String String   String String   deriving deriving (  (Show Show) ) data  Person

Vale. El primer campo es el e l nombre, nombre, el segund se gundoo eell apellido, el tercero su edad y seguimos contando. Vamos Vamos a crear c rear una persona. ghci > let ghci> let guy  guy = Person Person   "Buddy" "Buddy"   "Finklestein" "Finklestein"   43 43   184.2 184.2   "526-2928" "526-2928"   "Chocolate" ghci> ghci > guy Person  Person  "Buddy" "Buddy"   "Finklestein" "Finklestein"   43 43   184.2 184.2   "526-2928" "526-2928"   "Chocolate"

Parece interesante, pero desde luego no muy legible legible ¿Y si queremos crear cre ar una función que obtenga información por separado de una persona? perso na? Una función que que obtenga el nombre de una persona, otra función que obtenga el apell apellido, ido, etc. Bueno, las tendríamos que definir así: firstName  :: firstName  ::   Person Person   -> ->   String firstName ( firstName  (Person Person firstname  firstname _   _   _   _     _  _     _  _   _   _  ) = firstname

4 de 32  

lastName   :: ::   Person Person   -> ->   String lastName lastName ( lastName  (Person Person     _  _   lastname  _   lastname _   _   _     _  _     _  _  ) = lastname age :: age  ::   Person Person   -> ->   Int age ( age  (Person Person   _   _     _  _   age  _   age _     _  _   _   _  ) = age height  :: height  ::   Person Person   -> ->   Float height ( height  (Person Person   _   _     _  _     _  _   height  _   height _   _   _  ) = height phoneNumber :: phoneNumber  ::   Person Person   -> ->   String phoneNumber ( phoneNumber  (Person Person     _  _     _  _   _   _     _  _   number  _   number _  ) = number flavor  :: flavor  ::   Person Person   -> ->   String flavor ( flavor  (Person Person   _   _     _  _     _  _   _   _   _   _   flavor) = flavor  flavor)

¡Fiuuu!! La verdad es que no me divertido ¡Fiuuu diverti do escribiendo escribie ndo esto. A parte de que este es te método sea un lío lío y un poco ABURRIDO ABURRIDO de escribir, funciona. ghci > let ghci> let guy  guy = Person Person   "Buddy" "Buddy"   "Finklestein" "Finklestein"   43 43   184.2 184.2   "526-2928" "526-2928"   "Chocolate" ghci> ghci > firstName guy "Buddy" ghci> ghci > height guy 184.2 ghci> ghci > flavor guy "Chocolate"

Ahora es cuando c uando piensas: debe de haber un método mej mejor. or. Pues no, lo siento mucho. Estaba de broma :P Si que lo hay. Los creadores de Haskell fueron muy inteligentes y anticiparon este escenario. Incluyeron un método método alternativo alternativ o de definir tipos de dato. Así es como c omo podríamos podríamos conseguir co nseguir la misma misma funcionalidad con la sintaxis de registro. data  Person data  Person   = Person Person {  {   ,   ,   ,   ,   ,   }

firstName :: ::   String lastName :: ::   String age :: ::   Int height :: ::   Float phoneNumber :: ::   String flavor :: ::   String deriving ( deriving  (Show Show) )

En lugar lugar de nombrar los campos uno tras otro o tro separados separa dos por espacios, es pacios, utilizamos un par par de llaves. Dentro, Dentro , primero escribimos el e l nombre nombre de un campo, por ejemplo 󰁦󰁩󰁲󰁳󰁴󰁎󰁡󰁭󰁥 y luego escribimos escr ibimos unos dobles puntos puntos :: (también conocido como co mo Paamayim Nekudotayim  xD)  xD) y luego especificamos especific amos el tipo. El tipo de dato

resultante es eexactamente xactamente el mismo. mismo. La principal

diferencia es que de esta forma se crean funciones que obtienen esos campos del tipo de dato. Al usar la sintaxis de registro con este tipo de dato, Haskell automáticamente crea estas funciones: 󰁦󰁩󰁲󰁳󰁴󰁎󰁡󰁭󰁥, 󰁬󰁡󰁳󰁴󰁎󰁡󰁭󰁥, 󰁡󰁧󰁥, 󰁨󰁥󰁩󰁧󰁨󰁴, 󰁰󰁨󰁯󰁮󰁥󰁎󰁵󰁭󰁢󰁥󰁲  y 󰁦󰁬󰁡󰁶󰁯󰁲 .

ghci > :t flavor ghci> flavor  flavor  :: ::   Person Person   -> ->   String ghci> ghci > :t firstName firstName  firstName  :: ::   Person Person   -> ->   String

Hay otro beneficio benefic io cuando utilizamos utilizamos la sintaxis si ntaxis de registro. registro . Cuando derivamos 󰁓󰁨󰁯󰁷 para un tipo, mostrará los datos de forma diferente si utilizamos utilizamos la sintaxis de registro re gistro para definir defi nir e instanciar instancia r el tipo. Supongamos Supongamos que tenemos un tipo que representa un coche. Queremos mantener un registro de la compañía que lo hizo, el nomb nombre re del modelo y ssuu años de producción. Mira.

5 de 32  

data   Car Car   = Car Car   String String   String String   Int Int   deriving deriving (  (Show Show) ) data

ghci > Car ghci> Car   "Ford" "Ford"   "Mustang" "Mustang"   1967 Car  Car  "Ford" "Ford"   "Mustang" "Mustang"   1967

Si lo definimos usando la sintaxis de registro, podemos crear un coche nuevo de esta forma: data  data  Car Car   = Car Car {company  {company :: ::   String String, , model :: ::   String String, , year :: ::   Int Int} } deriving deriving (  (Show Show) )

ghci > Car ghci> Car {company  {company= ="Ford" "Ford", , model= model="Mustang" "Mustang", , year= year=1967 1967} } Car {company Car  {company = "Ford" "Ford", , model = "Mustang" "Mustang", , year = 1967 1967} }

Cuando creamos un coche nuevo, no hace falta poner los ca camp mpos os en el e l orden adecuado mientras que los pongamos pongamos todos. Pero si no usamos la sintaxis de registro debemos especificarlos en su orden correcto. Utiliza la sintaxis de registro cuando un constructor tenga varios campos y no sea obvio que campo es cada uno. Si definimos el tipo de un vector 3D como 󰁤󰁡󰁴󰁡  󰁖󰁥󰁣󰁴󰁯󰁲 =  󰁖󰁥󰁣󰁴󰁯󰁲  I󰁮󰁴 I󰁮󰁴 I󰁮󰁴, es bastante obvio que esos campos son las componentes del vector. Sin embargo, en nuestros tipo 󰁐󰁥󰁲󰁳󰁯󰁮 y C󰁡󰁲, no es tan obvio y nos beneficia mucho el uso de esta sintaxis.

Parámetros de tipo Un constructor de datos puede tomar algunos valores como c omo parámetros y producir un nuevo valor. Por ejemplo, el constructor C󰁡󰁲 toma tres valores v alores y produce un valor del tipo coche. De forma similar, uunn constructor de tipos puede tomar tipos como parámetros y producir nuevos tipos. Esto puede parecer un poco recursivo al principio, pero no es nada complicado. Si has utilizado las plantillas de C++  te  te será familiar. Para obtener una imagen clara de como los parámetros de tipo funcionan en realidad, vamos va mos a ver ve r un ejemplo ejemplo de como co mo un tipo que ya conocemos c onocemos es implementado. implementado. data  Maybe data  Maybe a  a = Nothing Nothing   | Just Just a  a

La 󰁡 es un parámetro de tipo. Debido a que hay un parámetro de tipo iinvolucrado nvolucrado en esta definición, definic ión, llamamos llamamos a 󰁍󰁡󰁹󰁢󰁥 un constructor de tipos. ti pos. Dependiendo de lo que queramos que este queramos est e tipo contenga co ntenga cuando un valor no es 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 , este tipo puede acabar produciendo tipos como 󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴, 󰁍󰁡󰁹󰁢󰁥 C󰁡󰁲, 󰁍󰁡󰁹󰁢󰁥 󰁓󰁴󰁲󰁩󰁮󰁧, etc. etc . Ningún valor puede tener un tipo que sea s ea simp s implem lemente ente 󰁍󰁡󰁹󰁢󰁥, ya que eso no es un tipo por si mismo, es un constructor de tipos. ti pos. Para que sea un tipo real rea l que algún algún valor pueda tener, tiene que tener todos los parámetros de tipo definidos. Si pasamos C󰁨󰁡󰁲 como parámetro de tipo a 󰁍󰁡󰁹󰁢󰁥, obtendremos el tipo 󰁍󰁡󰁹󰁢󰁥 C󰁨󰁡󰁲. Por ejemplo, el valor J󰁵󰁳󰁴 '󰁡' tiene el tipo 󰁍󰁡󰁹󰁢󰁥 C󰁨󰁡󰁲. Puede que no lo sepas, pero utilizamos utilizamos un tipo que tenía un parámetro parámetro de tipo ti po antes de que empezáramos empezáramos a utilizar utilizar el e l tipo 󰁍󰁡󰁹󰁢󰁥. Ese tipo es el tipo lista. Aunque hay un poco decoración sintáctica, el tipo lista toma un parámetro para producir un tipo concreto. Los valores pueden tener un tipo 󰁛I󰁮󰁴󰁝, un tipo 󰁛C󰁨󰁡󰁲󰁝, 󰁛󰁛󰁓󰁴󰁲󰁩󰁮󰁧󰁝󰁝 , etc. pero no puede haber un valor cuyo tipo sea se a simplemente simplemente 󰁛󰁝. Vamos a jugar un poco con c on el tipo 󰁍󰁡󰁹󰁢󰁥.

6 de 32  

ghci> > Just Just   "Haha" ghci Just  Just  "Haha" ghci> ghci > Just Just   84 Just  Just  84 ghci> ghci > :t Just Just   "Haha" Just  Just  "Haha" "Haha"   :: ::   Maybe Maybe [  [Char Char] ] ghci> ghci > :t Just Just   84 Just  Just  84 84   :: :: (  (Num Num t)  t) => =>   Maybe Maybe t  t ghci> ghci > :t Nothing Nothing  Nothing  :: ::   Maybe Maybe a  a ghci> ghci > Just Just   10 10   :: ::   Maybe Maybe   Double Just  Just  10.0

Los parámetros de tipo son útiles ya que nos permiten crear diferentes tipos dependiendo del tipo que queramos almacenar en nuestros tipos de datos (valga (v alga la redundancia). redundancia). Cuando hacemos :󰁴 J󰁵󰁳󰁴 "H󰁡󰁨󰁡" el motor de inferencia de tipos deduce que el tipo debe ser 󰁍󰁡󰁹󰁢󰁥 󰁛C󰁨󰁡󰁲󰁝 , ya que la 󰁡 en J󰁵󰁳󰁴 󰁡 es una cadena, ca dena, luego el 󰁡 en 󰁍󰁡󰁹󰁢󰁥 󰁡 debe ser se r también una cadena. Como habrás visto el tipo de 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 es 󰁍󰁡󰁹󰁢󰁥 󰁡. Su tipo es polimórfico. Si S i una función requiere un 󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴 como parámetro le podemos pasar un 󰁎󰁯󰁴󰁨󰁩󰁮󰁧  ya que no contiene co ntiene ningún valor. El tipo 󰁍󰁡󰁹󰁢󰁥 󰁡 puede comportarse como un 󰁍󰁡󰁹󰁢󰁥 c omo un I󰁮󰁴 o como un D󰁯󰁵󰁢󰁬󰁥. De forma similar el tipo de las listas vacías I󰁮󰁴, de la misma forma que 5 puede comportarse como es 󰁛󰁡󰁝. Una lista vacía puede comportarse como cualquier otra lista. Por eso podemos hacer cosas como 󰁛1,2,3󰁝  ++ 󰁛󰁝 y 󰁛"󰁨󰁡","󰁨󰁡","󰁨󰁡"󰁝  ++ 󰁛󰁝.

El uso de parámetros de tipo ti po nos puede beneficiar, pero so solo lo en los casos cas os que tenga sentido. Normalmente Normalmente los utilizamos utilizamos cuando nuestro tipo de dato funcionará igual sin importar el tipo de dato que co contenga, ntenga, justo como nuestro 󰁍󰁡󰁹󰁢󰁥 󰁡. Si nuestro tipo es como una especie de caja, es un buen lugar para usar los parámetros de tipo. Podríamos cambiar nuestro tipo C󰁡󰁲 de: data  Car data  Car   = Car Car {  {   ,   ,   }

company :: ::   String model :: ::   String year :: ::   Int deriving ( deriving  (Show Show) )

A: data  Car data  Car a  a b c = Car Car {  { company :: :: a  a   , model :: :: b  b    

, year :: :: c  c } deriving deriving (  (Show Show) )

Pero ¿Tiene algún beneficio? La respuesta es: probablemente no, ya que al final acabaremos escribiendo funciones que solo funcionen con el tipo C󰁡󰁲 󰁓󰁴󰁲󰁩󰁮󰁧 󰁓󰁴󰁲󰁩󰁮󰁧 I󰁮󰁴. Por ejemplo, ejemplo, dada la primera definición definici ón de C󰁡󰁲, podríamos crear una función que mostrara mostrara las propiedades de un coche con c on un pequeño pequeño texto: tellCar :: tellCar  ::   Car Car   -> ->   String tellCar ( tellCar  (Car Car {company  {company = c, model = m, year = y}) = "This "  " ++ ++ c  c ++ ++   " "  " ++ ++ m  m ++ ++   " was

ghci > let ghci> let stang  stang = Car Car {company  {company= ="Ford" "Ford", , model= model="Mustang" "Mustang", , year= year=1967 1967} } ghci> ghci > tellCar stang "This Ford Mustang was made in 1967"

¡Una ¡Una función mu muyy bonita! La L a declaración declaraci ón de tipo es simple simple y funciona perfectamente. Ahora ¿Cómo sería si C󰁡󰁲 fuera en realidad C󰁡󰁲 󰁡 󰁢 󰁣?

7 de 32  

tellCar   :: :: (  (Show Show a)  a) => =>   Car Car   String String   String String a  a -> ->   String tellCar tellCar ( tellCar  (Car Car {company  {company = c, model = m, year = y}) = "This "  " ++ ++ c  c ++ ++   " "  " ++ ++ m  m ++ ++   " was

Tenemos que forzar a que la función tome un C󰁡󰁲 del tipo (󰁓󰁨󰁯󰁷 󰁡) => C󰁡󰁲 󰁓󰁴󰁲󰁩󰁮󰁧 󰁓󰁴󰁲󰁩󰁮󰁧 󰁡. Podemos ver como la definición de tipo es e s mucho más más comp co mplicada licada y el e l único beneficio que hemos obtenido es que podamos podamos usar cualquier tipo que sea una instancia de la clase de tipos 󰁓󰁨󰁯󰁷 como parámetro 󰁣. ghci > tellCar (Car ghci> (Car   "Ford" "Ford"   "Mustang" "Mustang"   1967 1967) ) "This Ford Mustang was made in 1967" ghci > tellCar (Car ghci> (Car   "Ford" "Ford"   "Mustang" "Mustang"   "nineteen sixty seven") seven") "This Ford Mustang was made in \" \"nineteen nineteen sixty seven\" seven\"" " ghci> ghci > :t Car Car   "Ford" "Ford"   "Mustang" "Mustang"   1967 Car  Car  "Ford" "Ford"   "Mustang" "Mustang"   1967 1967   :: :: (  (Num Num t)  t) => =>   Car Car [  [Char Char] ] [Char [ Char] ] t ghci> ghci > :t Car Car   "Ford" "Ford"   "Mustang" "Mustang"   "nineteen sixty seven" Car  Car  "Ford" "Ford"   "Mustang" "Mustang"   "nineteen sixty seven"  seven" :: ::   Car Car [  [Char Char] ] [Char [ Char] ] [Char [ Char] ]

A la hora de la verdad, acabaríamos ac abaríamos utilizando utilizando C󰁡󰁲 󰁓󰁴󰁲󰁩󰁮󰁧  󰁓󰁴󰁲󰁩󰁮󰁧 I󰁮󰁴 la mayor parte del tiempo y nos daríamos cuenta de que parametrizar el tipo C󰁡󰁲 realmente no importa. Normalmente utilizamos utiliz amos los parámetros de tipo ti po cuando el tipo que está contenido dentro del tipo de dato no es realmente importante a la hora de trabajar con éste. Una lista de cosas es una lista de cosas y no importa imp orta que sean se an esas cosas, cosa s, funcionará igual. i gual. Si queremos queremos sumar una lista de números, mas tarde podemos podem os especifica es pecificarr en la propia función de suma de que queremos específicamente una lista de números. Lo mismo pasa con 󰁍󰁡󰁹󰁢󰁥. 󰁍󰁡󰁹󰁢󰁥 representa la opción de tener o no tener un valor. Realmente Realm ente no importa de que tipo sea ese valor. va lor. Otro ejemplo ejemplo de un tipo parametrizado que que ya conocemos es el tipo 󰁍󰁡󰁰 󰁫 󰁶 de D󰁡󰁴󰁡.󰁍󰁡󰁰. 󰁫 es el tipo para las claves del diccionario mientras que 󰁶 es el tipo de los valores. Este es un buen ejemplo ejemp lo en donde los parámetros de tipo son s on útiles. Al tener los diccionari diccionarios os parametrizados nos permiten asociar cualquier tipo con cualquier otro tipo, siempre que la clave del tipo sea de la clase de tipos 󰁏󰁲󰁤. Si estuviéramos definiendo el tipo diccionario podríamos añadir una restricción de clase en la definición: data ( data  (Ord Ord k)  k) => =>   Map Map k  k v = ...

Sin embargo, existe un consenso co nsenso en eell mundo mundo Haskell de de que nunca debemos debe mos añadir restricciones de clase a las definiciones definici ones de tipo. ¿Por qué? Bueno, porque no nos beneficia mucho, pero al final acabamos escribiendo más restricciones de clase, incluso aunque no las necesitemos. Si ponemos o no podemos la restricción de clase 󰁏󰁲󰁤 󰁫 en la definición de tipo de

󰁍󰁡󰁰 󰁫 󰁶, tendremos que poner de todas formas la restricción de clase en las funciones que asuman que las claves son

ordenables. Pero si no ponemos la restricción en la definición de tipo, no tenemos que poner (󰁏󰁲󰁤 󰁫) => en la declaración de tipo de las funciones que no les importe si la clave puede es ordenable o no. Un ejemp ejemplo lo de esto sería s ería la función 󰁴󰁯󰁌󰁩󰁳󰁴 que simplemente convierte un diccionario en una lista de asociación. Su declaración de tipo es 󰁴󰁯󰁌󰁩󰁳󰁴 :: 󰁍󰁡󰁰 󰁫 󰁡 ‐> 󰁛(󰁫, 󰁡)󰁝. Si 󰁍󰁡󰁰 󰁫 󰁶 tuviera una restricción en su declaración, el tipo de 󰁴󰁯󰁌󰁩󰁳󰁴  debería haber sido 󰁴󰁯󰁌󰁩󰁳󰁴  :: (󰁏󰁲󰁤 󰁫) => 󰁍󰁡󰁰 󰁫 󰁡 ‐> 󰁛(󰁫, 󰁡)󰁝 aunque la función función no necesite nece site comp c omparar arar ninguna clave.

Así que no pongas restricciones de clase en las declaraciones de tipos aunque tenga sentido, ya que al final las vas a tener que poner de todas formas en las declaraciones de tipo de las funciones. Vamos a implementar implementar un tipo para vectores ve ctores 3D y crear cre ar algunas operaciones operacio nes con ellos. Vamos Vamos a usar un tipo parametrizado ya que, aunque normalm normalmente ente contendrá números, queremos que soporte varios va rios titipos pos de ellos. data  data  Vector Vector a  a = Vector Vector a  a a a deriving deriving (  (Show Show) )

8 de 32  

vplus   :: :: (  (Num Num t)  t) => =>   Vector Vector t  t -> ->   Vector Vector t  t -> ->   Vector Vector t  t vplus (Vector Vector i  i j k) `vplus` (Vector ( Vector l  l m n) = Vector Vector (i  (i+ +l) (j+ (j+m) (k+ (k+n) vectMult :: vectMult  :: (  (Num Num t)  t) => =>   Vector Vector t  t -> -> t  t -> ->   Vector Vector t  t (Vector Vector i  i j k) `vectMult` m = Vector Vector (i  (i* *m) (j* (j*m) (k* (k*m) scalarMult  :: scalarMult  :: (  (Num Num t)  t) => =>   Vector Vector t  t -> ->   Vector Vector t  t -> -> t  t (Vector Vector i  i j k) `scalarMult` (Vector (Vector l  l m n) = i  i* *l + j  j* *m + k  k* *n

󰁶󰁰󰁬󰁵󰁳  sirve para sumar dos vectores. Los vectores son sumados simplemente sumando sus correspondientes

componentes. 󰁳󰁣󰁡󰁬󰁡󰁲󰁍󰁵󰁬󰁴  calcula el producto escalar de dos vectores y 󰁶󰁥󰁣󰁴󰁍󰁵󰁬󰁴  calcula el producto de un vector y un escalar. Estas funciones pueden operar con tipos como  󰁖󰁥󰁣󰁴󰁯󰁲 I󰁮󰁴,  󰁖󰁥󰁣󰁴󰁯󰁲 I󰁮󰁴󰁥󰁧󰁥󰁲,  󰁖󰁥󰁣󰁴󰁯󰁲  F󰁬󰁯󰁡󰁴 o cualquier otra cosa mientras 󰁡 de  󰁖󰁥󰁣󰁴󰁯󰁲 󰁡 sea miembro de clase de tipos 󰁎󰁵󰁭. También, si miras la declaración de tipo de estas funciones, veras que solo pueden operar con vectores ve ctores del mismo mismo tipo y los números involucrados (como (c omo en 󰁶󰁥󰁣󰁴󰁍󰁵󰁬󰁴 ) también deben ser del mismo tipo que el que contengan los vectores. Fíjate en que no hemos puesto una restricción de clase 󰁎󰁵󰁭 en la declaración del tipo  󰁖󰁥󰁣󰁴󰁯󰁲, ya que deberíamos haberlo repetido también en las declaraciones de las funciones. Una vez más, más, es muy muy importante distinguir entre constructores constructor es de datos y constructores constructo res de tipo. ti po. Cuando declaramos declaramos un tipo de dato, la parte anterior al = es el constructor de tipos, mientras que la parte que va después (posiblemente separado por 󰁼) son los constructores de datos. Dar a una función el tipo  󰁖󰁥󰁣󰁴󰁯󰁲 󰁴 󰁴 󰁴 ‐>  󰁖󰁥󰁣󰁴󰁯󰁲 󰁴 󰁴 󰁴 ‐> 󰁴 sería incorrecto ya que hemos usado tipos en la declaración y el constructor de tipos vector toma un solo parámetro, mientras que el constructor de datos toma tres. Vamos a jugar un poco con los vectores: ghci > Vector ghci> Vector   3 5 8 `vplus` Vector Vector    9  2   8 Vector  Vector  12 12   7 16 ghci> ghci > Vector Vector   3 5 8 `vplus` Vector Vector   9 2 8 `vplus` Vector Vector   0   2  3 Vector  Vector  12 12   9 19 ghci> ghci > Vector Vector   3 9 7 `vectMult` 10 Vector  Vector  30 30   90 90   70 ghci> ghci > Vector Vector   4 9 5 `scalarMult` Vector Vector   9.0 9.0   2.0 2.0   4.0 74.0 ghci> ghci > Vector Vector   2 9 3 `vectMult` (Vector (Vector   4 9 5 `scalarMult `scalarMult` ` Vector Vector   9   2  4) Vector  Vector  148 148   666 666   222

Instancias derivadas En la sección Clases de tipos paso a paso (1ª parte), explicamos las bases de las clases de tipo. Dijimos Dijim os que una clase de tipos es una especie de interfaz i nterfaz que que define un comportamiento. Un tipo puede ser una instancia de esa clase si soporta ese comportamiento. Ejemplo: El tipo I󰁮󰁴 es una instancia de la clase E󰁱, ya que la clase de tipos E󰁱 define el comp co mportamiento ortamiento de cosas cos as que se pueden equiparar. equiparar. Y como los enteros se pueden equiparar, I󰁮󰁴 es parte de la clase E󰁱. La utilidad real está en las funciones que actúan como interfaz interfa z de E󰁱, que son == y /=. Si un tipo forma parte de la clase E󰁱, podemos usar las funciones como == con valores de ese tipo. Por este motivo, expresiones como 4 == 4 y "󰁦󰁯󰁯" /= "󰁢󰁡󰁲"  son correctas.

Mencionamos también que que las cclases lases de tipos ti pos suelen ser confund c onfundidas idas con co n las clases de lenguajes lenguajes como Java, Python, C++ y demás, cosa que más tarde desconcierta a la gente. En estos lenguajes, las clases son como un modelo del cual podemos crear objetos que contienen un estado y pueden hacer realizar algunas acciones. Las clases de tipos son más bien como las interfaces. No creamos instancias a partir de las interfaces. i nterfaces. En su lugar, lugar, primero primero creamos c reamos nuestro tipo de dato y luego pensamos como qué puede comportarse comportarse.. Si puede comportarse como co mo algo algo que puede ser equiparado, e quiparado, hacemos que sea miembro miem bro de la clase E󰁱. Si puede ser puesto en algún orden, hacemos hacemos que se seaa miembro miembro de la clase 󰁏󰁲󰁤.

Más adelante veremos ver emos como podemos podemos hacer hace r manualmen manualmente te que nuestros tipos ti pos sean sea n una instancia de una clase de tipos

9 de 32  

implemen imp lementando tando las funciones que esta es ta define. Pero Per o ahora, vamos a ver ve r como Haskell puede puede automáticamente hacer que nuestros tipos pertenezcan a una de las siguientes clases: E󰁱, 󰁏󰁲󰁤, E󰁮󰁵󰁭, B󰁯󰁵󰁮󰁤󰁥󰁤, 󰁓󰁨󰁯󰁷 y 󰁒󰁥󰁡󰁤. Haskell puede puede derivar deriva r el comportamiento de nuestros tipos en estos contextos si usamos la palabra clave 󰁤󰁥󰁲󰁩󰁶󰁩󰁮󰁧 cuando los definimos. Considera el siguiente tipo de dato: data   Person Person   = Person Person {  { firstName :: ::   String data   , lastName ::  :: String   , age :: ::   Int   }

Describe a una persona. perso na. Vamos Vamos a asum a sumirir que ninguna persona tiene tie ne la misma combinació combinaciónn de nombre, nombre, apellid a pellidoo y edad. Ahora, si tenemos registradas a dos personas ¿Tiene sentido saber si estos dos registros pertenecen a la misma persona? Parece que sí. Podemos compararlos por igualdad y ver si son iguales o no. Por esta razón tiene sentido que este tipo se miembro de la clase de tipo E󰁱. Derivamos la instancia: data  Person data  Person   = Person Person {  {   ,   ,   }

firstName :: ::   String lastName :: ::   String age :: ::   Int deriving ( deriving  (Eq Eq) )

Cuando derivamos una instancia de E󰁱 para un tipo y luego intentamos comparar dos valores de ese tipo usando == o /=, Haskell comprobará si los constructores c onstructores de tipo coinciden coi nciden (aunque (aunque aquí solo hay un constructor de tipo) y luego comprobará comprobará si todos los campos de ese constructor coinciden utilizando el operador = para cada par de campos. Solo tenemos que tener en cuenta una cosa, todos los camp c ampos os del tipo deben ser también miembros miembros de la clase de tipos t ipos E󰁱. Como 󰁓󰁴󰁲󰁩󰁮󰁧  y I󰁮󰁴 ya son miembros, miem bros, no hay ningún problema. problema. Vamos Vamos a ccomp omprobar robar nuestra instancia E󰁱. ghci > let ghci> let mikeD  mikeD = Person Person {firstName  {firstName = "Michael" "Michael", , lastName = "Diamond" "Diamond", , age = 43 43} } ghci> ghci > let let adRock  adRock = Person Person {firstName  {firstName = "Adam" "Adam", , lastName = "Horovitz" "Horovitz", , age = 41 41} } ghci> ghci > let let mca  mca = Person Person {firstName  {firstName = "Adam" "Adam", , lastName = "Yauch" "Yauch", , age = 44 44} } ghci> ghci > mca == == adRock  adRock False ghci> ghci > mikeD == == adRock  adRock False ghci> ghci > mikeD == == mikeD  mikeD True ghci> ghci > mikeD == ==   Person Person {firstName  {firstName = "Michael" "Michael", , lastName = "Diamond" "Diamond", , age = 43 43} } True

Como ahora 󰁐󰁥󰁲󰁳󰁯󰁮 forma parte de la clase E󰁱, podemos utilizarlo como co mo 󰁡 en las funciones que tengan una restricción de clase del tipo E󰁱 󰁡 en su declaración, como 󰁥󰁬󰁥󰁭. ghci > let ghci> let beastieBoys  beastieBoys = [mca, adRock, mikeD] ghci> ghci > mikeD `elem` beastieBoys True

Las clases de tipos 󰁓󰁨󰁯󰁷 y 󰁒󰁥󰁡󰁤 son para cosas que pueden ser convertidas a o desde cadenas, respectivamente. Como pasaba con E󰁱, si un constructor de tipos tiene campos, su tipo debe ser miembro de la clase` 󰁓󰁨󰁯󰁷 o 󰁒󰁥󰁡󰁤 si querem queremos os que también forme parte de estas clases. Vamos a hacer que nuestro tipo de dato 󰁐󰁥󰁲󰁳󰁯󰁮 forme parte también de las clases 󰁓󰁨󰁯󰁷 y 󰁒󰁥󰁡󰁤. data  Person data  Person   = Person Person {  { firstName :: ::   String   , lastName ::  :: String

 

, age :: ::   Int

10 de 32  

 

} deriving deriving (  (Eq Eq, , Show Show, , Read Read) )

Ahora podemos podemos mostrar una persona por la terminal. ghci > let ghci> let mikeD  mikeD = Person Person {firstName  {firstName = "Michael" "Michael", , lastName = "Diamond" "Diamond", , age = 43 43} } ghci> ghci > mikeD Person {firstName Person  {firstName = "Michael" "Michael", , lastName = "Diamond" "Diamond", , age = 43 43} } ghci> ghci > "mikeD is: "  " ++ ++ show  show mikeD "mikeD is: Person {firstName = \" \"Michael Michael\" \", , lastName = \" \"Diamond Diamond\" \", , age = 43}"

Si hubiésemos intentado mostrar en la terminal una persona antes de hacer que el tipo 󰁐󰁥󰁲󰁳󰁯󰁮  formara parte de la clase hubiera quejado, quejado, diciéndonos dicié ndonos que no sabe como representar represe ntar una persona con una ca cadena. dena. Pero ahora a hora que 󰁓󰁨󰁯󰁷, Haskell se hubiera hemos derivado la clase 󰁓󰁨󰁯󰁷 ya sabe como hacerlo. 󰁒󰁥󰁡󰁤 es prácticamente la clase inversa de 󰁓󰁨󰁯󰁷. 󰁓󰁨󰁯󰁷 sirve para convertir nuestro tipo a una cadena, 󰁒󰁥󰁡󰁤  sirve para

convertir una cadena a nuestro tipo. Aunq Aunque ue recuerda que cuando uses la función 󰁲󰁥󰁡󰁤 hay que utilizar una una anotación anotac ión de tipo explícita para decirle a Haskell qque ue tipo queremos como resultado. Si no ponemos ponemos el tipo ti po que queremos queremos como co mo resultado explícitamente, explícitam ente, Haskell no sabrá sa brá que tipo queremos. queremos. ghci > read "Person {firstName =\" ghci> =\"Michael Michael\" \", , lastName = =\" \"Diamond Diamond\" \", , age = 43}"  43}" :: ::   Pers Person {firstName Person  {firstName = "Michael" "Michael", , lastName = "Diamond" "Diamond", , age = 43 43} }

No hace falta utilizar una anotación de tipo explícita en caso de que usemos el resultado de la función 󰁲󰁥󰁡󰁤 de forma que Haskell pueda pueda inferir el tipo. ghci > read "Person {firstName =\" ghci> =\"Michael Michael\" \", , lastName =\" =\"Diamond Diamond\" \", , age = 43}"  43}" == == mike  mike True

También podemos podemos leer tipos parametriz par ametrizados, ados, pero tenemos que especificar especifica r todos los parámetros del tipo. Así que no podemos podem os hacer 󰁲󰁥󰁡󰁤 "J󰁵󰁳󰁴 '󰁴'" :: 󰁍󰁡󰁹󰁢󰁥 󰁡 pero si podemos hacer 󰁲󰁥󰁡󰁤 "J󰁵󰁳󰁴 '󰁴'" :: 󰁍󰁡󰁹󰁢󰁥 C󰁨󰁡󰁲. Podemos derivar instancias para la clase de tipos 󰁏󰁲󰁤, la cual es para tipos cuyos valores puedan ser ordenados. Si comparamos dos valores del mismo tipo que fueron definidos usando diferentes constructores, el valor cuyo constructor fuera definido primero es considerado consi derado menor que el otro. Por ejem e jemplo, plo, el tipo B󰁯󰁯󰁬 puede tener valores F󰁡󰁬󰁳󰁥 o 󰁔󰁲󰁵󰁥. Con el objetivo de ver como se comporta cuando es comparado, podemos pensar que está implementado de esta forma: data  data  Bool Bool   = False False   | True True   deriving deriving (  (Ord Ord) )

Como el valor F󰁡󰁬󰁳󰁥 está definido primero y el valor 󰁔󰁲󰁵󰁥 está definido después, podemos considerar que 󰁔󰁲󰁵󰁥 es mayor que F󰁡󰁬󰁳󰁥. ghci> True compare  False  False GT ghci> True True > False Fa lse True ghci> True True < False Fa lse False En el tipo 󰁍󰁡󰁹󰁢󰁥 󰁡, el constructor de datos 󰁎󰁯󰁴󰁨󰁩󰁮󰁧  esta definido antes que el constructor J󰁵󰁳󰁴, así que un valor 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 es siemp si empre re más pequeño que cualquier valor J󰁵󰁳󰁴 󰁡󰁬󰁧󰁯, incluso si ese e se algo es menos un billon billon de trillones. trillones. Pero Per o si comparamos dos valores J󰁵󰁳󰁴, entonces se compara lo que hay dentro de él. ghci> ghci > Nothing Nothing    ghci > Nothing Nothing   > Just Just (  (-49999 49999) ) False

ghci> ghci > Just Just   3  compare

Just  Just  2

11 de 32  

GT ghci> ghci > Just Just   100 100   > Just Just   50 True

No podemos podemos hacer hace r algo como J󰁵󰁳󰁴 (*3) > J󰁵󰁳󰁴 (*2), ya que (*3) y (*2) son funciones, las cuales no tienen definida una instancia de 󰁏󰁲󰁤. Podemos usar fácilmente los tipos de dato algebraicos para crear enumeraciones, y las clases de tipos E󰁮󰁵󰁭 y B󰁯󰁵󰁮󰁤󰁥󰁤  nos ayudarán a ello. Considera el siguiente tipo de dato: data  data  Day Day   = Monday Monday   | Tuesday Tuesday   | Wednesday Wednesday   | Thursday Thursday   | Friday Friday   | Saturday Saturday   | Sunday

Como ningún contructor de datos tiene parámetros, podemos hacerlo miembro de la clase de tipos E󰁮󰁵󰁭. La clase E󰁮󰁵󰁭 son para cosas cos as que tinen un predecesor y sucesor. T Tamb ambien ien podemos hacerlo miembro miembro de la clase de tipos B󰁯󰁵󰁮󰁤󰁥󰁤 , que es para cosas que tengan un valor mínimo mínimo posible y valor máxim máximoo posible. Ya que nos ponemos, vamos a hacer que este tipo tenga una instancia para todas las clases de tipos derivables que hemos visto y veremos que podemos hacer con él. data  Day data  Day   = Monday Monday   | Tuesday Tuesday   | Wednesday Wednesday   | Thursday Thursday   | Friday Friday   | Saturday Saturday   | Sunday   deriving ( deriving  (Eq Eq, , Ord Ord, , Show Show, , Read Read, , Bounded Bounded, , Enum Enum) )

Como es parte de las clases de tipos 󰁓󰁨󰁯󰁷 y 󰁒󰁥󰁡󰁤, podemos convertir valores de est tipo a y desde cadenas. ghci > Wednesday ghci> Wednesday ghci> ghci > show Wednesday "Wednesday" ghci> ghci > read "Saturday" "Saturday"   :: ::   Day Saturday

Como es parte de las clases de tipos E󰁱 y 󰁏󰁲󰁤, podemos comparar o equiparar días. ghci > Saturday ghci> Saturday   == ==   Sunday False ghci> ghci > Saturday Saturday   == ==   Saturday True ghci> ghci > Saturday Saturday   > Friday True ghci> ghci > Monday Monday `compare`  `compare` Wednesday LT

También forma parte de B󰁯󰁵󰁮󰁤󰁥󰁤, así que podemos podemos obtener el e l día mas bajo o el día más alto. ghci > minBound ::  ghci> :: Day Monday ghci> ghci > maxBound :: ::   Day Sunday

También es una instancia de la clase E󰁮󰁵󰁭. Podemos obtener el predecesor y el sucesor de un día e incluso podemos crear listas de rangos con ellos. ghci > succ Monday ghci> Tuesday ghci> ghci > pred Saturday Friday ghci> ghci > [  [Thursday Thursday   .. ..   Sunday Sunday] ]

12 de 32  

Thursday, ,Friday Friday, ,Saturday Saturday, ,Sunday Sunday] ] [Thursday ghci> ghci > [minBound .. .. maxBound]  maxBound] :: :: [  [Day Day] ] [Monday Monday, ,Tuesday Tuesday, ,Wednesday Wednesday, ,Thursday Thursday, ,Friday Friday, ,Saturday Saturday, ,Sunday Sunday] ]

Bastante impresionante.

Sinónimos de tipo Anteriormente Anteriorm ente mencionamos que los tipos 󰁛C󰁨󰁡󰁲󰁝 y 󰁓󰁴󰁲󰁩󰁮󰁧 eran equivalentes e intercamb interca mbiables. iables. Esto está e stá implementado implementado con los sinónimos de tipo. Los sinónimos de tipo no hacen nada por si s i solo, simplemente simplemente dan a algún tipo un nombre nombre diferente, de forma que obtenga algún algún significado para alguien que que está leyendo nuestro código o documentación. documentación. Aquí tienes como define la librería estándar 󰁓󰁴󰁲󰁩󰁮󰁧 como sinónim si nónimoo de 󰁛C󰁨󰁡󰁲󰁝 . type  type  String String   = [  [Char Char] ]

Acabamos de intrudir la palabra clave 󰁴󰁹󰁰󰁥. Esta palabra clave podría inducir a errores a algunos, algun os, ya y a que en realidad no estamos esta mos haciendo haciendo nada nuevo (lo ( lo hacemos con la palabra clave 󰁤󰁡󰁴󰁡). Simplemente Simplemente estamos dando un sinónimos a un tipo que ya existe. Si hacemos una función que convierta una cadena a mayúscuals y la llamamos 󰁴󰁯󰁕󰁰󰁰󰁥󰁲󰁓󰁴󰁲󰁩󰁮󰁧  o algo parecido, podemos darle una declaración de tipo como 󰁴󰁯󰁕󰁰󰁰󰁥󰁲󰁓󰁴󰁲󰁩󰁮󰁧 :: 󰁛C󰁨󰁡󰁲󰁝  ‐> 󰁛C󰁨󰁡󰁲󰁝  o 󰁴󰁯󰁕󰁰󰁰󰁥󰁲󰁓󰁴󰁲󰁩󰁮󰁧  :: 󰁓󰁴󰁲󰁩󰁮󰁧  ‐>  󰁓󰁴󰁲󰁩󰁮󰁧 . Ambas Ambas son so n esecialm eseci almente ente lo mismo, solo que la última es más legible.

Cuando estabamos hablando del módulo D󰁡󰁴󰁡.󰁍󰁡󰁰 , primero presentamos una agenda a genda de teléfonos representada con una lista de asociación para luego convertirla en un diccionario. Como ya sabemos, una lista de asociación no es más que una lista de duplas clave-valor. Vamos a volver a ver la lista que teníamos. phoneBook  :: phoneBook  :: [(  [(String String, ,String String)] )] phoneBook  phoneBook  =   [("betty" [( "betty", ,"555-2938" "555-2938") )   ,("bonnie" ,( "bonnie", ,"452-2928" "452-2928") )   ,("patsy" ,( "patsy", ,"493-2928" "493-2928") )   ,("lucille" ,( "lucille", ,"205-2928" "205-2928") )   ,("wendy" ,( "wendy", ,"939-8282" "939-8282") )   ,("penny" ,( "penny", ,"853-2492" "853-2492") )   ]

Vemos que el tipo de 󰁰󰁨󰁯󰁮󰁥B󰁯󰁯󰁫  es 󰁛(󰁓󰁴󰁲󰁩󰁮󰁧,󰁓󰁴󰁲󰁩󰁮󰁧)󰁝 . Esto nos dice que es una lista de asociación que asocia cadenas con co n cadena, pero nada más. Vamos Vamos a crear un sinónimo sinónimo de tipo para transmitir algo más más de información i nformación en la declaración de tipo. type  type  PhoneBook PhoneBook   = [(  [(String String, ,String String)] )]

Ahora la declaración de tipo de nuestra función 󰁰󰁨󰁯󰁮󰁥B󰁯󰁯󰁫  sería 󰁰󰁨󰁯󰁮󰁥B󰁯󰁯󰁫  :: 󰁐󰁨󰁯󰁮󰁥B󰁯󰁯󰁫 . Vamos a hacer un sinónimo de tipo para las cadenas también.

type  PhoneNumber type  PhoneNumber   = String type  type  Name Name   = String type  type  PhoneBook PhoneBook   = [(  [(Name Name, ,PhoneNumber PhoneNumber)] )]

Dar un sinónimo al tipo 󰁓󰁴󰁲󰁩󰁮󰁧 es algo que suelen hacer los programadores de Haskell cuando quieren transmitir algo más

13 de 32  

de información acerca del cometido de las cadenas en sus funciones y que representan. Así que ahora, cuando c uando implem implementemos entemos una función que tome el nombre y el número de teléfono y busque si esa e sa comb co mbinación inación está en nuestra agenda telefónica, podremos darle una declaración de tipo muy descriptiva: inPhoneBook :: inPhoneBook  ::   Name Name   -> ->   PhoneNumber PhoneNumber   -> ->   PhoneBook PhoneBook   -> ->   Bool inPhoneBook name inPhoneBook  name pnumber pbook = (name,pnumber) `elem` pbook

Si decidimo no utilizar utilizar sinónimos de tipo, nuestra función tendría la declaración de tipo ti po 󰁓󰁴󰁲󰁩󰁮󰁧 ‐> 󰁓󰁴󰁲󰁩󰁮󰁧 ‐> 󰁛(󰁓󰁴󰁲󰁩󰁮󰁧,󰁓󰁴󰁲󰁩󰁮󰁧)󰁝  ‐> B󰁯󰁯󰁬. En este caso, c aso, la declaración dec laración de tipo que utiliza utiliza los sinónimos s inónimos de tipo es mucho más clara y

fácil de entender. Sin embargo, embargo, no debes abusar a busar de ellos. Utilizamos Utilizamos los sinónimos de tipo o bien para indicar que re representa presenta un tipo que ya existe en nuestras funciones (y de esta forma nuestras delcaraciones de tipo se convierten en la mejor documentación) docum entación) o bien cuando algo tiene un tipo muy largo que se repite re pite much muchoo (como (c omo 󰁛(󰁓󰁴󰁲󰁩󰁮󰁧,󰁓󰁴󰁲󰁩󰁮󰁧)󰁝 ) y tiene un significado concreto para nosotros. Los sinónimos de tipo también pueden ser parametrizados. Si queremos un tipo que represente las listas de asociación pero también queremos queremos que sea lo suficientemente s uficientemente general como para utilizar cualq cualquier uier tipo de cclave lave y valor, v alor, podemos podemos utilizar esto: type  type  AssocList AssocList k  k v = [(k,v)]

Con esto, una función que tomara un valor por clave en una lista de asociación puede tener el tipo (E󰁱 󰁫) => 󰁫 ‐> A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  󰁫 󰁶 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁶. A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  es un constructor de tipos que toma dos tipos y produce un tipo concreto, como A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  I󰁮󰁴 󰁓󰁴󰁲󰁩󰁮󰁧  por ejemplo.

Nota

Cuandoo hablamos Cuand hablamos de tipos ti pos concretos concreto s nos referimos re ferimos a tipos completamente completamente aplicados, como co mo 󰁍󰁡󰁰 I󰁮󰁴 󰁓󰁴󰁲󰁩󰁮󰁧. A veces, los chicos y yo decimos que 󰁍󰁡󰁹󰁢󰁥 es un tipo, pero no queremos referirnos a eso, ya que cualquier idiota sabe que 󰁍󰁡󰁹󰁢󰁥 es un constructor de tipos. Cuando aplico un tipo extra a 󰁍󰁡󰁹󰁢󰁥, como 󰁍󰁡󰁹󰁢󰁥 󰁓󰁴󰁲󰁩󰁮󰁧, entonces tengo un tipo concreto. Ya sabes, los valores solo pueden tener tipos que sean tipos concretos. Concluyendo, vive rápido, quiere mucho y no dejes que nadie te tome el pelo.

De la misma forma forma que podemos aplicar parcialmente funciones para oobtener btener nuevas funciones, podemos aplicar parcialmente los parámetros de tipo y obtener nuevos co constructores nstructores de tipo. ti po. De la misma misma forma que llamamos llamamos a la funciones con c on parámetros de menos para obtener nuevas funciones, podemos especificar un constructor de tipos con parámetros de menos y obtener un constructor de tipos parcialmente aplicado. Si queremos un tipo que represente un diccionario (de D󰁡󰁴󰁡.󰁍󰁡󰁰 ) que asocie enteros con cualquier otra cosa, podemos utilizar esto: type type   IntMap IntMap v  v = Map Map   Int Int v  v

O bien esto otro: type  type  IntMap IntMap   = Map Map   Int

De cualquier forma, el constructor de tipos I󰁮󰁴󰁍󰁡󰁰 tomará un parámetro y ese será el tipo con el que se asociarán los enteros.

Nota

14 de 32  

Si vas a intentar implementar implementar esto, es to, seguramente imporatarás de forma for ma cualificada el módu módulo lo D󰁡󰁴󰁡.󰁍󰁡󰁰 . Cuando realizas una importació imp ortaciónn cualificada, los constructores de tipo también deben estar precedidos prece didos con el nombre del m módulo. ódulo. Así que que tienes que escribir algo como 󰁴󰁹󰁰󰁥 I󰁮󰁴󰁍󰁡󰁰 = 󰁍󰁡󰁰.󰁍󰁡󰁰 I󰁮󰁴. Asegurate de que realmente entiendes la diferencia entre constructores de tipos y constructores de datos. Solo porque hayamos creado cre ado un sinónimo llamado I󰁮󰁴󰁍󰁡󰁰 o A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  no significa que podamos hacer cosas como A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  󰁛(1,2), (4,5),(7,9)󰁝 . Lo único que significa es que podemos referirnos a ese tipo usando nombres diferentes. Podemos hacer 󰁛(1,2),(3,5),(8,9)󰁝  :: A󰁳󰁳󰁯󰁣󰁌󰁩󰁳󰁴  I󰁮󰁴 I󰁮󰁴, lo cual hará que los número de adentro asuman el tipo I󰁮󰁴, pero podemos

seguir usando esta lista como si fuera una lista que albergara duplas de enteros. Lo sinónimos de tipo (y los tipos en general) solo pueden ser utlizados utlizados en la porción porc ión de Haskell dedicada a los tipos. Estaremos E staremos en esta es ta porción porció n de Haskell cuando estemos definiendo tipos nuevos (tanto en las declaraciones 󰁤󰁡󰁴󰁡 como en las de 󰁴󰁹󰁰󰁥) o cuando nos situemos s ituemos después de un ::. :: se utiliza solo para las declaraciones o anotaciones de tipo. Otro tipo de dato interesante que toma dos tipos como parámetro es el tipo E󰁩󰁴󰁨󰁥󰁲 󰁡 󰁢. Así es como se define más o menos: data   Either Either a  a b = Left Left a  a | Right Right b  b deriving deriving (  (Eq Eq, , Ord Ord, , Read Read, , Show Show) ) data

Tiene dos constructores de datos. Si se utiliza 󰁌󰁥󰁦󰁴, entonces contiene datos del tipo 󰁡 y si se utiliza 󰁒󰁩󰁧󰁨󰁴 contiene datos del tipo 󰁢. Podemos utilizar utilizar este tipo para encapsular e ncapsular un valor de un tipo u otro y así obtener un valor del tipo E󰁩󰁴󰁨󰁥󰁲 󰁡 󰁢. Normalm Norm almente ente utilizaremos utilizaremos un ajuste de patrones con amb a mbos, os, 󰁌󰁥󰁦󰁴 y 󰁒󰁩󰁧󰁨󰁴, y nos diferenciaremos según sea uno u otro. ghci > Right ghci> Right   20 Right  Right  20 ghci> ghci > Left Left   "w00t" Left  Left  "w00t" ghci> ghci > :t Right Right   'a' Right  Right  'a' 'a'   :: ::   Either Either a  a Char ghci> ghci > :t Left Left   True Left  Left  True True   :: ::   Either Either   Bool Bool b  b

Hasta ahora hemos visto que 󰁍󰁡󰁹󰁢󰁥 󰁡 es utilizado para representar represe ntar resultados de cálcul cá lculos os que podrían haber fallado o no. Pero a veces, 󰁍󰁡󰁹󰁢󰁥 󰁡 no es suficientemente bueno ya que 󰁎󰁯󰁴󰁨󰁩󰁮󰁧  únicamente nos informa de que algo ha fallado. Esto esta bien para funciones que solo pueden fallar fallar de una forma o si no nos interesa interes a saber porque y como han fallado. Una Una búqueda en un D󰁡󰁴󰁡.󰁍󰁡󰁰  solo falla cuando la clave que estamos buscando no se encuentra en el diccionario, así que sabemos exacmente que ha pasado. Sin embargo, cuando estamos interesados interesa dos en el cómo có mo o el porqué a fallado algo, solemos utiliz utilizar ar com co mo resultado el tipo E󰁩󰁴󰁨󰁥󰁲 󰁡 󰁢, donde 󰁡 es algu a lguna na especie de tipo que pueda decirnos algo sobre un posible fallo, fallo, y 󰁢 es el tipo de un cálculo satisfactorio. Por lo tanto, los errores usan el constructor de datos 󰁌󰁥󰁦󰁴 mientras que los resultado usan 󰁒󰁩󰁧󰁨󰁴. Un ejemplo: ejemplo: un instituto posee taquill taquillas as para que sus estudiantes tengan un lugar lugar donde guardar sus posters de Guns’n’Roses . Cada taquilla tiene una combinación. Cuando un estudiante estudiante quiere una taquilla nueva, le dice al a l supervisor de las

taquillas taquill as que número número de taquill ta quillaa quiere y él le da un código para esa es a taquilla. Sin embargo, embargo, si alguien ya está usando la taquilla, taquilla, no le puede decir el código y tienen que elegir una taquilla diferente. Utilizaremos Utilizaremos un diccionario de D󰁡󰁴󰁡.󰁍󰁡󰁰  para representar las taquillas. Asociará el e l núm número ero de la taquilla taquilla con duplas que contengan si la taquilla está en uso o no y el código de la taquilla.

import  import  qualified qualified Data.Map  Data.Map as as Map  Map data  data  LockerState LockerState   = Taken Taken   | Free Free   deriving deriving (  (Show Show, , Eq Eq) ) type  type  Code Code   = String

type  type  LockerMap LockerMap   = Map Map. .Map Map   Int Int (  (LockerState LockerState, , Code Code) )

15 de 32  

Bastante simple. Hemo creado un nuevo tipo de dato para representar si una taquilla está libre o no, y hemos creado un sinónimo para representar el código de una taquilla. T También ambién creado otro sinónimo para el tipo que asocia aso cia los los números números de las taquillas taquill as con co n las duplas duplas de estado esta do y código. có digo. Ahora, Ahora, vamos v amos a hacer una función que que busque un número número de taquilla taquilla en el diccionario. Vamos a usar el tipo E󰁩󰁴󰁨󰁥󰁲 󰁓󰁴󰁲󰁩󰁮󰁧 C󰁯󰁤󰁥 para representar re presentar el resultado, res ultado, ya que nuestra búsqueda puede puede fallar de dos formas: la taquilla ya ha sido s ido tomada, en cuyo ca caso so decimos quien la posee o si el no hay ninguna taquilla taquilla con ese número. número. Si la búqueda búqueda falla, vamos a utilizar utilizar una cadena para obtener el porqué. lockerLookup :: lockerLookup  ::   Int Int   -> ->   LockerMap LockerMap   -> ->   Either Either   String String   Code lockerLookup lockerNumber lockerLookup  lockerNumber map =          

case Map case  Map. .lookup lockerNumber map of Nothing  Nothing  -> ->   Left Left   $ "Locker number "  " ++ ++ show  show lockerNumbe lockerNumber r ++ ++   " doesn't exist!" Just (state, Just  (state, code) -> ->   if if state  state /= /=   Taken then  then  Right Right code  code else  else  Left Left   $ "Locker "  " ++ ++ show  show lockerNumber ++ ++   " is al

Hacemos una búsqueda búsqueda normal en un diccionario. Si obtenemos 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 , devolvemos un valor con el tipo 󰁌󰁥󰁦󰁴 󰁓󰁴󰁲󰁩󰁮󰁧 que diga que esa taquilla no existe. Si la encontramos, hacemos una comprobación adicional para ver si la taquilla está libre. Si no lo está, devolvemos un 󰁌󰁥󰁦󰁴 diciendo que la taquilla a sido tomad to mada. a. Si lo está, devolvemos devo lvemos un valor del tipo 󰁒󰁩󰁧󰁨󰁴 C󰁯󰁤󰁥, el cual daremos al estudiante. En realidad es un 󰁒󰁩󰁧󰁨󰁴 󰁓󰁴󰁲󰁩󰁮󰁧, aunque hemos creado un sinónim sinónimoo para añadir un poco más de información en la declaración declaració n de tipo. Aquí tienes un diccionario de ejemplo: ejemplo: lockers  lockers  :: ::   LockerMap lockers = Map lockers  Map. .fromList   [(100 [( 100,( ,(Taken Taken, ,"ZD39I" "ZD39I")) ))   ,(101 ,( 101,( ,(Free Free, ,"JAH3I" "JAH3I")) ))   ,(103 ,( 103,( ,(Free Free, ,"IQSA9" "IQSA9")) ))   ,(105 ,( 105,( ,(Free Free, ,"QOTSA" "QOTSA")) ))   ,(109 ,( 109,( ,(Taken Taken, ,"893JJ" "893JJ")) ))   ,(110 ,( 110,( ,(Taken Taken, ,"99292" "99292")) ))   ]

Vamos a buscar el código de unas cuantas taquill ta quillas: as: ghci > lockerLookup 101 ghci> 101 lockers  lockers Right  Right  "JAH3I" ghci> ghci > lockerLookup 100 100 lockers  lockers Left  Left  "Locker 100 is already taken!" ghci> ghci > lockerLookup 102 102 lockers  lockers Left  "Locker number 102 doesn't exist!" Left  ghci> ghci > lockerLookup 110 110 lockers  lockers Left  Left  "Locker 110 is already taken!" ghci> ghci > lockerLookup 105 105 lockers  lockers Right  Right  "QOTSA"

Podríamos haber utlizado utlizado el tipo ti po 󰁍󰁡󰁹󰁢󰁥 󰁡 para representar el resultado pero entonces no sabríamos el motivo por el cual no podemos podem os obtener el código. Ahora, tenemos información ace acerca rca del fallo en nuestro tipo del resultado.

Estructuras de datos recursivas Como ya hemos visto, un costructor de un tipo de dato algebraico puede tener (o no tener) varios campos y cada uno de estos debe ser un tipo concreto. Teniendo esto en cuenta, podemos crear tipos cuyos campos de constructor sean el propio tipo. De esta forma, podemos crear estructuras de datos recursivas, en el que un valor de un cierto tipo contenga valores de ese mismoo tipo, el mism e l cual seguirá conteniendo valores va lores del mism mismoo tipo y así sucesivamente. s ucesivamente. Piensa en la lista 󰁛5󰁝. Es lo mismo que 5:󰁛󰁝. A la izquierda del : hay un valore, y a la derecha

16 de 32  

hay una lista. En este caso, una lista vacía. ¿Qué pasaría con la lista 󰁛4,5󰁝? Bueno, es e s lo mismo que 4:(5:󰁛󰁝) . Si miramos el primer :, vemos que también tiene un elemento elemento a su izquierda izquierda y una lista a su derecha (5:󰁛󰁝). Lo mismo sucede para la lista 3:(4:(5:6:󰁛󰁝)) , que tamb ta mbién ién podría escribirse como 3:4:5:6:󰁛󰁝  (ya que : es asociativo as ociativo por la derecha) o 󰁛3,4,5,6󰁝 . Podemos decir que una lista es o bien una lista vacia va cia o bien un elemento elemento unido con un : a otra lista (que puede ser una lista vacía o no). ¡Vamos ¡Vamos a usar los tipod de datos algebraicos para implementar implementar nuestra propia lista! data   List List a  a = Empty Empty   | Cons Cons a  a (List ( List a)  a) deriving deriving (  (Show Show, , Read Read, , Eq Eq, , Ord Ord) ) data

Se lee de la misma forma que se leía nuestra definición de lista en un párrafo anterior. Es o bien una lista vacía o bien una combinación de un elemento y otra lista. Si estás confundido con esto, quizás te sea más fácil entenderlo con la sintaxis de registro: data  data  List List a  a = Empty Empty   | Cons Cons {  { listHead :: :: a,  a, listTail :: ::   List List a}  a} deriving deriving (  (Show Show, , Read Read, ,

Puede que también también estes este s confundido con el constructor C󰁯󰁮󰁳. C󰁯󰁮󰁳 es otra forma de decir :. En realidad, en las listas, : es un constructor que toma un valor y otra lista y devuleve una lista. En otras palabras, tiene dos campos. Uno es del tipo 󰁡 y otro es del tipo 󰁛󰁡󰁝. ghci > Empty ghci> Empty ghci> ghci > 5 `  `Cons Cons` ` Empty Cons  Cons  5 Empty ghci> ghci > 4 `  `Cons Cons` ` (5 (5 `  `Cons Cons` ` Empty Empty) ) Cons  Cons  4 (  (Cons Cons   5 Empty Empty) ) ghci> ghci > 3 `  `Cons Cons` ` (4 (4 `  `Cons Cons` ` (5 ( 5 `  `Cons Cons` ` Empty Empty)) )) Cons  Cons  3 (  (Cons Cons   4 (  (Cons Cons   5 Empty Empty)) ))

Si hubiésemos llamado llamado a nuestro constructor de forma fo rma infija podrías ver mejor como es ssimp implemente lemente :. E󰁭󰁰󰁴󰁹 es como 󰁛󰁝 y 4 󰁠C󰁯󰁮󰁳󰁠  (5 󰁠C󰁯󰁮󰁳󰁠  E󰁭󰁰󰁴󰁹)  es como 4:(5:󰁛󰁝) .

Podemos definir funciones que automáticamente sean infijas si las nombramos únicamente con caracteres especiales. Podemos hacer lo mismo con los constructores, constructore s, ya que son simplemente simplemente funciones que devuelve un tipo de dato co concreto. ncreto. Mira esto: infixr  5 :-: infixr  data  data  List List a  a = Empty Empty   | a :-: :-: (  (List List a)  a) deriving deriving (  (Show Show, , Read Read, , Eq Eq, , Ord Ord) )

Antes de nada, vemos que hay una nueva construcción construcci ón sintáctic sintáctica, a, una declaración declaració n infija. Cuando Cuando definimos funciones como co mo operadores, podemos podemos usar esta cosntrucción c osntrucción para darles un determ determinado inado comportamiento (aunque (aunque no estamos es tamos obligados a hacerlo). De esta forma definimos el orden de precedencia de un operador y si asociativo por la izquierda o por la derecha. Por ejemplo, * es 󰁩󰁮󰁦󰁩󰁸󰁬 7 * y + es 󰁩󰁮󰁦󰁩󰁸󰁬 6 +. Esto siginifica que ambos son asociativos por la izquierda de forma que (4 * 3 * 2) es (4 * 3) * 2) pero * tiene un orden de precedencia mayor que +, por lo que 5 * 4 + 3 es equivalente a (5 * 4) + 3.

De qualquier modo, al final acabamos escribiendo 󰁡 :‐: (󰁌󰁩󰁳󰁴 󰁡) en lugar de `` Cons a (List a)``. Ahora podemos escribir

las listas así:

17 de 32  

ghci ghci> > 3 :-: :-:   4 :-: :-:   5 :-: :-:   Empty (:-: :-:) ) 3 ((  ((:-: :-:) ) 4 ((  ((:-: :-:) ) 5 Empty Empty)) )) ghci> ghci > let let a  a = 3 :-: :-:   4 :-: :-:   5 :-: :-:   Empty ghci> ghci > 100 100   :-: :-: a  a (:-: :-:) ) 100 100 ((  ((:-: :-:) ) 3 ((  ((:-: :-:) ) 4 ((  ((:-: :-:) ) 5 Empty Empty))) )))

Haskell serguirá mostrando el cosntructor como una función prefija cuando derivemos 󰁓󰁨󰁯󰁷, por este motivo aparecen los poréntesis alrededor del constructor (recuerda que 4 + 3 es igual i gual que que (+) 4 3). Vamos a crear crea r una función que una dos de nuestras listas. Así es co como mo está definida la función ++ para listas normales: infixr  5  ++ infixr  (++ ++) ) :: :: [a]  [a] -> -> [a]  [a] -> -> [a]  [a] []  []   ++ ys ++  ys = ys (x: (x :xs) ++ ++ ys  ys = x : (xs ++ ++ ys)  ys)

Así que copiamos esta definición y la aplicamos a nuestras listas: infixr  5  .++ infixr  (.++ .++) ) :: ::   List List a  a -> ->   List List a  a -> ->   List List a  a Empty  Empty  .++ .++ ys  ys = ys (x :-: :-: xs)  xs) .++ .++ ys  ys = x :-: :-: (xs  (xs .++ .++ ys)  ys)

Y así es como funciona: ghci > let ghci> let a  a = 3 :-: :-:   4 :-: :-:   5 :-: :-:   Empty ghci> ghci > let let b  b = 6 :-: :-:   7 :-: :-:   Empty ghci> ghci > a .++ .++ b  b (:-: :-:) ) 3 ((  ((:-: :-:) ) 4 ((  ((:-: :-:) ) 5 ((  ((:-: :-:) ) 6 ((  ((:-: :-:) ) 7 Empty Empty)))) ))))

Bien. Si te apetece puedes implementar todas las funciones que operan con listas con nuestro tipo de listas. Fíjate que hemos utilizado un ajuste ajuste de patrón (󰁸 :‐: 󰁸󰁳). Esto función ya que el ajuste de patrones en realidad funciona ajustando constructores. Podemos ajustar un patrón :‐: porque es un constructor de nuesto tipo de la misma forma que : es un constructor de las listas estándar. Lo mismo sucede para 󰁛󰁝. Ya que el ajuste de patrones funciona (solo) con constructores de datos, podemos ajustar patrones como los constructores prefijos normales, constructores infijos o cosas como 8 o '󰁡', que al fin y al cabo son constructores de tipos númericos y caracteres. Vamos a implem implementar entar un árbol ár bol binario de búsqueda. búsqueda. Si no estás está s familiarizado familiariz ado con los árboles binarios de búsqueda búsqueda de otros otro s lenguajes como C , aquí tienes una expliación de lo que son: so n: un elemento elemento apunta a otros dos elementeos, uno esta a la izquierda y otro a la derecha. El elemento a la izquierda es más pequeño y el segundo es más grande. Cada uno de estos dos elementos puede apuntar a otros dos elementos (o a uno o a ninguno). En efecto, cada elemento tienen sus propios sub-árboles. Lo bueno de los árboles á rboles binarios de búsqueda es que sabemos que todos los elem e lementos entos que están está n en el sub-árbol de la iquierda de, 5, por ejem e jemplo, plo, son menores que 5. Lo elementos que están en el sub-árbol de la derecha son mayores. mayores . Así que si esta estamos mos buscando el elemento elemento 8 en e n nuestro árbol, emepezamos emepezamos comparándoloo con 5, comparándol 5 , como vemos que es menor que 5, nos vamos al sub-árbol de la derecha. Ahora est estaríamos aríamos en 7, ccomo omo es menor que 8 continuaríamos hacia la derecha. De esta formá encontraríamos el elemento en tres pasos. Si estuvieramos

usando una lista (o un árbol no balanceado), nos hubiera costado unos 7 pasos encontrar el 8.

18 de 32  

Los conjuntos y diccionario de D󰁡󰁴󰁡.󰁓󰁥󰁴  y D󰁡󰁴󰁡.󰁍󰁡󰁰 está  estánn implementand implementandos os utilizando árboles, solo so lo que en lugar de árboles binarios de búsqueda, utilizan utilizan árboles binarios de búsqueda búsqueda balanceados, de forma que estén siempre balanceados. Ahora implemen imp lementaremos taremos simplemente simplemente árboles binarios de búsqueda normales. Vamos a decir que: un árbol es o bien un árbol vacío o bien un elemento que contiene un elem elemento ento y otros o tros dos árboles. Tiene pinta de que va a encajar perfectamente con los tipos de datos algebraicos. data   Tree Tree a  a = EmptyTree EmptyTree   | Node Node a  a (Tree ( Tree a)  a) (Tree ( Tree a)  a) deriving deriving (  (Show Show, , Read Read, , Eq Eq) ) data

Vale. En lugar de construir manualm manualmente ente un árbol, vamos va mos a crear cre ar una función que tome to me un elemento elemento y un árbol e inserte dicho elemento en su posición adecuada adec uada dentro del árbol. Hacem Hacemos os esto es to comparando el elemento que que queremos insertar con la raíz del árbol y si es e s menor, menor, vamos a la izquierda izquierda y si no a la derecha. derec ha. Hacemos lo mismo mismo para coda c oda nodo siguiente hasta que alcanzemos alcanz emos un árbol vacío. vacío . Cuando lo hagamos simplem simplemente ente insertamos inserta mos el elmento en lugar lugar del árbol vacío. va cío. En lenguajes como C , realizamos esta tarea tare a modificando los punteros y va valores lores del árbol. En Haskell Haskell,, no podemos modificar modificar nuestro árboles, así a sí que tenemos que crear un nu nuevo evo sub-árbol cada vez que que decidamos si vamos a la derecha dere cha o a la izquierda izquierda y al final la función de inserción inserci ón devolver un árbol complentam complentamente ente nuevo, ya que Haskell Haskell no tiene el concepto conce pto de puntero. Así pues la declaración de tipo de nuestra función será alfgo como 󰁡 ‐> 󰁔󰁲󰁥󰁥 󰁡 ‐ > 󰁔󰁲󰁥󰁥 󰁡. Toma un elemento y un árbol y devuelve un nuevo árbol que posee en su interior dicho elemento. Puede parecer ineficienciente pero la evaluación perezosa de Hasekell ya se encarga de ello. Aquii tienes dos funciones. Una de ellas es una función auxiliar para ccrear Aqu rear un árbol unitario (que solo so lo contiene un elemento) y la otra es una función que inserta elementos en un árbol. singleton  :: singleton  :: a  a -> ->   Tree Tree a  a singleton x singleton  x = Node Node x  x EmptyTree EmptyTree   EmptyTree treeInsert  :: treeInsert  :: (  (Ord Ord a)  a) => => a  a -> ->   Tree Tree a  a -> ->   Tree Tree a  a treeInsert x treeInsert  x EmptyTree EmptyTree   = singleton x treeInsert x treeInsert  x (Node ( Node a  a left right)   | x == == a  a = Node Node x  x left right   | x  a = Node Node a  a left (treeInsert x right)

La función 󰁳󰁩󰁮󰁧󰁬󰁥󰁴󰁯󰁮 es forma rápida de crear un árbol que contenga un elemento y dos sub-árboles vacios. En la función de inserción, tenemos como primer patrón el caso base. Si hemos alcanzado un sub-árbol vacio, esto significa que estamos donde queríamos queríamos y en e n lugar lugar de un árbol vacío, vacío , queremos un árbol unitario unitario que contenga el elemento a insertar. Si no estamos e stamos insertando el elemento en un árbol vacío tenemos que comprobar varias cosas. Primero, si el elemento que vamos a insertar es el mismo mismo que la raíz ra íz del sub-árbol, sub-árbol, simplemente simplemente devolvemos devo lvemos el árbol como estaba. es taba. Si es menor, devolvemos un árbol que tenga la misma misma raíz, el mimso sub-árbol derecho pero en lugar de su sub-árbol izqu izquierdo, ierdo, ponemos el árbol ár bol que va a contener dicho elemento. Lo mismo ocurre (pero en sentido contrario) para los valores que son mayores que el elemento raíz. A continuación vamos a crear cr ear una función que compruebe si un elem elemento ento pertence a un árbol. Primero vamos a definir el caso base. Si estamos e stamos buscando un elemento elemento en un árbol vacío, obviamente el elemento no está ahí. Vale, fíjate que esto eess básicamente lo mismo mismo que el caso cas o base de la búsqueda búsqueda en listas: si estamos es tamos buscando un elemento elemento en una lista vacía, va cía, obviamente el elemento no está ahí. a hí. De todos modos, si no estamos buscando el elemento en un árbol vacío, entonces e ntonces tenemos que hacer varias comprobaciones. Si el elemento que estamos buscando es el elemento raíz ¡Genial! ¿Y si no lo es? Bueno, tenemos la ventaja de que sabemos que todos los elementos elementos menores que la raíz raí z están en el sub-árbol izquierdo. izquierdo. Así que si el elemento que estamos buscando es menor que la raiz, comprobam comprobamos os si el elemento está en el sub-árbol izquierdo. izquierdo. Si es mayor, comprobamos comprobam os el e l sub-árbol derecho.

19 de 32  

treeElem treeElem   :: :: (  (Ord Ord a)  a) => => a  a -> ->   Tree Tree a  a -> ->   Bool treeElem x treeElem  x EmptyTree EmptyTree   = False treeElem x treeElem  x (Node ( Node a  a left right)   | x == == a  a = True   | x  a = treeElem x right

¡Vamos ¡Vamos a divertirnos dive rtirnos con c on nuestro árboles! En lugar lugar de contruir manualmente manualmente un árbol (aunque podríamos), usaremos un pliegue para construir un árbol a partir de una lista. Recuerda, casi cualquier cosa que recorra una lista elemento a elemento y devuelve alguna especie de valor puede ser se r implementado implementado con un pliegue. Empezarem Empezaremos os con co n un árbol vacío y luego recorreremos la lista desde la derecha e iremos insertando elementos a nuestro árbol acumulador. ghci > let ghci> let nums  nums = [  [8 8 , 6, 4,1 , 7, 3 ,5] ghci> ghci > let let numsTree  numsTree = foldr treeInsert EmptyTree EmptyTree nums  nums ghci> ghci > numsTree Node  Node  5 (  (Node Node   3 (  (Node Node   1 EmptyTree EmptyTree   EmptyTree EmptyTree) ) (Node ( Node   4 EmptyTree EmptyTree   EmptyTree EmptyTree)) )) (Node ( Node   7 (  (No No

En este 󰁦󰁯󰁬󰁤󰁲, 󰁴󰁲󰁥󰁥I󰁮󰁳󰁥󰁲󰁴  es la función de pliegue pliegue (toma un árbol y un elemento de la lista y produce un nuevo árbol) y E󰁭󰁰󰁴󰁹󰁔󰁲󰁥󰁥  es el valor inicial. Por supuesto, 󰁮󰁵󰁭󰁳 es la lista que estamos plegando.

No es muy legible legible el árbol que se muestra por la consola, co nsola, pero si lo intentamos, podemos podemos descifrar desci frar su estructura. es tructura. Vemos Vemos que el nodo raíz es 5 y luego tiene dos sub-árboles, uno que tiene como elemento raíz a 3, y otro a 7. ghci > 8 `treeElem` numsTree ghci> True ghci> ghci > 100 100 `treeElem`  `treeElem` numsTree False ghci> ghci > 1 `treeElem` numsTree True ghci> ghci > 10 10 `treeElem`  `treeElem` numsTree False

Vamos que comprobar la pertencia de un elemento a un árbol funciona perfec perfectamente. tamente. Genial. Como puede puede ver los tipos de datos dato s algebraicos en Hasekll son un concepto muy muy intersante a la vez que pontentes. pontentes. Podem Po demos os utilizarlos desde para representar valores booleanos hasta enumeraciónes de los días de la semana, e incluso árboles binarios de búsquedas.

Clases de tipos paso a paso (2ª parte) Hasta ahora hemos aprendido a utilizar algunas algunas clases c lases de tipos estándar de Haskell y hemos visto que tipos son so n miembros miembros de ellas. e llas. Tamb También ién hemos aprendido a crear automáticamente instancias de nuestros tipos para las clases de tipos estándar, pidiéndole a Haskell que las derive por nostros. En esta sección vamos a ver como podemos crear nuestras propias clases de tipo y a como crear instancias de tipos para ellas a mano. Un pequeño recordatorio acerca de las clases de tipos: las clases de tipos son como las interfaces.. Una clase de tipos define un comportamiento interfaces comportamiento (como comparar por igualdad, comparar por orden, una enumeración, etc.) y luego ciertos tipos pueden comportarse de forma a la instancia de esa clase de tipos. El comportamiento de una clase de tipos se consigue definiendo funciones o simplemente simplemente definiendo tipos que luego implementaremos. ementaremos. Así que cuando digamos que un tipo es una instancia de un clase de tipos, estamos diciendo que podemos usar las funciones de esa clase de tipos con ese tipo.

20 de 32  

Las clases de tipos no tienen nada que ver con las clases de Java   oo Pyhton . Esto suele confundir a mucha mucha gente, así que me me gustaría que olvidaras olvidara s ahora mismo todo lo que sabes sobre las clases en los lenguajes imperativos. Por ejemplo, la clase de tipos E󰁱 es para cosas que pueden ser equiparadas. Define las funciones == y /=. Si tenemos te nemos un tipo (digamos, C󰁡󰁲) y el comparar dos coches con la función == tiene sentido, entonces tiene sentido que C󰁡󰁲 sea una instancia de E󰁱.

Así es como está defina la clase E󰁱 en 󰁐󰁲󰁥󰁬󰁵󰁤󰁥 : class   Eq Eq a  a where class   (== ==) ) :: :: a  a -> -> a  a -> ->   Bool   (/= /=) ) :: :: a  a -> -> a  a -> ->   Bool   x == == y  y = not (x /= /= y)  y)   x /= /= y  y = not (x == == y)  y)

¡Alto, ¡Alto, alto, atlo! ¡Hay mu mucha cha sintaxis y palabras raras ahí! No te preocupes, estará es tará todo claro en un segundo. Lo primero de todo, cuando escribimos 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁡 󰁷󰁨󰁥󰁲󰁥 significa que estamos definiendo una clase de tipos nueva y que se va a llamar E󰁱. La 󰁡 es la variable de tipo y significa que 󰁡 representará el tipo que dentro de poco hagamos instancia de E󰁱. No tiene

porque llamarse 󰁡, de hecho no tiene tie ne ni que ser de una sola so la letra, solo debe ser s er una palabra en minúsculas. minúsculas. Luego definim defi nimos os varias funciones. No es obligatorio implementar los cuerpos de las funciones, solo debemos especificar las declaraciones de tipo de las funciones. Nota

Hay gente que entederá esto mejor si escribimos algo como 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁥󰁱󰁵󰁩󰁰󰁡󰁲󰁡󰁢󰁬󰁥  󰁷󰁨󰁥󰁲󰁥 y luego definimos el tipo ti po de las funciones como (==) :: 󰁥󰁱󰁵󰁩󰁰󰁡󰁲󰁡󰁢󰁬󰁥  ‐> 󰁥󰁱󰁵󰁩󰁰󰁡󰁲󰁡󰁢󰁬󰁥  ‐> B󰁯󰁯󰁬. De todos modos, hemos implem implementado entado el cuerpo cuer po de las funciones que define E󰁱, solo que las hemos implementado implementado en terminos de recursión mutua. Decimos que dos instancias de la clase E󰁱 son iguales si no son desiguales y son desiguales y no son iguales. En realidad no teníamos porque haberlo echo, pero pronto veremos de que forma nos ayuda. a yuda.

Nota

Si tenemos un 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁡 󰁷󰁨󰁥󰁲󰁥 y definimos una declaración de tipo dentro de la clase como (==) :: 󰁡 ‐> ‐󰁡 ‐> examinemos eell tipo de esa es a función obtendremos (E󰁱 󰁡) => 󰁡 ‐> 󰁡 ‐> B󰁯󰁯󰁬. B󰁯󰁯󰁬, luego, cuando examinemos Así que ya tenemos una clase ¿Qué podemos hacer con ella? Bueno, no mucho. Pero una vez empez empezemos emos a declarar instancias para esa clase, empezaremos a obtener algun funcionalidad útil. Mira este tipo: data   TrafficLight TrafficLight   = Red Red   | Yellow Yellow   | Green data

Define los estados de un semáforo. Fijate que no hemos derivado ninguna instancia, ya que vamos a escribirlas a mano, aunque podríamos haberlas derivado para las clases E󰁱 y 󰁓󰁨󰁯󰁷. Aquí tienes como creamos la instancia para la clase E󰁱. instance  instance  Eq Eq   TrafficLight TrafficLight   where        

Red  == Red  ==   Red Red   = True Green  Green  == ==   Green Green   = True Yellow  Yellow  == ==   Yellow Yellow   = True  _   == ==     _  _   = False

21 de 32  

Lo hicimos utilizando la palabra clave 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥. Así que 󰁣󰁬󰁡󰁳󰁳 es para definir nuevas clases de tipos y 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  para hacer que nuestros tipos tengan una instancia para cierta clase de tipos. Cuando estabamos definiendo E󰁱 escribimos 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁡 󰁷󰁨󰁥󰁲󰁥 y dijim dijimos os que 󰁡 representaría el tipo que hiciéramos instancia después. Lo podemos ver claramente ahora, ya que

cuando estamos escribiendo una instancia, escribrimos 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  E󰁱 󰁔󰁲󰁡󰁦󰁦󰁩󰁣󰁌󰁩󰁧󰁨󰁴  󰁷󰁨󰁥󰁲󰁥. Hemo remplazado la 󰁡 por el tipo actual. Como == fue definido en la definición de clase en términos de /= y viceversa, solo tenemos que sobreescribir una de ellas en la delcaración delcaració n de instancia. A esto se le llama la definición compl completa eta mínima de una una clase de tipos, o dicho de otra forma, f orma, el mínimo número mínimo número de funciones que tenemos que implementar implementar para que nuestro tipo pertenezca a una determinada clase de tipos. Para rellenar r ellenar la definición comp co mpleta leta mínim mínimaa de E󰁱, tenemos que sobreescribir o bien == o /=. Si E󰁱 hubiese sido definido como: class Eq class  Eq a  a where   (== ==) ) :: :: a  a -> -> a  a ->  -> Bool   (/= /=) ) :: :: a  a -> -> a  a -> ->   Bool

Tendríamos que que haber implementado implementado ambas funciones a la hora de crea crearr una instancia, ya y a que Hasekell sabría como están relacionadas esas funciones. De esta forma, la definición completa mínima serían ambas, == y /=. Como has visto vi sto hemos implemen implementado tado == usando ajuste de patrones. Como hay muchos más casos donde dos semáforos no están en el mismo estado, especificamos es pecificamos para cuales son iguales y luego utilizamos utilizamos un patrón que se ajuste a cualqu c ualquier ier caso ca so que no sea ninguno de los anteriores para decir que no son iguales. Vamos a crear crea r también una una instancia para 󰁓󰁨󰁯󰁷. Para satisfacer la definición completa mínima de 󰁓󰁨󰁯󰁷, solo tenemos que implementar la función 󰁳󰁨󰁯󰁷, la cual toma un valor y lo convierte a una cadena. instance Show instance  Show   TrafficLight  where   show Red Red   = "Red light"   show Yellow Yellow   = "Yellow light"   show Green Green   = "Green light"

Una vez más más hemos utilizado utilizado el ajuste de patrones patro nes para co conseguir nseguir nuestros objetivos. objetivos . V Vamos amos a verlo ver lo en acción: acció n: ghci > Red ghci> Red   == ==   Red True ghci> ghci > Red Red   == ==   Yellow False ghci> ghci > Red Red `elem`  `elem` [Red [Red, , Yellow Yellow, , Green Green] ] True ghci> ghci > [  [Red Red, , Yellow Yellow, , Green Green] ] [Red Red light,  light,Yellow Yellow light,  light,Green Green light]  light]

Perfecto. Podríamos haber derivado E󰁱 y hubiera tenido el e l mismo mismo efecto. efe cto. Sin embargo, derivar 󰁓󰁨󰁯󰁷 hubiera representando directamente los constructores como cadenas. Pero si queremos que las luces aparezcan como "󰁒󰁥󰁤 󰁬󰁩󰁧󰁨󰁴" tenemos que crear esta instancia a mano. También podemos crear clases de tipos que sean subclases de otras clases de tipos. La declaración de la clase 󰁎󰁵󰁭 es un poco larga, pero aquí tienes el principio: class (Eq class ( Eq a)  a) => =>   Num Num a  a where   ...

Como ya hemos mencionado anteriormente, hay un montón de sitios donde podemos poner restriciones de clases. Esto es

22 de 32  

lo mismo mismo que escribir esc ribir 󰁣󰁬󰁡󰁳󰁳 󰁎󰁵󰁭 󰁡 󰁷󰁨󰁥󰁲󰁥, solo que decimos que nuestro tipo ti po 󰁡 debe ser una instancia de E󰁱. Basicamente decimos que hay que crear la instancia E󰁱 de un tipo antes de que éste forme parte forme parte de la clase 󰁎󰁵󰁭. Antes de que un tipo se pueda considerar un nú número, mero, tiene sentido que podamos determinar si los vvalores alores de un tipo puede sen equiparados o no. Esto es todo lo que hay que saber de las subclases ya que simplemente son restriscciones de clase dentro de la definición de una clase. Cuando definamos funciones en la declaración declaraci ón de una clase o en la definición de una instancia, instancia , podemos asumir asumir que 󰁡 es parte de la clase E󰁱 así que podemos usar == con los valores de ese tipo. ¿Pero cómo son creadas las instancias del tipo 󰁍󰁡󰁹󰁢󰁥 o de las listas? Lo que hace diferente a 󰁍󰁡󰁹󰁢󰁥 de, digamos, 󰁔󰁲󰁡󰁦󰁦󰁩󰁣󰁌󰁩󰁧󰁨󰁴  es que 󰁍󰁡󰁹󰁢󰁥 no es por si mismo un tipo concreto, es e s un constructor de tipos que toma un parámetro (como C󰁨󰁡󰁲 o cualquier otra cosa) para producir un tipo concreto. Vamos a echar un vistazo a la clase E󰁱 de nuevo:

class   Eq Eq a  a where class   (== ==) ) :: :: a  a -> -> a  a -> ->   Bool   (/= /=) ) :: :: a  a -> -> a  a -> ->   Bool   x == == y  y = not (x /= /= y)  y)   x /= /= y  y = not (x == == y)  y)

A partir de la declaración de tipo, podemos observar que 󰁡 es utilizado como un tipo concreto ya que todos los tipos que aparecer en una función deben deben ser concretos (Recuerda, no puedes tener una función con el tipo 󰁡 ‐> 󰁍󰁡󰁹󰁢󰁥 pero si una función 󰁡 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁡 o 󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁓󰁴󰁲󰁩󰁮󰁧). Por este motivo no podemos hacer cosas como:

instance Eq instance  Eq   Maybe Maybe   where   ...

Ya que que como hemos visto, visto , 󰁡 debe ser un tipo concreto pero 󰁍󰁡󰁹󰁢󰁥 no lo es. Es un constructor de tipos ti pos que toma un parámetro y produce un tipo concreto. Sería algo pesado tener que escribir 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  E󰁱 (󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴)󰁠 󰁷󰁨󰁥󰁲󰁥, 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥 E󰁱 (󰁍󰁡󰁹󰁢󰁥  C󰁨󰁡󰁲) 󰁷󰁨󰁥󰁲󰁥, etc. para cada tipo. Así que podemos escribirlo así:

instance Eq instance  Eq (  (Maybe Maybe m)  m) where   Just x Just  x == ==   Just Just y  y = x == == y  y   Nothing  Nothing  == ==   Nothing Nothing   = True    _   == ==     _  _   = False

Esto es como decir que queremos hacer una instancia de E󰁱 para todos los tipos 󰁍󰁡󰁹󰁢󰁥 󰁡󰁬󰁧󰁯. De hecho, podríamos haber escrito 󰁍󰁡󰁹󰁢󰁥 󰁡󰁬󰁧󰁯, pero preferimos prefer imos elegir nombres nombres con co n una sola letra para ser fieles fie les al estilo esti lo de Haskell. Haskell. Aquí, Aquí, (󰁍󰁡󰁹󰁢󰁥  󰁭) hace el papel de 󰁡 en 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁡 󰁷󰁨󰁥󰁲󰁥. Mientras que 󰁍󰁡󰁹󰁢󰁥 no es un tipo concreto, 󰁍󰁡󰁹󰁢󰁥 󰁭 sí. Al utilizar un parámetro tipo (󰁭, que está en e n minúsculas), minúsculas), decimos que queremos queremos todos los tipos que sean sea n de la forma 󰁍󰁡󰁹󰁢󰁥 󰁭, donde 󰁭 es cualquier tipo que forme parte de la clase E󰁱. Sin embargo, hay un problema problema con co n esto ¿Puedes averiguarlo? ave riguarlo? Utilizamos Utilizamos == sobre los contenidos de 󰁍󰁡󰁹󰁢󰁥 pero nadie nos asegura de que lo que contiene 󰁍󰁡󰁹󰁢󰁥 forme parte de la clase E󰁱. Por este motivo tenemos que modificar nuestra declaración de instancia: instance (Eq instance ( Eq m)  m) => =>   Eq Eq (  (Maybe Maybe m)  m) where   Just x Just  x == ==   Just Just y  y = x == == y  y   Nothing  Nothing  == ==   Nothing Nothing   = True    _   == ==     _  _   = False

Hemos añadido una restricción de clase. Con esta instancia estamos diciendo: Queremos que todos los tipos con la forma 󰁍󰁡󰁹󰁢󰁥  󰁭 sean miembros de la clase de tipos E󰁱, pero solo aquellos tipos donde 󰁭 (lo que está contenido dentro de 󰁍󰁡󰁹󰁢󰁥 ) sean

miembros también de E󰁱. En realidad así sería como Haskell derivaría esta instancia.

23 de 32  

La mayoría de las veces, las restricciones de clase en las declaraciones de clases  son  son utilizadas para crear una clases de tipos que sean subclases de otras clases de tipos mientras que las restricciones de clase en las declaraciones de instancia instancias  s  son utilizadas utilizadas para expresar los requisitos re quisitos de algú a lgúnn tipo. Por ejemplo, ahora hemos expresado que el contenido de 󰁍󰁡󰁹󰁢󰁥 debe formar parte de la clase de tipos E󰁱. Cuando creas una instancia, si ves que un tipo es utilizado como un tipo concreto en la declaración de tipos (como 󰁡 en 󰁡 ‐> 󰁡 ‐> B󰁯󰁯󰁬), debes añadir los parámetros de tipos correspondientes y rodearlo con paréntesis de forma que acabes

teniendo un tipo concreto.

Nota

Ten en cuenta que el tipo para el cual estás trantando de hacer una instancia insta ncia remplaz remplazará ará el e l parámetro de la declaración de clase. La 󰁡 de 󰁣󰁬󰁡󰁳󰁳 E󰁱 󰁡 󰁷󰁨󰁥󰁲󰁥 será remplaz remplazada ada con un tipo real re al cuando crees una instancia, así as í que trata mentalmente mentalmente de poner el tipo en la declaración de tipo de las funiones. (==) :: 󰁍󰁡󰁹󰁢󰁥 ‐> 󰁍󰁡󰁹󰁢󰁥 ‐> B󰁯󰁯󰁬 no tiene mucho sentido, pero (==) :: (E󰁱 󰁭) => 󰁍󰁡󰁹󰁢󰁥 󰁭 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁭 ‐> B󰁯󰁯 sí. Pero esto es simplemente una forma de ver las cosas, ya que == simpre tendrá el tipo (==) :: (E󰁱 󰁡) => 󰁡 ‐> 󰁡 ‐> B󰁯󰁯󰁬, sin importar las instancias insta ncias que hagamos. Oh, una cosa más. Si quieres ver las instancias que existen de una clase de tipos, simplemente haz :󰁩󰁮󰁦󰁯 󰁙󰁯󰁵󰁲󰁔󰁹󰁰󰁥C󰁬󰁡󰁳󰁳 en GHCi. Así que si utilizamos :󰁩󰁮󰁦󰁯 󰁎󰁵󰁭 nos mostrará que funciones están definidas en la clase de tipos y nos mostrará también una lista con los tipos que forman parte de esta clase. :󰁩󰁮󰁦󰁯 también funciona con tipos y constructores de tipo. Si hacemos :󰁩󰁮󰁦󰁯 󰁍󰁡󰁹󰁢󰁥 veremos todas las clases de tipos de las que éste forma parte. :󰁩󰁮󰁦󰁯 también te muestra el tipo ti po de una función. Bastante útil.

La clase de tipos Yes-No En JavaScript y otros lenguajes débilmente tipados, puedes poner casi cualquier cosa dentro de una expresión. Por ejem e jemplo, plo, puedes puedes hacer hace r todo lo siguiente: si guiente: 󰁩󰁦 (0) 󰁡󰁬󰁥󰁲󰁴("󰁙EAH!")  󰁥󰁬󰁳󰁥 󰁡󰁬󰁥󰁲󰁴("󰁎󰁏!") , 󰁩󰁦 ("") 󰁡󰁬󰁥󰁲󰁴 ("󰁙EAH!")  󰁥󰁬󰁳󰁥 󰁡󰁬󰁥󰁲󰁴("󰁎󰁏!") , 󰁩󰁦 (󰁦󰁡󰁬󰁳󰁥)  󰁡󰁬󰁥󰁲󰁴("󰁙EAH")  󰁥󰁬󰁳󰁥  󰁡󰁬󰁥󰁲󰁴("󰁎󰁏!) , etc. Y todos estos

mostrarán un mensaje diciendo 󰁎󰁏!. Si hacemos 󰁩󰁦 ("󰁗HA󰁔") 󰁡󰁬󰁥󰁲󰁴 ("󰁙EAH")  󰁥󰁬󰁳󰁥 󰁡󰁬󰁥󰁲󰁴("󰁎󰁏!")  mostrará "󰁙EAH!"  ya que en JavaScript las cadenas no vacías son

consideradas valores verdaderos. Aunque Aun que el uso estricto estric to de B󰁯󰁯󰁬 para la semántica de booleanos boolea nos funciona mejor en Haskell, vamos a intentar implementar implementar este comportamiento de JavaScript ¡Solo para divertirnos! Empecemos con la declaración de clase. class  class YesNo YesNo a  a where   yesno :: :: a  a -> ->   Bool

Muy simple. La clase de tipos 󰁙󰁥󰁳󰁎󰁯 define una función. Esta Es ta función toma to ma un valor de un tipo cualquiera que puede expresar algún valor de verdad y nos dice si es verdadero o no. Fíjate en la forma que usamos 󰁡 en la función, 󰁡 tiene que ser un tipo concreto. Lo siguiente si guiente es definir algu a lgunas nas instancias. instancia s. Para los números, números, asumim a sumimos os que (como en JavaScript) Ja vaScript) cualquier número que no sea 0 es verdad ve rdadero ero y 0 es falso. instance YesNo instance  YesNo   Int Int   where   yesno 0 = False   yesno _  yesno _   = True

24 de 32  

La listas vacías (y por extensión las cadenas) son valores falsos, mientras que las listas no vacías tienen un valor verdadero. instance instance   YesNo YesNo [a]  [a] where   yesno [] []   = False   yesno _  yesno _   = True

Fíjate como hemos puesto un parámetro de tipo dentro para hacer de la lista un tipo concreto, aunque aunque no suponemos nada acerca de lo que contiene co ntiene la lista. Qué más... Mmmm... Mmmm... ¡Ya ¡Ya se se!! B󰁯󰁯󰁬 también puede contener valores verdaderos y falos y es bastante obvio cual es cual. instance YesNo instance  YesNo   Bool Bool   where   yesno = id

¿Eh? ¿Qué es 󰁩󰁤? Simplemente Simplemente es una función de la librería estándar e stándar que toma un parámetro y devuelve lo mismo, lo cual es lo mismo mismo que tendríamos que esc escribir ribir aquí. Vamos a hacer también una una instancia para 󰁍󰁡󰁹󰁢󰁥 󰁡. instance YesNo instance  YesNo (  (Maybe Maybe a)  a) where   yesno (Just Just   _   _  ) = True   yesno Nothing Nothing   = False

No necesitamos una restricción de clase ya que no suponemos nada acerca de los contenidos de 󰁍󰁡󰁹󰁢󰁥. Simplemente decimos que es verdadero si es un valor J󰁵󰁳󰁴 y falso si es 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 . Seguimos teniendo que escribir (󰁍󰁡󰁹󰁢󰁥 󰁡) en lugar de solo 󰁍󰁡󰁹󰁢󰁥 ya que, si lo piensas un poco, una función 󰁍󰁡󰁹󰁢󰁥 ‐> B󰁯󰁯󰁬 no puede existir (ya que 󰁍󰁡󰁹󰁢󰁥 no es un tipo concreto), mientras que 󰁍󰁡󰁹󰁢󰁥 󰁡 ‐> B󰁯󰁯󰁬 es correcto. Aun así, sigue siendo genial ya que ahora, cualquier tipo 󰁍󰁡󰁹󰁢󰁥 󰁡󰁬󰁧󰁯 es parte de la clase` 󰁙󰁥󰁳󰁎󰁯 y no importa lo que sea 󰁡󰁬󰁧󰁯. Antes definimos un tipo 󰁔󰁲󰁥󰁥 󰁡 para representar la búsqueda binaria. Podemos decir que un árbol vacío tiene un valor falso mientras cualquier otra cosa tiene un valor verdadero. instance  instance  YesNo YesNo (  (Tree Tree a)  a) where    

yesno EmptyTree EmptyTree   = False yesno _  yesno _   = True

¿Puede ser el estado de un semáforo un valor verdadero o falso? Claro. Si está rojo, paras. Si está verde, continuas. ¿Si está ámbar? Ehh... normalmente suelo acelerar ya que vivo por y para la adrenalina. instance YesNo instance  YesNo   TrafficLight TrafficLight   where   yesno Red Red   = False   yesno _  yesno _   = True

Genial, ahora tenemos unas cuantas instancias, vamos a jugar con ellas: hci> yesno $ length [] hci> False ghci> ghci > yesno "haha" True ghci> ghci > yesno "" False

ghci > yesno $ Just ghci> Just   0 True

25 de 32  

ghci> > yesno True ghci True ghci> ghci > yesno EmptyTree False ghci> ghci > yesno [] False ghci> ghci > yesno [0 [ 0 ,0, 0] True ghci> ghci > :t yesno yesno  yesno  :: :: (  (YesNo YesNo a)  a) => => a  a -> ->   Bool

Bien ¡Funciona! Vamos Vamos a hacer hace r una función que imite el comportam c omportamiento iento de una sentencia 󰁩󰁦, pero que funcione con valores 󰁙󰁥󰁳󰁎󰁯 .

yesnoIf :: yesnoIf  :: (  (YesNo YesNo y)  y) => => y  y -> -> a  a -> -> a  a -> a ->  a yesnoIf yesnoVal yesnoIf  yesnoVal yesResult noResult = if yesno if yesno yesnoVal then then yesResult  yesResult else else noResult  noResult

Bastante simple. Toma un valor con un grado de verdad y otros dos valores más. Si el primer valor es verdadero, devuelve el primer valor de los otros dos, de otro modo, devuelve el segundo. ghci > yesnoIf []  ghci> [] "YEAH!" "YEAH!"   "NO!" "NO!" ghci> ghci > yesnoIf [2 [2,3,4] "YEAH!" "YEAH!"   "NO!" "YEAH!" ghci> ghci > yesnoIf True True   "YEAH!" "YEAH!"   "NO!" "YEAH!" ghci> ghci > yesnoIf (Just (Just   500 500) ) "YEAH!" "YEAH!"   "NO!" "YEAH!" ghci> ghci > yesnoIf Nothing Nothing   "YEAH!" "YEAH!"   "NO!" "NO!"

La clase de tipos funtor Hasta ahora, nos hemos encontrado con un montón de de clases de tipos de la librería estándar. Hem Hemos os jugado con 󰁏󰁲󰁤, la cual es para cosas que pueden ser ordenadas. Hemos visto E󰁱, que es para cosas cosa s que pueden ser equiparadas. Vimos Vimos tamb ta mbién ién 󰁓󰁨󰁯󰁷, la cual sirve como interfaz para los tipos cuyos valores pueden ser representados como cadenas. Nuestro buen amigo 󰁒󰁥󰁡󰁤 estará aquí siempre que necesitemos convertir una cadena a un valor de algún tipo. Y ahora, vamos a echar un vistazo a

la clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲, la cual es básicamente bás icamente para cosas co sas que se pueden mapear. mapear. Seguramente Seguramente ahora mismo mismo estés esté s pensando en listas, ya que mapear mapear una lista es algú a lgúnn muy muy común en Haskell. Haskell. Y estás en lo cierto, cierto , el tipo lista es e s miembro miembro de la clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲. ¿Qué mejor forma de conocer la clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲 que ver como c omo está implementada? implementada? Vamos Vamos a echar ec har una ojeada. class  class Functor Functor f  f where   fmap :: :: (a  (a -> -> b)  b) -> -> f  f a -> -> f  f b

De acuerdo. Hemos visto que define una función, 󰁦󰁭󰁡󰁰, y no proporciona ninguna implemen imp lementació taciónn por defecto para ella. El tipo de 󰁦󰁭󰁡󰁰 es interesante. En las definiciones de clases de tipos que hemos visto hasta ahora, la variable de tipo que ha tenido un papel importante en la clase de tipos ha sido un tipo concreto, como 󰁡 en (==) :: (E󰁱 󰁡) => c oncreto (un tipo que puede puede tener un 󰁡 ‐> 󰁡 ‐>  B󰁯󰁯󰁬. Pero ahora, 󰁦 no es un tipo concreto valor, como I󰁮󰁴, B󰁯󰁯󰁬 o 󰁍󰁡󰁹󰁢󰁥 󰁓󰁴󰁲󰁩󰁮󰁧 ), sino un constructor de tipos que toma un tipo como parámetro. Un ejemplo ejemplo rápido para recordar: 󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴 es un tipo concreto, pero 󰁍󰁡󰁹󰁢󰁥  es un constructor de tipos que toma un tipo como parámetro. De cualquier modo,

hemo visto que 󰁦󰁭󰁡󰁰 toma una función de un tipo a otro y un fun funtor tor aplicado a un tipo y

26 de 32  

devuelve otro funtor aplicado con el otor tipo. Si esto te suena un poco confuso, no te preocupes. Lo verás todo más claro ahora cuando mostremos un cuantos ejemplos. ejemplos. Mmm... Mmm... esta declaración de tipo me recuerda a algo. Si no sabes cual es el tipo de 󰁭󰁡󰁰, es este: 󰁭󰁡󰁰 :: (󰁡 ‐> 󰁢) ‐> 󰁛󰁡󰁝 ‐> 󰁛󰁢󰁝. ¡Interesante! Toma Toma una función de un tipo a otro y una lista de un tipo y devuelve una lista del otro tipo. t ipo. Amigos, Amigos, creo cre o que acabamos de descubir desc ubir un funtor. funtor. De hecho, 󰁭󰁡󰁰 es 󰁦󰁭󰁡󰁰 pero solo funciona con listas. Aquí tienes como las listas tienen una instancia para la clase F󰁵󰁮󰁣󰁴󰁯󰁲 .

instance   Functor Functor   [] []   where instance   fmap = map

¡Eso es! Fíjate que no hemos escrito 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  F󰁵󰁮󰁣󰁴󰁯󰁲 󰁛󰁡󰁝 󰁷󰁨󰁥󰁲󰁥, ya que a partir de 󰁦󰁭󰁡󰁰 :: (󰁡 ‐> 󰁢) ‐> 󰁦 󰁡 ‐> 󰁦 󰁢 vemos que 󰁦 tiene que ser un cosntructor de tipos que toma un parámetro. 󰁛󰁡󰁝 ya es un tipo concreto (un lista con

cualquier tipo dentro), mientras que 󰁛󰁝 es un constructor de tipos que toma un parámetro y produce cosas como 󰁛I󰁮󰁴󰁝, 󰁛󰁓󰁴󰁲󰁩󰁮󰁧󰁝  o incluso 󰁛󰁛󰁓󰁴󰁲󰁩󰁮󰁧󰁝󰁝 .

Como para las listas, 󰁦󰁭󰁡󰁰 es simplemente 󰁭󰁡󰁰, obtenemos el mismo mismo resultado cuando las usamos con co n listas. map :: map  :: (a  (a -> -> b)  b) -> -> [a]  [a] -> -> [b]  [b] ghci> ghci > fmap (* (*2) [1 [1.. ..3 3] [2, 4 ,6 ] ghci> ghci > map (* ( *2) [1 [1.. ..3 3] [2, 4 ,6 ]

¿Qué pasa cuando realizamos realizamos 󰁭󰁡󰁰 o 󰁦󰁭󰁡󰁰 sobre listas vacías? Bien, desde luego obenemos una lista vacía. Simplemente convierte una lista vacía con el tipo 󰁛󰁡󰁝 a una lista vacía con el tipo 󰁛󰁢󰁝. Los tipos t ipos que pueden actuar como una caja pueden ser funtores. Puede pensar en una lista como co mo una una caja que tiene ti ene un número núm ero ilim i limitado itado de pequeños compartimientos y puden estar todos vacíos, vacíos , o pueden estár algunos algunos llenos. Asi que, ¿Qué más tiene la propiedad de comp co mportarse ortarse como una caja? Por ejemplo, ejemplo, el tipo 󰁍󰁡󰁹󰁢󰁥 󰁡. De algú a lgúnn modo, es como co mo una caja que puede o bien no contener nada, en cuyo caso su valor será 󰁎󰁯󰁴󰁨󰁩󰁮󰁧, o puede contener algo, como "HAHA", en cuyo caso su valor ser`á co mo 󰁍󰁡󰁹󰁢󰁥 es un funtor: J󰁵󰁳󰁴 "HAHA" . Aquí tienes como instance Functor instance  Functor   Maybe Maybe   where   fmap f (Just Just x)  x) = Just Just (f  (f x)   fmap f Nothing Nothing   = Nothing

De nuevo, fíjate que hemos escrito 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  F󰁵󰁮󰁣󰁴󰁯󰁲  󰁍󰁡󰁹󰁢󰁥 󰁷󰁨󰁥󰁲󰁥 en lugar de 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥  F󰁵󰁮󰁣󰁴󰁯󰁲  (󰁍󰁡󰁹󰁢󰁥 󰁭) 󰁷󰁨󰁥󰁲󰁥 , como hicimos cuando utilizamos utilizamos la clase 󰁙󰁥󰁳󰁎󰁯 junto con 󰁍󰁡󰁹󰁢󰁥. F󰁵󰁮󰁣󰁴󰁯󰁲 quiere un constructor de tipos que tome un

tipo y no un tipo concreto. co ncreto. Si mentalemente mentalemente remplazas remplazas las 󰁦 con 󰁍󰁡󰁹󰁢󰁥, 󰁦󰁭󰁡󰁰 actua como (󰁡 ‐> 󰁢) ‐> 󰁍󰁡󰁹󰁢󰁥 󰁡 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁢 para este tipo en particular, lo cual se ve bien. Pero si remplazas 󰁦 con (󰁍󰁡󰁹󰁢󰁥  󰁭), entonces parecerá que actua como (󰁡 ‐> 󰁢) ‐> 󰁍󰁡󰁹󰁢󰁥  󰁭 󰁡 ‐> 󰁍󰁡󰁹󰁢󰁥 󰁭 󰁢, lo cual no tiene t iene ningún maldito maldito sentido ya que 󰁍󰁡󰁹󰁢󰁥 toma un solo parámetro.

De cualquier forma, fo rma, la impl implementación ementación de 󰁦󰁭󰁡󰁰 es muy simple. Si es un valor vacío o 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 , eentonces ntonces simplemente simplemente devolvemos 󰁎󰁯󰁴󰁨󰁩󰁮󰁧 . Si mapeamos una caja vacía obtenemos una caja vacía. vac ía. Tiene sentido. De la misma forma que si mapeamos una lista vacía obtenemos un lista vacía. Si no es un valor vacío, sino más bien un único valor envuelto por J󰁵󰁳󰁴,

entonces aplicamos la función al contenido co ntenido de J󰁵󰁳󰁴.

27 de 32  

ghci ghci> > fmap (++ (++   " HEY GUYS IM INSIDE THE JUST") JUST") (Just (Just   "Something serious.") serious.") Just  Just  "Something serious. HEY GUYS IM INSIDE THE JUST" ghci> ghci > fmap (++ (++   " HEY GUYS IM INSIDE THE JUST") JUST") Nothing Nothing ghci> ghci > fmap (* (*2) (Just (Just   200 200) ) Just  Just  400 ghci> ghci > fmap (* (*2) Nothing Nothing

Otra cosa que puede ser mapeada y por tanto puede tener una instancia de F󰁵󰁮󰁣󰁴󰁯󰁲  es nuestro tipo 󰁔󰁲󰁥󰁥 󰁡. También puede ser visto como una caja (contiene varios o ningún valor) y el constructor de tipos 󰁔󰁲󰁥󰁥 toma exactamente un parámetro de tipo. Si vemos la función 󰁦󰁭󰁡󰁰 como si fuera una función hecha exclusivamente para 󰁔󰁲󰁥󰁥, su declaración de tipo sería como (󰁡 ‐> 󰁢) ‐> 󰁔󰁲󰁥󰁥 󰁡 ‐> 󰁔󰁲󰁥󰁥  󰁢. Vamos Vamos a utilizar la recursión co conn éste. Mapear un árbol vacío poducirá un árbol vacío. vac ío.

Mapear un árbol no vacío producirá un árbol en el que la función será aplicada al a l elemento elemento raíz y sus sub-árboles s ub-árboles derechos e izquierdos izquierdos ser serán án los mismos mismos sub-árboles, solo s olo que serán mapeado con la función. instance Functor instance  Functor   Tree Tree   where   fmap f EmptyTree EmptyTree   = EmptyTree   fmap f (Node Node x  x leftsub rightsub) = Node Node (f  (f x) (fmap f leftsub) (fmap f rightsub)

ghci > fmap (* ghci> (*2) EmptyTree EmptyTree ghci> ghci > fmap (* (*4) (foldr treeInsert EmptyTree EmptyTree [  [5 5,7,3,2,1,7]) Node  Node  28 28 (  (Node Node   4 EmptyTree EmptyTree (  (Node Node   8 EmptyTree EmptyTree (  (Node Node   12 12   EmptyTree EmptyTree (  (Node Node   20 20   EmptyTree EmptyTree   Empt

¡Bien! ¿Qué pasa con E󰁩󰁴󰁨󰁥󰁲 󰁡 󰁢? ¿Puede ser un funtor? La clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲 quiere constructores de tipos que tomen un solo parámetro parámetro de tipo pero E󰁩󰁴󰁨󰁥󰁲  toma dos. Mmm... Mmm... ¡Ya ¡Ya se se!! aplicaremos aplicare mos parcialmente E󰁩󰁴󰁨󰁥󰁲 suministrando un solo parámetro de forma que solo tenga un parámetro libre. Aquí Aquí tienes como c omo el tipo E󰁩󰁴󰁨󰁥󰁲 󰁡 es un funtor en las librerías estándar. instance Functor instance  Functor (  (Either Either a)  a) where   fmap f (Right Right x)  x) = Right Right (f  (f x)   fmap f (Left Left x)  x) = Left Left x  x

Bueno, bueno ¿Qué hemos hemos hecho aquí? a quí? Pudes ver como co mo hemos hemos creado cre ado una instancia para par a E󰁩󰁴󰁨󰁥󰁲 󰁡 en lugar de para solo E󰁩󰁴󰁨󰁥󰁲 . Esto es así porque` E󰁩󰁴󰁨󰁥󰁲  󰁡 es un constructor co nstructor de tipos que toma to ma un parámetro, mientras que E󰁩󰁴󰁨󰁥󰁲 toma dos. Si 󰁦󰁭󰁡󰁰 fuese específicamente para E󰁩󰁴󰁨󰁥󰁲  󰁡 entonces su declaración de tipo sería (󰁢  ‐>  󰁣) ‐> E󰁩󰁴󰁨󰁥󰁲  󰁡 󰁢 ‐> E󰁩󰁴󰁨󰁥󰁲  󰁡 󰁣 ya que es lo mismo que 󰁢 ‐> 󰁣) ‐> (E󰁩󰁴󰁨󰁥󰁲  󰁡) 󰁢 ‐> (E󰁩󰁴󰁨󰁥󰁲  󰁡)  󰁣 . En la implementación, implementación, mapeamos en el caso ca so del

constructor de tipos 󰁒󰁩󰁧󰁨󰁴, pero no lo hacemos para el caso de 󰁌󰁥󰁦󰁴. ¿Por qué? Bueno, si volvemos atrás para ver como se define el tipo E󰁩󰁴󰁨󰁥󰁲 󰁡 󰁢, varíamos algo como: data  data  Either Either a  a b = Left Left a  a | Right Right b  b

Bueno, si quisieramos quisiera mos mapear mapear una función sobre ambos, 󰁡 y 󰁢 deberían tener el mimso tipo. Quiero decir, si quisieramos mapear una función función que toma una cadena y devuelve otra cadena c adena y 󰁢 fuese una cadena pero 󰁡 fuese un número, número, ésto é sto no funcionaria. También, viendo 󰁦󰁭󰁡󰁰 si operara solo con valores de E󰁩󰁴󰁨󰁥󰁲, veríamos vería mos que el primer primer parámetro tiene tie ne que permanecer igual mientras que el segundo puede variar y el primer parámetro está asociado al constructor de datos 󰁌󰁥󰁦󰁴.

Esto también encaja con nuestra analogía de las cajas si pensamos en 󰁌󰁥󰁦󰁴 como una especie de caja vacía con un mensaje de error escrito en un lado diciendonos porque la caja está vacía. Los diccionarios de D󰁡󰁴󰁡.󰁍󰁡󰁰  también son funtores ya que pueden contener (o no) valores. En el caso de 󰁍󰁡󰁰 󰁫 󰁶, 󰁦󰁭󰁡󰁰

mapearía una función 󰁶 ‐> 󰁶' sobre un diccionario 󰁍󰁡󰁰 󰁫 󰁶 y devolvería un diccionario con el tipo 󰁍󰁡󰁰 󰁫 󰁶'.

28 de 32  

Nota

Fíjate que ' no tiene ningún significado especial especia l en los tipos de la misma forma que no tienen ningú ningúnn significado especial espec ial a la hora de nombrar valores. Se suele utilizar para referirse a cosas que son similares, solo que un poco cambiadas. ¡Trata de imaginarte como se crea la instancia de 󰁍󰁡󰁰 󰁫 para F󰁵󰁮󰁣󰁴󰁯󰁲  tú mismo! Con la clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲  hemos visto como las clases de tipos puden representar conceptos de orden superior interesantes. También hemos hemos tenido un poco de práctica aplicando parcialmente tipos y creando cre ando instancias. En uno de los siguientes capítulos veremos algunas de las leyes que se aplican a los funtores. Nota

Los funtores deben obedecer algunas leyes de forma fo rma que tengan tengan unas propiedades de las que podamos podamos depender para no tener que pensar mucho mucho luego. Si usamos 󰁦󰁭󰁡󰁰 (+1) sobre un la lista 󰁛1,2,3,4󰁝  esperemamos obtener 󰁛2,3,4,5󰁝  y no su inversa, 󰁛5,4,3,2󰁝 . Si usamos 󰁦󰁭󰁡󰁰 (󰁜󰁡 ‐> 󰁡) (la función identidad, que simp si mplem lemente ente devuelve su parámetro) sobre so bre un lista, esperamos obtener la misma lista como resultado. Por ejemplo, ejemplo, si le damos una instancia erronea a nuestro tipo ti po 󰁔󰁲󰁥󰁥, al usar 󰁦󰁭󰁡󰁰 en un árbol donde el sub-árbol s ub-árbol izquierdo izquierdo de un nodo solo contenga c ontenga elementos menores que el nodo y el

sub-árbol derecho solo contenga co ntenga elem elementos entos mayores que el nodo podría producir un árbol dond dondee no se cumpliera cumpliera esto. es to. Veremos la leyes leye s de los funtores con más detalle en un próximo próximo ca capítul pítulo. o.

Familias Fami lias y ar artes tes marciales Los constructores de tipos toman otros tipos como parámetros y terminan produciendo tipos concretos. Esto me recuerda a las funciones, las cuales toman valores como parámetros y producen valores. Hemos visto que los constructores de tipos pueden ser parcialmente aplicados ( E󰁩󰁴󰁨󰁥󰁲 󰁓󰁴󰁲󰁩󰁮󰁧 es un constructor de tipos que toma to ma un tipo tipo y devuelve dev uelve un tipo concreto, como E󰁩󰁴󰁨󰁥󰁲  󰁓󰁴󰁲󰁩󰁮󰁧  I󰁮󰁴), al igual que la funciones. Muy interesante. En esta e sta

sección, definiremos formalmente como los tipos son aplicados a los constructores de tipos, de la misma definiremos formalmente formalmente como los va valores lores son aplicados a las funciones utilizando declaraciones de tipo. No necesitas leer esta sección para continuar con tu búsqueda de la sabiduría sobre Haskell y no consigues entenderlo, no te preocupes. Sin embargo, si lo haces

conseguiras un conocimiento conoci miento profundo profundo del sistema de tipos. Así que, valores como 3, "󰁙EAH" o 󰁴󰁡󰁫󰁥󰁗󰁨󰁩󰁬󰁥 (las funciones también son valores ya que podemos usarlas como parámetros) tienen sus correspondientes tipos. Los tipos son una pequeña etiqueta que llevan los valores de forma que nos permitan razonar sobre estos. Pero los tipos tienen sus propias pequeñas etiquetas, eti quetas, llamadas llamadas familias. Una familia familia es más o menos el tipo de un tipo. Puede sonar un poco enrevesado y confuso, pero en realidad es un concepto muy intersante. ¿Qué son las familias y para que son útiles? B Bueno, ueno, vamos a examinar la familia de un tipo utiliz utilizando ando el comando :󰁫 en GHCi. ghci> ghci > :k Int Int  Int  :: ::   *

¿Unaa estrella? ¿Un est rella? Intrigante... ¿Qué significa? Una Una * significa que el tipo es un tipo concreto. Un tipo concreto es un tipo que no

29 de 32  

toma ningún parámetro de tipo y valores solo pueden tener tipos que sean tipos concretos. Si tuviera que leer * en voz vo z alta (hasta ahora no he tenido que hacerlo), diría estrella  o   o simplemente tipo . Vale, ahora vamos v amos a ver cual es la familia de 󰁍󰁡󰁹󰁢󰁥. ghci > :k Maybe ghci> Maybe  Maybe  :: ::   * -> ->   *

El constructor de tipos 󰁍󰁡󰁹󰁢󰁥 toma un tipo concreto (como I󰁮󰁴) y luego devuelve un tipo concreto como 󰁍󰁡󰁹󰁢󰁥 I󰁮󰁴. Y esto es lo que la familia nos está es tá diciendo. De la misma misma forma que I󰁮󰁴 ‐> I󰁮󰁴 represe  representa nta una función que toma un I󰁮󰁴 y devuelve un I󰁮󰁴, * ‐> * representa un constructor de tipos que toma un tipo concreto y devuelve otro tipo concreto. Vamos a aplicar el parámetro de tipo a 󰁍󰁡󰁹󰁢󰁥 y ver cual es su familia. ghci > :k Maybe ghci> Maybe   Int Maybe  Maybe  Int Int   :: ::   *

¡Justo como esperaba! Hemo Hemo pasado un parámetro parámetro de tipo a 󰁍󰁡󰁹󰁢󰁥 y hemos obtenido un tipo concreto (esto es lo que significa * ‐> *). Un símil (aunque no equivalente, los tipos y las familias son dos cosas distintas) sería si hicieramos :󰁴 󰁩󰁳󰁕󰁰󰁰󰁥󰁲  y :󰁴 󰁩󰁳󰁕󰁰󰁰󰁥󰁲  'A'. 󰁩󰁳󰁕󰁰󰁰󰁥󰁲  tiene el tipo C󰁨󰁡󰁲 ‐> B󰁯󰁯󰁬 y 󰁩󰁳󰁕󰁰󰁰󰁥󰁲  'A' tiene el tipo B󰁯󰁯󰁬 ya que su valor es

básicamente 󰁔󰁲󰁵󰁥. Utilizamos :󰁫 con un tipo para obtener su familia, de la misma forma que utiliz utilizamos amos :󰁴 con un valor para obtener su tipo. Como ya hemos dicho, los tipos son las etiquetas de los valores y las familias son las etiquetas de los tipos y hay similitudes entre ambos. Vamos a ver otra familia. ghci > :k Either ghci> Either  Either  :: ::   * -> ->   * -> ->   *

¡Aha! Esto nos dice que E󰁩󰁴󰁨󰁥󰁲 toma dos tipos concretos c oncretos como parámetros de tipo y produce un tipo concreto. También se parece a una declaracion de tipo de una función que toma dos valores y devuelve algo. Los construcotores de tipos están currificados (como las funciones), así que podemos aplicarlos parcialmente. ghci > :k Either ghci> Either   String Either  Either  String String   :: ::   * -> ->   * ghci> ghci > :k Either Either   String String   Int Either  Either  String String   Int Int   :: ::   *

Cuando quisimos que E󰁩󰁴󰁨󰁥󰁲 formara parte de la clase de tipos F󰁵󰁮󰁣󰁴󰁯󰁲 , tuvimos que aplicarlo parcialmen parc ialmente te ya que F󰁵󰁮󰁣󰁴󰁯󰁲  quiere tipos que tomen un solo parámetro`,` mientras que E󰁩󰁴󰁨󰁥󰁲  toma dos. En otras palabras, F󰁵󰁮󰁣󰁴󰁯󰁲  quiere tipos

de la familia * ‐> * y por eso tuvimos que aplicar parcialmente E󰁩󰁴󰁨󰁥󰁲 para obtener una familia * ‐> * en lugar de su familia original * ‐> * ‐> *. Si vemos la definición de F󰁵󰁮󰁣󰁴󰁯󰁲 otra vez class Functor class  Functor f  f where   fmap :: :: (a  (a -> -> b)  b) -> -> f  f a -> -> f  f b

veremos que la variable de tipo 󰁦 es utiliza como un tipo que que toma toma un tipo y produce un tipo co concreto. ncreto. Sabemos que

produce un tipo concreto porque es utilizada utilizada como el tipo de un valor en una función. Podemos deducir que los tipos que quieren

30 de 32  

amigableess con F󰁵󰁮󰁣󰁴󰁯󰁲 debe ser de la familia * ‐> *. Ahora vamos a practicar un poco de artes marciales. Echa un vistazo a la clase de tipos que voy a utilizar: class class   Tofu Tofu t  t where   tofu :: :: j  j a -> -> t  t a j

Parece complicado ¿Cómo podríamos crear un tipo que tuviera una instancia para esta clase de tipos estraña? Bueno, vamos a ver ve r que familia tiene que tener. Como 󰁪 󰁡 es utilizado como el tipo del valor que la función 󰁴󰁯󰁦󰁵 toma como parámetro, for ma que que podemos inferir que 󰁪 pertenece a la familia * ‐> *. Vemos que 󰁴 󰁪 󰁡 debe tener la familia *. Asumimos * para 󰁡 de forma también tiene que producir producir un tipo co concreto ncreto y toma dos tipos. Sabiendo que 󰁡 es de la familia * y 󰁪 de * ‐> *, podemos inferir que 󰁴 es de la familia * ‐> (* ‐> *) ‐> *. Así que toma un tipo tipo concreto concre to ( 󰁡), un constructor de tipos ( 󰁪) que toma un tipo concreto y devuelve un tipo concreto. Wau. Vale, vamos a crear un tipo con c on una familia * ‐> (* ‐> *) ‐> *. Aquí tienes una posible solución. so lución. data  data  Frank Frank a  a b

= Frank Frank {frankField  {frankField :: :: b  b a} deriving deriving (  (Show Show) )

¿Cómo sabemos que este tipo pertenece a la familia * ‐> (* ‐> *) ‐> *? Bueno, los camp ca mpos os de un TDA (tipos de datos algebraicos, ADT  en  en inglés) sirven para contener valores, así que obviamente pertenecen a la familia *. Asumimos * para 󰁡, lo que significa que 󰁢 toma un parámetro de tipo y por lo tanto pertenece a la familia * ‐> *. Ahora que sabemos las familia de 󰁡 y 󰁢 ya que son parámetros de F󰁲󰁡󰁮󰁫, vemos que F󰁲󰁡󰁮󰁫  pertenece a la familia * ‐>  (* ‐> *) ‐>  *. El primer * representa 󰁡 y

Vamos a crea crearr algunos valores de F󰁲󰁡󰁮󰁫 y comprobar sus tipos. (* ‐> *) representa 󰁢. Vamos ghci > :t Frank ghci> Frank {frankField  {frankField = Just Just   "HAHA" "HAHA"} } Frank {frankField Frank  {frankField = Just Just   "HAHA" "HAHA"} } :: ::   Frank Frank [  [Char Char] ] Maybe ghci> ghci > :t Frank Frank {frankField  {frankField = Node Node   'a' 'a'   EmptyTree EmptyTree   EmptyTree EmptyTree} } Frank {frankField Frank  {frankField = Node Node   'a' 'a'   EmptyTree EmptyTree   EmptyTree EmptyTree} } :: ::   Frank Frank   Char Char   Tree ghci> ghci > :t Frank Frank {frankField  {frankField = "YES" "YES"} } Frank {frankField Frank  {frankField = "YES" "YES"} } :: ::   Frank Frank   Char Char   []

Mmm... Mmm ... Como 󰁦󰁲󰁡󰁮󰁫F󰁩󰁥󰁬󰁤  tiene el tipo en forma de 󰁡 󰁢, sus valores valore s deben tener tipos de forma similar. Puede Puede ser como J󰁵󰁳󰁴 "HAHA" , el cual tiene el tipo 󰁍󰁡󰁹󰁢󰁥  󰁛C󰁨󰁡󰁲󰁝  o puede ser como 󰁛'󰁙','E','󰁓'󰁝  que tiene el tipo 󰁛C󰁨󰁡󰁲󰁝  (si usaramos

nuestro tipo de listas que creamos anteriormente, sería 󰁌󰁩󰁳󰁴 C󰁨󰁡󰁲). Y vemos que los tipos de los valores de F󰁲󰁡󰁮󰁫 se corresponden con la familia de F󰁲󰁡󰁮󰁫. 󰁛C󰁨󰁡󰁲󰁝 pertenece a la familia * y 󰁍󰁡󰁹󰁢󰁥 pertenece a * ‐> *. Como para poder tener valores un tipo tiene que ser un tipo concreto y por lo tanto debe ser completamente aplicado, cada valor de F󰁲󰁡󰁮󰁫 󰁢󰁬󰁡 󰁢󰁬󰁡󰁡󰁡 pertenece a la familia *. Crear la instancia de F󰁲󰁡󰁮󰁫 para 󰁔󰁯󰁦󰁵 es bastante bas tante simple. Hemos Hemos visto que 󰁴󰁯󰁦󰁵 toma un 󰁪 󰁡 (que por ejem e jemplo plo podría ser 󰁍󰁡󰁹󰁢󰁥  I󰁮󰁴) y devuelve un 󰁴 󰁪 󰁡 . Así que si remplazamos 󰁪 por F󰁲󰁡󰁮󰁫, el tipo del resultado sería F󰁲󰁡󰁮󰁫 I󰁮󰁴 󰁍󰁡󰁹󰁢󰁥 .

instance Tofu instance  Tofu   Frank Frank   where   tofu x = Frank Frank x  x

ghci > tofu (Just ghci> (Just   'a' 'a') ) :: ::   Frank Frank   Char Char   Maybe Frank {frankField Frank  {frankField = Just Just   'a' 'a'} } ghci> ghci > tofu ["HELLO" ["HELLO"] ] :: ::   Frank Frank [  [Char Char] ] [] Frank {frankField Frank  {frankField = [  ["HELLO" "HELLO"]} ]}

No es muy útil, pero hemos hemos calentado. ca lentado. Vamos Vamos a continuar haciedo hacie do artes marciales. Tenemos Tenemos este tipo: ti po:

31 de 32  

data data   Barry Barry t  t k p = Barry Barry {  { yabba :: :: p,  p, dabba :: :: t  t k }

Y ahora queremos crear una instancia para la clase F󰁵󰁮󰁣󰁴󰁯󰁲. F󰁵󰁮󰁣󰁴󰁯󰁲 requiere tipos cuya familia sea * ‐> * pero B󰁡󰁲󰁲󰁹 no parece que pertenezca a esa fa famil milia. ia. ¿Cúal es la familia de B󰁡󰁲󰁲󰁹? Bueno, vemos que toma tres parámetros de tipo, así que va ser algo como 󰁡󰁬󰁧󰁯 ‐> 󰁡󰁬󰁧󰁯 ‐> 󰁡󰁬󰁧󰁯 ‐> *. Esta claro que 󰁰 es un tipo concreto y por lo tanto pertenece a la familia *. Para 󰁫 asumimos * y por extensión, 󰁴 pertenece a * ‐> *. Ahora solo tenemos que remplazar remplazar estas familia por los algos  que  que hemos utilizado utilizado y veremos v eremos que el tipo pertenece a la familia (* ‐> *) ‐> *󰁠 󰁠‐> * ‐> *. Vamos a comprobarlo con GHCi. ghci > :k Barry ghci> Barry  Barry  :: :: (  (* * -> ->   *) -> ->   * -> ->   * -> ->   *

Ah, teníamos teníamos razón. Ahora, para hacer que este tipo forme f orme parte de la clase F󰁵󰁮󰁣󰁴󰁯󰁲  tenemos que aplicar parcialmente parc ialmente los dos primeros parámetros de tipo de forma que nos quedemos quedemos con co n * ‐> *. Esto significa que comenzaremos comenzaremos con nuestra declaración de instancia así: 󰁩󰁮󰁳󰁴󰁡󰁮󰁣󰁥 F󰁵󰁮󰁣󰁴󰁯󰁲  (B󰁡󰁲󰁲󰁹 󰁡 󰁢) 󰁷󰁨󰁥󰁲󰁥. Si vemos 󰁦󰁭󰁡󰁰 como si estuviese hecho exclusivamente para` B󰁡󰁲󰁲󰁹, tendría un tipo 󰁦󰁭󰁡󰁰 :: (󰁡 ‐> 󰁢) ‐> B󰁡󰁲󰁲󰁹 󰁣 󰁤 󰁡 ‐> B󰁡󰁲󰁲󰁹 󰁣 󰁤 󰁢, ya que simplemente hemos remplazado la 󰁦 de F󰁵󰁮󰁣󰁴󰁯󰁲 por B󰁡󰁲󰁲󰁹 󰁣 󰁤. El tercer parámetro de tipo de B󰁡󰁲󰁲󰁹 tendría que cambiar y de esta forma tendríamos: instance Functor instance  Functor (  (Barry Barry a  a b) where   fmap f (Barry Barry {yabba  {yabba = x, dabba = y}) = Barry Barry {yabba  {yabba = f x, dabba = y}

¡Ahí lo tienes! Simplemente hemos aplicado 󰁦 sobre el primer campo. En esta sección, se cción, hemos dado un buen vistazo a como funcionan los parámetros de tipos ti pos y como co mo se formalizan formalizan con la familias, de la misma misma forma que formalizamos formalizamos los parámetros de las funciones con las declaraciones dec laraciones de tipo. Hemos visto que hay similitudes entre las funciones y los constructores de tipos. De todas formas, son cosas totalmente distintas. Cuando trabajamos con Haskell, normalm normalmente ente no debes preocuparte por la familias ni inferir inferi r mentalmente mentalmente las familias como hemos hecho aquí. Lo normal es que tengas que aplicar parcialm parci almente ente tu tipo a * ‐> * o * cuando creamos una instancia para alguna clase de la librería estándar, pero está bien saber como funciona realmente. Es interesante saber que los tipos tienen sus propios pequeños tipos también. De nuevo, no tienes porque entender todo lo que acabamos de hace hacerr aquí, pero si entiendes como funcionan las familias, tienes más posibilidades de entender e ntender correctamente correcta mente el sistema de tipos de Haskell.

siguiente

anterior |

Índice »

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF