Aprende Haskell en 10 minutos

From HaskellWiki

Introducción[edit]

Haskell es un lenguaje funcional (donde todo se hace con llamadas a funciones), estático, implícitamente tipado (los tipos los revisa el compilador, pero no hace falta declararlos), perezoso (nada se hace hasta que es completamente necesario). Los lenguajes populares más parecidos son la familia ML (que no son, sin embargo, lenguajes perezosos).

El compilador de Haskell más común es GHC. Puede descargarlo de http://www.haskell.org/ghc/download . Los ejecutables de GHC están disponibles para GNU/Linux, FreeBSD, MacOS, Windows, y Solaris. Una vez instalado GHC, se tienen dos programas que son de interés ahora: ghc, y ghci. El primero compila librerías y aplicaciones escritas en Haskell a código binario. El segundo es un interprete que permite escribir código Haskell y obtener un resultado inmediato.


Expresiones simples[edit]

Es posible escribir varias expresiones matemáticas directamente en ghci y obtener un resultado. Prelude es el prompt por defecto de GHCi.

 Prelude> 3 * 5
 15
 Prelude> 4 ^ 2 - 1
 15
 Prelude> (1 - 5)^(3 * 2 - 4)
 16

Las cadenas van entre "comillas dobles." Puede concatenarlas con ++.

 Prelude> "Hello"
 "Hello"
 Prelude> "Hello" ++ ", Haskell"
 "Hello, Haskell"

Las funciones se llaman colocando los argumentos directamente después del nombre de la función. No hay paréntesis en la llamada a la función:

 Prelude> succ 5
 6
 Prelude> truncate 6.59
 6
 Prelude> round 6.59
 7
 Prelude> sqrt 2
 1.4142135623730951
 Prelude> not (5 < 3)
 True
 Prelude> gcd 21 14
 7

La consola[edit]

Las Acciones I/O se pueden usar para leer y escribir en la consola. Algunas son:

 Prelude> putStrLn "Hello, Haskell"
 Hello, Haskell
 Prelude> putStr "No newline"
 No newline
 Prelude> print (5 + 4)
 9
 Prelude> print (1 < 2)
 True

Las funciones putStr y putStrLn imprimen cadenas en la terminal. La función print imprime cualquier tipo de valor. (Si se hace print de una cadena, tendrá comillas).

Si necesita múltiples acciones I/O en una expresión, puede usar un bloque do. Las acciones se separan con punto y coma.

 Prelude> do { putStr "2 + 2 = " ; print (2 + 2) }
 2 + 2 = 4
 Prelude> do { putStrLn "ABCDE" ; putStrLn "12345" }
 ABCDE
 12345

La lectura se puede hacer con <haske>getLine</hask> (que retorna una cadena String) o readLn (que retorna un valor de cualquier tipo que se desee). El símbolo <- se usa para asignar un nombre al resultado de la acción I/O.

 Prelude> do { n <- readLn ; print (n^2) }
 4
 16

(El 4 fue la etrada. El 16 la salida)

Existe otra forma de escribir los bloques do. Si se suprimen las llaves y los punto y coma, la indentación se vuelve importante. Esto no funciona muy bien en ghci, pero intente poniéndolo en un fichero (por ejemplo, Test.hs) y compilelo.

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

Puede compilar con ghc --make Test.hs, y el resultado se llamará Test. (en Windows, Test.exe) Se tiene una expresión if como un extra.

El primer carácter no blanco luego de do es especial. En este caso, es la p en putStrLn. Cada linea que empieza en la misma columna que la p es otra instrucción en el bloque do. Si se indenta más, se vuelve parte de la instrucción anterior. Si se indenta menos, se termina el bloque do. Esto se llama "layout", y Haskell lo usa para evitar el uso de puntos y coma y llaves todo el tiempo. (Las frases then y else deben estar indentadas por esta razón. Si empiezan en la misma columna, serán instrucciones separadas, lo que sería incorrecto).

(Nota: No indente con tabulaciones si está usando "layout". Técnicamente funcionará si usa tabulaciones de 8 espacios, pero no es una buena idea. Tampoco use fuentes proporcionales que, aparentemente algunas personas usan, incluso para programar!).


