How to check if a variable is instantiated in Mercury

224 Views Asked by At

I am a complete beginner in Mercury language, although I have learned Prolog before. One of the new aspects of Mercury is dererminism. The main function has to be deterministic. In order to make it so, I have to check if a variable is unified/bound to a value, but I cannot find how to do that. Particularly see the code:

main(!IO) :-
mother(X,"john"),
( if bound(X)    <-----this is my failed try; how to check if X is unified?
  then
    io.format("%s\n", [s(X)], !IO)
  else
    io.write_string("Not available\n",!IO)
).

Such main could not fail, i.e. it would (I guess) satisfy the deterministic constraint. So the question is how to check if a variable is bound.

3

There are 3 best solutions below

3
Sebastian On BEST ANSWER

I've translated the family tree from a Prolog example found on this side for comparison. I have specified all facts (persons) and their relationship with each other and a few helper predicates. Note that this typed version did actually find the error that you see in the top answer: female(jane).

The main predicate does not have to be deterministic, it can also be cc_multi, which means Mercury will commit (not try other) choices it has; you can verify this by replacing mother with parent.

You also do not have to check for boundness of your variables, instead you just use any not deterministic term in the if clause, and on succeeding the then part will have guaranteed bound variables, or unbound in the else part.

If you want this example to be more dynamic, you will have to use the lexer or term module to parse input into a person atom.

If you want all solutions you should check the solution module.

%-------------------------------%
% vim: ft=mercury ff=unix ts=4 sw=4 et
%-------------------------------%
% File: relationship.m
%-------------------------------%
% Classical example of family relationship representation,
% based on: https://stackoverflow.com/questions/679728/prolog-family-tree
%-------------------------------%

:- module relationship.

:- interface.

:- import_module io.

%-------------------------------%

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

%-------------------------------%
%-------------------------------%

:- implementation.

:- type person
    --->    john
    ;       bob
    ;       bill
    ;       ron
    ;       jeff
    ;       mary
    ;       sue
    ;       nancy
    ;       jane
    .

:- pred person(person::out) is multi.

person(Person) :- male(Person).
person(Person) :- female(Person).

:- pred male(person).
:- mode male(in) is semidet.
:- mode male(out) is multi.

male(john).
male(bob).
male(bill).
male(ron).
male(jeff).

:- pred female(person).
:- mode female(in) is semidet.
:- mode female(out) is multi.

female(mary).
female(sue).
female(nancy).
female(jane).

:- pred parent(person, person).
:- mode parent(in, in) is semidet.
:- mode parent(in, out) is nondet.
:- mode parent(out, in) is nondet.
:- mode parent(out, out) is multi.

parent(mary, sue).
parent(mary, bill).
parent(sue, nancy).
parent(sue, jeff).
parent(jane, ron).

parent(john, bob).
parent(john, bill).
parent(bob, nancy).
parent(bob, jeff).
parent(bill, ron).

:- pred mother(person, person).
:- mode mother(in, in) is semidet.
:- mode mother(in, out) is nondet.
:- mode mother(out, in) is nondet.
:- mode mother(out, out) is nondet.

mother(Mother, Child) :-
    female(Mother),
    parent(Mother, Child).

:- pred father(person, person).
:- mode father(in, in) is semidet.
:- mode father(in, out) is nondet.
:- mode father(out, in) is nondet.
:- mode father(out, out) is nondet.

father(Father, Child) :-
    male(Father),
    parent(Father, Child).

%-------------------------------%

main(!IO) :-
    Child = john, % try sue or whatever for the first answer
    ( if mother(Mother, Child) then
        io.write(Mother, !IO),
        io.print(" is ", !IO),
        io.write(Child, !IO),
        io.print_line("'s mother", !IO)
    else
        io.write(Child, !IO),
        io.print_line("'s mother is unknown", !IO)
    ).

%-------------------------------%
:- end_module relationship.
%-------------------------------%
0
Paul Bone On

You do not need to check variables for their instantiation state. I have never done it in almost 10 years of using Mercury on a daily basis. For each predicate and predicate's mode, Mercury knows statically the instantiation of each variable at each point in the program. So using your example:

% I'm making some assumptions here.
:- pred mother(string::out, string::in) is det.

main(!IO) :-
    mother(X,"john"),
    io.format("%s\n", [s(X)], !IO).

The declaration for mother says that its first argument is an output argument, this means that after the call to mother its value will be ground. and so it can be printed. If you did use a var predicate (and there is one in the standard library) it would always fail.

To do this Mercury places other requirements on the programmer. For example.

(
    X = a,
    Y = b
;
    X = c
)
io.write(Y, !IO)

The above code is illegal. Because Y is produced in the first case, but not in the second so it's groundness is not well defined. The compiler also knows that the disjunction is a switch (when X is already ground) because only one disjunct can possibly be true. so there it produces only one answer.

Where this static groundness can become tricky is when you have a multi-moded predicate. Mercury may need to re-order your conjunctions to make the program mode correct: eg to put a use of a variable after it's production. Nevertheless the uses and instantiation states of variables will always be statically known.

I understand this is really quite different from Prolog, and may require a fair amount of unlearning.

Hope this helps, all the best.

0
Ben On
main(!IO) :-
  mother(X,"john"),
  ( if bound(X)    <-----this is my failed try; how to check if X is unified?
    then
      io.format("%s\n", [s(X)], !IO)
    else
      io.write_string("Not available\n",!IO)
  ).

This code doesn't make a lot of sense in Mercury. If X is a standard output variable from mother, it will never succeed with X unbound.

If mother is deterministic (det) in this mode, it will always give you a single value for X. In that case there would be no need to check anything; mother(X, "john") gives you John's mother, end of story, so there's no need for the "Not available case".

Since you're trying to write cases to handle both when mother gives you something and when it doesn't, I'm assuming mother is not deterministic. If it's semidet then there are two possibilities; it succeeds with X bound to something, or it fails. It will not succeed with X unbound.

Your code doesn't say anything about how main (which is required to always succeed) can succeed if mother fails. Remember that the comma is logical conjunction (and). Your code says "main succeeds if mother(X, "john") succeeds AND (if .. then ... else ...) succeeds". But if mother fails, then what? The compiler would reject your code for this reason, even if the rest of your code was valid.

But the if ... then ... else ... construct is exactly designed to allow you to check a goal that may succeed or fail, and specify a case for when the goal succeeds and a case for when it fails, such that the whole if/then/else always succeeds. So all you need to do is put mother(X, "john") in the condition of the if/then/else.

In Mercury you don't switch on variables that are either bound or unbound. Instead just check whether the goal that might have bound the variable succeeded or failed.