Aprende Haskell en 10 minutos
Introducción
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
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
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
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 declarar5
comoInt
o comoDouble
).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
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
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
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
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:
- Useful data structures
- Concurrent and parallel programming
- Graphics and GUI libraries
- Networking, POSIX, and other system-level stuff
- Two test frameworks, QuickCheck and HUnit
- Regular expressions and predictive parsers
- More...
- Estructuras de datos útiles
- Concurrencia y programación paralela
- Librerías de GUI y gráficos
- Redes, POSIX, y otras cosas al nivel del sistema
- Dos frameworks de tests, QuickCheck y HUnit
- Expresiones regulares y parsers
- Más...
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
- Tipos de datos avanzados
- Aritmética de listas
- Comprensión de listas
- Sinónimos de tipos
- data vs newtype (y aquí)
- Clases de tipos e instancias
- Sintaxis avanzada
- Funciones avanzadas
- Monads
- I/O
- Leer ficheros
- Escribir ficheros