Make the predicate reversible

I am new to the prologue; I come from a structured programming background, which will become apparent :)

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

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

the predicate number_chars/2 does not like an unreasonable variable if I do: reverse_num(X,123) (where I expect X be 321 ).

I'm trying too hard to do reverse_num to do something that it shouldn't (should you understand that it only works with the number as the first parameter and the variable as the second)?

Or is there a simple / direct way to handle a variable as the first parameter?

+6
source share
5 answers

Relational Naming

Before moving on to coding, take a step back. After all, the idea in Prolog is to define relationships. Your name reverse_num/2 more likely to offer some action, num_reversed/2 might be a better name.

Define attitude

Your definition is not so bad, let me rewrite it to 1 :

 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 template? All numbers N*10^I have the same result!

Now let me ask you more:

 ?- num_reversed(Num, 321). ERROR: number_chars/2: Arguments are not sufficiently instantiated 

Hmm, what did we expect? In fact, we wanted to print all 123*10^I These are infinitely many solutions. Therefore, over the request, if he answered correctly, it will require infinitely many printing solutions. If we print them directly, it will take all the life of our universe and much more!

It is for this reason that Prolog creates a creation error instead. Thus, Prolog essentially states:

This goal is too general that I can give a good answer. Perhaps there are infinitely many solutions, maybe not. I dont know. But at least I point this out by issuing an error. To remove this error, you need to create some arguments.

So the Prologue response was not so bad! In fact, it is much better to make a pure mistake than, say, unsuccessfully. In general, Prolog errors are often a very useful hint for those semantic problems that you may encounter. See all error classes as.

Coroutining

Like the other suggested answers, coroutining using when/2 can solve this problem. However, the processing itself has many semantic problems. Not without reason systems such as XSB do not offer this because of the many problems associated with verification of jurisdiction. An implementation that would be compatible with it would be unexpectedly ineffective.

But for the sake of this, we could make our definition more universal by requesting it as

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

Now we will return as an answer exactly to the request we requested. It is also known as floundering . Thus, there is a way to represent infinitely solvable solutions in a compact way! However, this comes at a fairly high price: you no longer know if a solution exists or not. Think:

 ?- when(nonvar(Num), num_reversed(Num, -1)). when(nonvar(Num), num_reversed(Num, -1)). 
Others suggested waiting for nonvar(Reversed) , which would be correct if we produced an infinitely many answers, but, as we saw, it takes too much time.

Corotting looked very promising in the early 80s. However, he never fell into the general programming methodology. Most of the time you get to flounder too much, which is just a pain and even more difficult to handle than, say, making mistakes.

However, a more promising descendant of this development is limitations. There mechanisms are much more clearly defined. For practical purposes, programmers will only use existing libraries such as CLPFD, CLPQ, or CHR. Implementing your own library is a completely non-trivial project. In fact, you can even provide an implementation of num_reversed/2 with library(clpfd) , that is, a restriction on the relationship to the integer case.

Mode dependent conditions

Traditionally, many such problems are resolved by explicitly checking for instances. It is a good style to do this exclusively with nonvar/1 and ground/1 as a condition in when/2 - other predicates of a type test are easily deduced for errors, an example of which is a different 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, the code very quickly breaks into floats using base 2 and a little later for base 10. Actually, with the classic base 2 it floats, the relation itself does not make much sense.

Regarding the definition of number_chars/2 , ISO / IEC 13211-1: 1995 has the following template and mode subheading :

8.16.7.2 Pattern and modes

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

The first case is when the first argument is created (thus nonvar ). The second case is when the first argument is not created. In this case, a second argument must be created.

Please note, however, that due to very similar problems, number_chars/2 not a relation. As an example, Chs = ['0','0'], number_chars(0, Chs) succeeds, while number_chars(0, Chs), Chs = ['0','0'] does not work.

Very thin print

1 This rewrite is necessary because in many Prologs reverse/2 completes only if the first argument is known. And in SWI, this rewriting is necessary because of some particular inefficiency.

+4
source

The predicate number_chars/2 has the signature:

 number_chars(?Number, ?CharList) 

But although it is not completely specified by the signature, you must create at least Number or CharList . Where the error comes from.

If you call:

 reverse_num(Num,123) 

You will call number_chars/2 with both uninstalled at this time so that the predicate is wrong.

A not-so-good solution to the problem is to ask if Num or RevNum number/2 s. You can do this by writing two versions. In addition, it will filter other calls, such as 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 use two irregular files (e.g. reverse_num(X,Y). ) Instead of a false error like @false:

 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). 

Cutting ( ! ) Is not a behavioral necessity, but it will slightly increase performance. I am not a fan of this implementation, but Prolog cannot always completely make predicates reversible because (a) reversibility is an unsolvable property because Prolog completes Turing; and (b) one of the characteristics of Prolog is that the atoms of the body are evaluated from left to right. otherwise, some programs will require age. There are logical mechanisms that can do this in random order and, thus, will be successful for this task.

If the predicate/2 variable is commutative, then the solution that can be generalized is as follows:

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

But you cannot just add the last sentence to the theory, because it can loop endlessly.

+3
source

It's nice to see that someone is also worried about defining flexible rules with no restrictions on the set of related arguments.

If you use a Prolog system that supports the accompanying and built-in when/2 predicate (for example, SICStus Prolog, SWI-Prolog, or YAP), try this:

 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). 

which gives:

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

(thanks to those who provided theses of the answers: Prolog: missing function? )

+2
source

This SWISH session shows my efforts to respond.

Then I came back here, where I found that I was configured for @PasabaPorAqui (+1), but I did not understand it.

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

 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 the PasabaPorAqui amendment, ground / 1 it 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 non-brevity / 1 was problematic: a list tied to the opposite is β€œfired” too soon when only the head becomes tied ... too fast!

maplist / 2 really is not needed: manually 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 like the perfect use of rewriting terms ... what do you think of -:- ? Implementation of what we could write bidirectional code like

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

edit SWISH may not be "term_rewrite" friendly ... so this 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). 
+1
source

Having discarded the problem of dropping zeros into leading zeros, it seems that it should not be much more complicated than something like this (it was somewhat complicated with the help of 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 requires at least one of the reverse_number/2 arguments.

0
source

Source: https://habr.com/ru/post/988247/


All Articles