Welcome to Day 20 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Mercury programming language.
Facts about the language
Some facts about Mercury:
- Based on Prolog
- Declarative programming
- Compiled language (unlike Prolog)
- Appeared in 1995
- Website: www.mercurylang.org
Installing and running Mercury
An official installation instruction seems to be difficult, but in reality I could install it with a single command on Mac:
$ brew install mercury
To compile the program, run the following command:
$ mmc helloworld.m
This command assumes you have the source located in helloworld.m. After the compilation, you will have an executable file:
Hello, wordy world! 🙂 Here is the minimal program to print the message using Mercury.
:- module helloworld. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. main(!IO) :- io.write_string("Hello, World!\n", !IO).
The program consists of the
module declaration, its
interface and the
What all those :- and . are about
In the above program, you can see a number of declarations and one clause. Declarations are those that start with
:- and end with
. Clauses are the constructs that have a part before
:-. In our case, this is the
Every program have to have the
main predicate, and it should be deterministic, which is marked by
:- pred main(io::di, io::uo) is det.
Look now at the definition of
main(!IO) :- io.write_string("Hello, World!\n", !IO).
The whole thing is called a rule. Its left part before
:- is the head. Its right part is the body. In the body, we have the goal. If there are more than one goal, they can be listed using comma as a separator.
:- located between the head and the body can also be called the neck.
In a functional language, variables are a different story comparing to procedural languages. Nevertheless, we already have seen a state variable
!IO in the main predicate.
Actually, this was a shortcut to replace the two arguments of the
main predicate, those that were mentioned in the interface part:
:- pred main(io::di, io::uo) is det.
!IO stands for two variables. The name of any variable should start with a capital letter or with an underscore. The full version of the
main predicate looks like this:
main(IOState_in, IOState_out) :- io.write_string("Hello, World!\n", IOState_in, IOState_out).
The highlighted names are variables. Of course, you can make their names shorter (or longer). Notice that these two variables are not the input and output stream. These are the initial and the final states (of the ‘world’). You take this variable; it is then being changed after you print something; then you pass the new changed variable to the next printing method, and so on. The
!IO substitutes the pair of these states and will be replaces with variables
IO2, etc.: you get as many indices as you need to organise your output.
main(IO0, IO) :- io.write_string("Hello, World!\n", IO0, IO1), io.write_string("Line 2\n", IO1, IO2), io.write_string("Line 3\n", IO2, IO).
Functions and predicates
Let us consider two variants of a program that computes factorials. The first implementation will be as much close to traditional programming languages as possible. Then, it will be re-written to be shaped more in the spirit of Prolog.
A Factorial example 1
Here is the first version of the program. It prints the value of 5!.
:- module factorial. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. :- import_module int. :- func factorial(int) = int is det. factorial(N) = F :- N < 2 -> F = 1 ; F = N * factorial(N - 1). main(!IO) :- F5 = factorial(5), io.write_int(F5, !IO), io.write_string("\n", !IO).
Most of the program is similar to the Hello, World! example you’ve seen earlier. Let us discuss what is new here.
To work with integers, you need to import the
int module (how strange that could sound). As we only need numbers in the implementation, the module is imported in the implementation, not in the interface as it is done for the
func factorial(int) = int construct declares a function taking and returning an integer. The
is det appendix says it is deterministic, that is it always returns the same result for the given input.
The next rule is a definition of this function. Notice how you save the result in the
F variable, which works as output from the function.
The three-part construction separated by
; is an equivalent of
factorial(N) = F :- if N < 2 then F = 1 else F = N * factorial(N - 1).
main predicate, there are three sub-goals: compute 5! and save it to
F5, and then print the integer result and the newline character.
Let us play with the code a bit more. It is also possible to define a function in a more traditional way (but you still need a separate
:- func factorial(int) = int is det. factorial(N) = ( N < 2 -> 1 ; N * factorial(N - 1) ).
Now let us look at the alternative variant.
A Factorial example 2
factorial is not a function but a regular predicate. It also affects the way it is used in
:- module factorial. :- interface. :- import_module io. :- pred main(io::di, io::uo) is det. :- implementation. :- import_module int. :- pred factorial(int::in, int::out) is det. factorial(N, F) :- N < 2 -> F = 1 ; factorial(N - 1, G), F = N * G. main(!IO) :- factorial(5, F5), io.write_int(F5, !IO), io.write_string("\n", !IO).
factorial is defined as a thing with two parameters, one integer for input
int::in, and one for output
The head of the definition rule has two variables:
factorial(N, F). Look at how it is used in
factorial(5, F5). The first variable,
N, gets the given value of 5. The second parameter is a variable
F5, whose value is unknown yet. It will get a value after the factorial has been computed.
Similarly, you can see how the intermediate value
G is calculated inside the
factorial(N - 1, G). It is set to (N-1)! here.
What unification is
Let me point out an important thing regarding the constructions like
F = 1. This is called a unificiation. It means that the two parts of the equation are matched at this moment. If their values are both known, it is a match with the Boolean outcome. If there is an undefined variable on either end, the variable will get a value of the other part. This means that both
F = 1 and
1 = F work the same way.
You can save the value of the factorial as a result of a function as
F5 = factorial(5) or mirrored:
factorial(5) = F5. When you are using a predicate (as in the second Factorial example), you can see that the output variable takes a value because it did not have the value yet:
What happens if you pass a defined value to the place where your function or a predicate wants to save the result to?
factorial(5, 120) is equivalent to the conjunction of the two goals:
factorial(5, Tmp), Tmp = 120. Thus, if the result of computing the factorial is 120, the end result is true.
main(!IO) :- if factorial(5, 120) then io.write_string("5! is 120 indeed\n", !IO) else io.write_string("5! is not 120 :-/\n", !IO).
A Polymorphic example
In this series of learning one language a day, it is not possible to describe how Mercury operates with types and classes. Let me just demonstrate how the zoo problem can be solved.
:- module zoo. :- interface. :- import_module io. :- pred main(io, io). :- mode main(di, uo) is det. :- implementation. :- import_module string. :- typeclass animal(T) where [ pred info(T, string), mode info(in, out) is det]. :- type dog ---> dog(string). :- type cat ---> cat(string). :- instance animal(dog) where [ (info(Dog, Message) :- Dog = dog(Name), Message = Name ++ " is a dog.")]. :- instance animal(cat) where [ (info(Cat, Message) :- Cat = cat(Name), Message = Name ++ " is a cat.")]. main(!IO) :- print_animal(cat("Mollie"), !IO), print_animal(dog("Charlie"), !IO). :- pred print_animal(T, io, io) <= animal(T). :- mode print_animal(in, di, uo) is det. print_animal(Animal, !IO) :- info(Animal, Message), io.write_string(Message, !IO), io.nl(!IO).
In this program, a type
animal is defined. This type has the
info function in it. It is a polymorphic function, where
T can be either
cat in our example. You may be familiar with such a definition because it expresses the same idea as generics and templates do in other languages.
cat are the types that have a string argument, which we use to store a pet’s nickname.
main predicate, the same
print_animal function for two different objects is called. The function is using the
info predicate that has a polymorphic behaviour depending on its
It may be a good help to learn Prolog before learning Mercury (because you will not be able to deal with determinism categories). Nevertheless, Mercury is probably one of the languages which you can’t learn in one day.
I found very useful the following resources:
- A Tutorial in Mercury Wiki
- Mercury Samples on GitHub
- Mercury Library Reference Manual
- Documentation resources
The source codes with today’s examples are located on GitHub.
Next: Day 21. D