Mi Primera Clase de Haskell
Introducción
Hay un paradigma de programación que se denomina “programación declarativa”. Surge en contraposición a la “programación imperativa”, que en sus comienzos estaba representada por lenguajes muy próximos a las peculiaridades de la propia máquina: operaciones artiméticas simples, instrucciones de acceso a la memoria, etc. Ese tipo de programas resultan a menudo crípticos, llegando a impedir su comprensión incluso a programadores entrenados, aunque en la actualidad los lenguajes imperativos han atenuado mucho este efecto.
La idea de la programación declarativa es escribir programas
especificando o “declarando” un conjunto de condiciones,
proposiciones, afirmaciones, restricciones, ecuaciones o
transformaciones que describen sin ambigüedad el problema y contienen
su solución. Dicha solución es obtenida mediante mecanismos internos
de control para la inferencia de información, sin que el programador
haya especificado exactamente cómo encontrarla. A la computadora se le
indica qué es lo que se desea obtener, no cómo hacerlo. No existen
asignaciones destructivas y las variables son utilizadas con
“transparencia referencial”. En otras palabras, el resultado de
evaluar una expresión compuesta depende únicamente del resultado de
evaluar las subexpresiones que la componen y de nada más; no depende
de la historia del programa en ejecución ni del orden de evaluación de
las subexpresiones que la componen. Esta propiedad no se da en
lenguajes imperativos, donde abundan los efectos colaterales por
asignaciones destructivas. Veamos a Python, que no tiene la
transparencia referencial, en un ejercicio de violación de la
misma. Para ello consideremos el retazo de código siguiente que
podríamos dar como contenido al fichero transparencia.py
:
counter = 0
def incremental(n):
global counter
i = 0
while i <= n:
counter += 1
i += 1
print counter
incremental(5)
incremental(5)
para entender el efecto, podemos interpretarlo y ver que ofrece el siguiente diálogo:
iMac-de-miUsuario:~ my_login$ python transparencia.py
6
12
iMac-de-miUsuario:~ my_login$
Es decir, dos invocaciones consecutivas de la misma función —pero en ambientes diferentes— han dado respuestas diferentes. Y es que las funciones en los lenguajes no funcionales, como es el caso de Python, no tienen por qué comportarse como funciones en el sentido matemático; son más bien algo así como “subrutinas”. Lo que un lenguaje funcional reclama, y consigue por serlo, es la recuperación de concepto “función” con el sentido dado por la matemática … y la computación.
En estas páginas hemos hablado ya de un subparadigma del paradigma de programación declarativa cuando presentamos el post de Prolog: la programación lógica. Tal paradigma está fundamentado por la Lógica de Primer Orden.
Sin embargo, los comienzos del siglo XX vieron como era alumbrada otra lógica, la llamada Lógica Combinatoria. Basándose en ella, o en una explicación suya denominada Lambda Calculus (desarrollado en la década de los 30 del siglo XX para investigar la noción de función, la aplicación de funciones y la recursión), surgió el segundo gran subparadigma de la programación declarativa: la programación funcional. Un lenguaje funcional es, a la postre, el resultado de una reelaboración del lambda calculus puro.
La programación funcional se basa en la utilización de funciones aritméticas que no manejan datos mutables o de estado. Hace uso de la aplicación de funciones en contraposición con el el paradigma de programación imperativa, que hace énfasis en los cambios de estado. La diferencia entre una función matemática y la noción de función utilizada en la programación imperativa es que las funciones imperativas pueden tener efectos secundarios, al cambiar el valor de cálculos realizados previamente, carecen de transparencia referencial: la misma expresión sintáctica puede resultar en valores diferentes en diferentes momentos dependiendo del estado del programa en ejecución. Con código funcional, en contraste, el valor generado por una función depende exclusivamente de los argumentos alimentados a la función. Al eliminar los efectos secundarios se pude entender y predecir el comportamiento de un programa mucho más fácilmente, y esta es una de las principales motivaciones para utilizar la programación funcional.
El tercer gran subparadigma de la programación declarativa es la
programación algebraica, representada por lenguajes como Maude
y
SQL
.
En la actualidad, el lenguaje que encarna por antonomasia los cánones de la programación funcional es Haskell, un lenguaje de programación: estandarizado, multipropósito, puramente funcional, perezoso, con semánticas no estrictas y con fuerte tipificación estática. Su nombre se debe al lógico estadounidense Haskell B. Curry por la utilidad que tuvieron al respecto sus observaciones y las de su escuela.
Haskell nació cuando en 1980 se constituyó un comité cuyo objetivo era crear un lenguaje funcional que reuniese las características de los múltiples lenguajes funcionales de la época, el más notable de los cuales era Miranda, y resolviera la confusión creada por la proliferación de los mismos.
Las características más interesantes de Haskell incluyen el soporte para tipos de datos y funciones recursivas, listas, tuplas, guardas y calce de patrones. La combinación de las mismas puede llevar en el caso de algunas funciones a una implementación casi trivial, y sin embargo ser su correspondiente versión en lenguajes imperativos extremadamente tediosas de programar.
Instalación
Al instalar Haskell en nuestro ordenador, conviene instalar la plataforma completa. En Ubuntu, cuando ésta está disponible, la instalación es trivial:
sudo apt install haskell-platform
y si somos felices usuarios de Mac OS X con
macport instalado (depende de
Xcode y
posterior instalación de Command Line Tools
), la instalación de la
plataforma Haskell la efectuaremos con la orden de consola:
sudo port install haskell-platform
Al instalar la plataforma tendremos instalado Cabal
. Cabal
es el
sistema de paquetes estándar para el software de Haskell. Ayuda a
configurar, compilar e instalar el software de Haskell y distribuirlo
fácilmente a otros usuarios y desarrolladores. Ayuda con la
instalación de paquetes existentes y también ayuda a los
desarrolladores con sus propios paquetes. Se puede utilizar para
trabajar con paquetes locales o para instalar paquetes desde archivos
de paquetes en línea, incluyendo las dependencias que instala de forma
automática. Por defecto está configurado para utilizar Hackage que es
archivo del paquete central de Haskell y que contiene miles de
bibliotecas y aplicaciones en el formato de paquete de Cabal.
Un ejemplo de utilización de Cabal para instalar paquetes de un archivo remoto de paquetes contenido en Hackage y que se llama xmonad sería utilizando la orden en línea llamada cabal:
cabal install xmonad
lo cual instalará el paquete xmonad
además de todas sus dependencias.
Una opción de gran interés, acaso imprescindible, es instalar Haskell
a través de
stack
. Para
los sistemas Unix comunes, incluido macOS, la instalación se llevará a
cabo con la orden:
curl -sSL https://get.haskellstack.org/ | sh
o bien, si se prefiere mediante wget
(en macOS habría que instalar
wget
antes mediante brew
, por ejemplo) con la orden:
wget -qO- https://get.haskellstack.org/ | sh
Alternativamente en macOS se instala también stack
mediante brew
con la orden:
brew install stack
Para Windows, aquí encontrará instrucciones y un instalador adecuado a su aquitectura.
Vía stack
tendremos GHC
aislado del resto del sistema y todas las
órdenes sobre Haskell deberán comenzar por stack
, por ejemplo:
stack ghc
stack ghci
stack runhaskell
La forma de proceder para un proyecto es la siguiente:
stack new my-project-name simple
lo que crea la carpeta my-project-name
(puede ser este nombre u otro) y
dentro estará la subcaperta src
que es donde pondremos todo lo
programado. En particular tendremos allí por defecto un fichero
Main.hs
que para la prueba contiene el hola mundo
, pero este fichero
puede ser cambiado al contenido conveniente. Desde src
o desde my-project-name
podemos ejecutar
stack exec my-project-name
y con ello se interpreta el Main.hs
.
Son necesarios los siguientes parámetros para la plantilla pero no
están provistos: author-email
, author-name
, category
,
copyright
y github-username
.
Puede proveerlos en su macOS en /Users/miUsuario/.stack/config.yaml
o en su sistema Linux en /Users/miUsuario/.stack/config.yaml
de la
siguiente forma:
templates:
params:
author-email: value
author-name: value
category: value
copyright: value
github-username: value
o puede pasarlos cada uno como parámetro así:
stack new mainExample simple -p "author-email:value" -p "author-name:value" \
-p "category:value" -p "copyright:value" -p "github-username:value”
Cuando vamos a otro equipo que por estar sincronizado comparte el
directorio my-project-name
, puede que hayamos sido debidamente
cuidadosos y no hayamos compartido los ficheros de configuración de la
forma .configfile
; deberá ejecutar allí, dentro de la carpeta
my-project-name
, la orden:
stack build
y ya funcionará desde ella o desde su subcarpeta src
la orden:
stack exec my-project-name
En Cuanto al Editor
Siempre decimos que nuestros lectores pueden usar su editor preferido, sin embargo en este caso hay algunas restricciones. Nunca nunca usaremos editores que al tabular introduzcan caracteres no imprimibles. La razón es que Haskell es un lenguaje que ha suprimido los terminadores de línea, lo que ha conducido a usar indentación en el código. Pues bien, la indentación no es real si el editor incluye caracteres no imprimibles. Hemos visto a compañeros y profesores volverse locos tratando de entender por qué no funciona un código que a todas luces era perfecto; y no funcionaba porque la indentación estaba rota por culpa de “fantasmas no visibles” en el renglón. Recomendación: emacs o Sublime Text.
Haskell y Emacs
Los archivos de los proyectos de Haskell son ficheros .hs
. Para que
emacs pueda manejar fácilmente dichos ficheros, instalaremos en
nuestro Ubuntu el paquete haskell-mode
sudo apt install haskell-mode
Haskell y Sublime Text
Instalaremos Sublime Text mediante:
sudo add-apt-repository ppa:webupd8team/sublime-text-3
sudo apt update
sudo apt install sublime-text-installer
Sublime Text está totalmente preparado para usar Haskell, salvo por un detalle: los caracteres no imprimibles introducidos por el tabulador. Para poder usar dicha tecla con toda comodidad y que no se incluyan estos indeseables caracteres debemos ir a
Preferences > Settings - User
y agregar dentro de las llaves
"translate_tabs_to_spaces": true,
El “hola mundo”
Haskell tiene un intérprete y un compilador. Saludemos al mundo primero desde el intérprete. Para ello invocaremos al intérprete desde la línea de comandos con la orden:
ghci
y el resultado será un diálogo del tipo:
Last login: Sat Sep 27 17:59:09 on console
iMac-de-miUsuario:~ my_login$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude>
Hay que saber que Prelude
es el ambiente general mínimo de librerías
y funciones cargadas al abrir el intérprete, ambiente que se puede
completar a deseo del usuario. Ahora escribiremos:
Prelude> putStrLn "Hola ... mundo!"
y al pulsar intro obtenemos el diálogo
Hola ... mundo!
Prelude>
Para salir del intérprete escribiremos
Prelude> :q
y pulsando intro saldremos a la línea de órdenes de la consola.
Si lo que queremos hacer es un ejecutable que nos permita difundir el saludo al mundo entre nuestros amigos, que puede que no tengan Haskell instalado, utilizaremos el compilador. Para ello usaremos nuestro editor preferido y con él abriremos un fichero que llamaremos hola.hs, por ejemplo. El contenido del fichero será exclusivamente la línea:
main = putStrLn "Hola ... mundo!"
Cerramos el fichero y desde la línea de órdenes de la consola ejecutamos la orden:
ghc -o hola hola.hs
por supuesto que hola es el nombre que le deseamos dar al ejecutable;
además del mismo, se han creado otros dos: el hola.hi
y el
hola.o
. Estos dos ficheros son
auxiliares y necesarios
para la compilación. Para ejecutar nuestro ejecutable, escribiremos en
la línea de órdenes (suponemos que todo se ha llevado a cabo donde
está ubicado el fichero hola.hs
):
./hola
y al pulsar intro tendremos el cortés mensaje:
Hola ... mundo!
Ejemplos
Para ejemplificar el uso de Haskell podemos recurrir a funciones
conocidas ya en los lenguaje imperativos y que produzcan fuerte
contraste. Con nuestro editor preferido abriremos un fichero
example.hs
y le pondremos el siguiente contenido, que irá creciendo:
qsort [] = []
qsort (x:xs) = qsort ([y | y <- xs, y < x]) ++ [x] ++ qsort ([y | y <- xs, y >= x])
o si se prefiere, el código más elaborado y eficaz:
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
Ahora, si estamos ubicados con la consola en el directorio que
contiene al fichero example.hs
, tenemos dos opciones: abrir el
intérprete y cargar el fichero example.hs
o abrir el intérprete para
él. La primera opción se materializa ejecutando:
ghci
pulsando intro y cuando aparezca el diálogo:
Last login: Sat Sep 27 17:59:09 on console
iMac-de-miUsuario:~ my_login$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude>
entonces escribiremos:
Prelude> :l example.hs
y al pulsar intro obtendremos el diálogo:
[1 of 1] Compiling Main ( example.hs, interpreted )
Ok, modules loaded: Main.
*Main>
El retazo Ok, modules loaded: Main.
indica que el fichero
example.hs
ha sido interpretado correctamente y el retazo de código
*Main>
indica que el ambiente Prelude
ha sido completado con las
funciones, tipos de datos, clases, etc. recién interpretadas en
example.hs
.
Ahora seremos capaces de ordenar una lista con nuestra función:
*Main> qsort ([2,-1,5,3])
[-1,2,3,5]
*Main>
¿No parece magnífico? Sin embargo no todo lo bello es apropiado. Lo
mejor para ordenar es utilizar la función primitiva sort
. Pero aquí
surge el primer problema:
*Main> sort([2,3,1])
<interactive>:5:1:
Not in scope: `sort'
Perhaps you meant one of these:
`sqrt' (imported from Prelude), `qsort' (line 1)
*Main>
Este mensaje significa, ni más ni menos, que la función sort no está en Prelude, ni por supuesto en example.hs. Para incluirla debemos cargar un módulo instalado por defecto en el árbol de Haskell que la contenga:
*Main> :m + Data.List
Prelude Data.List >
y ahora podemos usar la función sort
como sugeríamos antes. Pero
obsérvese que hemos perdido nuestro contenido del fichero
example.hs
; ahora deberíamos recargarlo.
A veces es útil tener información sobre funciones. Podemos hacer, por ejemplo:
*Main Data.List> :t qsort
qsort :: Ord a => [a] -> [a]
*Main Data.List> :i qsort
qsort :: Ord a => [a] -> [a] -- Defined at example.hs:2:1
*Main Data.List> :i sort
sort :: Ord a => [a] -> [a] -- Defined in `Data.List'
*Main Data.List>
Ahora sabemos que la línea
qsort :: Ord a => [a] -> [a]
define el tipo de la función qsort
; debemos leerla así: si a
es un
objeto incluido en la clase Ord
de objetos, qsort
asigna a una
lista de objetos a
otra lista de objetos a
.
Para importar varios módulos se puede proceder así:
iMac-de-miUsuario:~ my_login$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m + Data.List Data.Char
Prelude Data.List Data.Char>
Prelude Data.Char List> :m - Data.Char
Prelude Data.List>
o también así:
iMac-de-miUsuario:~ my_login$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> import Data.Char
Prelude Data.Char> import Data.List as List
Prelude Data.Char List> :m - Data.List
Prelude Data.Char>
Para saber más de las órdenes básicas del intérprete podemos hacer:
Prelude> :h
Si queremos introducir nuestras funciones en el intérprete sin depender de un fichero del tipo example.hs, podemos. Habríamos de proceder así:
*Prelude> let fac n = if n == 0 then 1 else n * fac (n-1)
*Prelude> fac 25
15511210043330985984000000
*Prelude>
aunque lo mejor sería incluir en example.hs el código:
fac :: Integer -> Integer
fac 0 = 1
fac n = n * fac (n-1)
y recargar example.hs
:
*Main Data.List> :l examples.hs
[1 of 1] Compiling Main ( examples.hs, interpreted )
Ok, modules loaded: Main.
*Main Data.List> fac 25
15511210043330985984000000
*Main Data.List>
Si queremos una impresión de la estadística de tiempo/memoria tras cada evaluación, podemos ejecutar:
Prelude> :set +s
desharemos lo hecho con la orden:
Prelude> :unset +s
Recordemos que para salir del intérprete ejecutaremos:
Prelude> :q
Para comentar una línea la hacemos preceder de la secuencia de caractéres “–” y para comentar un párrafo de código lo ponemos entre “{-” y “-}":
-- esta línea está comentada
{- este párrafo está comentado
por ser sólo una explicación -}
Un Ejemplo Más Elaborado
De D. José E. Labra G. hemos tomado el contenido ligeramente modificado de los ficheros Pila.hs y Main.hs. El segundo es un ejemplo de menú que importa y opera con los objetos del primero.
Si queremos hacer un ejecutable con Main.hs podemos operar de dos maneras. Suponemos que en la consola nos hemos colocado donde están los dos ficheros en cuestión. La primera forma sería:
ghc --make Main.hs
y ahora podríamos ejecutar el ejecutable resultado con
./Menu
La segunda forma sería ejecutar consecutivamente las siguientes dos órdenes en la consola:
ghc -c -O Pila.hs Main.hs
ghc -o menu -O Pila.o Menu.o
y ahora podríamos ejecutar el ejecutable menu con
./Menu
La ventaja de esta última forma de proceder es que con la orden:
ghc -c -O Pila.hs Main.hs
creamos los ficheros Pila.o
y Main.o
que nos valdrían para
pasarlos a otra parte del equipo que deba elaborar el fichero
ejecutable Main
, pero que sin embargo no deba, o no pueda, conocer
los ficheros Main.hs
y/o Pila.hs
. Una vez hayan sido pasados los
mencionados ficheros Pila.o
y Main.o
, los receptores compondrían
el fichero ejecutable menu mediante la orden de consola:
ghc -o menu -O Pila.o Main.o
Ejecutar Sin Compilar y Sin Interpretar
Supongamos que tenemos un fichero llamado, por ejemplo,
fmapping_io.hs
y con el siguiente contenido:
import Data.Char
import Data.List
main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine
putStrLn line
Podríamos ejecutarlo como sigue:
iMac-de-miUsuario:~ my_login$ runhaskell fmapping_io.hs
esto es una prueba
A-B-E-U-R-P- -A-N-U- -S-E- -O-T-S-E
La Principal Arma de Haskell
Como explican los autores en su libro
Razonando con Haskell,
en muchos lenguajes imperativos (p. e. C++
) con el mecanismo de paso
por valor (call by value) siempre se evalúan los argumentos antes de
la llamada. Una alternativa a este mecanismo es el paso por necesidad
(call by need), en el cual se evalúa solamente los argumentos
necesarios; el problema de tal mecanismo para los lenguajes
imperativos es que su implementación es compleja.
En el contexto de los lenguajes funcionales se habla de dos modos de evaluación: impaciente (eager) y perezosa (lazy); en el primer caso el evaluador hace todo lo que puede mientras que en el segundo hace solamente lo preciso. El mecanismo impaciente corresponde al paso por valor mientras que el perezoso al paso por necesidad.
Le proponemos que tomen de nuevo su fichero example.hs
y que
incluyan en él el siguiente código:
desde :: Integer -> [Integer]
desde n = [n..]
suma :: Integer -> [Integer] -> Integer
suma 0 _ = 0
suma n (x:xs) = x + suma (n-1) xs
La función desde, dado un valor entero n, entrega la lista de todos
los números enteros superiores o iguales a n, ordenados en el orden de
los números enteros según se avanza hacia el final de la lista. Así,
[0..]
es la lista en orden creciente de todos los números enteros
comenzando en 0
: la lista de los números naturales en su propio
orden.
La función suma
tiene dos argumento: un número entero (que pensamos
será siempre un número natural), como primer argumento, y una lista de
números enteros, como segundo argumento. Su labor es sumar las n
primeras entradas de la lista argumento, y si ese n
fuese 0
,
ofrecer 0
como resultado; esto último es la condición de
detención. Si se piensa bien, todo esto se puede entender como un
bucle while
de los lenguajes imperativos.
Pero qué pasa si nuestra malicia natural nos lleva, por afán de diversión, a hacer una jugarreta al intérprete de Haskell, pasándole como segundo argumento una lista infinita. ¿Qué espera el lector? ¿será éste el final de Haskell, de nuestra máquina y del tiempo relativista? Nada más lejos de la realidad y valga para ello el siguiente ejemplo:
ghci example.hs
y recibimos el diálogo siguiente, señal de que ¡todo ha ido bien por ahora!
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main ( example.hs, interpreted )
Ok, modules loaded: Main.
Como el coyote, probemos que la trampa está hecha y funciona … y
atentos a ejecutar Ctrl + C
inmediatamente después de desde 0
o
… esto no acabará realmente.
*Main> desde 0
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,
57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,
8^CInterrupted.
Seguidamente hagamos este inocente cálculo:
*Main> 1 + 2 + 3 + 4 + 5
15
Finalmente pedirle al correcaminos que caiga en la trampa del precipicio infinito:
*Main> suma 5 (desde 1)
15
(0.00 secs, 1030416 bytes)
*Main>
Nos costará, si no conocemos de antes a Haskell, sobreponernos a la impresión: hemos pedidoque sume los primeros 5 números de una ¡lista infinita! y ¡lo ha hecho en 0.00 segundos! ¿Dónde está el truco? Ni más ni menos en que Haskell evalúa perezosamente y al recibir la orden:
*Main> suma 5 (desde 1)
no ha confeccionado primero desde 1
, tomado luego sus primeras 5
entrada y sumado finalmente para ofrecer el resultado 15
. En su
lugar lo que hizo es calcular la primera entradade desde 1
,
separarla y mandar que le sea sumada la segunda entrada de desde 1
,
la cualpasa a calcular sabiendo que queda una selección menos que
hacer, etc. El proceso continúa hasta que sabemos que no queda
selección de entrada alguna que hacer ya en desde 1
y entonces
mandamos sumar 0
y damos la orden de parar de sumar, para
seguidamente efectuar definitivamente la suma hilvanada durante todo
este tiempo.
La habilidad de Haskell: ¿para qué calcular desde 1
y luego sumar
sus 5 primeras entradas, si para nuestro propósito sólo son
imprescindibles esas 5 entradas? ¡cuando sea necesario
calcular/extraer más entradas de desde 1
, ya lo haremos! ¡No nos
precipitemos haciendo más trabajo del necesario! ¡Hagamos el
sucintamente imprescindible no sea que nos cansemos … eh!
Con el código anterior hay un pequeño problema, que realmente sólo es aparente:
*Main> suma 5 [1,2,3,4]
*** Exception: example.hs:(23,1)-(24,33): Non-exhaustive patterns in function suma
*Main>
Como vemos se genera una excepción, que podría ser capturada convenientemente. Un código alternativo para la función suma podría ser:
suma :: Integer -> [Integer] -> Integer
suma 0 _ = 0
suma _ [] = error "lista demasiado corta"
suma n (x:xs) = x + suma (n-1) xs
que ahora proporciona el siguiente diálogo:
*Main> suma 5 [1,2,3,4]
*** Exception: lista demasiado corta
*Main> suma 5 [1,2,3,4,5]
15
*Main> suma 5 [1,2,3,4,5,6]
15
*Main> suma 5 (desde 1)
15
*Main>
De nada serviría la evaluación perezosa con un código como éste:
desde :: Integer -> [Integer]
desde n = [n..]
longitud :: [a] -> Integer
longitud [] = 0
longitud (x:xs) = 1 + longitud xs
sumaB :: Integer -> [Integer] -> Integer
sumaB n lst@(x:xs)
| n == 0 = 0
| n > len = error "lista demasiado corta."
| otherwise = x + sumaB (n-1) xs
where len = longitud lst
cuando lst
es infinita:
*Main> suma 5 (desde 1)
sencillamente no funciona, pues la condición de la ejecución exige hacer el cálculo de una longitud, el cual no acaba.
Otro gran recurso en la programación funcional con Haskell es la utilización en las funciones de “argumento con patrón”. En la línea anterior:
suma n (x:xs) = x + suma (n-1) xs
el segundo argumento de la función suma
, a saber (x:xs)
, es
aportado con un patrón, lo que permite un uso suyo eficiente dentro de
la definición del caso: x + suma (n-1) xs
Así es Haskell.
Nuestro Reto
Esta vez consiste en pensar qué significa el siguiente código Haskell, que podríamos poner en nuestro fichero Criba.hs, cargar en el intérprete y ejecutar:
module Criba (module Criba) where
criba :: [Integer] -> [Integer]
criba (p:xs) = [x | x <- xs, p `noDivideA` x]
where m `noDivideA` n = mod n m /= 0
primos :: [Integer]
primos = map head (iterate criba [2..])
primosEq :: [Integer]
primosEq = map head lprimos
where lprimos = [2..] : map criba lprimos
sin ánimo de producir vértigo.
No nos emocionemos demasiado, pues aunque es muy elegante no es eficiente ni la criba real; es solamente un código de gran valor pedagógico.
Enlaces de Interés
Son los siguientes:
- The Glorious Glasgow Haskell Compilation System User’s Guide, Version 7.8.2
- Una introducción a Haskell telegráfica y en español.
- Podemos echar 10 minutos más bien empleados y aprenderlo casi todo.
- Información técnica sobre los módulos o también.
- Información técnica vía zvon, más antigua pero de mucho interés.
- Wikihaskell de la UCA.
- Una guía de usuario para Cabal y la guía de Cabal de Wikihaskell.
- Learn You a Haskell for Great Good de Miran Lipovaca.
- Aprende Haskell, por el bien de todos.
- El mundo real de Haskell.
- Razonando con Haskell. Un curso sobre programación funcional.
- Una introducción amable a Haskell.
Y … esto es todo por hoy.