Mercury Introduction

by "Blag" - Senior Developer Evangelist

Return to Geeky Thursday

What is Mercury?


Mercury is a logical/functional programming language which combines the clarity and expressiveness of declarative programming with advanced static analysis and error detection features.


In other words...Mercury looks like Prolog, but feels like strict Haskell or pure OCaml.


It's open source.


How to install Mercury?


You can download the source from Mercury


On Linux...


./configure --> make --> make install


On Windows...there's a couple of binaries...

Who uses Mercury?


  • YesLogic -> PrinceXML, convert HTML documents to PDF. Beautiful priting with CSS.
  • Mission Critical IT -> ODASE Platform (Ontology Drive Architecture for Software Engineering).
  • Opturion -> Platform for High-level modelling, hybrid constraint solving.

Starting out...


Mercury doesn't comes with a REPL...


So the code needs to be compiled and then run...


Compile --> mmc --make -E "filename"

Run --> ./"filename"

Basic Structure


:- module "module_name". ==> This is the name of our app


:- interface. ==> Not sure...but it's needed...


:- import_module io. ==> We need to import the IO module for IO operations...


:- pred main(io::di, io::uo) is det. ==> First param is a destructive input, second is a unique output. "det" means that it's deterministic...same output everytime...


:- implementation. ==> Code of our app...not visible to the outside...


main(!IO) :- ==> Main function...something comes in and something comes out...

Variables


Variables must start with a capital letter or _

Everything else must start with a lower case...

Variables are immutable...once assigned...it can't be reassigned...

				
main(!IO) :-

    IVariable = 15,
    
    io.write(IVariable,!IO),
    
    SVariable = "This is Mercury!",
    
    io.write_string("\n",!IO),
    
    io.write(SVariable,!IO).
				

Determinism


Most used options of determinism are det and semidet.


"Det" means that the function must return a value.


"SemiDet" means that the function might or not return a value.


We'll see how this works in the next section...

Lists


As with most Functional languages, lists are very important.

				
:- implementation.

:- import_module list.



main(!IO) :-

	MyList = [1],
	
	Head = det_head(MyList), %det
	
	io.write(Head,!IO), io.write_string("\n",!IO),
	
	(
	
        if MoreHead = head(MyList) %semidet then 
        
            io.write(MoreHead, !IO)
			
        else
		
            io.write("Sorry...nothing left",!IO)
			
	).
				

Arrays

Arrays are supposed to be a work in progress...but I haven't got any problems using them...so far...

				
:- implementation.

:- import_module list, array.



main(!IO) :-
					
	MyArray = array([1,2,3]),

	io.write(MyArray,!IO),

	io.write_string("\n",!IO),

	First = elem(0, MyArray),

	io.write(First,!IO).
				

Pairs

Sometimes we need to group values together...for that, we can use Pairs.

Pairs can only has two values...

				
:- implementation.

:- import_module pair.



main(!IO) :-
			
    MyPair = pair("Blag",1977),
    
    io.write(MyPair,!IO),
    
    io.write_string("\n",!IO),
    
    First:string = fst(MyPair),
    
    Second:int = snd(MyPair),
    
    io.write(First,!IO),
    
    io.write_string("\n",!IO),
    
    io.write(Second,!IO).
				

If


Being a logical programming language, Mercury has a nice particularity...

An If must always have an Else...always...

				
:- implementation.

:- import_module int.



main(!IO) :-
			
    Num = 8,
	
    (
	
        if Num > 10 then
		
            io.write("It's bigger!",!IO)
			
        else
		
            io.write("Nope...smaller...",!IO)
			
    ).
				

Functions


Every function needs an input and an output as well as a determinism...

				
:- implementation.

:- import_module int.



:- pred square(int::in, int::out) is det.


square(A, Result) :-

(

	Result = A * A

).


main(!IO) :-
			
    square(8, Result),
    
    io.write(Result,!IO).
				

Recursion


Recursion is one of the key points in Mercury...you call a function that calls itself multiple times.


