λ Haskell — 1990

⬅️ Atlas · 🗂️ Todas las fichas · 📚 Índice de clases

Haskell es el lenguaje del que la industria lleva treinta años copiando ideas sin adoptarlo. Las funciones de primera clase, la inferencia de tipos, las lambdas, Option/Maybe, las colecciones inmutables, los tipos algebraicos y async estaban aquí antes de estar en ningún lenguaje mayoritario — y su lema no oficial lo resume: "evitar el éxito a toda costa".

🎯 Por qué está en este programa

Haskell es el representante de la familia funcional tipada (ML) (Atlas), que no tiene representante en el núcleo — su influencia llega al curso a través de Rust, Scala, F# y Elm.

Aporta al programa dos conceptos que ningún otro lenguaje del Atlas enseña igual: la evaluación perezosa por defecto (clase 115) y la pureza con efectos en el sistema de tipos (clase 118).

Año 1990; Haskell 98 el estándar de referencia; GHC 2021/2024 los conjuntos actuales
Autoría Comité académico (Hudak, Peyton Jones, Wadler, Hughes y otros)
Familia Funcional tipada (ML); nombrado por Haskell Curry, lógico
Paradigma Funcional puro
Tipado Estático, inferido (Hindley-Milner extendido), con clases de tipos
Memoria Recolección de basura generacional, muy afinada para asignación rápida
Ejecución Compilado a nativo con GHC, vía su propia representación intermedia
Estado 🟢 Vivo, minoritario y muy influyente

📜 Historia

A finales de los ochenta había más de una docena de lenguajes funcionales perezosos, cada uno de un grupo de investigación, y ninguno con masa crítica. En 1987, en una conferencia en Portland, se formó un comité para hacer uno solo, abierto y estándar.

El resultado, en 1990, fue Haskell — con el nombre del lógico Haskell Curry, cuyo trabajo sobre lógica combinatoria fundamenta el cálculo lambda tipado.

El problema que tuvieron que resolver era serio: en un lenguaje puro y perezoso, ¿cómo se hace entrada y salida? Si una función no puede tener efectos y el orden de evaluación no está determinado, imprimir en pantalla es un problema conceptual, no técnico.

La solución, hacia 1992, fue la mónada IO —trabajo de Eugenio Moggi y Philip Wadler—, y es la idea más influyente y peor explicada de la informática moderna: los efectos se representan como valores de un tipo, y se componen.

GHC —el compilador de Glasgow— se convirtió en la implementación de referencia y en un laboratorio de investigación de tipos, del que han salido las extensiones que después llegaron a otros lenguajes: GADT, familias de tipos, tipos de rango superior, deriving automático, Applicative.

Y el lema "avoid success at all costs" —una broma de Simon Peyton Jones con doble lectura— refleja una postura real: priorizar la corrección del diseño sobre la adopción, para poder seguir cambiando cosas.

🏭 Dónde vive hoy

🧠 Lo que enseña: pereza y efectos en el tipo

Uno, la evaluación perezosa por defecto (clase 115):

naturales = [1..]                    -- una lista INFINITA
primeros10 = take 10 naturales        -- [1,2,3,4,5,6,7,8,9,10]

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)   -- ← se define en términos de sí misma

Nada se calcula hasta que se necesita, así que una estructura infinita es un valor normal. Eso permite separar la generación del consumo —el productor no sabe cuánto se va a consumir— y es la misma idea que los generadores de Python, los iteradores perezosos de Rust y los rangos de D, con la diferencia de que aquí es la norma y no la excepción.

Y el coste hay que decirlo: la pereza hace muy difícil razonar sobre el consumo de memoria. Un acumulador que no se fuerza construye una cadena de cálculos pendientes que puede agotar la memoria — el problema clásico de Haskell, que se resuelve con anotaciones de rigor (seq, !).

Dos, los efectos en el tipo:

longitud :: String -> Int              -- pura: mismo argumento, mismo resultado, SIEMPRE
leerFichero :: FilePath -> IO String    -- ← el IO en el tipo DECLARA que hay efectos

El tipo dice si una función toca el mundo. No es una convención ni una anotación opcional: una función pura no puede llamar a una con efectos sin que aparezca en su firma.

Y eso da lo que la clase 118 busca: al leer una firma se sabe qué puede hacer la función, y el compilador lo garantiza. Es la versión más fuerte de la idea que Fortran tiene con pure y D con @nogc.

Y tres, las clases de tipos, que son el sistema de polimorfismo más limpio de esta lista:

class Mostrable a where
    mostrar :: a -> String

instance Mostrable Bool where
    mostrar True = "sí"
    mostrar False = "no"

Se añade un comportamiento a un tipo existente sin tocarlo y sin herencia — lo que Rust llama rasgos, Scala implementa con implícitos y Go aproxima con interfaces estructurales (clase 112).

🔄 Lo que se ha modernizado

⚙️ Cómo se ejecuta hoy

runghc main.hs < entrada.txt          # ejecutar directamente
ghc -O2 main.hs -o venta               # compilar optimizado
ghci                                    # consola interactiva

cabal test && hlint src                 # pruebas y análisis (clases 139 y 146)

🧪 El programa de la clase 041 en Haskell

Haskell no está en el primos.md de la clase 041, así que esta versión se escribe aquí y no está verificada en CI — como el resto de los primos de lectura (clase 040).

import Text.Printf (printf)

main :: IO ()
main = do
    linea <- getLine
    let [precio, cantidad, descuento] = map read (words linea) :: [Double]
    printf "Total: %.2f\n" (precio * cantidad * (1 - descuento))

Lo que hay que ver.

📚 Fuentes y bibliografía


⏮️ Volver al Atlas · 🗂️ Todas las fichas · 🔗 Relacionadas: OCaml · F# · Elm · Scala · Rust