5

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.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
Lord Rixuel
  • 1,173
  • 6
  • 24
  • 43

2 Answers2

8

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).
coredump
  • 37,664
  • 5
  • 43
  • 77
  • Does the "fail" has to be before the ";" and the "!" after the ";" all the time? – Lord Rixuel Apr 24 '15 at 21:18
  • 1
    I don't really understand your question. If you negate your test, then `fail` and `!` will be swapped, along with the different subterms. It might take a while to absorb how Prolog evaluation works. Try to `break` your program and execute it step-by-step. Use different examples, don't hesitate to experiment. – coredump Apr 24 '15 at 21:23
  • Thank you, I think I know my answer now. – Lord Rixuel Apr 24 '15 at 21:28
2

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.

Ludwig
  • 101
  • 1
  • 3
  • I'm not allow to make another new function beside "test". – Lord Rixuel Apr 24 '15 at 21:19
  • @repeat: See if I change it in a more readable way, if not... feel free to add an edit field below my script. ;) – Ludwig Apr 25 '15 at 11:35
  • 1
    @repeat Note that the behavior originally described by OP was exactly that: everything that is not `yes` means `no` ;-) I just followed the same logic. But see my edits where I also consider more explicit checks. – coredump Apr 25 '15 at 21:05