Prolog:满足目标的变量实例化

Prolog: variable instantiation in satisfying a goal

阅读 Mellish 的 Programming in Prolog (2003) 时出现问题。

假设我们使用列表从一个句子映射到另一个

change(keith, he).
change(very, too).
change(X, X).

alter([], []). 
alter([H|T], [X|Y]) :- change(H, X), alter(T, Y).

有人能解释一下变量是如何实例化的吗?我的书对此没有特别深入。

例如,当使用跟踪时:

Call: (7) alter([keith, is, very, cool], _G2676) ? creep
Call: (8) change(keith, _G2756) ? creep
Exit: (8) change(keith, he) ? creep
Call: (8) alter([is, very, cool], _G2757) ? creep
Call: (9) change(is, _G2759) ? creep
Exit: (9) change(is, is) ? creep
Call: (9) alter([very, cool], _G2760) ? creep
Call: (10) change(very, _G2762) ? creep
Exit: (10) change(very, too) ? creep
Call: (10) alter([cool], _G2763) ? creep
Call: (11) change(cool, _G2765) ? creep
Exit: (11) change(cool, cool) ? creep
Call: (11) alter([], _G2766) ? creep
Exit: (11) alter([], []) ? creep
Exit: (10) alter([cool], [cool]) ? creep
Exit: (9) alter([very, cool], [too, cool]) ? creep
Exit: (8) alter([is, very, cool], [is, too, cool]) ? creep
Exit: (7) alter([keith, is, very, cool], [he, is, too, cool]) ? creep
B = [he, is, too, cool] .

我看到几个内存地址引用被打开,就像在规则中遇到 alter 时一样,并递归配对为:

alter([is, very, cool], ???? tail of Z

新的Y好像取的是Z的尾部的值,但是Z只是内存中一个点的引用,这些变量在返回前是怎么赋值的?同样源于这种混淆的是,我无法阐明 alter predicate 中尾部 X 的头部值如何能够 "build up",就像它所在的那样,从变化中保留新值谓词。

您在阅读此跟踪时带来了很多程序性想法 ("baggage")。让我们尝试用 Prolog 的眼睛来阅读它:

Call: (7) alter([keith, is, very, cool], _G2676) ? creep

Prolog 只是在这里重申您的目标。可以忽略生成变量中的数字_G2676;它不是内存位置,生成这些变量名的方法因实现和平台而异,Prolog 中没有读取任意内存位置的机制,而且这些生成的变量号在几乎所有意义上都无法访问。这只是内部簿记的事情。

您可能发出了这样的查询:

?- alter([keith, is, very, cool], B).

这样想。你在问 Prolog,"prove alter([keith, is, very, cool], B) and tell me what B is." 所以它会尝试去做。

Call: (8) change(keith, _G2756) ? creep

这里发生了什么? Prolog 查看了您的查询,发现您正在使用 alter/2。它将 alter/2head 替换为 alter/2body。快速 Prolog 解剖课:

alter([H|T], [X|Y]) :- change(H, X), alter(T, Y).
-------------------    --------------------------
   "the head"                  "the body" 

所以 Prolog 说,“要证明 alter([keith,is,very,cool], B) 我必须证明 alter([H|T], [X|Y])[keith,is,very,cool] = [H|T]B = [X|Y]。 (Prolog 以合理的方式管理这些变量名称。)所以现在 Prolog 将活动查询替换为 change(H, X), alter(T, Y)。除了 [H|T] = [keith|[is,very,cool]]B = [X|Y]。所以在跟踪中打印的是:

Call: (8) change(keith, _G2756) ? creep

现在 Prolog 必须查看其 change/2 的第一个子句,并找到 change(keith, he)。所以 Prolog 说,"Aha! X = he" 然后打印:

Exit: (8) change(keith, he) ? creep

Prolog 现在说,“好的,我只需要通过查看 alter/2 主体中的下一步来完成我在一秒钟前开始的查询,即:

Call: (8) alter([is, very, cool], _G2757) ? creep

好吧,就像以前一样,Prolog 现在要用 alter/2 的主体替换 alter/2 查询并尝试实现它,这意味着它现在必须证明:

Call: (9) change(is, _G2759) ? creep

这个稍微有点意思,因为change/2的第一个子句不匹配。 Prolog 无法将 keithis 统一,因此它失败并转到第二个子句。它无法将 isvery 统一起来,因此它转到第三个子句并将 is 与自身统一起来:

Exit: (9) change(is, is) ? creep

重复此过程,处理 very/too 和 cool/cool,然后 Prolog 用完列表成分,您输入 alter/2:

的基本情况
Call: (11) alter([], _G2766) ? creep
Exit: (11) alter([], []) ? creep

如果你注意的话,你会注意到带有匿名变量的查询行以 Call: 开头,表示 Prolog 正在更改问题,当 Prolog 回答问题时,该行以 Exit:。一行可以从其他一些东西开始,告诉您有关 Prolog 执行模型的其他信息:Retry:Fail:。但是这里没有这些,因为您的查询实际上在第一次尝试时就起作用了。

从这里开始,您将得到 Exit,因为一切都已成功统一并且 Prolog 已基本完成。但这是 "building up" 发生的地方:

Exit: (11) alter([], []) ? creep
Exit: (10) alter([cool], [cool]) ? creep
Exit: (9) alter([very, cool], [too, cool]) ? creep
Exit: (8) alter([is, very, cool], [is, too, cool]) ? creep
Exit: (7) alter([keith, is, very, cool], [he, is, too, cool]) ? creep

这里发生的是 alter([], []) 被证明,所以它 returns 到外部调用(注意那里的数字;他们告诉你关于调用堆栈的信息),这意味着 Prolog 现在已证明 alter([cool], [cool]) 为真,这意味着它已证明 alter([very, cool], [too, cool]) 为真,依此类推。这只是尾递归按照人们预期的方式工作。最后它输出您的查询成功:

B = [he, is, too, cool] .

因此,正如您所说,这里确实没有打开内存地址。

It seems the new Y would take the value of the tail of Z, but since Z is just a reference to a spot on the memory, how are these variables assigned values before returning?

在 "normal" 编程语言中,您使用一些值调用一个函数,然后您将返回一个值作为 return 值。 Prolog 不是一种普通的编程语言;您不是在定义功能,而是在定义关系。关系比函数受到的限制更少:有时您的参数是输入,有时它们是输出。 Prolog 中的变量不是 "references to spots in memory." 它们只是数据的名称;它们可以是 groundfree 取决于它们是否已被弄清楚。

Prolog 中计算的每一步,Prolog 都在搜索自由变量的绑定。这称为 unification 并且是 Prolog 中的基本执行模型。当您发出像 change(keith, X) 这样的查询时,您是在要求 Prolog 证明 change(keith, X) 为真,并为您提供使其为真的值 X。 Prolog 将查看它并返回 true, X = he。但你也可以问它 change(X, he),Prolog 会查看它并说,true, X = keith。这是使它成为一种关系而不是一种功能的部分原因。你也可以说 change(X, Y) 并且它会返回 true, X = keith & Y = he; X = very & Y = too; X = Y 并且结果的多样性是你知道你正在处理关系而不是函数的另一种方式。

因此在 Prolog 中,变量不是 "spot in memory",它是名称和值之间的绑定,Prolog 能够在评估的任何步骤建立该绑定,而不仅仅是在途中在计算中或计算期间,但也在计算成功的途中!

我敢打赌,如果您回顾一下您的书,您会发现书中有很多关于此的细节。它是目前最好的 Prolog 书籍之一,而且它肯定详细介绍了这些东西。但我希望这有帮助!