如何在Prolog中返回“重复”?

5

在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.
Print this message again? (yes/no)
yes.

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

程序结束。

2个回答

8

重复

repeat/0简单地定义为

repeat.
repeat :- repeat.

或者,等价地说:
repeat :- true ; repeat.

为了重复执行,您需要通过失败返回到repeat调用,可以通过fail显式或通过另一个失败的谓词(请参见上面链接中的示例)来实现。
...
repeat,
...,
fail.

如果你想退出重复模式,你可以(而且应该)剪切决策树中的 ,这样你就不会有悬空的 repeat 选择点。

如果没有这么做,解释器仍然有可能回溯到 repeat

注意: !/0 的规则可以在这里找到

示例

对于你的示例来说,也就是说(顺便说一句,我使用 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),它可以读取到行尾并返回一个字符串。对于 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 和错误输入的情况:毕竟,当用户说“是”时会发生什么?我们会重复,就像当用户输入意外的内容时一样。唯一不重复的情况是当 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).

“fail” 必须始终出现在 “;” 之前,而 “!” 则必须出现在 “;” 之后吗? - Lord Rixuel
1
我不太理解你的问题。如果你否定你的测试,那么fail!将会交换位置,以及不同的子项也会被交换。理解Prolog评估工作原理可能需要一些时间。尝试使用break命令逐步执行程序。使用不同的例子,不要犹豫去尝试。 - coredump
谢谢,我现在想我知道答案了。 - Lord Rixuel

2

为什么不尝试做以下事情:

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

编辑

如果您无法将谓词分成两部分,另一个解决方案(使用 core dump 的方法)可能是:

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)(参见SWI-Prolog手册中关于控制谓词的部分)。此外,不同的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)的代码。

我不允许在“test”之外再创建新函数。 - Lord Rixuel
@repeat: 看看我是否以更易读的方式进行了更改,如果没有...请随意在我的脚本下方添加编辑字段。 ;) - Ludwig
1
请注意,OP最初描述的行为正是如此:除了“是”之外的所有内容都意味着“否”;-)我只是遵循相同的逻辑。但请查看我的编辑,我还考虑了更明确的检查。 - coredump

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接