7

I'm new to prolog; I'm coming from a structured programming background, as will become obvious :)

I am building up a prolog query that involves reversing a number; eg. reverse_num(123,X) results in X = 321. I came up with the following definition, but it only works when I provide a number as the first parameter.

reverse_num(Num, Revnum) :-
  number_chars(Num, Atoms),
  reverse(Revatoms, Atoms),
  number_chars(Reversed, Revatoms),
  Reversed = Revnum.

the number_chars/2 predicate doesn't like an unsubstantiated variable if I do: reverse_num(X,123) (where I'm expecting X to be 321).

Am I trying too hard to make reverse_num do something it shouldn't (should it be understood to work only with a number as the first parameter and variable as the second)?

Or is there an easy / straight-forward way to handle a variable as the first parameter?

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33
Jeff Schaller
  • 2,352
  • 5
  • 23
  • 38
  • Wow, I knew I was new to Prolog, but the answers are taking me some time to digest. Thanks to all who replied so quickly! It seems I ran into a known limitation of number_chars, so the proximate answer seems to be to band-aid that limitation by checking for grounding or instantiation of one of the parameters. – Jeff Schaller Jun 02 '15 at 12:50

5 Answers5

5

Relational naming

Before jumping into coding, let's take a step back. After all, the idea in Prolog is to define relations. Your name reverse_num/2 rather suggests some actions, num_reversed/2 might be a better name.

Determine the relation

Your definition is not that bad, let me rewrite it to1:

num_reversed(Num, Reversed) :-
   number_chars(Num, Chars),
   reverse(Chars, Revchars),
   number_chars(Reversed, Revchars).

?- num_reversed(123,X).
   X = 321.
?- num_reversed(1230,X).
   X = 321.
?- num_reversed(12300,X).
   X = 321.

Do you see the pattern? All numbers N*10^I have the same result!

Now, let's ask some more:

?- num_reversed(Num, 321).
   error(instantiation_error,number_chars/2).

Hm, what did we expect? Actually, we wanted all 123*10^I to be printed. That's infinitely many solutions. So above query, if correctly answered, would require infinitely many solutions to be printed. If we print them directly, that will take all our universe's lifetime, and more!

It is for this reason, that Prolog produces an instantiation error instead. By this, Prolog essentially states:

This goal is too general that I can make a good answer. Maybe there are infinitely many solutions, maybe not. I know not. But at least I indicate this by issuing an error. To remove this error you need to instantiate the arguments a bit more.

So the answer Prolog produced was not that bad at all! In fact, it is much better to produce a clean error than to, say, fail incorrectly. In general, Prolog's errors are often a very useful hint to what semantic problems you might have. See all error classes how.

Coroutining

As have other answers suggested, coroutining, using when/2 might solve this problem. However, coroutining itself has many semantic problems. Not without reason, systems like XSB do not offer it, due to the many problems related to subsumption checking. An implementation that would be compatible to it would be unexpectedly inefficient.

But for the sake of the point, we could make our definition more versatile by querying it like

 ?- when(nonvar(Num), num_reversed(Num, Reversed)).
    when(nonvar(Num), num_reversed(Num, Reversed)).

Now we get back as an answer exactly the query we entered. This is also known as floundering. So there is a way to represent infinitely may solutions in a compact manner! However, this comes at a rather high price: You no longer know whether a solution exists or not. Think of:

?- when(nonvar(Num), num_reversed(Num, -1)).
   when(nonvar(Num), num_reversed(Num, -1)).

Others have suggested to wait also for nonvar(Reversed) which would only be correct if we would produce infinitely many answers - but, as we have seen - this just takes too much time.

Coroutining looked as a very promising road at the beginning of the 1980s. However, it has never really caught on as a general programming methodology. Most of the time you get much too much floundering which is just a pain and even more difficult to handle than, say instantiation errors.

However, a more promising offspring of this development are constraints. There, the mechanisms are much cleaner defined. For practical purposes, programmers will only use existing libraries, like CLPFD, CLPQ, or CHR. Implementing your own library is an extremely non-trivial project in its own right. In fact it might even be possible to provide an implementation of num_reversed/2 using library(clpfd) that is, restricting the relation to the integer case.

Mode dependent conditionals

Traditionally, many such problems are solved by testing for instantiations explicitly. It is good style to perform this exclusively with nonvar/1 and ground/1 like the condition in when/2- other type test predicates lead easily to errors as exemplified by another answer.

