Paradigma Lógico · UNAL 2026-1

Mercury

Programación lógico-funcional con tipos estáticos,
modos e inferencia de determinismo.

01 — Primeros Pasos

¿Qué es Mercury?

Mercury es un lenguaje de programación lógico-funcional diseñado para aplicaciones de alta confiabilidad. Combina la expresividad de Prolog con un sistema de tipos estático, análisis de modos e inferencia de determinismo. A diferencia de Prolog, Mercury está compilado y produce código de máquina eficiente.

Fue desarrollado en la Universidad de Melbourne a mediados de los 90 y sigue en desarrollo activo. Es ideal para sistemas donde la corrección es crítica: compiladores, verificación formal, inteligencia artificial simbólica.

🧠

Lógico-funcional

Basado en predicados de primer orden. Cada predicado describe una relación, no una instrucción imperativa.

🔒

Tipos estáticos

Sistema de tipos fuerte y estático. Los errores de tipo se detectan en compilación, nunca en ejecución.

Compilado

Compila a C, Java o C#. Rendimiento comparable a lenguajes imperativos compilados como C.

🎯

Determinismo

El compilador verifica si un predicado siempre tiene solución, puede fallar, o puede producir múltiples resultados.

Instalación

Mercury está disponible para Linux (recomendado), macOS y Windows. En Ubuntu/Debian:

bash — Ubuntu/Debian
sudo apt install wget ca-certificates
cd /tmp && wget https://paul.bone.id.au/paul.asc
sudo cp paul.asc /etc/apt/trusted.gpg.d/paulbone.asc

# Reemplazar "focal" con tu distro: jammy, bookworm, etc.
echo "deb http://dl.mercurylang.org/deb/ focal main" | sudo tee -a /etc/apt/sources.list

sudo apt update && sudo apt install mercury-recommended

Compilar y ejecutar

bash — Compilación
# Compilar (genera ejecutable + archivos intermedios)
mmc hello.m && ./hello

# Compilar limpio (recomendado, solo el ejecutable)
mmc --make hello && ./hello

¿Sin instalación? Usa Glot.io — un editor Mercury online gratuito. Cada ejemplo de este tutorial tiene un botón directo.

02 — Tour del Lenguaje

Breve especificación

Mercury tiene una sintaxis basada en Prolog, pero con adiciones fundamentales para tipos, modos y determinismo. Todo programa es una colección de módulos.

Estructura de un módulo

modulo.m
▶ Glot.io
:- module mi_modulo.          % 1. Nombre del módulo (coincide con el archivo .m)

:- interface.                  % 2. INTERFAZ: lo que es público
:- import_module io.           %    Importar módulo de E/S
:- pred main(io::di, io::uo) is det.   % Declarar predicado principal

:- implementation.             % 3. IMPLEMENTACIÓN: código privado
main(!IO) :-
    io.write_string("Hola desde Mercury!\n", !IO).

Tipos primitivos y algebraicos

TipoDescripciónEjemplo
intEntero de precisión nativa42, -7, 0
floatPunto flotante doble3.14, -2.5e10
stringCadena de caracteres UTF-8"hola mundo"
charCarácter Unicode'a', 'ñ', '\n'
boolBooleanoyes, no
list(T)Lista polimórfica homogénea[1,2,3], ["a","b"]

Sistema de determinismo

Cada predicado declara su comportamiento. El compilador verifica que la declaración sea correcta — esto elimina errores en tiempo de ejecución:

DeclaraciónSoluciones posibles¿Puede fallar?Uso típico
detExactamente 1NoFunciones, main
semidet0 ó 1Búsquedas, checks
multi1 ó másNoGeneradores
nondet0 ó másBacktracking general
failureSiempre 0SiempreCasos imposibles
erroneousNunca retornaerror/1, throw

Modos de instanciación

modos.m
▶ Glot.io
% in  = ya instanciado (entrada)
% out = no instanciado (salida)
% di  = estado destruible (E/S entrada)
% uo  = estado único (E/S salida)

:- pred suma(int::in, int::in, int::out) is det.
suma(X, Y, Z) :- Z = X + Y.

% Azúcar sintáctico: func declara in/in -> out automáticamente
:- func suma_f(int, int) = int.
suma_f(X, Y) = X + Y.

% Predicado semidet: puede fallar si X no está en la lista
:- pred miembro(T::in, list(T)::in) is semidet.
miembro(X, [X|_]).
miembro(X, [_|T]) :- miembro(X, T).

03 — Particularidades

Lo que hace único a Mercury

Mercury comparte la filosofía declarativa de Prolog, pero añade garantías estáticas que lo hacen apto para software de producción de alta integridad.

🛡️

Pureza declarativa

Los predicados son puros por defecto. Los efectos secundarios son explícitos: se modela como paso de estado único de E/S.

