Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to go back to "repeat" in Prolog?

Tags:

repeat

prolog

Is it possible to go back to a repeat in Prolog without calling the predicate and without making a new predicate?

I have the following code

test :- nl,
write('Welcome.'),nl,
repeat, write('Print this message again? (yes/no)'),nl,
read(Ans),nl,
(
    Ans == yes -> write('You selected yes.'), nl
;
    write('You selected no.')
).

The current output I get is

Welcome.
Print this message again? (yes/no)
yes.
You selected yes.
true.

Program end.

The output I want is

Welcome.
Print this message again? (yes/no)
yes.
You selected yes.
Print this message again? (yes/no)
no.

Program ends.

The easy output way I want to avoid (I don't want this output. I don't want it shows Welcome multiple of times):

Welcome.
Print this message again? (yes/no)
yes.

Welcome.
You selected yes.
Print this message again? (yes/no)
no.

Program ends.

like image 528
Lord Rixuel Avatar asked Apr 24 '15 21:04

Lord Rixuel


2 Answers

Repeat

repeat/0 is simply defined as:

repeat.
repeat :- repeat.

Or, equivalently:

repeat :- true ; repeat.

In order to repeat, you need to backtrack up to the repeat call by failing, either explicitely with fail or through another failing predicate (see example in the above link).

...
repeat,
...,
fail.

Once you want to exit the repeating pattern, you can (and should) cut ! the decision tree so that you don't have a dangling repeat choice point. If you don't, the interpreter will still have the possibility to backtrack to repeat later.

NB: the rules for !/0 can be found here.

Example

For you example specifically, that means (btw, I use writeln):

test :- 
  nl,
  writeln('Welcome.'),
  repeat, 
  writeln('Print this message again? (yes/no)'),
  read(Ans),nl,
  (Ans == yes -> 
    writeln('You selected yes.'), 
    fail % backtrack to repeat
  ; writeln('You selected no.'),
    ! % cut, we won't backtrack to repeat anymore
  ).

Other remarks

Notice that OP used atoms, whereas strings are sufficient. Indeed, atoms (single-quotes) are hashed and prefered for symbolic reasoning, whereas strings (double-quotes) are not interned and are more adequate for displaying messages.

In the same spirit, when reading, I'd rather use read_string(end_of_line,_,S), which reads up-to the end of line and returns a string. With read/1, I had to close the input stream with Ctrl+D, which was annoying.

Also, we can get rid of -> completely:

test :- 
  nl,
  writeln("Welcome."),
  repeat, 
  writeln("Print this message again? (yes/no)"),
  read_string(end_of_line,_,Ans),
  nl,
  write("You selected "),
  write(Ans),
  writeln("."),
  Ans == "no", % Otherwise, repeat
  !.

Removing -> might be controversial seeing how other people argue about having more cases. Here is the rationale: since the original question seems to be an homework about repeat, the parts about handling yes, no and bad inputs explicitely seems to be underspecified, and frankly, not really relevant. I kept the original semantics and merged the yes and bad-input cases: after all, what happens when user says yes? we repeat, exactly as when a user types an unexpected input. The only case where we do not repeat is when Ans == no.

Now, if we want to change the behavior of the original code in order to explicitely check for all possible kind of inputs, here is an attempt:

test :- 
  nl,
  writeln("Welcome."),
  repeat, 
  writeln("Print this message again? (yes/no)"),
  read_string(end_of_line,_,Ans),
  nl,
  (memberchk(Ans,["yes","no"]) ->
    write("You selected "),
    write(Ans),
    writeln("."),
    Ans == "no",
    !
  ; writeln("Bad input" : Ans),
    fail).
like image 60
coredump Avatar answered Sep 23 '22 16:09

coredump


Why you don't try to do:

test :- 
     nl, write('Welcome.'), 
     nl, test_internal.

test_internal :- 
     write('Print this message again? (yes/no)'), nl,
     read(Ans), nl,
     (    Ans == yes 
     ->   write('You selected yes.'), nl, test_internal
     ;    Ans == no, write('You selected no.'), !
     ;    test_internal
     ).

EDIT

If you cannot separate the predicate in two, another solution (use the coredump one's) could be:

test :- 
     nl, write('Welcome.'), 
     repeat, nl,
     write('Print this message again? (yes/no)'), nl,
     read(Ans), nl,
     (    Ans == yes 
     ->   write('You selected yes.'), fail
     ;    Ans == no, write('You selected no.'), !
     ;    fail
     ).

Edit: alternative using if-then and different layout

To further increase readability, (->)/2 (if-then-ELSE-FAIL) can be used (c.f. SWI-Prolog manual section on control predicates). Also, a different layout of the if-then-else cascade can help.

test :- 
     nl, write('Welcome.'), 
     repeat, nl,
     write('Print this message again? (yes/no)'), nl,
     read(Ans), nl,
     (    Ans == yes -> write('You selected yes.'), fail
     ;    Ans == no  -> write('You selected no.'),  !
     ).

Note that the use of if-then-ELSE-FAIL is not strictly necessary---conjunction could be used. Using it, however, makes it easy to add code handling additional cases (maybe, i_dont_know, i_m_afraid, i_gotta_go) in the future.

like image 21
Ludwig Avatar answered Sep 26 '22 16:09

Ludwig