As we can't change the value of a variable, we can pass different values as parameters. This way we can update the variable value without changing the variable value...


First Application


In this example we're going to create a Fibonacci sequence generator...


Call your file "fibonacci.m" (all in lowercase).


				
:- module fibonacci.

:- interface.

:- import_module io.



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



:- implementation.

:- import_module string, int.


:- pred fibo(int::in, int::in, 
             
             int::in, string::out) is det.
				
					
fibo(Num, A, B, Fibs) :-
    
    (	
    
        if A = 0 
    
        then fibo(Num-1,A+B,B,Fib), 
        
             Fibs = int_to_string(A) ++ " " ++ 
             
             int_to_string(B) ++ " " ++ 
             
             int_to_string(A+B) ++ " " ++ Fib
    
        else if A > 0, Num > 1
    
        then fibo(Num-1,A+B,A,Fib), 
        
             Fibs = int_to_string(A+B) ++ " " ++ Fib
    
        else Fibs = ""
    
    ).
				
					
main(!IO) :-

    io.write_string("Enter a number: ",!IO),
    
    io.read_line_as_string(Result, !IO),
    
    (	if
    
            Result = ok(String),
            
            string.to_int(string.strip(String), N)
            
        then
        
            fibo(N,0,1,Fibs),
            
            io.write_string(Fibs,!IO)
            
        else
        
            io.write_string("Not a number...",!IO)
            
    ).
				

When we run it we're going to see...


Second Application


In this example we're going to create an LED Numbers app...


Call your file "led_numbers.m" (all in lowercase).


				
:- module led_numbers.


:- interface.

:- import_module io.



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



:- implementation.

:- import_module list, string, int, array, char.


:- pred get_leds(list.list(character)::in, 

                 list.list(character)::in, 

                 int::in, list.list(string)::out ) 
                 
                 is det.
				
				
get_leds(LDIGITS, LDIGITS_AUX, N, RESPONSE) :-

    (
    
        LEDS = array([array([" _  ","| | ","|_| "]),
        
                      array(["  ","| ","| "]),
                      
                      array([" _  "," _| ","|_  "]),
                      
                      array(["_  ","_| ","_| "]),
                      
                      array(["    ","|_| ","  | "]),
                      
                      array([" _  ","|_  "," _| "]),
                      
                      array([" _  ","|_  ","|_| "]),
                      
                      array(["_   "," |  "," |  "]),
                      
                      array([" _  ","|_| ","|_| "]),
                      
                      array([" _  ","|_| "," _| "])]),
				
				
        list.length(Ldigits,Len),
        
        ( if Len > 0 then
        
            Head = det_head(Ldigits),
            
            Tail = det_tail(Ldigits),
            
            char.to_int(Head, Head_I:int),
            
            Head_N:int = Head_I - 48,
            
            Line = elem(Head_N, Leds),
            
            Sub_Line = elem(N, Line),
            
            get_leds(Tail, Ldigits_aux, N, Result),
            
            Response = [Sub_Line] ++ Result
            
          else if N < 2 then
          
            get_leds(Ldigits_aux, Ldigits_aux, 
            
                     N+1, Result),
            
            Response = ["\n"]  ++ Result
				
				
          else if N = 2 then
          
            Response = ["\n"]
            
          else
          
            Response = [] )
            
    ).
				
				
main(!IO) :-

    io.write_string("Enter a number: ",!IO),
    
    io.read_line_as_string(Result, !IO),
    
    (	if
    
            Result = ok(String),
            
            Num = string.strip(String),
            
            to_char_list(Num,Ldigits),
            
            get_leds(Ldigits, Ldigits, 0, Response)
            
        then
        
            io.write_string(string.join_list
            
                           ("", Response), !IO)
            
        else
        
            io.write_string("Not a number...",!IO)
            
    ).
				

When we run it we're going to see...


Third Application


In this example we're going to create a Decimals to Romans application


Call your file "decimal_to_romans.m" (all in lowercase).


				
:- module decimal_to_romans.

