【问题标题】:What does the following recursive Prolog call output?以下递归 Prolog 调用输出什么?
【发布时间】:2018-02-22 22:48:20
【问题描述】:

我正在尝试学习序幕,但是我遇到了麻烦。

我在下面有一个示例以及它输出的内容,我显然停留在一些概念上,但不确定是什么。

output([]).
output([c|R]):- output(R), !, nl.
output([X|R]) :- output(R), write(X).

?- output([a,b,c,d,e]).

答案:

ed 
ba 
true.

如果我错了,请纠正我,但这是我目前所理解的......

当我们拨打output([a,b,c,d,e])时。

prologue 使用统一寻找解决方案,

它尝试output([]) 并失败,所以它继续到第二个output([c|R]),然后将列表的尾部递归地传递到output([c|R]),直到它遇到output([]) 的基本情况。

现在我很困惑...然后它会锁定R[]c 的值e?之后的输出如何发生?我真的很困惑。

【问题讨论】:

  • 所以它继续到第二个output([c|R]),然后将列表的尾部递归地传递到output([c|R])...这是不正确的。 output([a,b,c,d,e]) 不与output([c|R]) 统一,因为[a,b,c,d,e](即[a|[b,c,d,e]])不与[c|R] 统一,这是因为原子a 不与c 统一。 output([a,b,c,d,e]) 的第一个匹配项是第三个子句,output([X|R]),它与 X = aR = [b,c,d,e] 统一。
  • ...将R 锁定为[]c,值为e 这在Prolog 中是不可能的。您无法统一两个不同的原子,因此尽管R = []R[] 的统一)将成功,但绑定到[]ceR(变量)无法统一。也就是说,如果您要查询c = e,您将失败。它们不是可以绑定的变量。没有变量且在各方面都相同的两个原子或项的统一将统一。

标签: prolog


【解决方案1】:

我认为您对 Prolog 正在做什么以及统一的含义存在根本性的误解。在 Prolog 中,当您进行诸如 output([a,b,c,d,e]). 之类的查询时,Prolog 将从您断言的事实和谓词的开头开始,并尝试将这个术语(您的查询)与事实或谓词的头部统一 .

统一

我们需要在这里停下来,了解一下统一是什么。在 Prolog 中,运算符=/2unification 运算符,可用于查询 term1 = term2 两个术语的统一。如果termterm2能成功统一,则查询成功。怎样才能成功统一?如果在 term1term2 中存在变量绑定,则可能会发生这种情况,使得术语本质上是相同的(“本质上”是指它们可能仅在句法表示上有所不同,但在 规范形式 - 请参阅下面的详细信息)。

以下是失败的统一尝试示例。您可以在 Prolog 提示符下输入这些内容,它会立即显示失败。

a = e.            % This fails because the atom `a` is different than the atom `e1`
                  %   There are no variables here that can change this fact

foo(X) = bar(Y)
                  % This fails because the functor `foo` is different than
                  % the functor `bar`. There's no way to get these terms to match
                  % regardless of how the variables `X` or `Y` might be instantiated

foo(a, Y) = foo(b, Y)
                  % This fails because no matter how the variable `Y` is instantiated
                  % the 1st argument of `foo` just cannot match. That is, the atom
                  % `a` doesn't match the atom `b`.

foo(a, b, X) = foo(a, b)
                  % This fails because the `foo/3` and `foo/2` have a different
                  % number of arguments. No instantiation of the variable `X` can
                  % change that fact

[1,2] = [1,2,3]   % Fails because a list of 2 elements cannot match a list of 3 elements

[] = [_|_]        % Fails because the empty list cannot match a list of at
                  % least one element.

[a,b,c] = [x|T]   % Fails, regardless of how `T` might be bound, because `[a,b,c]`
                  % is a list whose first element is `a`
                  % and `[x|T]` is a list whose first element is `x`. The
                  % atoms `a` and `x` do not and cannot match.

以下是成功统一的示例。您也可以在 Prolog 提示符下测试这些,您应该会获得成功,或者,如果涉及变量,则至少获得一个解决方案,显示导致其成功的变量绑定:

a = a.           % Trivial case: an atom successfully unifies with itself

X = a.           % Succeeds with `X` bound to `a`

foo(X) = foo(a). % Succeeds with `X` bound to `a`

[a,b,c] = [a|T]  % Succeeds with `T` bound to `[b,c]` because the first element
                 % `a` is the same in both cases.

[1,2,3] = [H|T]  % Succeeds with `H` bound to 1, and `T` bound to `[2,3]`
                 % since `[1,2,3]` is equivalent to `[1|[2,3]]` (they are two
                 % different syntaxes representing the same term)

顺便说一句:Prolog 列表语法

我们正在使用其他语言熟悉的形式编写列表。所以[] 是一个空列表,[1,2,3] 是 3 个元素 1、2 和 3 的列表。您也可以在列表中包含列表,或者在列表中包含任何术语。例如,这是 3 个元素的有效列表:[a, [1,foo(a)], bar(x,Y,[])]。第一个元素是a,第二个是两个元素的列表[1, foo(a)],第三个元素是bar(x,Y,[])。在 Prolog 中,您还可以以描述一个或多个元素中的第一个和尾部的形式编写列表。例如[H|T] 是一个列表,其第一个元素是H,列表的其余部分是T(本身就是一个列表)。至少有两个元素的列表可以写成[H|T],你会知道T 至少有一个元素。或者您可以将其写为 [H1,H2|T] 并明确指出前两个元素并理解 T 将是零个或多个参数的列表。第一个元素是列表的单个元素,尾部是表示列表的 rest 的列表。以下表格均代表列表[a,b,c,d,e]