num_reversed(Num, Reversed) :-
   (  nonvar(Num)
   -> original_num_reversed(Num, Reversed)
   ;  original_num_reversed(Reversed, Base),
      (  Base =:= 0
      -> Num is 0
      ;  length(_, I),
         Num is Base*10^I
      )
   ).

Above code breaks very soon for floats using base 2 and somewhat later for base 10. In fact, with classical base 2 floats, the relation itself does not make much sense.

As for the definition of number_chars/2, ISO/IEC 13211-1:1995 has the following template and mode subclause:

8.16.7.2 Template and modes

number_chars(+number, ?character_list)
number_chars(-number, +character_list)

The first case is when the first argument is instantiated (thus nonvar). The second case, when the first argument is not instantiated. In that case, the second argument has to be instantiated.

Note, however, that due to very similar problems, number_chars/2 is not a relation. As example, Chs = ['0','0'], number_chars(0, Chs) succeeds, whereas number_chars(0, Chs), Chs = ['0','0'] fails.

Very fine print

1 This rewrite is necessary, because in many Prologs reverse/2 only terminates if the first argument is known. And in SWI this rewrite is necessary due to some idiosyncratic inefficiencies.

false
  • 10,264
  • 13
  • 101
  • 209
  • Interesting, now I will try a CLP(FD) implementation. Some hint ? – CapelliC May 31 '15 at 21:44
  • @CapelliC: So far, I only thought that `num_reversed(X,Y) :- X #> 0, Y #>= 0, Y #=< X*9, ...`. – false May 31 '15 at 21:47
  • ... or maybe it should rather be `Y #< X*10`. – false May 31 '15 at 21:52
  • props for pointing out the leading/trailing zero issue; it wasn't specified, but the intention was to view the inputs and outputs as numbers, so reverse(1230) could be 321. Also appreciate the ISO reference, as that makes it clear (as does the [swi-prolog](http://www.swi-prolog.org/pldoc/man?predicate=number_chars/2) man page, if I had read it more closely, that one of the parameters has to be instantiated. – Jeff Schaller Jun 02 '15 at 12:56
  • @JeffSchaller: please note that SWI7 is neither conforming to ISO, nor are many of the statements in the manual accurate. The footnote in your reference goes at length into spaces but omits that numbers do have a lot of alternate representations even without taking spaces into account. Think of floats! `0.0000`, exponents. The number `-0`, alternate bases like `0x10` and more ... – false Jun 02 '15 at 13:18
  • I get: Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.19) ?- reverse(X,[3,4]). X = [4, 3] ; false. So it terminates, but is non-deterministic. –  Oct 17 '18 at 21:42
  • @j4nbur53: Exactly this is one of SWI's idiosyncratic inefficiencies – false Oct 18 '18 at 09:17
3

The number_chars/2 predicate has the signature:

number_chars(?Number, ?CharList) 

But although not fully specified by the signature, at least Number or CharList have to be instantiated. That's where the error occurs from.

If you call:

reverse_num(Num,123)

You will call number_chars/2 with both uninstatiated at that time so the predicate will error.

A not very nice solution to the problem is to ask whether Num or RevNum are number/2s. You can do this by writing two versions. It will furthermore filter other calls like reverse_num(f(a),b), etc.:

reverse_num(Num,Revnum) :-
    \+ number(Num),
    \+ number(Revnum),
    throw(error(instantiation_error, _)).

reverse_num(Num, Revnum) :-
    ground(Num),
    number(Num),
    !,
    number_chars(Num, Atoms),
    reverse(Revatoms, Atoms),
    number_chars(Revnum, Revatoms).

reverse_num(Num, Revnum) :-
    ground(Revnum),
    number(Revnum),
    reverse_num(Revnum,Num).

Or you can in case you use two nongrounds (e.g. reverse_num(X,Y).) an instantiation error instead of false as @false says:

reverse_num(Num,Revnum) :-
    \+ number(Num),
    \+ number(Revnum),
    !,
    throw(error(instantiation_error, _)).

reverse_num(Num, Revnum) :-
    number(Num),
    !,
    number_chars(Num, Atoms),
    reverse(Revatoms, Atoms),
    number_chars(Revnum, Revatoms).

reverse_num(Num, Revnum) :-
    reverse_num(Revnum,Num).

