Personal tools

Es/Guía de Haskell para autoestopistas

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
m
m
Line 15: Line 15:
 
Así que aquí está la tarea para este capítulo: para liberar espacio en tu disco duro para todo el código de haskell que vas a escribir en un futuro cercano, vas a guardar algo de la vieja y polvorienta información en CDs y DVDs. Mientras que grabar CDs (o DVDs) es fácil hoy en día, normalmente ocupa algo (o mucho) tiempo decidir como grabar algunos GB de fotos digitales en CD-Rs, cuando los directorios con las imágenes varían desde 10 a 300 Mb de espacio, y no quieres grabar CDs medio llenos (o medio vacíos).
 
Así que aquí está la tarea para este capítulo: para liberar espacio en tu disco duro para todo el código de haskell que vas a escribir en un futuro cercano, vas a guardar algo de la vieja y polvorienta información en CDs y DVDs. Mientras que grabar CDs (o DVDs) es fácil hoy en día, normalmente ocupa algo (o mucho) tiempo decidir como grabar algunos GB de fotos digitales en CD-Rs, cuando los directorios con las imágenes varían desde 10 a 300 Mb de espacio, y no quieres grabar CDs medio llenos (o medio vacíos).
   
El ejercicio consiste en escribir un programa que nos ayude a poner un
+
El ejercicio consiste en escribir un programa que nos ayude a poner un conjunto de directorios en la cantidad mínima posible de discos, al mismo tiempo que aprovechamos cada disco lo máximo posible. Llamemos a este programa “cd-fit”.
conjunto de directorios en la cantidad mínima posible de discos, al mismo
 
tiempo que aprovechamos cada disco lo máximo posible. Llamemos a este programa
 
“cd-fit”.
 
   
 
Oh. Espera. Hagamos el usual programa “hola mundo”, antes de que nos
 
Oh. Espera. Hagamos el usual programa “hola mundo”, antes de que nos
Line 38: Line 38:
 
Vale, ya lo hicimos. Sigamos ahora, no hay nada interesante aquí :)
 
Vale, ya lo hicimos. Sigamos ahora, no hay nada interesante aquí :)
   
Cualquier desarrollo serio se debe hacer con un sistema de control de
+
Cualquier desarrollo serio se debe hacer con un sistema de control de versiones, y no haremos una excepción. Usaremos el moderno sistema de control de versiones distribuido “darcs”. “Moderno” significa que está escrito en Haskell, “distribuido” significa que cada copia que funcione es un repositorio en si mismo.
versiones, y no haremos una excepción. Usaremos el moderno sistema de control
 
de versiones distribuido “darcs”. “Moderno” significa que está escrito en
 
Haskell, “distribuido” significa que cada copia que funcione es un repositorio
 
en si mismo.
 
   
Primero, creemos un directorio vacío para nuestro todo nuestro código, y
+
Primero, creemos un directorio vacío para nuestro todo nuestro código, y ejecuta “darcs init” en él, que creará un subdirectorio “_darcs” para guardar todo lo relacionado con el control de versiones dentro de él.
ejecuta “darcs init” en él, que creará un subdirectorio “_darcs” para guardar
 
todo lo relacionado con el control de versiones dentro de él.
 
   
Inicia tu editor favorito y crea un fichero nuevo llamado “cd-fits.hs” en
+
Inicia tu editor favorito y crea un fichero nuevo llamado “cd-fits.hs” en nuestro directorio de trabajo. Ahora pensemos por un momento como funcionará nuestro programa y expresémoslo en pseudocódigo:
nuestro directorio de trabajo. Ahora pensemos por un momento como funcionará
 
nuestro programa y expresémoslo en pseudocódigo:
 
   
 
<haskell>
 
<haskell>
Line 52: Line 52:
 
¿Parece lógio? Pienso que si.
 
¿Parece lógio? Pienso que si.
   
Vamos a simplificar nuestra vida un poco y asumir por ahora que vamos calcular
+
Vamos a simplificar nuestra vida un poco y asumir por ahora que vamos calcular el espacio de los directorios de alguna forma fuera de nuestro programa (por ejemplo, con “du -sb *”) y leer esa información desde stdin. Convirtamos esto a Haskell:
el espacio de los directorios de alguna forma fuera de nuestro programa (por
 
ejemplo, con “du -sb *”) y leer esa información desde stdin. Convirtamos esto
 