🚫

Sin cut (!)

Mercury no tiene el operador cut de Prolog. El control de flujo se maneja con el sistema de determinismo — más predecible y seguro.

♻️

Garbage Collector

Gestión automática de memoria con GC de Boehm. Sin manejo manual de memoria.

📦

Módulos

Sistema de módulos con interfaces separadas. Facilita encapsulación y desarrollo de librerías reutilizables.

🌐

Multi-backend

Compila a C, C#, Java o Erlang. Un mismo código, múltiples plataformas y entornos.

📋

Documentación automática

El compilador genera HTML de documentación a partir de comentarios especiales (%>) en el código.

El estado de E/S — !IO

La E/S en Mercury es pura y explícita. En lugar de efectos secundarios ocultos, se pasa un token de estado !IO que garantiza la secuencialidad y la pureza referencial:

io_estado.m
▶ Glot.io
% !IO es azucar sintactico para IO0::di, IO::uo
% Cada llamada consume el estado anterior y produce uno nuevo.
% El compilador garantiza que no se use el mismo estado dos veces.

main(!IO) :-
    io.write_string("Linea 1\n", !IO),   % consume IO0, produce IO1
    io.write_string("Linea 2\n", !IO),   % consume IO1, produce IO2
    io.write_string("Linea 3\n", !IO).   % consume IO2, produce IO3

% Nota: !IO expandido es:
%   main(IO0, IO3) :-
%       io.write_string("Linea 1\n", IO0, IO1),
%       io.write_string("Linea 2\n", IO1, IO2),
%       io.write_string("Linea 3\n", IO2, IO3).

Tipos algebraicos

tipos_algebraicos.m
▶ Glot.io
% Tipo algebraico: arbol binario polimórfico
:- type arbol(T)
    --->    hoja
    ;       nodo(arbol(T), T, arbol(T)).

% Pattern matching exhaustivo (el compilador verifica)
:- func altura(arbol(T)) = int.
altura(hoja) = 0.
altura(nodo(Izq, _, Der)) =
    1 + int.max(altura(Izq), altura(Der)).

:- func contar(arbol(T)) = int.
contar(hoja) = 0.
contar(nodo(Izq, _, Der)) =
    1 + contar(Izq) + contar(Der).

04 — Ejemplos Básicos

Empezando con Mercury

Los ejemplos básicos cubren los conceptos fundamentales. Haz clic en ▶ Glot.io para ejecutarlos directamente en el navegador.

B1 · Hello, World!

El programa más simple posible. Observa la estructura obligatoria: módulo → interfaz → implementación.

hello.m
:- module hello.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
main(!IO) :-
    io.write_string("Hello, World!\n", !IO).

B2 · Factorial

Función factorial recursiva usando func. El compilador verifica que los casos sean exhaustivos y el determinismo sea det.

factorial.m
:- module factorial.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int.

:- func factorial(int) = int.
factorial(0) = 1.
factorial(N) = N * factorial(N - 1) :- N > 0.

main(!IO) :-
    io.format("factorial(0)  = %d\n", [i(factorial(0))],  !IO),
    io.format("factorial(5)  = %d\n", [i(factorial(5))],  !IO),
    io.format("factorial(10) = %d\n", [i(factorial(10))], !IO).

B3 · Fibonacci

La secuencia de Fibonacci con múltiples cláusulas. Nota la simetría con la definición matemática.

fibonacci.m
:- module fibonacci.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int.

:- func fib(int) = int.
fib(0) = 0.
fib(1) = 1.
fib(N) = fib(N - 1) + fib(N - 2) :- N > 1.

main(!IO) :-
    io.format("fib(0)  = %d\n",  [i(fib(0))],  !IO),
    io.format("fib(1)  = %d\n",  [i(fib(1))],  !IO),
    io.format("fib(5)  = %d\n",  [i(fib(5))],  !IO),
    io.format("fib(10) = %d\n",  [i(fib(10))], !IO),
    io.format("fib(15) = %d\n",  [i(fib(15))], !IO).

B4 · Variables y tipos primitivos

Mercury infiere el tipo de cada variable desde su primer uso — no puede cambiar ni reasignarse. Este ejemplo muestra los cuatro tipos primitivos fundamentales: int, float, string y list.

variables.m
▶ Glot.io
:- module variables.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, float, string, list.

