Comparte esta página

Aprende X en Y minutos

Donde X=Haskell

Haskell fue diseñado como lenguaje de programación funcional práctico y puro. Es famoso por sus mónadas y su sistema de tipos, pero siempre regreso a él debido a su elegancia. Haskell hace la codificación una verdadera alegría para mí.

-- Para comentar una sola línea utiliza dos guiones.
{- Para comentar múltiples líneas puedes encerrarlas
en un bloque como este.
-}

----------------------------------------------------
-- 1. Tipos de datos primitivos y Operadores
----------------------------------------------------

-- Tienes números a tu disposición
3 -- 3

-- Matématicas, es lo que esperas
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20
35 / 5 -- 7.0

-- Por defecto la división no devuelve un entero
35 / 4 -- 8.75

-- Para la división entera utiliza
35 `div` 4 -- 8

-- Valores booleanos
True
False

-- Operaciones booleanas
not True -- False
not False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True

-- En los ejemplos superiores, `not` es una función que toma un valor.
-- Haskell no necesita paréntisis para las llamadas a funciones...todos los argumentos
-- son enlistados después de la función. Entonces el patrón general es:
-- func arg1 arg2 arg3...
-- Observa la sección de funciones para obtener información de como escribir tu propia función.

-- Cadenas y caracteres
"Esto es una cadena."
'a' -- caracter
'No puedes utilizar comillas simples para cadenas.' -- ¡error!

-- Concatenación de cadenas
"¡Hola " ++ "mundo!" -- "¡Hola mundo!"

-- Una cadena es una lista de caracteres
['H', 'o', 'l', 'a'] -- "Hola"
"Esto es una cadena" !! 0 -- 'E'


----------------------------------------------------
-- 2. Listas y Tuplas
----------------------------------------------------

-- Cada elemento en una lista debe ser del mismo tipo.
-- Estas dos listas son iguales:
[1, 2, 3, 4, 5]
[1..5]

-- Los rangos son versátiles.
['A'..'F'] -- "ABCDEF"

-- Puedes crear un paso en un rango.
[0,2..10] -- [0, 2, 4, 6, 8, 10]
[5..1] -- Esto no funciona debido a que Haskell incrementa por defecto.
[5,4..1] -- [5, 4, 3, 2, 1]

-- indexación en una lista
[0..] !! 5 -- 5

-- También tienes listas infinitas en Haskell!
[1..] -- una lista de todos los números naturales

-- Las listas infinitas funcionan porque Haskell tiene "lazy evaluation". Esto significa
-- que Haskell solo evalúa las cosas cuando lo necesita. Así que puedes pedir
-- el elemento 1000 de tú lista y Haskell te devolverá:

[1..] !! 999 -- 1000

-- Y ahora Haskell ha evaluado elementos 1 - 1000 de esta lista...pero el
-- resto de los elementos de esta lista "infinita" ¡no existen todavía! Haskell no lo hará
-- en realidad los evalúa hasta que los necesita.

-- uniendo dos listas
[1..5] ++ [6..10]

-- añadiendo a la cabeza de la lista
0:[1..5] -- [0, 1, 2, 3, 4, 5]

-- más operaciones con listas
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5

-- Listas por comprensión
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]

-- Listas por comprensión utilizando condicionales
[x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10]

-- Cada elemento en una tupla puede ser de diferente tipo, pero una tupla tiene
-- longitud fija.
-- Ejemplo de una tupla:
("haskell", 1)

-- acceder a los elementos (por ejemplo una tupla de longitud 2)
fst ("haskell", 1) -- "haskell"
snd ("haskell", 1) -- 1

----------------------------------------------------
-- 3. Funciones
----------------------------------------------------
-- Una función simple que recibe dos variables
add a b = a + b

-- Nota: Si estas utilizando ghci (el interprete de Haskell)
-- Necesitas utilizar `let`, por ejemplo
-- let add a b = a + b

-- Utilizando la función
add 1 2 -- 3

-- También puedes llamar a la función enmedio de dos argumentos
-- con acentos abiertos:
1 `add` 2 -- 3

-- ¡También puedes definir funciones sin tener que utilizar letras! De este modo
-- ¡Tú defines tus propios operadores! Aquí esta un operador que realiza
-- una división entera
(//) a b = a `div` b
35 // 4 -- 8

-- Guardas: son una manera fácil para ramificar funciones
fib x
  | x < 2 = 1
  | otherwise = fib (x - 1) + fib (x - 2)

-- La coincidencia de patrones es similar. Aquí hemos dado tres diferentes
-- definiciones para fib. Haskell llamará automáticamente la primer
-- función que coincide con el patrón del valor.
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)