a Haskell:
 
   
 
<haskell>
 
<haskell>
Line 63: Line 63:
 
</haskell>
 
</haskell>
   
No funciona en realidad, pero bastante parecido al Español, ¿no? Paremos por
+
No funciona en realidad, pero bastante parecido al Español, ¿no? Paremos por un momento y miremos más de cerca que hemos escrito línea por línea.
un momento y miremos más de cerca que hemos escrito línea por línea.
 
   
 
Empecemos desde arriba:
 
Empecemos desde arriba:
Line 72: Line 72:
 
</haskell>
 
</haskell>
   
Esto es un ejemplo de la sintaxis de Haskell para hacer IO (en este caso,
+
Esto es un ejemplo de la sintaxis de Haskell para hacer IO (en este caso, entrada de datos). Esta línea es una instrucción para leer toda la información disponible desde stdin, devolverla como una única cadena, y unirla al símbolo “input”, de forma que podemos procesar esta cadena de la forma que querramos.
entrada de datos). Esta línea es una instrucción para leer toda la información
 
disponible desde stdin, devolverla como una única cadena, y unirla al símbolo
 
“input”, de forma que podemos procesar esta cadena de la forma que querramos.
 
   
¿Cómo lo sabía? ¿Memoricé todas las funciones? ¡Claro que no! Cada función
+
¿Cómo lo sabía? ¿Memoricé todas las funciones? ¡Claro que no! Cada función tiene un tipo, que con su nombre, tiende a decir mucho sobre lo que hace la función.
tiene un tipo, que con su nombre, tiende a decir mucho sobre lo que hace la
 
función.
 
   
 
Lanza un entorno interactivo de Haskell y examinemos esta función de cerca:
 
Lanza un entorno interactivo de Haskell y examinemos esta función de cerca:
Line 91: Line 91:
 
 
   
Vemos que “getContents” es una función sin argumentos, que devuelve un “IO
+
Vemos que “getContents” es una función sin argumentos, que devuelve un “IO String”. El prefijo “IO” significa que es una acción de IO. Devolverá un String, cuando se evalue. La acción será evaluada cuando usemos “<-” para enlazar su resultado a un símbolo.
String”. El prefijo “IO” significa que es una acción de IO. Devolverá un
 
String, cuando se evalue. La acción será evaluada cuando usemos “<-” para
 
enlazar su resultado a un símbolo.
 
   
Ten en cuenta que “<-” no es una forma bonita de asignar un valor a una
+
Ten en cuenta que “<-” no es una forma bonita de asignar un valor a una variable. Es una forma de evaluar (ejecutar) acciones de IO, en otras palabras - para hacer alguna operación de I/O y devolver su resultado (si es que tiene).
variable. Es una forma de evaluar (ejecutar) acciones de IO, en otras palabras
 
- para hacer alguna operación de I/O y devolver su resultado (si es que
 
tiene).
 
   
Podemos escoger no evaluar la acción que obtenemos de “getContents”, y en vez
+
Podemos escoger no evaluar la acción que obtenemos de “getContents”, y en vez de eso dejarla por ahí y evaluarla más tarde:
de eso dejarla por ahí y evaluarla más tarde:
 
   
 
<haskell>
 
<haskell>
Line 103: Line 103:
 
</haskell>
 
</haskell>
   
Como puedes ver, las acciones de IO pueden ser usadas como valores ordinarios.
+
Como puedes ver, las acciones de IO pueden ser usadas como valores ordinarios. Supón que hemos construído una lista de acciones IO y hemos encontrado una forma de ejecutarlas una por una. Sería una forma de simular programación imperativa con su notación de “orden de ejecución”.
Supón que hemos construído una lista de acciones IO y hemos encontrado una
 
forma de ejecutarlas una por una. Sería una forma de simular programación
 
imperativa con su notación de “orden de ejecución”.
 
   
 
Haskell te permite hacerlo mejor.
 