main(!IO) :-
    % Declaración de variables de distintos tipos.
    % El tipo se infiere del primer uso — no puede cambiar.
    IntVar    = 42,
    FloatVar  = 3.14,
    StringVar = "Hola, Mercury",
    ListVar   = [1, 2, 3, 4, 5],

    io.write_string("Valor de IntVar: ",    !IO),
    io.write_int(IntVar,                    !IO),
    io.nl(!IO),

    io.write_string("Valor de FloatVar: ",  !IO),
    io.write_float(FloatVar,                !IO),
    io.nl(!IO),

    io.write_string("Valor de StringVar: ", !IO),
    io.write_string(StringVar,              !IO),
    io.nl(!IO),

    io.write_string("Valores de ListVar: ", !IO),
    io.write_list(ListVar, ", ", io.write_int, !IO),
    io.nl(!IO).

B5 · Seguridad de tipos y conversión explícita

Mercury tiene un sistema de tipos fuerte y estático. No existe conversión implícita. Para combinar un int con una string se debe usar string.from_int/1 explícitamente — el compilador rechaza el código si los tipos no coinciden.

tipos.m
▶ Glot.io
:- module tipos.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module string.

:- pred ejemplo_predicado(int::in, string::out) is det.
ejemplo_predicado(X, Y) :-
    % Esto sería un error de tipo en Mercury:
    %   Y = X + "hola".
    % Forma correcta con conversión explícita:
    Y = string.from_int(X) ++ " hola".

main(!IO) :-
    T = 1,
    % No se puede reasignar ni cambiar el tipo de una variable:
    %   T = "hola",
    %   T = 2,
    _ = T,
    ejemplo_predicado(42, Resultado),
    io.write_string(Resultado, !IO),
    io.nl(!IO).

B6 · Determinismo: det y semidet en práctica

La tabla de determinismo de la sección 02 cobra vida aquí. add/3 es det — produce siempre exactamente un resultado. is_even/1 y my_member/2 son semidet — pueden fallar, por eso se usan dentro de if-then-else.

det_semidet.m
▶ Glot.io
:- module det_semidet.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, list.

% Predicado determinista: siempre produce exactamente un resultado.
:- pred add(int::in, int::in, int::out) is det.
add(X, Y, Z) :- Z = X + Y.

% Predicado semideterminista: puede fallar (si X es impar).
:- pred is_even(int::in) is semidet.
is_even(X) :- X mod 2 = 0.

% Predicado semideterminista: puede fallar (si X no está en la lista).
:- pred my_member(int::in, list(int)::in) is semidet.
my_member(X, [X | _]).
my_member(X, [_ | Tail]) :- my_member(X, Tail).

main(!IO) :-
    add(3, 4, Sum),
    io.write_string("La suma es: ", !IO),
    io.write_int(Sum, !IO),
    io.nl(!IO),

    ( if is_even(Sum) then
        io.write_string("La suma es par\n",   !IO)
    else
        io.write_string("La suma es impar\n", !IO)
    ),

    MyList = [1, 2, 3, 4],
    ( if list.member(3, MyList) then
        io.write_string("3 está en la lista\n",    !IO)
    else
        io.write_string("3 no está en la lista\n", !IO)
    ).

05 — Ejemplos Intermedios

Listas, recursión y tipos

Los ejemplos intermedios exploran el trabajo con listas, algoritmos recursivos y tipos de datos algebraicos — el núcleo de la programación lógico-funcional.

I1 · Operaciones sobre listas

Implementación manual de operaciones comunes: longitud, inversión y concatenación usando recursión estructural.

listas.m
:- module listas.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, list.

:- func mi_longitud(list(T)) = int.
mi_longitud([]) = 0.
mi_longitud([_|T]) = 1 + mi_longitud(T).

% Inversión con acumulador (eficiente O(n))
:- func invertir(list(T)) = list(T).
invertir(L) = aux(L, []).

:- func aux(list(T), list(T)) = list(T).
aux([], Acc)    = Acc.
aux([H|T], Acc) = aux(T, [H|Acc]).

:- func concatenar(list(T), list(T)) = list(T).
concatenar([], L)    = L.
concatenar([H|T], L) = [H | concatenar(T, L)].

main(!IO) :-
    Lista = [1, 2, 3, 4, 5],
    io.format("Lista:       %s\n", [s(string(Lista))],              !IO),
    io.format("Longitud:    %d\n", [i(mi_longitud(Lista))],          !IO),
    io.format("Invertida:   %s\n", [s(string(invertir(Lista)))],     !IO),
    io.format("Concat [6..8]:%s\n",[s(string(concatenar(Lista,[6,7,8])))], !IO).

I2 · Ordenamiento por inserción

Algoritmo de ordenamiento clásico que muestra cómo los predicados tienen múltiples cláusulas con guardas de condición.

ordenamiento.m
:- module ordenamiento.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, list.

:- pred insertar(int::in, list(int)::in, list(int)::out) is det.
insertar(X, [],     [X]).
insertar(X, [H|T],  [X,H|T]) :- X =< H.
insertar(X, [H|T],  [H|T1])  :- X > H, insertar(X, T, T1).