:- interface.

:- import_module io.



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



:- implementation.

:- import_module list, string, int, array, char.



:- pred get_romans(int::in, int::in, 

                   list.list(string)::out ) is det.
				
				
get_romans(Num, Ctr, Response

( Roman_Nums = array([array(["1000","M"]), 
    
                        array(["900","CM"]),
    
                        array(["500","D"]), 
                        
                        array(["400","CD"]),
                        
                        array(["100","C"]), 
                        
                        array(["90","XC"]),
                        
                        array(["50","L"]), 
                        
                        array(["40","XL"]),
                        
                        array(["10","X"]), 
                        
                        array(["9","IX"]),
                        
                        array(["5","V"]),
                        
                        array(["4","IV"]),
                        
                        array(["1","I"])]),
				
				
        Line = elem(Ctr, Roman_Nums),
        
        Key = elem(0, Line),
        
        IKey = det_to_int(Key),
        
        (if Num >= IKey then
        
            Value = elem(1, Line),
            
            get_romans(Num - IKey, Ctr, Result),
            
            Response = [Value] ++ Result
            
         else if Num < IKey, Num > 0 then
         
            get_romans(Num, Ctr + 1, Result),
            
            Response = Result
            
         else
         
            Response = [] )
            
).
				
				
main(!IO) :-

    io.write_string("Enter a number: ",!IO),
    
    io.read_line_as_string(Result, !IO),
    
    (	if
    
            Result = ok(String),
            
            SNum = string.strip(String),
            
            Num = det_to_int(SNum),
            
            get_romans(Num, 0, Response)
            
        then
        
            io.write_string(string.join_list
            
                           ("", Response), !IO)
            
        else
        
            io.write_string("Not a number...",!IO)
            
    ).
				

When we run it we're going to see...


Fourth Application


In this example we're going to read a file and count how many time a letter appears...


Call your file "countletters.m" (all in lowercase).


Create a file called "readme.txt" with the following text...


"This is a text file that we're going to read it using Mercury"


				
:- module countletters.

:- interface.

:- import_module io.

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


:- implementation.

:- import_module string, list, array, char, int.

:- pred search_array(string::in, 

                     array(array(string))::in, 
                     
                     int::in, int::out) is det.

:- pred count_letters(list(char)::in, 

                     array(array(string))::in, 
                     
                     array(array(string))::out ) is det.

:- pred print_counter(list(array(string))::in, 

                      io::di, io::uo) is det.
				
				
print_counter(LstResult, !IO) :-


( if Tail = tail(LstResult)
    
        then
        
            Head = det_head(LstResult),
            
            Letter = elem(0, Head),
            
            Counter = elem(1, Head),
            
            io.write(Letter,!IO),
            
            io.write_string("-->",!IO),
            
            io.write(Counter,!IO),
            
            io.write_string("\n",!IO),
            
            print_counter(Tail, !IO)
            
    else
    
        io.write_string("",!IO) ).
				
				
search_array(KeyValue, LstResult, Ctr, Result) :-

(

    size(LstResult, LenResult),
    
    ( if Ctr < LenResult then
        
            Line = elem(Ctr, LstResult),
            
            Key = elem(0, Line),
            
            ( if Key = KeyValue 
                
                then Result = Ctr
                
                else search_array(KeyValue, LstResult, 
                
                                  Ctr + 1, Result) )
            
        else
        
            Result = -1 )
    
).
				
				
count_letters(LstContents, LstResultIn ,LstResult) :-

(

    ( if Tail = tail(LstContents)
        
        then
        
            Head = det_head(LstContents),
            
            char_to_string(Head, Letter),
            
            search_array(Letter, LstResultIn, 0, Result),
            
            ( if Result >= 0 then
                
                    Line = elem(Result, LstResultIn),
                    
                    Value = elem(1, Line),
                    
                      ( if Value \= " " then
                        
                            IValue = det_to_int(Value)+1
				
				      
                        else
                        
                            IValue = 1 ),
                    
                    SValue = int_to_string(IValue),
                    
                    set(1, SValue , Line, UpdatedResult),
                    
                    count_letters(Tail, LstResultIn, 
                    
                                  LstResult)
                    
                else
                
                    UpdatedResult = append(LstResultIn, 
                    
                          array([array([Letter,"1"])])),
                    
                    count_letters(Tail, UpdatedResult, 
                    
                                  LstResult) )
            
         else
          
            LstResult = LstResultIn )
    
).
				
				
main(!IO) :-

    io.open_input("readme.txt", InputRes, !IO),
    
    ( InputRes = ok(Input),
    
        io.read_file_as_string(Input, ReadRes, !IO),
        
        io.close_input(Input, !IO),
        
        ( ( if
        
                ReadRes = ok(Contents),
                
                to_char_list(Contents,LstContents),
                
                LstResultIn = array([array(["",""])]),
                
                count_letters(LstContents, LstResultIn, 
                
                              LstResult)
				
				
              then
              
                to_list(LstResult,LstList),
                
                print_counter(LstList,!IO)
                
			  else
			  
                io.write("Error!", !IO)
                
			) );
			
        InputRes = error(InputError),
        
        io.write(InputError, !IO)
        
    ).
				

When we run it we're going to see...


Fifth Application


This App will generate 100,000 random names using two 16 elements arrays


We will measure the runtime


Name your file "random_names.m"


				
:- module random_names.

:- interface.

:- import_module io.


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


:- implementation.

:- import_module int, random, array, list, string.


:- pred time(int::out, io::di, io::uo) is det.
 

:- pragma foreign_decl("C","#include <time.h>").

:- pragma foreign_proc("C",time(Int::out,_IO0::di,

                       _IO1::uo),[will_not_call_mercury, 
                       
                       promise_pure],

                       "Int = time(NULL);").
				
				
:- pred generate_random_names(array(string)::in, 

                              array(string)::in, 

                              int::in, array(string)::in, 
                              
                              array(string)::out, 
                              
                              io::di, io::uo) is det.
				
				
generate_random_names(Names, Last_Names, Ctr, 

                      Full_NamesIn, Full_Names, !IO) :-

( ( if Ctr =< 100000 then
		
       time(Time, !IO),
			
       random.init(Time, Rand),
			
       random.random(0, 15, N1, Rand, _),
			
       random.random(0, 15, N2, Rand, _),
			
       Name = elem(N1, Names),
			
       LastName = elem(N2, Last_Names),
			
       Full_Name = Name ++ " " ++ LastName,
			
       UpdatedResult = append(Full_NamesIn, array([Full_Name])),
			
       generate_random_names(Names, Last_Names, Ctr + 1, 
       
                             UpdatedResult, Full_Names, 
                             
                             !IO)
				
				
        else
			
            Full_Names = Full_NamesIn ) ).
            
main(!IO) :-

    Names = array(["Anne","Gigi","Blag","Juergen","Marek",
    
                   "Ingo","Lars","Julia", "Danielle",
                   
                   "Rocky","Julien","Uwe","Myles",
                   
                   "Mike","Steven", "Fanny"]),
                   
    Last_Names = array(["Hardy","Read","Tejada",
    
                        "Schmerder","Kowalkiewicz",
    
                        "Sauerzapf","Karg","Satsuta",
                        
                        "Keene","Ongkowidjojo",
                        
                        "Vayssiere","Kylau",
                        
                        "Fenlon","Flynn","Taylor","Tan"]),
				
				
    Full_NamesIn = array([]),
    
    generate_random_names(Names, Last_Names, 0, 
    
                          Full_NamesIn, Full_Names, !IO),
    
    io.write("Done", !IO).
				

When we run it we're going to see...

Let's see how the time was in Julia

What about Python?

That's it for now...


I hope you had some fun...


Mercury's documentation is not the best...


and the only available book is not even finished...


Still...it's good to know some Logical/Functional programming...


Contact Information


Blag --> blag@blagarts.com

@Blag on Twitter

Go back home...