Haskell te permite hacerlo mejor.
   
La librería estandard del lenguaje (llamada “Prelude”) nos da acceso a muchas
+
La librería estandard del lenguaje (llamada “Prelude”) nos da acceso a muchas funcinoes que devuelven primitivas de acciones de IO muy útiles. Para combinarlas entre ellas para producir acciones más complejas, usamos “do”:
funcinoes que devuelven primitivas de acciones de IO muy útiles. Para
 
combinarlas entre ellas para producir acciones más complejas, usamos “do”:
 
   
 
<haskell>
 
<haskell>
Line 124: Line 124:
 
* después, imprime la palabra “hecho”
 
* después, imprime la palabra “hecho”
   
¿Cuando se ejecutará todo esto en realidad? Respuesta: tan rápido como
+
¿Cuando se ejecutará todo esto en realidad? Respuesta: tan rápido como evaluemos “c” usando “<-” (si devuelve un resultado, como hace “getContents”) o usándola como el nombre de una función (si no devuelve un resultado, como hace “print”):
evaluemos “c” usando “<-” (si devuelve un resultado, como hace “getContents”)
 
o usándola como el nombre de una función (si no devuelve un resultado, como
 
hace “print”):
 
   
 
<haskell>
 
<haskell>
Line 132: Line 132:
 
</haskell>
 
</haskell>
   
Date cuenta de que hemos cogido unas cuantas funciones (“algunaAcción”,
+
Date cuenta de que hemos cogido unas cuantas funciones (“algunaAcción”, “algunaOtraAcción”, “print”, “putStrLn”) y usando “do” creamos a partir de ellas una nueva función, que enlazamos al símbolo “c”. Ahora podemos usar “c” como una pieza de construcción para construir una función más compleja “proceso”, y podríamos continuar haciendo esto más y más. Finalmente, algunas de las funciones las mencionaremos en el código de la función “main”, función a la cual la última y más importante acción de IO en cualquier programa de Haskell está asociada.
“algunaOtraAcción”, “print”, “putStrLn”) y usando “do” creamos a partir de
 
ellas una nueva función, que enlazamos al símbolo “c”. Ahora podemos usar “c”
 
como una pieza de construcción para construir una función más compleja
 
“proceso”, y podríamos continuar haciendo esto más y más. Finalmente, algunas
 
de las funciones las mencionaremos en el código de la función “main”, función
 
a la cual la última y más importante acción de IO en cualquier programa de
 
Haskell está asociada.
 
   
¿Cuándo se ejececutará/evaluará/forzará “main”? Tan rápido como ejecutemos el
+
¿Cuándo se ejececutará/evaluará/forzará “main”? Tan rápido como ejecutemos el programa. Lee esto dos veces y trata de comprenderlo:
programa. Lee esto dos veces y trata de comprenderlo:
 
   
''La ejecución de un programa de Haskell es una evaluación del símbolo “main” a
+
''La ejecución de un programa de Haskell es una evaluación del símbolo “main” a la que hemos asociado una acción de IO. Mediante esta evaluación obtenemos el resultado de la acción''.
la que hemos asociado una acción de IO. Mediante esta evaluación obtenemos el
 
resultado de la acción''.
 
   
Los lectores que estén familiriazados con programación en C++ o Java avanzado
+
Los lectores que estén familiriazados con programación en C++ o Java avanzado y ese conjunto de conocimiento arcano llamado “OOP Patrones de Diseño” se darán cuenta de que “construir acciones a partir de acciones” y “evaluar acciones para obtener resultados” es esencialmente un “Patrón de comando” y “Patrón de composición” combinados. Buenas noticias: en Haskell las tienes para todo tu IO, y lo tienes '''gratis''' :)
y ese conjunto de conocimiento arcano llamado “OOP Patrones de Diseño” se darán
 
cuenta de que “construir acciones a partir de acciones” y “evaluar acciones
 
para obtener resultados” es esencialmente un “Patrón de comando” y “Patrón de
 
composición” combinados. Buenas noticias: en Haskell las tienes para todo tu
 
IO, y lo tienes '''gratis''' :)
 
   
 
----
 
----
Line 160: Line 160:
 
