Los lenguajes de programación modernos buscan equilibrio entre pureza funcional y efectos del mundo real.
Queremos mantener la lógica limpia y predecible… pero también necesitamos leer archivos, hacer peticiones HTTP o modificar estado.
Ahí entra Koka, un lenguaje que intenta resolver uno de los dilemas más antiguos de la programación funcional:
¿Cómo escribir código puro que también interactúe con el mundo impuro?
La respuesta de Koka son los efectos algebraicos —una forma elegante de expresar qué puede hacer una función, sin acoplarla al cómo lo hace.
En la mayoría de los lenguajes funcionales, los efectos se encapsulan.
Por ejemplo, en Haskell todo efecto está representado por un monad.
Una acción de entrada/salida se modela como un valor de tipo IO, que representa un programa que, al ejecutarse, producirá un efecto y un resultado.
El sistema es poderoso, pero tiene un precio: una vez que entrás en IO, todo tu código queda contaminado por ese contexto.
El efecto se propaga por toda la cadena de funciones.
Para componer varias acciones con efectos distintos, terminás anidando monads o recurriendo a transformadores de monads (monad transformers), una solución conceptualmente pura… pero incómoda en la práctica.
Koka propone algo diferente: cada función anota sus efectos directamente en su tipo.
Por ejemplo:
fun readLine() : string {io}
fun toUpper(s : string) : string {}
fun greet() : string {io} {
val name = readLine()
return "Hello " ++ toUpper(name)
}
El tipo {io} indica que la función realiza operaciones de entrada/salida.
Si una función no tiene efectos, simplemente aparece {}.
Esto convierte los efectos en una dimensión más del tipo: no solo sabemos qué tipo de valor devuelve una función, sino también qué efectos puede causar.
Lo brillante ocurre cuando los efectos se vuelven algebraicos.
En Koka, los efectos son parámetros abiertos: se pueden combinar, interceptar o redefinir sin romper la pureza.
Podés definir tus propios efectos, como exception, state o async, y luego manejarlos explícitamente.
Por ejemplo:
fun divide(x : int, y : int) : int {exception} {
if (y == 0) raise DivideByZero
else x / y
}
fun safeDivide(x : int, y : int) : int {
handle divide(x, y) with
exception.DivideByZero -> 0
}
Aquí, divide declara que puede lanzar un efecto de tipo exception.
safeDivide maneja ese efecto sin necesidad de un try/catch ni un monad especial.
El control del flujo se modela como una transformación algebraica:
el efecto se produce, se captura y se resuelve, todo dentro del sistema de tipos.
¿Por qué algebraicos? Porque los efectos se tratan como operaciones algebraicas: se definen por sus constructores (las acciones que pueden producir) y sus leyes de manejo (cómo se interpretan).
Esto permite una separación total entre:
- la semántica del efecto (qué significa `raise`, `print`, `read`, etc.)
- y su implementación concreta (cómo se maneja ese efecto en tiempo de ejecución).
El resultado es un sistema en el que los efectos pueden componerse y combinarse libremente, sin comprometer la pureza ni forzar estructuras de control artificiales.
Mientras Haskell encapsula efectos dentro de estructuras monádicas cerradas, Koka los representa como restricciones abiertas y composables.
En Haskell, IO es una caja: sabés que hay efectos dentro, pero no podés mirar sin ejecutarla.
En Koka, los efectos son etiquetas en el tipo, que el compilador puede seguir, combinar y hasta eliminar si son innecesarios.
Esto hace que Koka se sienta más cercano a un lenguaje con tipado por capacidades que a un sistema monádico clásico.
El resultado: código más declarativo, menos boilerplate y una pureza que no castiga la expresividad.
En lugar de ocultar los efectos, Koka los expone y los describe con precisión matemática.
El compilador sabe qué puede pasar —y lo sabe sin ejecutar el código—, lo que permite optimizaciones, razonamiento formal y mayor seguridad.