Tipos Simples[edit]

Hasta ahora no se a mencionado ni un solo tipo. Esto es por que Haskell hace inferencia de tipos. Generalmente no es necesario declararlos a menos que se así se quiera. Si desea declarar tipos, se puede usar :: para hacerlo.

 Prelude> 5 :: Int
 5
 Prelude> 5 :: Double
 5.0

Tipos (y las clases de tipos, de las cuales se hablará luego) siempre empiezan con una letra mayúscula. Las variables siempre empiezan con una letra minúscula. Esta es una regla del lenguaje, no una |convención.

Se puede preguntar a ghci que tipo se ha elegido para algo. Esto es útil por que generalmente no hay necesidad de declarar los tipos.

 Prelude> :t True
 True :: Bool
 Prelude> :t 'X'
 'X' :: Char
 Prelude> :t "Hello, Haskell"
 "Hello, Haskell" :: [Char]

(En caso de que lo haya notado, [Char] es otra forma de decir String. Vea la sección sobre listas luego.)

Algunas cosas se tornan más interesantes con los números.

 Prelude> :t 42
 42 :: (Num t) => t
 Prelude> :t 42.0
 42.0 :: (Fractional t) => t
 Prelude> :t gcd 15 20
 gcd 15 20 :: (Integral t) => t

Estos tipos usan "Clases de tipo". Significan:

  • 42 se puede usar como cualquier tipo numérico. (Esta es la razón por la que podemos declarar 5 como Int o como Double).
  • 42.0 puede ser cualquier tipo fraccionario, pero no un tipo integral.
  • gcd 15 20 (que es una llamada de función) puede ser cualquier tipo integral, pero no un tipo fraccionario.

Existen 5 tipos numéricos en el "preludio" de Haskell (la parte de la librería que se obtiene sin tener que importar nada):

  • Int es un entero con al menos 30 bits de precisión.
  • Integer es un entero con precisión ilimitada.
  • Float es un numero de punto flotante con precisión simple.
  • Double es un numero de punto flotante con precisión doble.
  • Rational es un tipo fraccionario, sin error de redondeo.

Los cinco son instancias de la clase de tipo Num. Los primeros dos son instancias de Integral, y los últimos tres son instancias de Fractional.

Poniéndolo todo junto:

 Prelude> gcd 42 35 :: Int
 7
 Prelude> gcd 42 35 :: Double
 <interactive>:1:0:
     No instance for (Integral Double)

El tipo final es un (), que se pronuncia "unidad". Solo tiene un valor, que se escribe también como () y se pronuncia "unidad" de igual manera.

 Prelude> ()
 ()
 Prelude> :t ()
 () :: ()

Se puede pensar en este tipo como en void de la familia de lenguajes de C. Se puede retornar () desde una acción de I/O cuando no se quiere retornar nada en particular.


Datos estructurados[edit]

Los tipos de datos básicos se pueden combinar fácilmente de dos maneras: listas, que van entre [corchetes], y tuplas, que van entre (paréntesis).

Las listas se usan para mantener múltiples valores del mismo tipo.

 Prelude> [1, 2, 3]
 [1,2,3]
 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> [1, 3 .. 10]
 [1,3,5,7,9]
 Prelude> [True, False, True]
 [True,False,True]

Las cadenas solamente son listas de caracteres.

 Prelude> ['H', 'e', 'l', 'l', 'o']
 "Hello"

El operador : agrega un elemento al principio de la lista. (Es la versión de Haskell de cons de la familia de lenguajes de Lisp).

 Prelude> 'C' : ['H', 'e', 'l', 'l', 'o']
 "CHello"

Las tuplas mantienen un numero fijo de valores, que pueden tener tipos distintos.

 Prelude> (1, True)
 (1,True)
 Prelude> zip [1 .. 5] ['a' .. 'e']
 [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]

El ultimo ejemplo usa zip, una función que convierte dos listas en una lista de tuplas.

Los tipos son probablemente lo que se esperaría.

 Prelude> :t ['a' .. 'c']
 ['a' .. 'c'] :: [Char]
 Prelude> :t [('x', True), ('y', False)]
 [('x', True), ('y', False)] :: [(Char, Bool)]