</haskell>
 
</haskell>
   
¿Te das cuenta de como indentamos las líneas con cuidado para que el código
+
¿Te das cuenta de como indentamos las líneas con cuidado para que el código parezca limpio? En realidad, el código de Haskell tiene que estar alineado de esta forma, o sino no compilará. Si usas tabulados para indentar tus códigos
parezca limpio? En realidad, el código de Haskell tiene que estar alineado de
+
fuente, ten en cuenta que los compiladores de Haskell aumen que el tabstop tiene de ancho 8 caracteres.
esta forma, o sino no compilará. Si usas tabulados para indentar tus códigos
 
fuente, ten en cuenta que los compiladores de Haskell aumen que el tabstop
 
tiene de ancho 8 caracteres.
 
   
A menudo las personas se quejan de que es una forma muy difícil de escribir en
+
A menudo las personas se quejan de que es una forma muy difícil de escribir en Haskell porque requiere que alinees el código. En realidad no es verdad. Si alineas tu código, el compilador adivinará cual es el principio y final de los bloques sintácticos. Sin embargo, si no indentas tu código, puedes especificarlos explícitamente en cada una de las expresiones y usar una distribución arbitraria como en este ejemplo:
Haskell porque requiere que alinees el código. En realidad no es verdad. Si
 
alineas tu código, el compilador adivinará cual es el principio y final de los
 
bloques sintácticos. Sin embargo, si no indentas tu código, puedes
 
especificarlos explícitamente en cada una de las expresiones y usar una
 
distribución arbitraria como en este ejemplo:
 
   
 
<haskell>
 
<haskell>
Line 178: Line 178:
 
</haskell>
 
</haskell>
   
De vuelta al ejercicio - ¿ves como hacemos código desde la nada? Trata de
+
De vuelta al ejercicio - ¿ves como hacemos código desde la nada? Trata de imaginar que hará este código, después ejecútalo y compruébalo por ti mismo.
imaginar que hará este código, después ejecútalo y compruébalo por ti
 
mismo.
 
   
 
¿Entiendas por qué “¡Hola!” y “¡Adiós!” no se imprimen?
 
¿Entiendas por qué “¡Hola!” y “¡Adiós!” no se imprimen?
Line 193: Line 193:
 
*Main>
 
*Main>
   
Vemos que “main” es en realidad una acción IO que no devuelve nada cuando la
+
Vemos que “main” es en realidad una acción IO que no devuelve nada cuando la evaluamos. Cuando combinamos acciones con “do”, el tipo del resultado será el tipo de la última acción, y “putStrLn algo” tiene tipo “IO ()”:
evaluamos. Cuando combinamos acciones con “do”, el tipo del resultado será el
 
tipo de la última acción, y “putStrLn algo” tiene tipo “IO ()”:
 
   
 
*Main> :type putStrLn "¡Hola mundo!"
 
*Main> :type putStrLn "¡Hola mundo!"
Line 199: Line 199:
 
*Main>
 
*Main>
   
Ah, por cierto: ¿te has dado cuenta que hemos compilado nuestro primer programa
+
Ah, por cierto: ¿te has dado cuenta que hemos compilado nuestro primer programa de Haskell para examinar “main”? :)
de Haskell para examinar “main”? :)
 
   
celebrémoslo poniéndolo bajo control de versión: ejecuta “darcs add cd-fit.hs”
+
celebrémoslo poniéndolo bajo control de versión: ejecuta “darcs add cd-fit.hs” y “darcs record”, responder “y” a todas las preguntas y dale un comentario al añadido “Esqueleto de cd-fit.hs”
y “darcs record”, responder “y” a todas las preguntas y dale un comentario al
 
añadido “Esqueleto de cd-fit.hs”
 
   
 
Vamos a probar a ejecutarlo:
 
Vamos a probar a ejecutarlo:

Revision as of 18:54, 22 October 2006

1 Preámbulo: ¡NO CORRER!

