Next: , Previous: , Up: Top   [Contents]


3 Input and output

Mercury is a purely declarative language. Therefore it cannot use Prolog’s mechanism for doing input and output with side-effects. The mechanism that Mercury uses is the threading of an object that represents the state of the world through the computation. The type of this structure is ‘io.state’. The modes of the two arguments that are added to calls are ‘di’ for “destructive input” and ‘uo’ for “unique output”. The first means that the input variable must be the last reference to the original state of the world, and that the output variable will be the only reference to the state of the world produced by this predicate.

Predicates that do input or output must have these arguments added. For example the Prolog predicate:

write_total(Total) :-
    write('The total is '),
    write(Total),
    write('.'),
    nl.

in Mercury becomes

:- pred write_total(int, io.state, io.state).
:- mode write_total(in, di, uo) is det.

write_total(Total, IO0, IO) :-
    print("The total is ", IO0, IO1),
    print(Total, IO1, IO2),
    print('.', IO2, IO3),
    nl(IO3, IO).

Definite Clause Grammars (DCGs) are convenient syntactic sugar to use in such situations. The above clause can also be written

write_total(Total) -->
    print("The total is "),
    print(Total),
    print('.'),
    nl.

In DCGs, any calls (including unifications) that do not need the extra DCG arguments are escaped in the usual way by surrounding them in curly braces ( { } ).

Note that in Mercury you normally use strings ("...") rather than atoms ('...') for messages like "The total is". (It is possible to use atoms, but you have to declare each such atom before-hand, so it is more convenient to use strings.) However, for strings and characters, ‘write’ prints out the quotes; to avoid this, you need to use ‘print’ instead of ‘write’.

Both ‘write’ and ‘print’ are defined in the ‘io’ module in the Mercury standard library.

One of the important consequences of our model for input and output is that predicates that can fail may not do input or output. This is because the state of the world must be a unique object, and each I/O operation destructively replaces it with a new state. Since each I/O operation destroys the current state object and produces a new one, it is not possible for I/O to be performed in a context that may fail, since when failure occurs the old state of the world will have been destroyed, and since bindings cannot be exported from a failing computation, the new state of the world is not accessible.

In some circumstances, Prolog programs that suffer from this problem can be fixed by moving the I/O out of the failing context. For example

    ...
    ( solve(Goal) ->
        ...
    ;
        ...
    ),
    ...

where ‘solve(Goal)’ does some I/O can be transformed into valid Mercury in at least two ways. The first is to make ‘solve’ deterministic and return a status:

    ...
    solve(Goal, Result, IO6, IO7),
    ( Result = yes ->
        ...
    ;
        ...
    ),
    ...

The other way is to transform ‘solve’ so that all the input and output takes place outside it:

    ...
    io.write_string("calling: ", IO6, IO7),
    solve.write_goal(Goal, IO7, IO8),
    ( solve(Goal) ->
        io.write_string("succeeded\n", IO8, IO9),
        ...
    ;
        IO9 = IO8,
        ...
    ),
    ...

Next: , Previous: , Up: Top   [Contents]