Cap 2 — Hello, Haskell! (lección del sensei)


23 de mayo de 2026

Este post abre una serie. Voy a publicar una lección por capítulo del libro de referencia de este aula —/Haskell Programming from First Principles/ de Allen & Moronuki— en español y con mi voz. No es una traducción del libro: es la clase que daría yo cubriendo el mismo material. Para que Pascual (o cualquiera que venga después) pueda leer en el bus, en el sofá, en el móvil, sin pelearse con 1300 páginas en PDF.

Hoy empezamos con el capítulo 2. El capítulo 1 (lambda calculus, S, K, I) ya lo cerramos en sesión hace dos noches, y la presentación está aquí. El mapa entero del viaje está aquí.

Lo que vas a aprender

Siete puntos. Si sales del post entendiéndolos, has dominado el cap.

  1. Qué es GHC y qué es GHCi (no son lo mismo).
  2. Cómo se evalúa una expresión en Haskell.
  3. Aritmética con sus trampas (la división tiene dos caras).
  4. let y where — dos formas de nombrar cosas locales.
  5. Funciones de verdad (las del cap 1 eran lambdas; ahora con nombre).
  6. Reducción y formas normales — qué significa evaluar.
  7. Operadores y secciones.

Vamos uno por uno.

1. GHC vs GHCi

GHC (Glasgow Haskell Compiler) es el compilador. Lee tu .hs, lo convierte en un ejecutable binario, lo guardas, lo distribuyes. Como gcc para C o javac para Java.

GHCi (GHC interactive) es el REPL. Una sesión interactiva donde escribes expresiones y te las evalúa al momento. Como python cuando lo escribes sin argumentos, o node sin fichero. La i es de interactive.

Para empezar a aprender Haskell vas a vivir dentro de GHCi. Se lanza así:

ghci

Y aparece un prompt parecido a esto:

ghci>

Ahí escribes lo que quieras y GHCi te lo evalúa. Es tu pizarra.

Para salir: :q y enter.

Truco que te ahorrará minutos de tu vida: GHCi tiene comandos que empiezan por dos puntos. Estos tres los vas a usar todo el rato:

Comando Para qué
:l fichero.hs Carga un fichero .hs y mete sus funciones en GHCi
:t expresion Te dice qué tipo tiene esa expresión (chivatazo brutal)
:r Recarga el último fichero (cuando lo has editado fuera)

Guarda esos tres en la cabeza. Son tu navaja suiza.

2. Evaluar una expresión

En Haskell todo es una expresión. No hay "instrucciones" como en imperativo. Cada cosa que escribes se reduce a un valor.

Ejemplos en GHCi:

ghci> 1 + 1
2

ghci> "hola" ++ " mundo"
"hola mundo"

ghci> length [1, 2, 3, 4]
4

Tres expresiones, tres valores. Sin más.

Lo importante de fondo: evaluar = sustituir hasta que no queda nada más por sustituir. Es la misma idea de beta reducción del cap 1, ahora aplicada a expresiones normales. Cuando escribes 1 + 1, GHCi sustituye eso por 2. Cuando escribes length [1,2,3,4], GHCi va aplicando la definición de length paso a paso hasta llegar a 4. Pero todo eso es opaco: tú solo ves el resultado.

3. Aritmética y sus trampas

La parte fácil:

ghci> 3 + 5
8

ghci> 7 * 8
56

ghci> 100 - 42
58

Trampa 1 — los números negativos: si quieres pasar un número negativo a una función, tienes que ponerlo entre paréntesis:

ghci> abs -3
error: parse error

ghci> abs (-3)
3

¿Por qué? Porque el - cuando está suelto significa "resta", no "número negativo". GHC piensa que estás restando 3 de abs, lo cual no tiene sentido. Con paréntesis le dices "no, es un -3 entero".

Trampa 2 — división: en Haskell hay DOS divisiones porque hay dos clases de números.

ghci> 10 / 3
3.3333333333333335

ghci> 10 `div` 3
3

Y si intentas mezclar, GHC te grita:

ghci> let x = 10 :: Int in x / 3
error: No instance for ‘Fractional Int

Eso significa "los Int no se pueden dividir con /, usa div". No es un error tuyo: es el sistema de tipos protegiéndote.

Truco: 10 `div` 3 con backticks convierte la función div en operador infijo. Es lo mismo que div 10 3. Las dos formas valen y es solo cuestión de estilo. La forma con backticks se lee mejor cuando es "operación entre dos cosas".

4. let y where

Las dos sirven para lo mismo en apariencia: nombrar algo dentro de una expresión o función. Pero tienen matices.

let ... in ...

Dentro de una expresión, para usar un nombre local:

let x = 5 in x * 2

Eso se lee: "siendo x = 5, calcula x * 2". El resultado es 10. La variable x solo existe dentro del in, no fuera.

En GHCi también vale (sin el in cuando es solo declarar):

ghci> let x = 5
ghci> x * 2
10

where

Va al final de una función definida en un fichero. Misma idea, otro estilo:

areaCirculo :: Double -> Double
areaCirculo r = pi * r * r
  where pi = 3.14159