[a,b,c,d,e]
[a|[b,c,d,e]]
[a,b|[c,d,e]]
[a,b,c|[d,e]]
[a,b,c,d|[e]]
[a,b,c,d,e|[]]

如果您有一个列表L,并希望prolog 确保L 至少有两个参数,您可以将L 与一个包含2 个元素的匿名列表统一起来:L = [_,_|_]。这只有在L 是一个至少包含两个元素的列表时才会成功。

另一边:规范形式

然而,

Prolog 有一个它所谓的规范形式的术语,它是给定术语的基本表示。您可以通过调用write_canonical(Term)查看术语的规范形式:

| ?- write_canonical([a,b,c]).
'.'(a,'.'(b,'.'(c,[])))

yes

这很有趣,那到底是什么?它看起来根本不像一个列表!它实际上是 Prolog 中列表在 Prolog 中的真实样子的规范形式(如果你想这样想的话)。 Prolog 中的基本术语形式是 functor 和零个或多个参数。原子a 是一个可以被视为没有参数的函子a 的术语。术语foo(1,X) 具有函子foo 和参数1 和X。以这种方式编写的列表[a,b,c] 对于程序员来说只是一种方便的语法,使其易于阅读。一个列表实际上是由函子'.' 和两个参数组成的:头部和尾部。所以列表[H|T] 通常是'.'(H,T) 而空列表[] 只是它自己,一个代表空列表的原子。当 Prolog 统一(或尝试统一)两个列表时,它实际上将列表视为'.'(H, T),因此它匹配'.' 函子,然后尝试匹配参数。在多个元素的情况下,这是一个递归匹配,因为T 本身就是一个列表。

Prolog 中的表达式,例如 X + 3,也是规范形式 '+'(X, 3) 的语法便利。

回到我们的故事

正如我们所说,当您查询 output([a,b,c,d,e]). 时,Prolog 会尝试将其与您已经断言的谓词子句或事实的头部统一起来。这是您所断言的:

output([]).
output([c|R]):- output(R), !, nl.
output([X|R]) :- output(R), write(X).

Prolog 从顶部开始尝试这种统一:

output([a,b,c,d,e]) = output([])

这失败了,因为没有变量可以更改术语以使它们匹配。失败是因为列表[a,b,c,d,e] 和空列表[] 无法匹配。

进入下一个子句:

output([a,b,c,d,e]) = output([c|R])

只有在 [a,b,c,d,e] = [c|R] 可以通过 R 的某些绑定成功时才能成功。您可以将其视为[a|[b,c,d,e,]] = [c|R]。显然,要使这种统一成功,每个列表的第一个元素必须匹配。但是ac 不匹配,所以失败了。

到下一个:

output([a,b,c,d,e]) = output([X|R])

Prolog 然后尝试将[a,b,c,d,e][X|R] 统一,或将[a|[b,c,d,e]][X|R]... 统一,因为XR 是变量,它们可以绑定为X = aR = [b,c,d,e]。现在可以执行子句的主体:

output([b,c,d,e]), write(a).

在我们到达write(a) 之前,调用output([b,c,d,e]) 必须首先执行并成功。按照上述相同的逻辑,output/1 谓词的第一个和第二个子句不匹配。但是第三个子句再次与[b,c,d,e] = [X|R] 匹配,导致X = bR = [c,d,e]。现在这个子句的主体再次被执行(你必须记住我们现在处于递归调用的一个深度......上面对output([b,c,d,e])的调用正在等待结果):

output([c,d,e]), write(b).

现在变得更有趣了。 output/1 的第一个子句仍然不匹配,因为 [c,d,e] = [] 失败。但是第二个子句现在匹配,因为[c,d,e] = [c|R] 成功绑定R = [d,e]。所以那个body被执行了:

output([d,e]), !, nl.

现在我们需要追踪对output([d,e]) 的调用(我们现在是递归的另一个层次,记住!)。这个不匹配前两个子句,但匹配第三个子句,由[d,e] = [X|R] 与绑定X = dR = [e]

我可以继续前进,但我已经厌倦了打字,而且我确实有一份真正的工作,而且我的时间不多了。你应该听到这个想法并开始自己处理这个逻辑。向前发展的重要提示是,当您最终在递归调用中到达 output([]) 并且匹配第一个子句时,您将开始“展开”递归调用(如果您这样做,则需要通过hand) 并且在 c 匹配为第一个元素的情况下,将开始执行 write(X) 调用以及第二个子句的 !, nl 部分。

玩得开心……

【讨论】:

  • 这解决了我的很多困难!非常翔实!我肯定在某些概念上遇到了一些麻烦,这有助于澄清很多。很棒的帖子。
  • @cicero866很高兴听到它。如果我的答案中的任何内容仍然模糊不清,请告诉我。
【解决方案2】:

您推理的主要问题是c 不是变量而是原子。它不能与任何其他值统一。

因此,对于您的示例输入,对于前 2 个调用,它将不会执行 output([c|R])(因为 ab 可以与 c 统一),但它会继续执行 output([X|R])。仅对第三次调用,当头部为c 时,调用前一个子句。在此之后,它将为 de 再调用后一个子句 2 次,然后达到基本情况。

从那时起,我们可以很容易地看到输出:如果先写 'e',然后是 'd',然后是一个新行(对于我们匹配 c 的时间),然后是 ba .最后得到true作为输出,表示谓词调用成功。

还请注意,由于剪切,我们只能得到一个输出。如果没有剪切,我们也会得到edcba,因为c 也可以匹配最后一个子句。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-23
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2020-05-28
    • 1970-01-01
    相关资源
    最近更新 更多