是的,但你必须担心你的谓词失败。如果可以,列表中的其余元素将不会被处理,因为它会产生一个合取而不是一个失败驱动的循环。
我会更热衷于使用maplist/2,因为我认为它比foreach/2 使用更广泛,但我之前也没有见过这个选项。 :)
编辑:让我们讨论一下我所说的失败驱动循环的含义。
Prolog 中有两种原始迭代方法:递归和失败驱动循环。假设我想打印出列表中的每个项目。递归方法将如下所示:
print_all([]).
print_all([X|Rest]) :- write(X), nl, print_all(Rest).
所以给定一个像[1,2,3] 这样的列表,它将像这样扩展:
print_all([1,2,3])
write(1), nl, print_all([2,3])
write(1), nl, write(2), nl, print_all([3])
write(1), nl, write(2), nl, write(3), nl, print_all([])
write(1), nl, write(2), nl, write(3), nl.
member/2 通常是这样实现的:
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
所以你可以看到递归方法非常简单和通用。
另一种简单但有点不受欢迎的方法是模拟使用回溯机制的失败。这称为故障驱动循环,如下所示:
print_all(List) :- member(X, List), write(X), nl, fail.
print_all(_).
当您运行这个版本的print_all/1 时,所发生的事情比简单的扩展要复杂一些。
print_all([1,2,3])
member([1,2,3], 1)
write(1), nl
fail
retry member([1,2,3], 2)
write(2), nl
fail
retry member([1,2,3], 3)
write(3), nl
fail
retry print_all(_)
true
口头上,fail 强制 Prolog 备份到它所做的最后一个选择点并尝试使用下一个解决方案。好吧,write/1 和 nl/0 不会产生选择点,因为它们只有一个解决方案,但 member/2 确实有多个解决方案 - 列表中的每个项目都有一个解决方案。因此,Prolog 将每个项目从列表中取出并打印出来。最后,当member/2 用完解决方案时,Prolog 会备份到上一个选择点,即print_all/1 谓词的第二个主体,始终成功。所以输出看起来是一样的。我认为现在的人们通常不喜欢使用失败驱动的循环,但我对这些论点的理解还不够好,无法有效地模仿它们。
可以帮助您了解正在发生的事情的一件事是使用trace 谓词并逐步扩展两个版本,看看您是否能理解差异。我上面的符号完全弥补了这个答案,可能不太清楚。
回顾一下我最初写的内容和你的实际问题:
-
foreach 将是确定性的
-
member 将始终按顺序迭代,因为列表的定义方式是您必须依次访问每个项目
此外,这些天至少在 S.O.你会得到很多人告诉你使用maplist 和它的同类,所以它可能不仅会起作用,而且也是一个好主意。