:- pred insertion_sort(list(int)::in, list(int)::out) is det.
insertion_sort([], []).
insertion_sort([H|T], Sorted) :-
    insertion_sort(T, ST),
    insertar(H, ST, Sorted).

main(!IO) :-
    Lista = [64, 25, 12, 22, 11, 90, 3],
    insertion_sort(Lista, Ordenada),
    io.format("Original: %s\n",  [s(string(Lista))],     !IO),
    io.format("Ordenada: %s\n",  [s(string(Ordenada))],  !IO).

I3 · Tipos algebraicos y pattern matching

Figuras geométricas modeladas con un tipo algebraico. El compilador verifica que todos los casos sean cubiertos en el pattern matching.

figuras.m
:- module figuras.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module float, math.

:- type figura
    --->    circulo(float)
    ;       rectangulo(float, float)
    ;       triangulo(float, float, float).

:- func area(figura) = float.
area(circulo(R))           = math.pi * R * R.
area(rectangulo(B, H))     = B * H.
area(triangulo(A, B, C))   = sqrt(S * (S-A) * (S-B) * (S-C)) :-
    S = (A + B + C) / 2.0.

:- func nombre(figura) = string.
nombre(circulo(_))          = "Circulo".
nombre(rectangulo(_, _))    = "Rectangulo".
nombre(triangulo(_, _, _))  = "Triangulo".

:- pred mostrar(figura::in, io::di, io::uo) is det.
mostrar(F, !IO) :-
    io.format("%s: area = %.4f\n", [s(nombre(F)), f(area(F))], !IO).

main(!IO) :-
    mostrar(circulo(5.0),          !IO),
    mostrar(rectangulo(4.0, 6.0),  !IO),
    mostrar(triangulo(3.0, 4.0, 5.0), !IO).

I4 · Predicados con múltiples modos — mother

Un predicado puede declarar el mismo argumento como in u out según el modo de llamada. mother/2 funciona hacia adelante (dado el hijo, busca la madre) y también puede usarse hacia atrás (dada la madre, busca sus hijos). El compilador verifica cada modo por separado.

mother.m
▶ Glot.io
:- module mother.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- type persona ---> laura ; rafael ; james.

% El mismo predicado, dos modos distintos:
%   dado el hijo (in) → busca la madre (out): semidet
%   dada la madre (out) → busca hijos (in):   nondet
:- pred mother(persona, persona).
:- mode mother(in, out) is semidet.
:- mode mother(out, in) is nondet.

mother(rafael, laura).
mother(james,  laura).

main(!IO) :-
    ( if mother(rafael, X) then
        io.write_string("La madre de rafael es ", !IO),
        io.write(X, !IO),
        io.nl(!IO)
    else
        io.write_string("Rafael no tiene madre registrada.\n", !IO)
    ).

I5 · Programación relacional y solutions/2

Un árbol genealógico con cuatro predicados relacionales (parent, father, mother, grandparent), cada uno con múltiples declaraciones de modo. solutions/2 recolecta todas las respuestas de un predicado nondet en una lista — similar a findall en Prolog pero con verificación de tipos.

family.m
▶ Glot.io
:- module family.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module list, solutions.

:- type person ---> ada ; bob ; dan ; ema ; fay ; joe.

:- pred female(person).
:- mode female(in)  is semidet.
:- mode female(out) is multi.
female(ada). female(ema). female(fay).

:- pred male(person).
:- mode male(in)  is semidet.
:- mode male(out) is multi.
male(bob). male(dan). male(joe).

:- pred parent(person, person).
:- mode parent(in,  in)  is semidet.
:- mode parent(in,  out) is nondet.
:- mode parent(out, in)  is nondet.
:- mode parent(out, out) is multi.
parent(ada, dan). parent(bob, dan).
parent(dan, fay). parent(ema, fay).
parent(dan, joe). parent(ema, joe).

:- pred grandparent(person, person).
:- mode grandparent(in,  in)  is semidet.
:- mode grandparent(in,  out) is nondet.
:- mode grandparent(out, in)  is nondet.
:- mode grandparent(out, out) is nondet.
grandparent(PP, C) :- parent(PP, P), parent(P, C).

main(!IO) :-
    % solutions/2 recolecta todas las soluciones en una lista.
    solutions(
        (pred(PP::out) is nondet :- grandparent(PP, fay)),
        GPs),
    (
        GPs = [],
        io.write_string("fay no tiene abuelos registrados.\n", !IO)
    ;
        GPs = [_ | _],
        io.write_string("Abuelos de fay: ", !IO),
        io.write(GPs, !IO),
        io.nl(!IO)
    ).

I6 · Entrada del usuario y manejo de errores