The cut (!) is not behaviorally necessary, but will increase performance a bit. I'm not really a fan of this implementation, but Prolog cannot always fully make predicates reversible since (a) reversibility is an undecidable property because Prolog is Turing complete; and (b) one of the characteristics of Prolog is that the body atoms are evaluated left-to-right. otherwise it will take ages to evaluate some programs. There are logic engines that can do this in an arbitrary order and thus will succeed for this task.

If the predicate/2 is commutative, a solution that can be generalized is the following pattern:

predicate(X,Y) :-
    predicate1(X,A),
    predicate2(A,B),
    % ...
    predicaten(C,Y).
predicate(X,Y) :-
    predicate(Y,X).

But you cannot simply add the last clause to the theory, because it can loop infinitely.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Fails incorrectly for `reverse_num(N,R)`. Rule-of-thumb: Always test for `nonvar(Num)` or `ground(Num)`. Never combine testing for a type with testing for instantiations. Unfortunately, Prolog's `number(Num)` does not produce an instantiation_error, when `Num` is a variable. This is one of the bad features Prolog inherited from DEC10. See [this question](http://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog). – false May 31 '15 at 12:26
  • @false: `number/1` on swipl fails with `number(X)` but no error... And it fails for `reverse_num(N,R)` but the question asks to make the predicate reversible. So there is no reason to start enumerating all numbers in case both are variables... – Willem Van Onsem May 31 '15 at 14:00
  • "So there is no reason to start enumerating all numbers in case both are variables" noone says that all numbers have to be enumerated. But silent failure means: There is no solution, and that is incorrect. – false May 31 '15 at 14:10
  • "reversible" means that you get the *same* answers back. You do not get the same answers back: `reverse_num(100,1)` succeeds, but `reverse_num(N,1), N=100` fails. – false May 31 '15 at 14:16
  • Happy the exclamation marks are gone (hopefully one day these are removed from the keyboard)... That is the result of the fact that reverse on the arabic numbering system is not commutative. – Willem Van Onsem May 31 '15 at 14:18
  • `reverse_num(a,b)` now gives an `instantiation_error`? – false May 31 '15 at 14:19
  • @false: well `reverse_num` hints it's a *number*. *a* is not a number. You can indeed split it into type-errors, But this makes the answer way too complicated for what it is worth. – Willem Van Onsem May 31 '15 at 14:22
2

Nice to see someone is also worried about define flexible rules with no restrictions in the set of bound arguments.

If using a Prolog system that supports coroutining and the when/2 built-in predicate (e.g. SICStus Prolog, SWI-Prolog, or YAP), try as:

reverse_num(Num, Reversed) :-
  when( ( ground(Num); ground(Atoms) ), number_chars(Num, Atoms) ),
  when( ( ground(Reversed); ground(Revatoms) ), number_chars(Reversed, Revatoms) ),
  reverse(Atoms , Revatoms).

that gives:

?- reverse_num( 123, X ).
X = 321.

?- reverse_num( X, 123 ).
X = 321 .

( thanks to persons who provided theses answers: Prolog: missing feature? )

Community
  • 1
  • 1
pasaba por aqui
  • 3,446
  • 16
  • 40
  • Note then `when/2` is not (an official or de facto) standard predicate. I suggest you add sone information on which systems this solution can be used as the question is generic and not Prolog dialect specific. – Paulo Moura May 31 '15 at 11:28
  • Tested in SWI-prolog, I've no information about remainder ones. Feel free of edit answer an improve it. – pasaba por aqui May 31 '15 at 11:30
  • 1
    If "when" is not standard, it should be. – pasaba por aqui May 31 '15 at 11:31
  • Unfortunately, nothing about coroutining, attributed variables, or constraints (to name just a few features), despite their importance, is standard. – Paulo Moura May 31 '15 at 11:35
  • Another example of the necessity of abandon ISO standard and jump to some more flexible ones. At least, RFC's. – pasaba por aqui May 31 '15 at 11:55
  • 2
    Features like `when/2` come with a rat tail of underspecified properties that you just not happen to realise. – false May 31 '15 at 12:19
  • 1
    Your definition produces a syntax error for `reverse_num(N, R)`. Intended? – false May 31 '15 at 12:21
  • 1
    It is the usual difference between this error "number_chars( A, [] )." and this one "number_chars( A, [_] ).". Initially, I though the case of two unbound as off-topic for this thread of message, but, if interesting, Improvements are welcome. – pasaba por aqui May 31 '15 at 14:52
  • see my post, just binding **all** join term, could be a solution ? – CapelliC May 31 '15 at 20:06