Experiencias recientes de algunos compañeros programadores de C++/Java indican que leyeron varios tutoriales sobre Haskell con “velocidad exponencial” (piensa en como las sesiones de TCP/IP aumentan). Al principio son pocas y prudentes, pero cuando ven que las primeras 3-5 páginas no contienen “nada interesante” en términos de código y ejemplos, empiezan a saltarse párrafos, después capítuos, después páginas enteras, tan sólo decelerando - a menudo hasta parar completamente - alrededor de la página 50, encontrándose con el grueso de conceptos como “clases de tipos”, “constructores de tipos”, “IO monádica”, punto en el que normalmente les entra el pánico, piensa en una escusa perfectamente racional para no seguir leyendo, y se olvida alegremente de este triste y escalofriante encuentro con Haskell (ya que los seres humanos tendemos a olvidar las cosas tristas y escalofriantes).

Este texto pretende introducir al lector a los aspectos prácticos de Haskell desde el principio del todo (los planes para el primer capítulo incluyen: I/O, darcs, Parsec, QuickCheck, depurar y perfilar, por mencionar algunos). Se supone que el lector sabe (donde encontrar) por lo menos los primeros pasos de Haskell: como ejecutar “hugs” o “ghci”, ese diseño es 2-dimensional, etc. Aparte de eso, no esperamos avanzar radicalmente, e iremos paso por paso para no perder a los lectores por el camino. Así que NO CORRER, coge la toalla contigo y continua leyendo.

Si te has saltado el párrafo anterior, me gustaría destacar una vez más que Haskell es sensible a la indentación y espaciado, así que presta atención cuando hagas copias o alineación manual de código en el editor de texto con fuentes proporcionales.

Ah, casi me olvido: el autor está muy interesado en CUALQUIER opinión. Escríbele algunas líneas o palabras (mira Adept para la información de contacto) o propón cambios al tutorial mediante darcs (el repositorio está aquí) o directamente a este Wiki.

2 Capítulo 1: Omnipresente “¡Hola mundo!” y otras formas de hacer IO en Haskell

Cada capítulo será dedicado a una pequeña tarea real que completaremos desde el principio.

Así que aquí está la tarea para este capítulo: para liberar espacio en tu disco duro para todo el código de haskell que vas a escribir en un futuro cercano, vas a guardar algo de la vieja y polvorienta información en CDs y DVDs. Mientras que grabar CDs (o DVDs) es fácil hoy en día, normalmente ocupa algo (o mucho) tiempo decidir como grabar algunos GB de fotos digitales en CD-Rs, cuando los directorios con las imágenes varían desde 10 a 300 Mb de espacio, y no quieres grabar CDs medio llenos (o medio vacíos).

El ejercicio consiste en escribir un programa que nos ayude a poner un conjunto de directorios en la cantidad mínima posible de discos, al mismo tiempo que aprovechamos cada disco lo máximo posible. Llamemos a este programa “cd-fit”.

Oh. Espera. Hagamos el usual programa “hola mundo”, antes de que nos olvidemos, y después sigamos con cosas más interesante:

-- Cogido de 'hola.hs'
-- A partir de ahora, un comentario al principio del trozo de código
-- especificará el archivo que contiene el programa entero del que fue cogido
-- el trozo. Puedes coger el código del repositorio de darcs
-- "http://adept.linux.kiev.ua/repos/hhgtth" introduciendo el comando 
-- "darcs get http://adept.linux.kiev.ua/repos/hhgtth"
module Main where
main = putStrLn "¡Hola mundo!"

Ejecútalo:

$ runhaskell ./hola.hs
¡Hola mundo!

Vale, ya lo hicimos. Sigamos ahora, no hay nada interesante aquí :)

Cualquier desarrollo serio se debe hacer con un sistema de control de versiones, y no haremos una excepción. Usaremos el moderno sistema de control de versiones distribuido “darcs”. “Moderno” significa que está escrito en Haskell, “distribuido” significa que cada copia que funcione es un repositorio en si mismo.

Primero, creemos un directorio vacío para nuestro todo nuestro código, y ejecuta “darcs init” en él, que creará un subdirectorio “_darcs” para guardar todo lo relacionado con el control de versiones dentro de él.

Inicia tu editor favorito y crea un fichero nuevo llamado “cd-fits.hs” en nuestro directorio de trabajo. Ahora pensemos por un momento como funcionará nuestro programa y expresémoslo en pseudocódigo:

