Programar solo con funciones que devuelven valores
Haskell es un lenguaje de programación funcional: un programa es un conjunto de funciones que reciben valores y devuelven valores. Cada función lleva un tipo que dice qué puede recibir y qué devuelve, y el compilador lo comprueba antes de ejecutar nada.
Así se descubren muchos errores antes de que un cliente los encuentre, por ejemplo sumar un precio a un nombre.
Al final de esta lección
Nota
Puedo leer una función pequeña de Haskell y su tipo.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Función pura | una función que con los mismos datos da siempre el mismo resultado | |
| Tipo | la clase de un valor: número, texto, sí o no | Int |
| Firma de tipo | la línea con :: que dice qué recibe y qué devuelve | square :: Int -> Int |
| Composición | unir dos funciones: lo que sale de una entra en la otra | f . g |
| Expresión | algo que se calcula hasta dar un valor | square 7 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una función pura solo depende de sus datos
Una función pura no mira nada fuera de sus datos y no cambia nada fuera. Si le das el mismo valor, devuelve siempre el mismo resultado.
square 7 es 49 hoy y mañana. En Haskell las funciones son puras, así que se pueden probar una a una y se leen sin miedo a efectos escondidos.
Dos funciones sobre precios
-- Los precios van en céntimos, para no usar decimales.
withVat :: Int -> Int
withVat price = div (price * 121) 100
total :: Int -> Int -> Int
total price units = price * units
-- withVat 1000 es 1210. total 250 3 es 750.
Lee las funciones del laboratorio
docker exec haskell cat Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta el archivo entero
docker exec haskell runghc Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
La firma dice qué entra y sale
La línea isEven :: Int -> Bool se lee así: isEven recibe un Int (un número entero) y devuelve un Bool (verdadero o falso).
Con varios datos, la flecha final es el resultado. Int -> Int -> Int recibe dos enteros y devuelve uno. Antes de ejecutar, Haskell comprueba que cada llamada respeta la firma.
Ejemplo: cómo leer tres firmas
| Firma | Recibe | Devuelve |
|---|---|---|
| square :: Int -> Int | Un entero | Un entero |
| isEven :: Int -> Bool | Un entero | Verdadero o falso |
| greet :: String -> String | Un texto | Un texto |
| total :: Int -> Int -> Int | Dos enteros | Un entero |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pregunta el tipo de square
docker exec haskell ghc -e ":type square" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Pregunta el tipo de greet
docker exec haskell ghc -e ":type greet" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Pregunta el tipo de una función ajena
docker exec haskell ghc -e ":type not"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Se llama a una función con un espacio
En Haskell no hay paréntesis para llamar: square 7 es la llamada, donde Python escribiría square(7). Los paréntesis solo agrupan.
Una llamada es una expresión: un valor calculado. Con ghc -e calculas una expresión suelta y ves su valor. Si añades un archivo al final, sus funciones quedan cargadas.
Llama a square con el 7
docker exec haskell ghc -e "square 7" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Llama a greet con un nombre
docker exec haskell ghc -e "greet \"Lucia\"" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un tipo equivocado se rechaza
docker exec haskell ghc -e "greet 5" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
El punto une dos funciones
f . g es la composición de dos funciones: una función nueva que aplica primero g y después f. Se lee de derecha a izquierda, como en matemáticas.
(square . square) 3 calcula square 3, que es 9, y después square 9, que es 81.
Dos cuadrados seguidos
docker exec haskell ghc -e "(square . square) 3" Basics.hsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Primero multiplicar y después sumar
docker exec haskell ghc -e "((+ 1) . (* 2)) 5"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Primero sumar y después multiplicar
docker exec haskell ghc -e "((* 2) . (+ 1)) 5"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente cree que (f . g) aplica primero f, porque se lee de izquierda a derecha. No es así: aplica primero g. El orden cambia el resultado, como muestran los dos últimos Runs: 11 y 12.
En Python una función también es un valor
# Una función es un valor: se puede pasar a otra función.
def square(n):
return n * n
def add_one(n):
return n + 1
def run(f, value):
return f(value)
print(run(square, 7))
print(run(add_one, 7))
# Cambia run(add_one, 7) por run(square, 12) y vuelve a ejecutar.
Para ir más lejos
Mira la firma de (.) con ghc -e ":type (.)". Dice (b -> c) -> (a -> b) -> a -> c. Las letras minúsculas son tipos cualquiera.
Explica con una frase por qué esa firma obliga a que lo que devuelve g sea lo que recibe f. Después piensa por qué greet 5 no llega a ejecutarse.