Las listas se usan mucho en Haskell. Has varias funcionas que haces cosas útiles con ellas.

 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> map (+ 2) [1 .. 5]
 [3,4,5,6,7]
 Prelude> filter (> 2) [1 .. 5]
 [3,4,5]

Hay dos funciones para pares ordenados (tuplas de dos elementos):

 Prelude> fst (1, 2)
 1
 Prelude> snd (1, 2)
 2
 Prelude> map fst [(1, 2), (3, 4), (5, 6)]
 [1,3,5]

También puede revisar como trabajar con listas.


Definición de funciones[edit]

Habíamos escrito la definición de acciones IO, llamada main:

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

Ahora podemos suplementar escribiendo la definición de una función y llamándola factorial. También agregaré un modulo de cabecera.

module Main where

factorial n = if n == 0 then 1 else n * factorial (n - 1)

main = do putStrLn "What is 5! ?"
          x <- readLn
          if x == factorial 5
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

Construyalo otra vez usando ghc --make Test.hs. y,

 $ ./Test
 What is 5! ?
 120
 You're right!

Ahí tenemos una función. Tal como las que vienen por defecto, se puede llamar como factorial 5 sin paréntesis.

Podemos preguntar el tipo a ghci.

 $ ghci Test.hs
 << GHCi banner >>
 Ok, modules loaded: Main.
 Prelude Main> :t factorial
 factorial :: (Num a) => a -> a

Los tipos de las funciones se escriben como el tipo del los argumentos, seguidos de ->, y luego el tipo del resultado. (Este ejemplo también tiene la clase de tipo Num).

El factorial se puede simplificar escribiendo el análisis de casos.

factorial 0 = 1
factorial n = n * factorial (n - 1)


Sintaxis conveniente[edit]

Algo más de sintaxis es útil.

secsToWeeks secs = let perMinute = 60
                       perHour   = 60 * perMinute
                       perDay    = 24 * perHour
                       perWeek   =  7 * perDay
                   in  secs / perWeek

La expresión let define nombres temporales. (Esto es usando layout otra vez. Se puede usar {llaves}, y separar los nombres con punto y comas, si se prefiere).

classify age = case age of 0 -> "newborn"
                           1 -> "infant"
                           2 -> "toddler"
                           _ -> "senior citizen"

La expresión case produce ramas de decisión. La etiqueta especial _ significa "cualquier otra cosa".

Usando librerías[edit]

Todo lo que se ha usado hasta ahora es parte del Preludio, que es un conjunto de funciones Haskell que siempre están disponibles sin solicitarlas.

La mejor forma de volverse productivo en Haskell (aparte de la práctica!) es familiarizarse con otras [[Applications and libraries|librerías] que hacen lo que necesite. La documentación sobre las librerías estándar se encuentra en [http://haskell.org/ghc/docs/latest/html/libraries/ http://haskell.org/ghc/docs/latest/html/libraries/]. Hay varios módulos con:

module Main where

import qualified Data.Map as M

errorsPerLine = M.fromList
    [ ("Chris", 472), ("Don", 100), ("Simon", -5) ]

main = do putStrLn "Who are you?"
          name <- getLine
          case M.lookup name errorsPerLine of
              Nothing -> putStrLn "I don't know you"
              Just n  -> do putStr "Errors per line: "
                            print n

La palabra reservada import dice que se usará el código de Data.Map y que tendrá el prefijo de M. (Eso es necesario por que algunas funciones tienen el mismo nombre que las del preludio. La mayoría de librerías no necesitan la parte as).

Si se quiere algo que no está en la librería estándar, intente buscar en http://hackage.haskell.org/packages/hackage.html o en la pagina de esta wiki aplicaciones y librerías. Esta es una colección de varias librerías distintas escritas por muchas personas para Haskell. Una vez que se obtienen una librería, se puede extraerla y en el directorio obtenido hacer:

 runhaskell Setup configure
 runhaskell Setup build
 runhaskell Setup install

En un sistema UNIX, se puede necesitar ser root para esta ultima parte.

Temas que no encajan en 10 minutos[edit]