main = leer lista de directorio y sus tamaños
       decidir como ajustarlos a los CD-Rs
       imprimir la solución

¿Parece lógio? Pienso que si.

Vamos a simplificar nuestra vida un poco y asumir por ahora que vamos calcular el espacio de los directorios de alguna forma fuera de nuestro programa (por ejemplo, con “du -sb *”) y leer esa información desde stdin. Convirtamos esto a Haskell:

-- Cogido de 'cd-fit-1-1.hs'
module Main where
 
main = do input <- getContents
          putStrLn ("DEBUG: tengo entrada" ++ input)
	  -- calcular solución e imprimirla

No funciona en realidad, pero bastante parecido al Español, ¿no? Paremos por un momento y miremos más de cerca que hemos escrito línea por línea.

Empecemos desde arriba:

-- Cogido de 'cd-fit-1-1.hs'
input <- getContents

Esto es un ejemplo de la sintaxis de Haskell para hacer IO (en este caso, entrada de datos). Esta línea es una instrucción para leer toda la información disponible desde stdin, devolverla como una única cadena, y unirla al símbolo “input”, de forma que podemos procesar esta cadena de la forma que querramos.

¿Cómo lo sabía? ¿Memoricé todas las funciones? ¡Claro que no! Cada función tiene un tipo, que con su nombre, tiende a decir mucho sobre lo que hace la función.

Lanza un entorno interactivo de Haskell y examinemos esta función de cerca:

$ ghci
   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 6.4.1, for Haskell 98.
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base-1.0 ... linking ... done.
Prelude> :type getContents
getContents :: IO String
Prelude> 

Vemos que “getContents” es una función sin argumentos, que devuelve un “IO String”. El prefijo “IO” significa que es una acción de IO. Devolverá un String, cuando se evalue. La acción será evaluada cuando usemos “<-” para enlazar su resultado a un símbolo.

Ten en cuenta que “<-” no es una forma bonita de asignar un valor a una variable. Es una forma de evaluar (ejecutar) acciones de IO, en otras palabras - para hacer alguna operación de I/O y devolver su resultado (si es que tiene).

Podemos escoger no evaluar la acción que obtenemos de “getContents”, y en vez de eso dejarla por ahí y evaluarla más tarde:

let x = getContents
-- 300 líneas de código
input <- x

Como puedes ver, las acciones de IO pueden ser usadas como valores ordinarios. Supón que hemos construído una lista de acciones IO y hemos encontrado una forma de ejecutarlas una por una. Sería una forma de simular programación imperativa con su notación de “orden de ejecución”.

Haskell te permite hacerlo mejor.

La librería estandard del lenguaje (llamada “Prelude”) nos da acceso a muchas funcinoes que devuelven primitivas de acciones de IO muy útiles. Para combinarlas entre ellas para producir acciones más complejas, usamos “do”:

c = do a <- algunaAcción
       b <- algunaOtraAcción
       print (bar b)
       print (foo a)
       putStrLn "hecho"

De esta forma asociamos “c” a una acción con el siguiente “escenario”:

  • evalua la acción “algunaAcción” y asocia su resultado a “a”
  • después, evalua “algunaOtraAcción” y asocia su resultado a “b”
  • después, procesa “b” con la función “bar” e imprime su resultado
  • después, procesa “a” con la función “foo” e imprime su resultado
  • después, imprime la palabra “hecho”

¿Cuando se ejecutará todo esto en realidad? Respuesta: tan rápido como evaluemos “c” usando “<-” (si devuelve un resultado, como hace “getContents”) o usándola como el nombre de una función (si no devuelve un resultado, como hace “print”):

process = do putStrLn "Procesará algo"
             c
             putStrLn "Hecho"

Date cuenta de que hemos cogido unas cuantas funciones (“algunaAcción”, “algunaOtraAcción”, “print”, “putStrLn”) y usando “do” creamos a partir de ellas una nueva función, que enlazamos al símbolo “c”. Ahora podemos usar “c” como una pieza de construcción para construir una función más compleja “proceso”, y podríamos continuar haciendo esto más y más. Finalmente, algunas de las funciones las mencionaremos en el código de la función “main”, función a la cual la última y más importante acción de IO en cualquier programa de Haskell está asociada.

