如何在 Prolog 中回到 "repeat"?

How to go back to "repeat" in Prolog?

是否可以在不调用谓词且不创建新谓词的情况下返回到 Prolog 中的 repeat

我有以下代码

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

我得到的当前输出是

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

节目结束。

我想要的输出是

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

计划结束。

我想避免的简单输出方式(我不想要这种输出。我不希望它多次显示 Welcome):

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

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

计划结束。

重复

repeat/0simply defined as:

repeat.
repeat :- repeat.

或者,等价地:

repeat :- true ; repeat.

为了重复,您需要通过失败回溯到 repeat 调用,明确地使用 fail 或通过另一个失败谓词(参见上面的示例 link ).

...
repeat,
...,
fail.

一旦您想退出重复模式,您可以(并且应该)切断 ! 决策树,这样您就没有悬空的 repeat 选择点。 如果你不这样做,解释器仍然有可能稍后回溯到 repeat

注意:!/0 的规则可以是 found here.

例子

具体来说,这意味着(顺便说一句,我使用 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
  ).

其他备注

请注意,OP 使用原子,而字符串就足够了。 实际上,原子(单引号)经过哈希处理,更适合用于符号推理,而字符串(双引号)未被保留,更适合显示消息。

本着同样的精神,在阅读时,我宁愿使用 read_string(end_of_line,_,S),它读取到行尾和 returns 一个字符串。使用 read/1,我不得不用 Ctrl+D 关闭输入流,这很烦人。

另外,我们可以完全去掉->

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

删除 -> 可能会引起争议,因为其他人是如何争论要有更多病例的。这是基本原理:由于最初的问题似乎是关于 repeat 的家庭作业,所以关于处理 yesno 和错误输入的部分似乎没有明确说明,坦率地说,并不是真的相关的。我保留了原始语义并合并了 yes 和错误输入的情况:毕竟,当用户说 yes 时会发生什么?我们重复,就像用户键入意外输入时一样。我们不 repeat 的唯一情况是 Ans == no.

现在,如果我们想要更改原始代码的行为以明确检查所有可能的输入类型,请尝试以下操作:

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

为什么你不尝试做:

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

编辑

如果您不能将谓词一分为二,另一种解决方案(使用 coredump 的)可能是:

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

编辑:使用 if-then 和不同布局的替代方法

为了进一步提高可读性,可以使用(->)/2 (if-then-ELSE-FAIL) (c.f. SWI-Prolog manual section on control predicates)。 此外,if-then-else 级联的不同布局也会有所帮助。

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.'),  !
     ).

请注意,使用 if-then-ELSE-FAIL 并不是绝对必要的——可以使用连词。但是,使用它可以在将来轻松添加处理其他情况(maybei_dont_knowi_m_afraidi_gotta_go)的代码。