io.read_line_as_string/3 devuelve un resultado que puede ser ok(String), eof o error(Error). El disjunctive pattern matching sobre ese tipo garantiza que todos los casos sean manejados — el compilador rechaza código que omita algún constructor.

suma.m
▶ Glot.io
:- module suma.
:- interface.
:- import_module io.

:- pred suma(int::in, int::in, int::out) is det.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, string, list.

suma(X, Y, Resultado) :- Resultado = X + Y.

main(!IO) :-
    io.read_line_as_string(Result1, !IO),
    (
        Result1 = ok(Line1),
        ( if string.to_int(string.strip(Line1), X) then
            io.read_line_as_string(Result2, !IO),
            (
                Result2 = ok(Line2),
                ( if string.to_int(string.strip(Line2), Y) then
                    suma(X, Y, Resultado),
                    io.format("La suma de %d y %d es %d\n",
                              [i(X), i(Y), i(Resultado)], !IO)
                else
                    io.write_string(
                        "Error: el segundo valor no es un número válido.\n", !IO)
                )
            ;
                Result2 = eof,
                io.write_string(
                    "Error: fin de entrada al leer el segundo número.\n", !IO)
            ;
                Result2 = error(Err2),
                io.format("Error al leer el segundo número: %s\n",
                          [s(io.error_message(Err2))], !IO)
            )
        else
            io.write_string(
                "Error: el primer valor no es un número válido.\n", !IO)
        )
    ;
        Result1 = eof,
        io.write_string(
            "Error: fin de entrada al leer el primer número.\n", !IO)
    ;
        Result1 = error(Err1),
        io.format("Error al leer el primer número: %s\n",
                  [s(io.error_message(Err1))], !IO)
    ).

06 — Ejemplos Avanzados

Higher-order, DCG y Estado

Capacidades avanzadas de Mercury: predicados de orden superior, gramáticas de cláusulas definidas y manejo de estado mediante recursión.

A1 · Predicados de orden superior

Mercury soporta funciones de orden superior: funciones que reciben otras funciones como argumento, similar a map, filter y fold en lenguajes funcionales.

orden_superior.m
:- module orden_superior.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, list.

% mi_map: aplica F a cada elemento de la lista
:- func mi_map(func(T) = U, list(T)) = list(U).
mi_map(_, [])     = [].
mi_map(F, [H|T])  = [F(H) | mi_map(F, T)].

% mi_foldl: reduce la lista acumulando con F
:- func mi_foldl(func(T, U) = U, list(T), U) = U.
mi_foldl(_, [], Acc)    = Acc.
mi_foldl(F, [H|T], Acc) = mi_foldl(F, T, F(H, Acc)).

% Funciones concretas para pasar como argumento
:- func doblar(int) = int.
doblar(X) = X * 2.

:- func cuadrado(int) = int.
cuadrado(X) = X * X.

:- func sumar(int, int) = int.
sumar(X, Acc) = X + Acc.

main(!IO) :-
    Lista   = [1, 2, 3, 4, 5],
    Dobles  = mi_map(doblar,   Lista),
    Cuadrs  = mi_map(cuadrado, Lista),
    Suma    = mi_foldl(sumar,  Lista, 0),
    io.format("Original:   %s\n", [s(string(Lista))],   !IO),
    io.format("Dobles:     %s\n", [s(string(Dobles))],  !IO),
    io.format("Cuadrados:  %s\n", [s(string(Cuadrs))],  !IO),
    io.format("Suma total: %d\n", [i(Suma)],             !IO).

A2 · Gramáticas de Cláusulas Definidas (DCG)

Las DCG permiten definir gramáticas formales y parsers de forma elegante. Mercury transforma las reglas DCG en predicados con argumentos de diferencia de listas automáticamente.

dcg_parser.m
:- module dcg_parser.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module list, char, string, int.

% Gramática: oracion --> frase_nominal, frase_verbal
%             frase_nominal --> sustantivo
%             frase_verbal  --> verbo | verbo, frase_nominal

:- pred sustantivo(list(string)::in, list(string)::out) is semidet.
sustantivo --> ["mercury"].
sustantivo --> ["lenguaje"].
sustantivo --> ["programador"].

:- pred verbo(list(string)::in, list(string)::out) is semidet.
verbo --> ["es"].
verbo --> ["usa"].

:- pred frase_nominal(list(string)::in, list(string)::out) is semidet.
frase_nominal --> sustantivo.

:- pred frase_verbal(list(string)::in, list(string)::out) is semidet.
frase_verbal --> verbo.
frase_verbal --> verbo, frase_nominal.

:- pred oracion(list(string)::in, list(string)::out) is semidet.
oracion --> frase_nominal, frase_verbal.

:- pred probar(string::in, list(string)::in, io::di, io::uo) is det.
probar(Desc, Tokens, !IO) :-
    ( oracion(Tokens, []) ->
        io.format("VALIDA  | %s\n", [s(Desc)], !IO)
    ;
        io.format("INVALIDA| %s\n", [s(Desc)], !IO)
    ).

main(!IO) :-
    probar("mercury es",           ["mercury", "es"],           !IO),
    probar("programador usa mercury",["programador","usa","mercury"], !IO),
    probar("es mercury",           ["es", "mercury"],           !IO).

A3 · Manejo de estado con recursión

En Mercury, el estado mutable se modela pasando el estado como argumento. Este patrón — llamado state threading — garantiza la pureza referencial sin sacrificar la expresividad.

estado.m
:- module estado.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, list.

% Cuenta regresiva: el estado (N) se pasa por recursión
:- pred cuenta_regresiva(int::in, io::di, io::uo) is det.
cuenta_regresiva(0, !IO) :-
    io.write_string("Despegue!\n", !IO).
cuenta_regresiva(N, !IO) :-
    N > 0,
    io.format("  %d...\n", [i(N)], !IO),
    cuenta_regresiva(N - 1, !IO).

% Suma acumulativa: el acumulador es el estado
:- pred sumar_lista(list(int)::in, int::in, int::out) is det.
sumar_lista([], Acc, Acc).
sumar_lista([H|T], Acc, Total) :-
    NuevoAcc = Acc + H,
    sumar_lista(T, NuevoAcc, Total).

% Producto con acumulador
:- pred producto_lista(list(int)::in, int::in, int::out) is det.
producto_lista([], Acc, Acc).
producto_lista([H|T], Acc, Prod) :-
    producto_lista(T, Acc * H, Prod).

main(!IO) :-
    io.write_string("=== Cuenta regresiva ===\n", !IO),
    cuenta_regresiva(5, !IO),
    io.nl(!IO),

    Lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    sumar_lista(Lista, 0, Suma),
    producto_lista(Lista, 1, Prod),
    io.format("=== Lista 1..10 ===\n", [], !IO),
    io.format("Suma:     %d\n", [i(Suma)], !IO),
    io.format("Producto: %d\n", [i(Prod)], !IO).

A4 · Inferencia lógica y cc_multi

Un sistema de inferencia simple donde las propiedades se derivan de hechos y reglas. cc_multi (committed choice multi) indica que el predicado produce al menos una solución pero sólo se compromete con la primera — equivalente a un corte implícito, pero verificado por el compilador.

martians.m
▶ Glot.io
/* ngtrks es pequeño y verde.
   pgvdrk es un marciano saltarín.
   Todas las criaturas saltarinas son verdes.
   Todas las criaturas pequeñas y saltarinas son marcianas.
   Todas las criaturas verdes y marcianas son inteligentes.
   ¿Cuál de los dos es inteligente? */

:- module martians.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is cc_multi.

:- implementation.

:- type marciano ---> ngtrks ; pgvdrk.

:- pred small(marciano::out)       is det.
:- pred green(marciano::out)       is multi.
:- pred martian(marciano::out)     is multi.
:- pred jumping(marciano::out)     is det.
:- pred intelligent(marciano::out) is nondet.

small(ngtrks).
green(ngtrks).
martian(pgvdrk).
jumping(pgvdrk).

green(X)   :- jumping(X).
martian(X) :- small(X), jumping(X).

intelligent(X) :- green(X), martian(X).

main(!IO) :-
    io.write_string("¿Qué marciano es inteligente?: ", !IO),
    ( if intelligent(X) then
        io.write(X, !IO),
        io.write_string(" es inteligente.\n", !IO)
    else
        io.write_string("No se puede determinar.\n", !IO)
    ).

A5 · E/S recursiva y cifrado ROT13

Lectura carácter a carácter con io.read_char/3 en un bucle recursivo que termina al alcanzar eof. El cifrado ROT13 se implementa como una función de pattern matching exhaustivo sobre char — cada letra del alfabeto se mapea a su par rotado 13 posiciones.

rot13.m
▶ Glot.io
:- module rot13.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module char, list, string.

main(!IO) :-
    io.read_char(Result, !IO),
    (
        Result = ok(Char),
        io.write_char(rot13(Char), !IO),
        main(!IO)
    ;
        Result = eof
    ;
        Result = error(ErrorCode),
        io.format("%s\n", [s(io.error_message(ErrorCode))], !IO)
    ).

:- func rot13(char) = char.
rot13(Char) = (
    if      Char = 'a' then 'n'  else if Char = 'b' then 'o'
    else if Char = 'c' then 'p'  else if Char = 'd' then 'q'
    else if Char = 'e' then 'r'  else if Char = 'f' then 's'
    else if Char = 'g' then 't'  else if Char = 'h' then 'u'
    else if Char = 'i' then 'v'  else if Char = 'j' then 'w'
    else if Char = 'k' then 'x'  else if Char = 'l' then 'y'
    else if Char = 'm' then 'z'  else if Char = 'n' then 'a'
    else if Char = 'o' then 'b'  else if Char = 'p' then 'c'
    else if Char = 'q' then 'd'  else if Char = 'r' then 'e'
    else if Char = 's' then 'f'  else if Char = 't' then 'g'
    else if Char = 'u' then 'h'  else if Char = 'v' then 'i'
    else if Char = 'w' then 'j'  else if Char = 'x' then 'k'
    else if Char = 'y' then 'l'  else if Char = 'z' then 'm'
    else Char
).

A6 · Recolector de basura y tipos personalizados

Mercury usa el GC de Boehm. Este ejemplo crea y descarta estructuras de datos sin liberar memoria manualmente: DataList e IntList se vuelven inalcanzables al final del predicado y el GC las libera automáticamente.

trash.m
▶ Glot.io
:- module trash.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module list, int.

:- type my_data ---> data(int).

:- func create_data_list(int) = list(my_data).
create_data_list(N) =
    ( if N =< 0 then [] else [data(N) | create_data_list(N - 1)] ).

:- func process_data_list(list(my_data)) = list(int).
process_data_list([]) = [].
process_data_list([data(X) | Xs]) = [X | process_data_list(Xs)].

main(!IO) :-
    DataList = create_data_list(10),
    IntList  = process_data_list(DataList),
    io.write_list(IntList, ", ", io.write_int, !IO),
    io.nl(!IO),
    % DataList e IntList ya no son referenciadas.
    % El recolector de basura (Boehm GC) libera su memoria automáticamente.
    io.write_string(
        "Memoria gestionada automáticamente por el recolector de basura.\n",
        !IO).

A7 · Interfaz con C — foreign_proc