-- Coincidencia de patrones en tuplas:
foo (x, y) = (x + 1, y + 2)

-- Coincidencia de patrones en listas. Aquí `x` es el primer elemento
-- en una lista, y `xs` es el resto de la lista. Podemos escribir
-- nuestra propia función map:
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)

-- Funciones anónimas son creadas con una diagonal invertida seguido de
-- todos los argumentos.
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]

-- utilizando pliegues (llamado `inject` en algunos lenguajes) con una función
-- anónima. foldl1 significa pliegue por la izquierda, y usa el primer valor 
-- en la lista como el valor inicial para el acumulador.
foldl1 (\acc x -> acc + x) [1..5] -- 15

----------------------------------------------------
-- 4. Más funciones
----------------------------------------------------

-- aplicación parcial: si no quieres pasar todos los argumentos a una función,
-- esta es "parcialmente aplicada". Esto significa que retorna una función que toma
-- el resto de los argumentos.

add a b = a + b
foo = add 10 -- foo es actualmente una función que toma un número y suma 10 a esta
foo 5 -- 15

-- Otra manera de escribir los mismo
foo = (+10)
foo 5 -- 15

-- composición de funciones
-- el (.) encadena funciones.
-- Por ejemplo, aquí foo es una función que toma un valor. Y se le suma 10,
-- posteriormente multiplica el resultado por 5, y devuelve el resultado final.
foo = (*5) . (+10)

-- (5 + 10) * 5 = 75
foo 5 -- 75

-- fijación de precedencia 
-- Haskell tiene otro operador llamado `$`. Este operador aplica a una función 
-- para un parámetro dado. En contraste a la aplicación de función estándar,  
-- la cúal tiene prioridad más alta posible de 10 y es asociativa por la izquierda, 
-- el operador `$` tiene prioridad de 0 y es asociativa por la derecha. Tal que
-- una baja prioridad significa que la expresión a su derecha es aplicada como parámetro a la función a su izquierda.

-- antes
even (fib 7) -- false

-- equivalentemente
even $ fib 7 -- false

-- composición de funciones
even . fib $ 7 -- false


----------------------------------------------------
-- 5. Firma de tipos
----------------------------------------------------

-- Haskell tiene un fuerte sistema de tipado, y cada cosa tiene una firma de tipo.

-- Algunos tipos básicos:
5 :: Integer
"hola" :: String
True :: Bool

-- Las funciones tienen muchos tipos.
-- `not` toma un booleano y devuelve un booleano:
-- not :: Bool -> Bool

-- Aquí, esta función toma dos argumentos:
-- add :: Integer -> Integer -> Integer

-- Cuando defines un valor, es una buena práctica escribir su tipo en una línea superior:
double :: Integer -> Integer
double x = x * 2

----------------------------------------------------
-- 6. Control de flujo y Expresiones If
----------------------------------------------------

-- expressiones if en una sola línea
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"

-- expressiones if en múltiples líneas, la identación es importante
haskell = if 1 == 1
            then "awesome"
            else "awful"

-- expressiones case: Aquí se muestra como analizar los argumentos 
-- desde línea de comandos
case args of
  "help" -> printHelp
  "start" -> startProgram
  _ -> putStrLn "bad args"

-- Haskell no tiene ciclos; en lugar de esto utiliza recursión.
-- map aplica una función sobre cada elemento en un arreglo

map (*2) [1..5] -- [2, 4, 6, 8, 10]

-- tú puedes crear una función utilizando map
for array func = map func array

-- y entonces utilizarla
for [0..5] $ \i -> show i

-- también podríamos haberlo escrito de esta manera:
for [0..5] show

-- Puedes utilizar foldl o foldr para reducir una lista
-- foldl <fn> <valor inicial> <lista>
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43

-- Esto es lo mismo que
(2 * (2 * (2 * 4 + 1) + 2) + 3)

-- foldl es izquierda, foldr es derecha
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16

-- Esto es los mismo que
(2 * 1 + (2 * 2 + (2 * 3 + 4)))

----------------------------------------------------
-- 7. Tipos de datos
----------------------------------------------------

-- Por ejemplo, para crear tu propio tipo de dato en Haskell

data Color = Rojo | Azul | Verde

-- Ahora puedes utilizarlo en una función:


say :: Color -> String
say Rojo = "¡Es Rojo!"
say Azul = "¡Es Azul!"
say Verde =  "¡Es Verde!"

-- Tus tipos de datos pueden tener parámetros también:

data Maybe a = Nothing | Just a

-- Estos son todos de tipo Maybe
Just "hello"    -- de tipo `Maybe String`
Just 1          -- de tipo `Maybe Int`
Nothing         -- de tipo `Maybe a` para cualquier `a`