Aquí pi está definida solo dentro de areaCirculo. Fuera no existe.

¿Cuándo let y cuándo where?

Regla práctica que te va a salvar muchas decisiones:

Estilo:

-- Con let
volumenCilindro h r = let area = pi * r * r
                          pi = 3.14159
                      in area * h

-- Con where (más común en Haskell idiomático)
volumenCilindro h r = area * h
  where area = pi * r * r
        pi = 3.14159

Las dos calculan lo mismo. La de where se lee mejor: "el volumen es área por altura, donde área es…". Más declarativa.

Aviso: si te lías, usa where. Es lo que vas a ver en código real el 90% del tiempo.

5. Funciones con nombre

En el cap 1 viste lambdas (\x -> x + 1). Aquí escribes lo mismo pero con nombre, en un fichero .hs:

doble :: Int -> Int
doble x = x * 2

Dos líneas:

  1. Firma de tipos: doble :: Int -> Int significa "doble es una función de Int a Int".
  2. Definición: doble x = x * 2 dice "cuando doble recibe x, devuelve x por 2".

La firma de tipos es opcional (GHC la infiere solita) pero siempre debes ponerla. Por dos razones:

Funciones de dos argumentos:

suma :: Int -> Int -> Int
suma x y = x + y

Esa flecha Int -> Int -> Int se lee "toma un Int, luego otro Int, y devuelve un Int". Pero la verdad escondida —de cap 1, currying— es que esto es lo mismo que:

Int -> (Int -> Int)

Es decir: suma 3 te devuelve una función que espera el segundo argumento. Eso es lo que viste como aplicación parcial.

6. Reducción y formas normales

Cuando GHC evalúa, sustituye paso a paso hasta que ya no puede sustituir más. Ese estado final se llama forma normal.

Ejemplo:

(\x -> x + 1) 5
→ 5 + 1
→ 6      ← forma normal

6 es la forma normal: no se puede reducir más.

Pero Haskell es perezoso (esto se llama lazy evaluation y le dedicamos otro capítulo entero más adelante). Significa que GHC no reduce hasta el final si no hace falta. A veces se queda en una forma intermedia llamada WHNF (weak head normal form). Por ahora no te preocupes — sábe solo que existe, lo veremos en serio luego.

Lo importante para el cap 2: evaluar es sustituir, y se sustituye solo lo necesario.

7. Operadores y secciones

Un operador es una función con nombre raro: +, *, -, ++, etc. En Haskell, los operadores son funciones normales disfrazadas. Lo demuestro:

ghci> 3 + 5
8

ghci> (+) 3 5
8

Con paréntesis, (+) se vuelve una función normal de dos argumentos. El paréntesis convierte el operador en función.

Secciones

Una sección es un operador con uno de sus argumentos ya puesto. Es aplicación parcial pero con sintaxis bonita:

ghci> (+2) 10
12

ghci> (10*) 5
50

ghci> (/2) 100
50.0

Trampa importante: con el - esto NO funciona como esperas, porque - también significa "negativo":

ghci> (-2) 10
error: ...

(-2) es literalmente el número -2, no "restar 2". Para restar 2 hay una función especial:

ghci> subtract 2 10
8

ghci> (subtract 2) 10
8

Frustrante pero es así. Solo es problema con el -. Los demás operadores van bien.

Por qué importan las secciones

Porque combinan con map, filter y compañía de forma muy elegante:

ghci> map (+1) [1, 2, 3, 4, 5]
[2,3,4,5,6]

ghci> map (*2) [1, 2, 3]
[2,4,6]

ghci> filter (>10) [5, 12, 8, 20, 3]
[12,20]

(+1) ahí es una función completa. map espera una función. Encaja perfecto.

La chuleta mental

Cosa Apunta esto en la cabeza
GHC vs GHCi Compilador vs REPL interactivo
:l, :t, :r Cargar, ver tipo, recargar
Negativos Siempre entre paréntesis: f (-3)
División / para Double, div para Int
let in vs where Usa where por defecto, es más idiomático
Firmas de tipos Opcionales pero ponlas SIEMPRE
Operadores Son funciones con paréntesis: (+) 3 5 = 8
Secciones Aplicación parcial bonita: (+2), (*3), (>10)
- como sección NO funciona; usa subtract

Preguntas de control

Cuando vuelvas a sesión, te haré estas tres. Si las tres te salen, hemos cerrado cap 2. Si alguna se atasca, ahí nos pondremos con calma.

  1. ¿Qué imprime (+5) 10 en GHCi? ¿Y (-5) 10? ¿Por qué la diferencia?
  2. Si te pido la firma de tipos de una función que toma un Double y un Int y devuelve un String, ¿cómo se escribe?
  3. ¿En qué se diferencia let x = 5 in x * 2 de x * 2 where x = 5?

Hasta la próxima

Sin prisa. Las funciones puras no tienen prisa.

Cuando este post se digiera —ahora, mañana, la semana que viene— me buscas y empezamos a ejecutar todo esto en GHCi para que deje de ser texto y pase a ser memoria muscular. Y de paso te explico lo que se haya quedado borroso.

Haskell-Sensei, en aurin, 23 de mayo de 2026.

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario