Introducción
Mercury es un lenguaje de programación lógica / funcional que combina la claridad y la expresividad de la programación declarativa con funciones avanzadas de análisis estático y detección de errores. Como lenguaje lógico, está basado en el cálculo de predicados de primer orden y utiliza un sistema de inferencia automático para deducir conclusiones a partir de hechos y reglas declarados.
Dentro de sus características, se destacan:
- Basado en prolog (recomendamos el tutorial disponible aquí)
- Soporta modos
- Tiene un fuerte sistema de determinismo
- Tiene garbage collector
- Es modularizado
- Su compilador facilita el análisis estático y la optimización de código
- Variedad de lenguajes de destino
- Compila a código de máquina
- Contiene un sistema de depuración avanzada
- Permite generar automáticamente documentación a partir de anotaciones dentro del código
Instalación
Unix (recomendado):
- Se requiere instalar GNU C (gcc) y GNU Make
- Descargar la clave GPG del autor del software y autorizar al sistema operativo para descargar cualquier software publicado por el autor.
- Se debe agregar el repositorio de software al sistema operativo.
- Instalar el conjunto de paquetes recomendado.
sudo apt update
sudo apt install build-essential
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
sudo nano /etc/apt/sources.list
# Dentro del archivo, agregar las siguientes líneas
deb http://dl.mercurylang.org/deb/ DISTRO main
deb-src http://dl.mercurylang.org/deb/ DISTRO main
# Donde “DISTRO” es el nombre de versión del sistema operativo (sid, bookworm, bullseye, disco, focal, jammy)
sudo apt install mercury-recommended
Compilación
Inicialmente se crea un archivo hello.m (o cualquier otro nombre) dónde irá el código del programa. Para compilarlo y ejecutarlo en la consola basta con escribir:
mmc hello.m
./hello
mmc hace referencia al compilador de Mercury. Y si queremos evitar la generación de archivos adicionales no necesarios, podemos simplemente escribir:
mmc --make hello.m
./hello
Tutorial
Tutorial básico de programación lógica en Mercury, donde se abordan cuatro temas principales:
- Modos
- Predicados
- Determinismo
- Tipos
Ejemplos
A continuación un conjunto de ejemplos progresivamente más complejos, cada uno con un link al ambiente en linea donde se puede ejecutar el código automáticamente:
- hello.m
- tipos.m
- det_semidet.m
- variables.m
- factorial.m
- suma.m
- trash.m
- addc.m
- puzzle.m
- palomar.m
:- module hello.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
main(!IO) :-
io.write_string("Hello World!", !IO).
Veamos un ejemplo de código en Mercury donde se ilustra la manipulación de tipos y la conversión de valores. Este ejemplo demuestra cómo manejar la conversión de tipos en Mercury y evita errores comunes relacionados con tipos al implementar un sistema básico de impresión de cadenas.
:- 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 puedo asignar otro tipo a una variable creada
% T = "hola",
% T = 2,
ejemplo_predicado(42, Resultado),
io.write_string(Resultado, !IO),
io.nl(!IO).
Este ejemplo en Mercury demuestra el uso de predicados deterministas y semideterministas para realizar operaciones aritméticas y verificar propiedades de los elementos en una lista. El código muestra cómo sumar dos números, verificar si un número es par y comprobar la membresía de un elemento en una lista.
:- module det_semidet.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module int, list.
% Predicado determinista: add
:- pred add(int::in, int::in, int::out) is det.
add(X, Y, Z) :- Z = X + Y.
% Predicado semideterminista: is_even
:- pred is_even(int::in) is semidet.
is_even(X) :- X mod 2 = 0.
% Predicado semideterminista: my_member
:- pred my_member(int::in, list(int)::in) is semidet.
my_member(X, [X | _]).
my_member(X, [_ | Tail]) :- my_member(X, Tail).
main(!IO) :-
% Usando add
add(3, 4, Sum),
io.write_string("La suma es: ", !IO),
io.write_int(Sum, !IO),
io.nl(!IO),
% Usando is_even
( if is_even(Sum) then
io.write_string("La suma es par\n", !IO)
else
io.write_string("La suma es impar\n", !IO)
),
% Usando list.member
List = [1, 2, 3, 4],
( if list.member(3, List) then
io.write_string("3 está en la lista\n", !IO)
else
io.write_string("3 no está en la lista\n", !IO)
).
Este ejemplo demuestra cómo declarar y usar variables en Mercury, un lenguaje de programación lógico y funcional. Se presentan ejemplos de variables enteras, flotantes, cadenas y listas, y se muestran cómo se pueden usar para realizar operaciones básicas de entrada y salida.
:- module variables.
:- interface.
:- import_module io.
% Predicado principal
:- pred main(io::di, io::uo) is det.
% Implementación del módulo
:- implementation.
:- import_module list.
main(!IO) :-
% Declaración de variables
IntVar = 42,
FloatVar = 3.14,
StringVar = "Hola, Mercury",
ListVar = [1, 2, 3, 4, 5],
% Uso de variables y salida
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).
Este ejemplo implementa el algoritmo de factorial dado un input n.
:- module factorial.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module int.
main(!IO) :-
factorial(10, Result),
io.write_string("Factorial de 10 es: ", !IO),
io.write_int(Result, !IO),
io.nl(!IO).
:- pred factorial(int::in, int::out) is det.
factorial(N, Result) :-
( if N = 0 then
Result = 1
else
factorial(N - 1, SubResult),
Result = N * SubResult
).
Este ejemplo implementamos un algoritmo de logica pura. En este caso es un programa simple para sumar dos números enteros introducidos por el usuario.
% Definición del módulo
:- module suma.
% Declaración de la interfaz
:- interface.
% Importar el módulo io en la interfaz
:- import_module io.
% Declaración del predicado suma
:- pred suma(int::in, int::in, int::out) is det.
% Declaración del predicado main
:- pred main(io::di, io::uo) is det.
% Sección de implementación
:- implementation.
% Importar los módulos necesarios
:- import_module int.
:- import_module string.
:- import_module list.
% Implementación del predicado suma/3
suma(X, Y, Resultado) :-
Resultado = X + Y.
% Implementación del predicado main/2
main(!IO) :-
io.read_line_as_string(Result1, !IO),
(
Result1 = ok(Line1),
( string.to_int(string.strip(Line1), X) ->
io.read_line_as_string(Result2, !IO),
(
Result2 = ok(Line2),
( string.to_int(string.strip(Line2), Y) ->
suma(X, Y, Resultado),
io.format("La suma de %d y %d es %d\n", [i(X), i(Y), i(Resultado)], !IO)
;
io.write_string("Error: El segundo valor ingresado no es un número válido.\n", !IO)
)
;
Result2 = eof,
io.write_string("Error: Fin de entrada inesperado al leer el segundo número.\n", !IO)
;
Result2 = error(Error),
io.format("Error al leer el segundo número: %s\n", [s(io.error_message(Error))], !IO)
)
;
io.write_string("Error: El primer valor ingresado no es un número válido.\n", !IO)
)
;
Result1 = eof,
io.write_string("Error: Fin de entrada inesperado al leer el primer número.\n", !IO)
;
Result1 = error(Error),
io.format("Error al leer el primer número: %s\n", [s(io.error_message(Error))], !IO)
).
El siguiente es un ejemplo de como Mercury implementa la recolección de basura mediante la gestión automática de la memoria.
:- module trash.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module list.
:- import_module int. % Importamos el módulo int para operadores aritméticos y relacionales
% Definimos un tipo simple que encapsula un entero
:- type my_data ---> data(int).
% Una función que crea una lista de 'my_data' con n elementos
:- func create_data_list(int) = list(my_data).
create_data_list(N) = (if N =< 0 then [] else [data(N) | create_data_list(N - 1)]).
% Una función que procesa la lista y la convierte en una lista de enteros
:- 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) :-
% Creamos una lista de 10 elementos
DataList = create_data_list(10),
% Procesamos la lista
IntList = process_data_list(DataList),
% Imprimimos la lista procesada
io.write_list(IntList, ", ", io.write_int, !IO),
io.nl(!IO),
% En este punto, DataList y IntList ya no son necesarios y el recolector de basura
% se encargará de liberar la memoria asociada a estas estructuras.
io.write_string("Memory has been managed automatically by the garbage collector.", !IO),
io.nl(!IO).
El siguiente es un ejemplo que demuestra la integración de Mercury con C, Java y C#. En este caso el código usa la función sumar implementada en C y la llama dentro del código de Mercury. Se usa el "pragma" foreign_proc para declarar la función.
:- 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],
"
int add(int a, int b) {
return a + b;
}
Result = add(A,B);
").
main(!IO) :-
io.read_line_as_string(ResultA, !IO),
(
ResultA = ok(LineA),
StrippedA = string.strip(LineA),
( if string.to_int(StrippedA, A) then
io.read_line_as_string(ResultB, !IO),
(
ResultB = ok(LineB),
StrippedB = string.strip(LineB),
( if string.to_int(StrippedB, B) then
add(A, B, Sum),
io.write_string("The sum is: ", !IO),
io.write_int(Sum, !IO),
io.nl(!IO)
else
B = 0,
add(A, B, Sum),
io.write_string("The sum is: ", !IO),
io.write_int(Sum, !IO),
io.nl(!IO)
)
;
ResultB = eof,
io.write_string("Unexpected end of second
nput\n", !IO)
;
ResultB = error(ErrorCode),
io.write_string(io.error_message(
rrorCode) ++ "\n", !IO)
)
else
A = 0,
B = 0,
add(A, B, Sum),
io.write_string("The sum is: ", !IO),
io.write_int(Sum, !IO),
io.nl(!IO)
)
;
ResultA = eof,
io.write_string("Unexpected end of first
input\n", !IO)
;
ResultA = error(ErrorCode),
io.write_string(io.error_message(ErrorCode) ++
\n", !IO)
).
Para demostrar las funcionalidades de Mercury para tareas más complejas. Vamos a tratar de resolver el siguiente problema:
Hay una calle con tres casas vecinas que tienen cada una un color diferente. Son rojos, azules y verdes. En las diferentes casas viven personas de diferentes nacionalidades y todos tienen una mascota diferente. Aquí hay algunos datos más sobre ellos:
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 los caracoles vive a la izquierda de la casa azul.
¿Quién se queda con la cebra?
:- module puzzle.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module list, string, solutions.
% especificacion de los tipos de datos y los valores posibles
:- type origenes ---> ingles; japones; espanol.
:- type colores ---> rojo; azul; verde.
:- type mascotas ---> jaguar; caracol; cebra.
:- type casa
---> casa(
origen :: origenes,
color :: colores,
mascota :: mascotas
).
% funcion para determinar que dos casas no sean iguales
:- 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),
% los japoneses viven a la derecha del cuidador de caracoles
X^mascota = caracol <=> Y^origen = japones,
Y^mascota = caracol <=> Z^origen = japones,
% el cuidador de los caracoles vive a la izquierda de la casa azul
Z^color = azul <=> Y^mascota = caracol,
Y^color = azul <=> X^mascota = caracol,
% los japoneses viven a la derecha del cuidador de caracoles
not X^origen = japones,
% el cuidador de los caracoles vive a la izquierda de la casa azul
not Z^mascota = caracol,
distinct(X, Y), distinct(Y, Z), distinct(X, Z).
:- pred casa(casa::out) is nondet.
casa(casa(O, C, M)) :-
% verificamos que los datos de la casa son validos
origen(O), color(C), mascota(M),
% el ingles vive en la casa roja
O = ingles <=> C = rojo,
% el jaguar es la mascota de la familia española
O = espanol <=> M = jaguar,
% los japoneses viven a la dereca del cuidador de caracoles
not (O = japones, M = caracol),
% el cuidador de caracoles vive a la izquierda de la casa azul
not (M = caracol, C = azul).
main(!IO) :-
solutions(fila, Soluciones),
( if Soluciones = [] then
io.write_string("Sin solucion.\n", !IO)
else
foldl((pred(L::in, !.IO::di, !:IO::uo) is det :-
io.print(L, !IO),
io.nl(!IO)), Soluciones, !IO)
).
:- 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).
Este ejemplo demuestra cómo se puede simular el teorema del palomar (o principio del casillero) en Mercury. Se comprueba si hay más palomas que casilleros y se imprime un mensaje correspondiente.
:- module palomar.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module list, int, string, bool.
% predicado que verifica el teorema del palomar
:- pred pigeonhole(int::in, int::in, bool::out) is det.
pigeonhole(N, M, Result) :-
( if N > M then
Result = yes
else
Result = no
).
% predicado para imprimir el resultado
:- 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.write_string(format("%d palomas en %d casilleros: Al menos un casillero contendrá más de una paloma.\n", [i(N), i(M)]), !IO)
else
io.write_string(format("%d palomas en %d casilleros: Ningún casillero contendrá más de una paloma.\n", [i(N), i(M)]), !IO)
).
main(!IO) :-
% Ejemplos de prueba
Pigeons1 = 10,
Holes1 = 9,
pigeonhole(Pigeons1, Holes1, Result1),
print_result(Pigeons1, Holes1, Result1, !IO),
Pigeons2 = 5,
Holes2 = 5,
pigeonhole(Pigeons2, Holes2, Result2),
print_result(Pigeons2, Holes2, Result2, !IO).
Recursos Extra
Para probar código Mercury online, sugerimos visitar: https://glot.io/new/mercury
Un buen curso con temas más avanzados: https://mercury-in.space/crash.html
Creado por:
Juan Castelblanco
Ivan Cepeda
Camilo Rodriguez
Stevan Valbuena
Samuel Salgado
Diego Bulla
Ejemplos adicionales por:
Simón Aparicio
Javier Toro
Juliana De Castro
William García