----------------------------------------------------
-- 8. Haskell IO
----------------------------------------------------

-- Mientras que IO no puede ser explicado plenamente sin explicar las mónadas,
-- no es difícil explicar lo suficiente para ponerse en marcha.

-- Cuando un programa en Haskell se ejecuta, `main` es
-- llamado. Este debe devolver un valor de tipo `IO ()`. Por ejemplo:

main :: IO ()
main = putStrLn $ "¡Hola, cielo! " ++ (say Blue)
-- putStrLn tiene tipo String -> IO ()

-- Es más fácil de hacer IO si puedes implementar tu programa como
-- una función de String a String. La función
--    interact :: (String -> String) -> IO ()
-- recibe como entrada un texto,  ejecuta una función e imprime
-- una salida.

countLines :: String -> String
countLines = show . length . lines

main' = interact countLines

-- Puedes pensar en el valor de tipo `IO ()` como la representación 
-- de una secuencia de acciones que la computadora hace, al igual que
-- un programa escrito en un lenguaje imperativo. Podemos utilizar
-- la notación `do` para encadenar acciones. Por ejemplo:

sayHello :: IO ()
sayHello = do
   putStrLn "¿Cual es tu nombre?"
   name <- getLine -- obtenemos un valor y lo proporcionamos a "name"
   putStrLn $ "Hola, " ++ name

-- Ejercicio: escribe tu propia version de `interact` que solo lea 
--           una linea como entrada.

-- Nunca se ejecuta el código en `sayHello`, sin embargo. La única
-- acción que siempre se ejecuta es el valor de `main`.
-- Para ejecutar `sayHello` comenta la definición anterior de `main`
-- y sustituyela por:
--   main = sayHello

-- Vamos a entender mejor como funciona la función `getLine` cuando
-- la utilizamos. Su tipo es:
--    getLine :: IO String
-- Puedes pensar en el valor de tipo `IO a` como la representación
-- programa que generará un valor de tipo `a`
-- cuando es ejecutado (además de cualquier otra cosa que haga). Podemos
-- almacenar y reutilizar el valor usando `<-`. También podemos
-- crear nuestra propia acción de tipo `IO String`:

action :: IO String
action = do
   putStrLn "Esta es una linea."
   input1 <- getLine
   input2 <- getLine
   -- El tipo de la sentencia `do` es la de su última línea.
   -- `return` no es una palabra clave, sino simplemente una función
   return (input1 ++ "\n" ++ input2) -- return :: String -> IO String

-- Podemos usar esto sólo como usabamos `getLine`:

main'' = do
    putStrLn "¡Volveré a repetir dos líneas!"
    result <- action
    putStrLn result
    putStrLn "Esto es todo, ¡amigos!"

-- El tipo `IO` es un ejemplo de una "mónada". La forma en que Haskell utiliza una monada
-- permite que sea un lenguaje puramente funcional. Cualquier función que
-- interactue con el mundo exterior (por ejemplo usar IO) obtiene una marca `IO`
-- como su firma de tipo. Esto nos permite pensar qué funciones son "puras"
-- (que no interactuan con el mundo exterior o modifican el estado) y que funciones no lo son.

-- Esta es una poderosa característica, porque es una manera fácil de ejecutar funciones puras
-- concurrentemente; entonces, la concurrencia en Haskell es muy fácil.


----------------------------------------------------
-- 9. El interprete de comandos de Haskell
----------------------------------------------------

-- Para comenzar escribe desde la terminal `ghci`.
-- Ahora puede escribir código en Haskell. Para cualquier valor nuevo
-- que necesites crear utiliza `let`:

let foo = 5

-- Puedes inspeccionar el tipo de cualquier valor con `:t`:

>:t foo
foo :: Integer

-- Puedes ejecutar acciones de tipo `IO ()`

> sayHello
¿Cual es tu nombre?
Amigo
Hola, Amigo

Existe mucho más de Haskell, incluyendo clases de tipos y mónadas. Estas son las grandes ideas que hacen a Haskell divertido. Te dejamos un ejemplo final de Haskell: una implementación del algoritmo QuickSort:

qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
    where lesser  = filter (< p) xs
          greater = filter (>= p) xs

Haskell es fácil de instalar. Obtenlo aquí.

Usted puede encontrar más información en: Learn you a Haskell o Real World Haskell o Aprende Haskell por el bien de todos


¿Tienes una sugerencia o rectificación? Abre un issue en el repositorio de GitHub, o haz un pull request tu mismo

Originalmente contribuido por Adit Bhargava, y actualizado por 3 colaboradores.