Mercury at a Glance — A Language a Day, Advent Calendar 2019 Day 20/24

About this ‘A Language a Day’ Advent Calendar 2019

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:

$ ./helloworld
Hello, World!

Hello, World!

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 implementation.

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 main predicate.

Every program have to have the main predicate, and it should be deterministic, which is marked by is det:

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

Look now at the definition of main.

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.

The symbol :- located between the head and the body can also be called the neck.

Variables

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.

So, !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 IO0, IO1, 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 io module.

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 -> and ; is an equivalent of ifthenelse:

factorial(N) = F :-
    if N < 2
    then F = 1
    else F = N * factorial(N - 1).

In the 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 declaration):

:- 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

This time, factorial is not a function but a regular predicate. It also affects the way it is used in main.

:- 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).

Here, the factorial is defined as a thing with two parameters, one integer for input int::in, and one for output int::out.

The head of the definition rule has two variables: factorial(N, F). Look at how it is used in main: 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 predicate: 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: factorial(5, F5).

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 dog or 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.

Both the dog and cat are the types that have a string argument, which we use to store a pet’s nickname.

In the 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 Animal parameter.

Get more

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:

The source codes with today’s examples are located on GitHub.

Next: Day 21. D

Leave a Reply