Mercury -- A simple Mercury module starter kit!

λ April 23, 2021
Tags: mercury

TL;DR – I just released my first self-built Mercury tool that creates me a new Makefile and a new module stub so I don’t have to keep doing it over and over.

You can find it here: https://github.com/emacstheviking/mcnew

Back in 2019 I started to learn Mercury and after some time away as I continued to learn more Prolog, I reached a point in my personal project from hell that made me realise that actually, having a REPL is a nice way to work, but it isn’t the only way to create code.

The code is here under an MIT license and serves as my first serious attempt at creating some useful utilities as I once blogged that my perfect language would be a cross between Haskell and Prolog and guess what, it exists, it’s called Mercury. Mostly it looks and feels like Prolog (DCG-s are alive), acts like Prolog but here and there it just feels so much nicer, and not so nicer, like making you declare the modes of a predicate, something I am still learning to get used to :)

It might help to have the GitHub page open, but if you can’t (small device!) then here’s the code all squashy washy down:

:- module mcnew.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module list, string.

main(!IO) :-
    io.command_line_arguments(Args, !IO),
    (
        Args = [Cmd, Name]
    ->
        (   if Cmd = "stub" then
                create_stub(Name, !IO)
            else if Cmd = "makefile" then
                create_makefile(Name, !IO)
            else
                io.format("Don't know how to do that.\n",[],!IO)
        )
    ;
        show_usage(!IO)
    ).

Under the Spotlight

Let’s just have a look at the code and see what’s going on. It’s probably the smallest program one could write but it has a lot going on, and because of the explicit and declarative nature of Mercury, you have to tell the compiler various things so it can generate the most efficient code it can.

:-module mcnew.

This says that we are defining a module. By convention the name of the file must match this name, and have the suffix .m.

:- interface.

This indicates that we are now defining the exported interface from this module. Everything in here is what other modules could have access to if this wasn’t a main application. In any bunch of files, the mmc compiler MUST see one and only one file that defines the main entry point. Then it can wire it to the platform specific boot code.

:- import_module io.

This tells the compiler that at this point, we want to be using everything that the io module exports. This is needed because on the next line we define the predicate for the application and that uses some types from that library.

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

This is a bit of mouthful but eaten slowly it doesn’t cause indigestion. The word pred is of course short for predicate, this then indicates that we are defining a predicate called main that has two arguments. The det means ‘deterministic’, that is, the predicate will not fail and will generate the same output for the same input. There’s so much more behind the scenes at this point, I’ve learned about modes, state variables and world-threading, types and something called instantiatedness trees which sound formidable but actually make very good sense. I might write something about those in coming times.

The final bit of note is the actual types for the two parameters, io::di is a type declared in the io module, it is short for ‘destructive input’ and `io::uo’ stands for ‘unique out’. This is Mercury speak for saying that the input will no longer exist on exit, and that the output value is now the new unique value of it. Why so? Haskell has the IO monad, Mercury uses State Variables to record how the ‘world’ has changed. It also uses this to guarantee the execution order of any code that uses IO. More later.

:- implementation.

This now indicates that the interface declaration has been concluded and that we are now entering the actual code that does the stuff. Any modules that were imported are still available at this point so we don’t need to mention them again. However, –nothing– is given for free in Mercury, it MAKES you be explicit, so even using [] for a list isn’t on the menu yet, which brings us to the final line of pre-amble code:

:- import_module list, string.

This is grabbing the exported predicates and functions from the list and string modules respectively. We use the lists for, well, everything, and the string module we want for formatted output to create the stub and the makefile.

More Later - State Variables

Not that long to wait was it?! As I said before, the predicate declaration mentions two variables, but you can see that we only have stated one in the code:

main(!IO) :-

Mercury provides a shorthand way for threading a variable through the code, by placing the exclamation character before the IO variable name (which doesn’t have to be called IO, it’s just a convention) means we don’t have to do this, manually create a new variable each time we use the IO side-effect and cause the world state to change, this is NOT a good way…

main(IOIn, IOOut) :-
    io.format("line 1\n", [], IOIn, IO1),
    io.format("line 2\n", [], IO1, IO2),
    io.format("line 3\n", [], IO2, IOOut).

and here is the better way, which takes care of any code editing.

main(!IO) :-
    io.format("line 1\n", [], !IO),
    io.format("line 2\n", [], !IO),
    io.format("line 3\n", [], !IO).

This is the only way to dictate the order of output or more accurately, the order of processing. I wrote a bit of an SDL FFI wrapper with Mercury once and it needs IO threading too or things don’t draw in the order you expect because the compiler is at liberty to rearrange the predicates how it sees fit because it is a logical paradigm not an imperative one.

The Main() Course

OK, so we briefly mentioned IO threading, the second thing any “C” programmers will spot is that the int argc, char* argv[]) idiom is not there so, given this utility required access to command line arguments, how do we get our hands on them ?

01 main(!IO) :-
02    io.command_line_arguments(Args, !IO),
03    (
04        Args = [Cmd, Name]
05    ->
06        (   if Cmd = "stub" then
07                create_stub(Name, !IO)
08            else if Cmd = "makefile" then
09                create_makefile(Name, !IO)
10            else
11                io.format("Don't know how to do that.\n",[],!IO)
12        )
13    ;
14        show_usage(!IO)
15    ).

The io.command_line_arguments/3 [02] predicate, that’s how. Note that although the arity is 3, it looks like two because of the ! prefix, that expands to !.IO, !:IO in the code, the form !. means the current value of the variable and !: means the next value. Clever isn’t it, and subtle and a cleaner and more obvious way than Haskell IO monads. At least for me, as much as I love Haskell, there is something about the utterly verbose declarativeness of Mercury that I am liking more and more.

Having asked that the command line arguments are made available in the Args variable, we then attempt to unify (thin pattern matching but it’s way smarter than that) it with a list that contains exactly two items. If the user gave more than two or less than two then this unification operation fails and the show_usage(!OP) line [14] executes.

Where’s the if test? It’s on line [04,05,06-12,14] and read as follows… if the unification [04] succeeds, i.e. exactly two arguments (the operation and the module name) are found, then true is returned from the unification operator (the =, not assignment!) and this means that the first branch of the -> operator is executed. In Mercury you can write

    C -> T ; E

which is the same as:

    if C then T else E

and is the preferred way of writing deterministic code. For short code I prefer it but for longer code… well, you should break it down!

Assuming two arguments, line [06-11] are a pretty conventional if-then-else sequence that checks what the command string was (yes, case sensitive because I don’t need anything smarter) and then calls the relevant predicate to do its thing.

Summary

I think writing some tools in mercury to help with on-going mercury development can’t be a bad thing. It’s surprising how much of a mess you can get into with the modes. The mercury compiler has some of the BEST error reporting I’ve ever seen, and the -E option, well, if you can’t figure it out from that then give up.

Good luck in the soup.