Mercury is a logic programming language, similar to Prolog, but with static types. It feels like a combination of SML and Prolog at times. It was designed to help with programming large systems - that is large programs, large teams and better reliability, etc. The commercial product Prince XML is written in Mercury.
I've played around with Mercury in the past but haven't done anything substantial with it. Recently I picked it up again. This post is a short introduction to building Mercury, and some example "Hello World" style programs to test the install.
Mercury is written in the Mercury language itself. This means it needs a Mercury compiler to bootstrap from. The way I got a build going from source was to download the source for a release of the day version, build that, then use that build to build the Mercury source from github. The steps are outlined in the README.bootstrap file, but the following commands are the basic steps:
$ wget http://dl.mercurylang.org/rotd/mercury-srcdist-rotd-2019-06-22.tar.gz $ tar xvf mercury-srcdist-rotd-2019-06-22.tar.gz $ cd mercury-srcdist-rotd-2019-06-22 $ ./configure --enable-minimal-install --prefix=/tmp/mercury $ make $ make install $ cd .. $ export PATH=/tmp/mercury/bin:$PATH
With this minimal compiler the main source can be built. Mercury has a number of backends, called 'grades' in the documentation. Each of these grades makes a number of tradeoffs in terms of generated code. They define the platform (C, assembler, Java, etc), whether GC is used, what type of threading model is available (if any), etc. The Adventures in Mercury blog has an article on some of the different grades. Building all of them can take a long time - multiple hours - so it pays to limit it if you don't need some of the backends.
For my purposes I didn't need the CSharp backend, but wanted to
explore the others. I was ok with the time tradeoff of building the
system. To build from the
master branch of the github repository I
did the following steps:
$ git clone https://github.com/Mercury-Language/mercury $ cd mercury $ ./prepare.sh $ ./configure --enable-nogc-grades --disable-csharp-grade \ --prefix=/home/myuser/mercury $ make PARALLEL=-j4 $ make install PARALLEL=-j4 $ export PATH=/home/myuser/mercury/bin:$PATH
prefix to where you want Mercury installed. Add the
relevant directories to the PATH as specified by the end of the build
A basic "Hello World" program in Mercury looks like the following:
:- module hello. :- interface. :- import_module io. :- pred main(io, io). :- mode main(di, uo) is det. :- implementation. main(IO0, IO1) :- io.write_string("Hello World!\n", IO0, IO1).
With this code in a
hello.m file, it can be built and run with:
$ mmc --make hello Making Mercury/int3s/hello.int3 Making Mercury/ints/hello.int Making Mercury/cs/hello.c Making Mercury/os/hello.o Making hello $ ./hello Hello World!
The first line defines the name of the module:
:- module hello.
Following that is the definitions of the public interface of the module:
:- interface. :- import_module io. :- pred main(io, io). :- mode main(di, uo) is det.
We publically import the
io module, as we use
io definitions in
main predicate. This is followed by a declaration of the
main - like C this is the user function called by the
runtime to execute the program. The definition here declares that
main is a predicate, it takes two arguments, of type
io. This is a
special type that represents the "state of the world" and is how I/O
is handled in Mercury. The first argument is the "input world state"
and the second argument is the "output world state". All I/O functions
take these two arguments - the state of the world before the function
and the state of the world after.
mode line declares aspects of a predicate related to the logic
programming side of things. In this case we declare that the two
arguments passed to
main have the "destructive input" mode and the
"unique output" mode respectively. These modes operate similar to how
linear types work in other languages, and the reference manual has a
now the details can be ignored. The
is det portion identifies the
function as being deterministic. It always succeeds, doesn't backtrack
and only has one result.
The remaining code is the implementation. In this case it's just the implementation of the
main(IO0, IO1) :- io.write_string("Hello World!\n", IO0, IO1).
The two arguments to
main, are the
io types representing the before and after representation of the world. We call
write_string to display a string, passing it the input world state,
IO0 and receiving the new world state in
IO1. If we wanted to call an additional output function we'd need to thread these variables, passing the obtained output state as the input to the new function, and receiving a new output state. For example:
main(IO0, IO1) :- io.write_string("Hello World!\n", IO0, IO1), io.write_string("Hello Again!\n", IO1, IO2).
This state threading can be tedious, especially when refactoring - the need to renumber or rename variables is a pain point. Mercury has syntactic sugar for this called state variables, enabling this function to be written like this:
main(!IO) :- io.write_string("Hello World!\n", !IO), io.write_string("Hello Again!\n", !IO).
When the compiler sees
!Variable_name in an argument list it creates two arguments with automatically generated names as needed.
Another syntactic short cut can be done in the
mode lines. They can be combined into one line that looks like:
:- pred main(io::di, io::uo) is det.
Here the modes
uo are appended to the type prefixed with a
::. The resulting program looks like:
- module hello. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. main(!IO) :- io.write_string("Hello World!\n", !IO), io.write_string("Hello Again!\n", !IO).
The following is an implementation of factorial:
:- module fact. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. :- import_module int. :- pred fact(int::in, int::out) is det. fact(N, X) :- ( N = 1 -> X = 1 ; fact(N - 1, X0), X = N * X0 ). main(!IO) :- fact(5, X), io.print("fact(5, ", !IO), io.print(X, !IO), io.print(")\n", !IO).
In the implementation section here we import the int
to access functions across machine integers. The
fact predicate is
declared to take two arguments, both of type
int, the first an input
argument and the second an output argument.
The definition of
fact uses Prolog syntax for an if/then
statement. It states that if
1 then (the
-> token) the
1. Otherwise (the
; token), calculate the
factorial recursively using an intermediate variable
X0 to hold the
There's a few other ways this could be written. Instead of the Prolog style if/then, we can use an if/then syntax that Mercury has:
fact(N, X) :- ( if N = 1 then X = 1 else fact(N - 1, X0), X = N * X0 ).
Instead of using predicates we can declare
fact to be a function. A function has no output variables, instead it returns a result just like functions in standard functional programming languages. The changes for this are to declare it as a function:
:- func fact(int) = int. fact(N) = X :- ( if N = 1 then X = 1 else X = N * fact(N - 1) ). main(!IO) :- io.print("fact(5, ", !IO), io.print(fact(5), !IO), io.print(")\n", !IO)
Notice now that the call to
fact looks like a standard function call and is inlined into the
main. A final syntactic shortening of function implementations enables removing the
X return variable name and returning directly:
fact(N) = (if N = 1 then 1 else N * fact(N - 1)).
Because this implementation uses machine integers it won't work for values that can overflow. Mercury comes with an arbitrary precision integer module, integer, that allows larger factorials. Replacing the use of the
int module with
integer and converting the static integer numbers is all that is needed:
:- module fact. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. :- import_module integer. :- func fact(integer) = integer. fact(N) = (if N = one then one else N * fact(N - one)). main(!IO) :- io.print("fact(1000, ", !IO), io.print(fact(integer(1000)), !IO), io.print(")\n", !IO).
There's a lot more to Mercury. These are just first steps to test the system works. I'll write more about it in later posts. Some further reading: