成员

转载自:https://mp.weixin.qq.com/s/CssLyqHaXRdUFW1UUiMwNw

2  成员

现在该来看一下我们用于处理表的递归Prolog程序的第一个示例。我们想知道的最基本的事情之一就是某物是否是表的元素。因此,让我们写一个程序,当输入任意对象X和表L作为输入时,它告诉我们X是否属于L。执行此操作的程序通常称为成员,这是Prolog程序的最简单示例利用表的递归结构。这里是:

 

member(X, [X|T]).

member(X, [H|T]):- member(X,T).

 

这就是全部:一个事实(即member(X, [X|T]))和一个规则(即member(X, [H|T]):-member(X, T))。但是请注意,该规则是递归的(毕竟,函子成员出现在规则的头部和规则体中),这就是为什么需要这么短的程序的原因。让我们仔细看看。 

我们将从声明式阅读程序开始。并以此方式阅读,这显然是明智的。第一个子句(事实)简单地说:如果对象X是该表的头部,则它是该表的成员。请注意,我们使用了内置|操作符陈述关于表的这一(简单但重要的)原理。

第二个子句,递归规则呢?这表示:如果对象X是表尾部的成员,则它是表的成员。再次注意,我们使用了|操作符这个原理的声明。

现在,显然此定义具有良好的声明意义。但是,该程序实际上执行了应做的事情吗?也就是说,它真的可以告诉我们对象X是否属于表L吗?如果是这样,它到底是如何做到的?要回答此类问题,我们需要考虑其程序含义。让我们通过一些示例进行工作。

假设我们提出了以下查询:

 

?- member(yolanda, [yolanda,trudy,vincent,jules]).

 

Prolog将立即回答true。为什么?因为它可以将member / 2的第一个子句(事实)中X的两次出现合一为yolanda,所以它立即成功。

接下来考虑以下查询:

 

?- member(vincent, [yolanda,trudy,vincent,jules]).

 

现在,第一个规则无济于事(vincent和yolanda是不同的原子),因此Prolog转到第二个子句,即递归规则。这给Prolog一个新的目标:现在必须查看是否

 

member(vincent, [trudy,vincent,jules]).

 

同样,第一个子句无济于事,因此Prolog(再次)采用了递归规则。这给了它一个新的目标,即

 

member(vincent, [vincent,jules]).

 

这次,第一个子句确实有帮助,查询成功。

到目前为止一切顺利,但我们需要提出一个重要问题。当我们提出失败的查询时会发生什么?例如,如果我们提出如下查询会发生什么

 

member(zed, [yolanda,trudy,vincent,jules]).

 

现在,这显然应该失败了(毕竟zed不在表中)。那么Prolog如何处理呢?特别是,我们如何确定Prolog确实会停止,然后说不,而是进入一个无限递归循环中?

让我们系统地思考一下。再一次,第一个子句无济于事,因此Prolog使用了递归规则,这给了它一个新的目标

 

member(zed, [trudy,vincent,jules]).

 

同样,第一个子句无济于事,因此Prolog重用了递归规则并试图证明

 

member(zed, [vincent,jules]).

 

同样,第一条规则也无济于事,因此Prolog再次重用第二条规则并尝试实现目标

 

member(zed, [jules]).

 

同样,第一个子句无济于事,因此Prolog使用第二个规则,这给了它目标

 

member(zed, []).

 

这就是事情变得有趣的地方。显然,第一 个子句在这里无济于事。但请注意:递归规则也无能为力。为什么不?很简单:递归规则依赖于将表分为头和尾,但正如我们已经看到的那样,无法以这种方式拆分空表。因此,递归规则也无法应用,并且Prolog停止搜索更多解决方案,并宣布false。也就是说,它告诉我们zed不属于该表,而这正是它应该做的。

我们可以将member / 2谓词总结如下。这是一个递归谓词,可以系统地在表的长度中搜索所需项。它通过将表逐步细分为较小的表,然后查看每个较小表的第一项来完成此操作。驱动此搜索的这种机制是递归,并且此递归安全的原因(即,它不会永远持续下去的原因)是,在行尾,Prolog必须询问有关空表的问题。空表不能细分为较小的部分,这允许您退出递归。

好了,我们现在已经知道了member / 2为何起作用,但实际上它比前面的示例所暗示的要有用得多。到目前为止,我们仅使用它来回答true/false问题。但是我们也可以提出包含变量的问题。例如,我们可以使用Prolog创建以下对话框:

 

 member(X, [yolanda,trudy,vincent,jules]).

 

X = yolanda ;

 

X = trudy ;

 

X = vincent ;

 

X = jules ;

 

false.

 

也就是说,Prolog告诉我们表的每个成员是什么。这是member / 2的一种非常普通的用法。实际上,通过使用变量,我们对Prolog说:“快!请给我一些表元素!”。在许多应用程序中,我们需要能够提取表的成员,而这通常是这样做的方式。

最后一句话。我们上面定义的member / 2的方法当然是正确的,但从某种角度来说,这有点混乱。

想一想。第一个子句在那里处理表的开头。但是,尽管尾部与第一个子句无关,但我们使用变量T来命名尾部。类似地,递归规则也可以用来处理表的尾部。但是尽管这里的头部无关紧要,但我们使用变量H对其进行了命名。这些不必要的变量名称令人分心:最好以专注于每个子句中真正重要内容的方式编写谓词,而匿名变量为我们提供了这样做的好方法。也就是说,我们可以如下重写member / 2:

 

member(X,[X|_]).

member(X,[_|T]):- member(X,T).

 

无论是声明性的还是程序性的,此版本都是完全相同的。但是,这一点更加清晰:阅读时,您不得不专注于基本内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值