El problema fundamental que Prism aborda es la gestión y optimización de efectos secundarios en lenguajes de programación funcional. Tradicionalmente, los lenguajes funcionales puros evitan los efectos para garantizar la referential transparency, lo que a menudo lleva a soluciones complejas como las mónadas en Haskell para encapsular y secuenciar operaciones impuras. Otros enfoques, como OCaml 5, introducen efectos pero los mantienen fuera del sistema de tipos, comprometiendo la seguridad.
Prism propone que los efectos no deben evitarse, sino modelarse explícitamente en el sistema de tipos. Al tratar los efectos como interfaces declarativas (efectos algebraicos) y gestionarlos con 'handlers', el lenguaje permite que las funciones declaren sus efectos de manera explícita y componible. Esto resuelve la dicotomía entre la pureza funcional y la necesidad práctica de efectos, ofreciendo una sintaxis que se asemeja a lenguajes imperativos mientras mantiene las garantías de tipo y la optimización de un lenguaje funcional avanzado.
La relevancia actual de este enfoque radica en la búsqueda de lenguajes que combinen la expresividad y seguridad de los paradigmas funcionales con el rendimiento y el control de recursos de los lenguajes de bajo nivel. Prism se alinea con la tendencia de integrar abstracciones de alto nivel que se compilan a código eficiente, evitando las penalizaciones de rendimiento asociadas con las implementaciones ingenuas de efectos o la sobrecarga de los sistemas de tipos tradicionales.
Arquitectura del Sistema
Prism se construye alrededor de un compilador funcional que modela los efectos mediante un sistema de tipos con 'row polymorphism' y 'algebraic effect handlers'. Un 'effect' declara operaciones (ej. yield para Gen), y un 'handler' proporciona su implementación, controlando la continuación (k) de la computación. Esto permite patrones como generadores, excepciones extensibles y lógica transaccional.
El compilador utiliza un algoritmo de inferencia de tipos bidireccional y de alto rango (Dunfield-Krishnaswami) para manejar polimorfismo de tipo, de diccionario (type classes al estilo Lean) y de efectos. La clave para las abstracciones de costo cero es el 'evidence passing', una estrategia de compilación inspirada en Koka. En lugar de reificar la computación en una 'free monad' y usar un intérprete (que implicaría una asignación de heap por operación), Prism pasa la cláusula del handler activo a cada sitio de operación como un parámetro directo. Esto convierte las operaciones de efecto en llamadas directas, eliminando asignaciones por operación y reduciendo la sobrecarga a una asignación por handler.
El modelo de memoria de Prism se basa en 'Perceus reference counting', una técnica que garantiza la liberación determinista de la memoria en puntos conocidos estáticamente, sin necesidad de un garbage collector. Esto se complementa con 'frame-limited reuse' y 'fully-in-place programming', que permiten que las actualizaciones funcionales de estructuras de datos se compilen a escrituras de puntero in-place cuando el valor es de propiedad única. El backend del compilador emite LLVM IR y MLIR, y se enlaza con un pequeño runtime en C (prism_rt.c) que implementa el conteo de referencias y primitivas básicas.
Flujo de un Efecto Algebraico con Handler
- 1 Función con Efecto Define una operación de efecto (ej. `yield(Int)`) y la declara en su tipo (`!...
- 2 Llamada a Operación La función invoca una operación de efecto (ej. `yield(n)`).
- 3 Handler Activo El compilador pasa la cláusula del handler activo como un parámetro directo.
- 4 Ejecución del Handler El handler intercepta la operación y ejecuta su lógica (ej. `yield(v, k) => v...
- 5 Manejo de Continuación El handler puede reanudar la computación (`k(())`), descartarla o reanudarla ...
- 6 Resultado La computación continúa o finaliza según la lógica del handler.
| Capa | Tecnología | Justificación |
|---|---|---|
| compute | LLVM IR | Target de compilación de bajo nivel para generación de código máquina eficiente. |
| compute | MLIR | Módulo intermedio para optimizaciones y análisis de alto nivel, complementario a LLVM. |
| storage | Perceus Reference Counting | Modelo de gestión de memoria determinista y sin GC, basado en conteo de referencias estático y reutilización de celdas. vs Garbage Collection (GC), Ownership/Borrowing (Rust) |
| compute | WebAssembly (WASM) | Target de compilación para ejecutar el intérprete en el navegador, facilitando un playground interactivo. |
| compute | C Runtime (prism_rt.c) | Pequeño runtime en C que implementa las operaciones de conteo de referencias y primitivas de bignum/string. |
Trade-offs
Ganancias
- ▲▲ Rendimiento de efectos
- ▲ Control de memoria
- ▲ Seguridad de tipos para efectos
- ▲ Componibilidad de efectos
Costes
- ▲ Complejidad del compilador
effect Gen {
ctl yield(Int) : Unit
}
fn produce(n) : !{Gen} Unit =
if n == 0 then
()
else
yield(n)
produce(n - 1)
fn total(n) =
handle produce(n) with
yield(v, k) => v + k(())
return r => 0
fn count(n) =
handle produce(n) with
yield(v, k) => 1 + k(())
return r => 0type Vec2 = Vec2 { x: Int, y: Int }
type Player = Player { pos: Vec2, hp: Int }
type Game = Game { player: Player, score: Int } deriving (Lens)
let g2 = { g | player.pos.x = 30, player.hp = 95, score = 110 }fn fib(n) =
var a := 0
var b := 1
repeat(n) fn
let t = a + b
a := b
b := t
aFundamentos Teóricos
La arquitectura de Prism se fundamenta en varias líneas de investigación académica. Los 'algebraic effect handlers' tienen sus raíces en el trabajo de Plotkin y Pretnar (Handlers of Algebraic Effects, ESOP 2009), que formalizaron cómo los efectos pueden ser capturados y manipulados. El 'row polymorphism' para efectos se basa en trabajos previos sobre registros extensibles y tipos de filas (Wand 1987; Leijen, Extensible Records with Scoped Labels, 2005).
La estrategia de 'evidence passing' para la compilación de efectos es una evolución de las técnicas presentadas por Xie y Leijen (Generalized Evidence Passing for Effect Handlers, ICFP 2021; Effect Handlers, Evidently, ICFP 2020), que buscan optimizar la ejecución de efectos algebraicos para evitar la sobrecarga de las 'free monads' o la reificación completa de la computación. El modelo de memoria 'Perceus reference counting' es una contribución significativa de Reinking, Xie, de Moura y Leijen (Perceus: Garbage Free Reference Counting with Reuse, PLDI 2021), que junto con 'frame-limited reuse' (Lorenzen y Leijen, ICFP 2021) y 'fully-in-place programming' (Lorenzen, Leijen, Swierstra, ICFP 2023) permite un control de memoria determinista y de costo cero, similar a lo que se encuentra en lenguajes como Rust o en asistentes de prueba como Lean 4.