1

This SWISH session shows my effort to answer.

Then I've come back here, where I found I was on @PasabaPorAqui' mood (+1), but I didn't get it right.

But, such an interesting topic: notice how regular is the join pattern.

reverse_num(X, Y) :-
    when((nonvar(Xs);nonvar(Ys)), reverse(Xs, Ys)),
    when((nonvar(X) ;nonvar(Xs)), atomic_chars(X, Xs)),
    when((nonvar(Y) ;nonvar(Ys)), atomic_chars(Y, Ys)).

So, we can generalize in a simple way (after accounting for PasabaPorAqui correction, ground/1 it's the key):

% generalized... thanks Pasaba Por Aqui
:- meta_predicate when_2(0).
when_2(P) :-
    strip_module(P,_,Q),
    Q =.. [_,A0,A1],
    when((ground(A0);ground(A1)), P).

reverse_num(X, Y) :-
    maplist(when_2, [reverse(Xs, Ys), atomic_chars(X, Xs), atomic_chars(Y, Ys)]).

I think I understand why nonvar/1 was problematic: the list bound for reverse get 'fired' too early, when just the head get bound... too fast !

maplist/2 is not really necessary: by hand we can write

reverse_num(X, Y) :-
    when_2(reverse(Xs, Ys)),
    when_2(atomic_chars(X, Xs)),
    when_2(atomic_chars(Y, Ys)).

this seems an ideal application of term rewriting... what do you think about -:- ? Implementing that we could write bidirectional code like

reverse_num(X, Y) -:-
    reverse(Xs, Ys),
    atomic_chars(X, Xs),
    atomic_chars(Y, Ys).

edit SWISH maybe is not 'term_rewrite' friendly... so here is a lower level approach:

:- op(900, xfy, ++).
A ++ B ++ C :- when_2(A), B ++ C.
A ++ B :- when_2(A), when_2(B).

reverse_num(X, Y) :- 
    reverse(Xs, Ys) ++ atomic_chars(X, Xs) ++ atomic_chars(Y, Ys).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • Any reason why you use the SWI specific `atomic_chars/2` in place of `number_chars/2`? – false May 31 '15 at 20:07
  • @false: I've inadvertedly used an homonimy. You see in my SWISH I named a local predicate (for debug purpose) like the library one... sorry – CapelliC May 31 '15 at 20:09
  • 1
    Waiting for `ground(X)` and `ground(Y)` is overkill, it means that many useless goals are left floundering. It is, however a good idea for `ground(Xs)` and `ground(Ys)`. – false May 31 '15 at 21:12
0

Setting aside the problem of trailing zeroes turning into leading zeroes, it doesn't seem like it should be much more complicated than something like this (made somewhat more complicated by dealing with negative numbers):

reverse_number(X,Y) :- number(X) , ! , rev(X,Y) .
reverse_number(X,Y) :- number(Y) , ! , rev(Y,X) .

rev(N,R) :-
  N < 0 ,
  ! ,
  A is abs(N) ,
  rev(A,T) ,
  R is - T
  .
rev(N,R) :-
  number_chars(N,Ns) ,
  reverse(Ns,Rs) ,
  number_chars(R,Rs)
  .

Note that this does require at least one of the arguments to reverse_number/2 to be instantiated.

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • For the goal `reverse_number(X,Y)` this produces silent failure which is incorrect. Example `X = 1, reverse_number(X,Y)` succeeds, but `reverse_number(X,Y), X = 1` fails. – false Jun 01 '15 at 21:29
  • @false, I don't understand the failing sequence. Is the point that prolog tries to instantiate all of the "reverse_number(X,Y)" before assigning X=1? – Jeff Schaller Jun 02 '15 at 13:00
  • @Nicholas-Carey - thanks for pointing out the case of negative numbers. I hadn't specified it, but the situation I am dealing with is in the non-negative number range. – Jeff Schaller Jun 02 '15 at 13:01
  • 1
    @JeffSchaller: For a reversible predicate's `Goal` it should always hold: Either `Vars = ..., Goal` and `Goal, Vars = ...` both succeed or both fail ; or there are errors or loops. But if one fails and the other succeeds, the implementation is faulty. This is trivial to fix for the case given: remove `number(Y)` – false Jun 02 '15 at 13:13