¿Cuándo se ejececutará/evaluará/forzará “main”? Tan rápido como ejecutemos el programa. Lee esto dos veces y trata de comprenderlo:

La ejecución de un programa de Haskell es una evaluación del símbolo “main” a la que hemos asociado una acción de IO. Mediante esta evaluación obtenemos el resultado de la acción.

Los lectores que estén familiriazados con programación en C++ o Java avanzado y ese conjunto de conocimiento arcano llamado “OOP Patrones de Diseño” se darán cuenta de que “construir acciones a partir de acciones” y “evaluar acciones para obtener resultados” es esencialmente un “Patrón de comando” y “Patrón de composición” combinados. Buenas noticias: en Haskell las tienes para todo tu IO, y lo tienes gratis :)


Ejercicio: Fíjate en el siguiente código:

-- Cogido de 'exercise-1-1.hs'
module Main where
c = putStrLn "C!"
 
combine before after =
  do before
     putStrLn "En el medio"
     after
 
main = do combine c c
          let b = combine (putStrLn "¡Hola!") (putStrLn "¡Adios!")
          let d = combine (b) (combine c c)
          putStrLn "¡Tanto tiempo!"

¿Te das cuenta de como indentamos las líneas con cuidado para que el código parezca limpio? En realidad, el código de Haskell tiene que estar alineado de esta forma, o sino no compilará. Si usas tabulados para indentar tus códigos fuente, ten en cuenta que los compiladores de Haskell aumen que el tabstop tiene de ancho 8 caracteres.

A menudo las personas se quejan de que es una forma muy difícil de escribir en Haskell porque requiere que alinees el código. En realidad no es verdad. Si alineas tu código, el compilador adivinará cual es el principio y final de los bloques sintácticos. Sin embargo, si no indentas tu código, puedes especificarlos explícitamente en cada una de las expresiones y usar una distribución arbitraria como en este ejemplo:

-- Cogido de 'exercise-1-2.hs'
combine before after = 
 do { before; 
  putStrLn "En el medio"; 
        after; };
 
main = 
  do { combine c c; let { b = combine (putStrLn "¡Hola!") (putStrLn "¡Adios!")};
         let {d = combine (b) (combine c c)}; 
   putStrLn "¡Tanto tiempo!" };

De vuelta al ejercicio - ¿ves como hacemos código desde la nada? Trata de imaginar que hará este código, después ejecútalo y compruébalo por ti mismo.

¿Entiendas por qué “¡Hola!” y “¡Adiós!” no se imprimen?


Examinemos nuestra función “main” más de cerca:

Prelude> :load cd-fit.hs
Compiling Main             ( ./cd-fit.hs, interpreted )
Ok, modules loaded: Main.
*Main> :type main
main :: IO ()
*Main> 

Vemos que “main” es en realidad una acción IO que no devuelve nada cuando la evaluamos. Cuando combinamos acciones con “do”, el tipo del resultado será el tipo de la última acción, y “putStrLn algo” tiene tipo “IO ()”:

*Main> :type putStrLn "¡Hola mundo!"
putStrLn "¡Hola mundo!" :: IO ()
*Main> 

Ah, por cierto: ¿te has dado cuenta que hemos compilado nuestro primer programa de Haskell para examinar “main”? :)

celebrémoslo poniéndolo bajo control de versión: ejecuta “darcs add cd-fit.hs” y “darcs record”, responder “y” a todas las preguntas y dale un comentario al añadido “Esqueleto de cd-fit.hs”

Vamos a probar a ejecutarlo:

$ echo "foo" | runhaskell cd-fit.hs
DEBUG: tengo entrada foo

Ejercicios:

  • Trata de escribir un programa que coja tu nombre de stdin y te felicite (palabras clave: getLine, putStrLn);
  • Trata de escribir un programa que pregunte por tu nombre, lo lea, te felicite, pregunte por tu color favotiro, y lo devuelva (palabras clave: getLine, putStrLn).