pragma foreign_proc permite implementar un predicado directamente en C (o Java, C#). Las anotaciones le indican al compilador qué garantías ofrece el código externo: will_not_call_mercury (no reingresa en Mercury), promise_pure (es referencialmente transparente) y thread_safe (seguro para concurrencia).

addc.m
▶ Glot.io
:- module addc.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module string, int.

:- pred add(int::in, int::in, int::out) is det.
:- pragma foreign_proc("C",
    add(A::in, B::in, Result::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Result = A + B;
").

main(!IO) :-
    io.read_line_as_string(ResultA, !IO),
    (
        ResultA = ok(LineA),
        ( if string.to_int(string.strip(LineA), A) then
            io.read_line_as_string(ResultB, !IO),
            (
                ResultB = ok(LineB),
                ( if string.to_int(string.strip(LineB), B) then
                    add(A, B, Sum),
                    io.write_string("La suma es: ", !IO),
                    io.write_int(Sum, !IO),
                    io.nl(!IO)
                else
                    io.write_string("Error: B no es un entero válido.\n", !IO)
                )
            ;
                ResultB = eof,
                io.write_string("Error: fin de entrada al leer B.\n", !IO)
            ;
                ResultB = error(ErrB),
                io.format("Error al leer B: %s\n",
                          [s(io.error_message(ErrB))], !IO)
            )
        else
            io.write_string("Error: A no es un entero válido.\n", !IO)
        )
    ;
        ResultA = eof,
        io.write_string("Error: fin de entrada al leer A.\n", !IO)
    ;
        ResultA = error(ErrA),
        io.format("Error al leer A: %s\n",
                  [s(io.error_message(ErrA))], !IO)
    ).

A8 · Resolución de restricciones — El puzzle de las casas

Tres casas con colores, nacionalidades y mascotas distintas. Cada restricción se expresa como un predicado puro. solutions/2 busca todas las asignaciones que satisfacen todas las restricciones simultáneamente — el motor de backtracking de Mercury hace el resto.

puzzle.m
▶ Glot.io
:- module puzzle.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module list, string, solutions.

% El inglés vive en la casa roja.
% El jaguar es la mascota de la familia española.
% Los japoneses viven a la derecha del cuidador de caracoles.
% El cuidador de caracoles vive a la izquierda de la casa azul.

:- type origenes ---> ingles ; japones ; espanol.
:- type colores  ---> rojo ; azul ; verde.
:- type mascotas ---> jaguar ; caracol ; cebra.
:- type casa     ---> casa(origen::origenes, color::colores, mascota::mascotas).

:- pred distinct(casa::in, casa::in) is semidet.
distinct(casa(O1,C1,M1), casa(O2,C2,M2)) :-
    not (O1 = O2 ; C1 = C2 ; M1 = M2).

:- pred fila(list(casa)::out) is nondet.
fila([X, Y, Z]) :-
    casa(X), casa(Y), casa(Z),
    ( X^mascota = caracol <=> Y^origen = japones ),
    ( Y^mascota = caracol <=> Z^origen = japones ),
    ( Z^color   = azul    <=> Y^mascota = caracol ),
    ( Y^color   = azul    <=> X^mascota = caracol ),
    not X^origen = japones,
    not Z^mascota = caracol,
    distinct(X, Y), distinct(Y, Z), distinct(X, Z).

:- pred casa(casa::out) is nondet.
casa(casa(O, C, M)) :-
    origen(O), color(C), mascota(M),
    ( O = ingles  <=> C = rojo   ),
    ( O = espanol <=> M = jaguar ),
    not (O = japones, M = caracol),
    not (M = caracol, C = azul).

:- pred origen(origenes::out) is multi.
origen(ingles). origen(japones). origen(espanol).

:- pred color(colores).
:- mode color(out) is multi.
:- mode color(in)  is det.
color(rojo). color(azul). color(verde).

:- pred mascota(mascotas::out) is multi.
mascota(jaguar). mascota(caracol). mascota(cebra).

main(!IO) :-
    solutions(fila, Soluciones),
    ( if Soluciones = [] then
        io.write_string("Sin solución.\n", !IO)
    else
        list.foldl(
            (pred(L::in, !.IO::di, !:IO::uo) is det :-
                io.print(L, !IO), io.nl(!IO)),
            Soluciones, !IO)
    ).

A9 · Teorema del palomar

El principio del casillero (pigeonhole principle) expresado como un predicado Mercury puro. Si hay más palomas que casilleros, algún casillero alberga más de una. Sin bucles, sin estado mutable — sólo lógica declarativa.

palomar.m
▶ Glot.io
:- module palomar.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module int, string, bool.

:- pred pigeonhole(int::in, int::in, bool::out) is det.
pigeonhole(N, M, Result) :-
    ( if N > M then Result = yes else Result = no ).

:- pred print_result(int::in, int::in, bool::in, io::di, io::uo) is det.
print_result(N, M, Result, !IO) :-
    ( if Result = yes then
        io.format(
            "%d palomas en %d casilleros: al menos uno tendrá más de una.\n",
            [i(N), i(M)], !IO)
    else
        io.format(
            "%d palomas en %d casilleros: ninguno tendrá más de una.\n",
            [i(N), i(M)], !IO)
    ).

main(!IO) :-
    pigeonhole(10, 9, R1), print_result(10, 9, R1, !IO),
    pigeonhole(5,  5, R2), print_result(5,  5, R2, !IO),
    pigeonhole(3,  7, R3), print_result(3,  7, R3, !IO).

07 — Recursos y PDF

Material de apoyo y lectura

El tutorial anterior del curso incluye un PDF con desarrollo profundo de cuatro temas: Modos, Predicados, Determinismo y Tipos. Es un complemento directo a las secciones 02 y 03 de este tutorial.

⬇ Descargar Tutorial PDF

Modos · Predicados · Determinismo · Tipos

Recursos adicionales

🚀

Crash Course avanzado

Curso de Mercury en profundidad: módulos, tipos avanzados, E/S, concurrencia y más. Por Paul Bone.

mercury-in.space/crash.html →
📖

Tutorial de Prolog

Mercury está basado en Prolog. Este tutorial UNAL cubre los fundamentos de programación lógica necesarios como base.

Tutorial Prolog UNAL →
📋

Manual de referencia

Referencia completa del lenguaje: toda la sintaxis, módulos estándar, modos, determinismo y directivas del compilador.

reference_manual.pdf →
🛠️

Guía del usuario

Cómo compilar, depurar, generar documentación y usar el sistema de módulos en proyectos reales.

user_guide.pdf →

08 — Créditos

Autores

Este tutorial integra el trabajo de dos generaciones de estudiantes del curso de Paradigmas de Programación de la Universidad Nacional de Colombia.

✦ Tutorial 2026-1 versión actual

Diseño, contenido, ejemplos e integración del tutorial actualizado.

JM
Juan José Medina
UNAL · 2026-1
CM
Cristian Javier Medina
UNAL · 2026-1
SV
Samuel Josué Vargas
UNAL · 2026-1
DS
Daniel Felipe Soracipa
UNAL · 2026-1

📜 Tutorial anterior base de este trabajo

Los ejemplos, explicaciones y estructura del tutorial anterior fueron la base sobre la que se construyó esta versión actualizada.

👥

Autores principales

Juan Castelblanco · Ivan Cepeda · Camilo Rodriguez · Stevan Valbuena · Samuel Salgado · Diego Bulla

Ejemplos adicionales

Simón Aparicio · Javier Toro · Juliana De Castro · William García