序言中的 Zebra 解决方案如何工作?

How does this Zebra solution in prolog work?

zebra_owner(Owner) :-
    houses(Hs),
    member(h(Owner,zebra,_,_,_), Hs).

water_drinker(Drinker) :-
    houses(Hs),
    member(h(Drinker,_,_,water,_), Hs).


houses(Hs) :-
    length(Hs, 5),                                            %  1
    member(h(english,_,_,_,red), Hs),                         %  2
    member(h(spanish,dog,_,_,_), Hs),                         %  3
    member(h(_,_,_,coffee,green), Hs),                        %  4
    member(h(ukrainian,_,_,tea,_), Hs),                       %  5
    adjacent(h(_,_,_,_,green), h(_,_,_,_,white), Hs),         %  6
    member(h(_,snake,winston,_,_), Hs),                       %  7
    member(h(_,_,kool,_,yellow), Hs),                         %  8
    Hs = [_,_,h(_,_,_,milk,_),_,_],                           %  9
    Hs = [h(norwegian,_,_,_,_)|_],                            % 10
    adjacent(h(_,fox,_,_,_), h(_,_,chesterfield,_,_), Hs),        % 11
    adjacent(h(_,_,kool,_,_), h(_,horse,_,_,_), Hs),              % 12
    member(h(_,_,lucky,juice,_), Hs),                         % 13
    member(h(japanese,_,kent,_,_), Hs),                       % 14
    adjacent(h(norwegian,_,_,_,_), h(_,_,_,_,blue), Hs),          % 15
    member(h(_,_,_,water,_), Hs),       % one of them drinks water
    member(h(_,zebra,_,_,_), Hs).       % one of them owns a zebra

adjacent(A, B, Ls) :- append(_, [A,B|_], Ls).
adjacent(A, B, Ls) :- append(_, [B,A|_], Ls).

为什么 houses(Hs) 谓词不会失败​​各种 member(elem, list) 检查,如果列表实际上一直是空的?我知道这听起来像是一个愚蠢的问题,但实际上很难理解 Prolog,尤其是在多年面向对象编程之后。感谢您的帮助!

编辑:我没有提到我问序言的查询,这是 zebra_owner(Owner)

编辑 2:同时发布问题的文本(有点著名)以供参考:

  1. 一排五个彩色房子,每个房子都有主人、宠物、香烟和饮料。
  2. 英国人住红房子
  3. 西班牙人有条狗。
  4. 他们在温室里喝咖啡。
  5. 乌克兰人喝茶。
  6. 绿房子紧挨着白房子。
  7. 温斯顿吸烟者有一条蛇。
  8. 在黄色的房子里,他们抽 Kool。
  9. 中屋喝牛奶
  10. 挪威人住在左起第一栋房子里。
  11. 切斯特菲尔德吸烟者住在养狐狸的人附近。
  12. 在有马的房子附近的房子里,他们抽 Kool。
  13. 好彩烟民喝果汁
  14. 日本人抽肯特
  15. 挪威人住在蓝房子附近

谁拥有斑马谁喝水?

房子列表 Hs 从来没有 是空的。它是在一开始就创建的

length(Hs, 5)

所以它是一个包含五个最初未初始化的逻辑变量的列表,当它们被依次调用时,它们会被所有后续目标实例化,特别是被你提到的相同 member/2 目标实例化。

要查看此示例,请尝试:

32 ?- L=[A,B,C], member(1, L).
L = [1, B, C] ;
L = [A, 1, C] ;    % three solutions are found by Prolog
L = [A, B, 1].     % in turn, one after another

那些无法完成的调用(无法实例化剩余的自由逻辑变量)会导致特定搜索分支失败。

一个例子:

33 ?- L=[1,2,C], member(3,L).    % a check succeeds, while 
L = [1, 2, 3], C = 3.            % instantiating the `C` logvar

34 ?- L=[1,2,3], member(3,L).    % a check succeeds
L = [1, 2, 3].

35 ?- L=[1,2,3], member(4,L).    % a check fails
false.

最后只剩下成功的实例化,从而形成解决方案,当Hs中的所有逻辑变量都完全实例化时,地面。

How come the Houses(Hs) predicate not fail the various member(elem, list) checks, if the list is actually empty all the time?

那是因为列表实际上并不为空! Prolog 中的空列表是 []。它不包含任何元素。

但是 houses(Hs) 调用中的列表由该谓词中的第一个目标控制,即 length(Hs, 5)。这个目标是什么意思?

了解 Prolog 的一件重要事情是目标或查询不仅仅意味着“这个陈述是真的吗?”。 Prolog 目标或查询意味着“在什么情况下 这个陈述是正确的?”。 Prolog 将尽力向您描述您的查询变为真的情况。

因此,即使我们之前对 Hs 一无所知,在执行此 length 查询时,GNU Prolog 会说:

| ?- length(Hs, 5).

Hs = [_,_,_,_,_]

所以 length(Hs, 5) 变为真 如果 Hs[_, _, _, _, _] 的形式。这不是一个空列表。它甚至不是一个可能为空的列表。这是一个绝对包含五个元素的列表。我们对这些元素一无所知!但是list的长度肯定是固定的

也许您被以下事实误导了:Prolog 允许您谈论一个肯定包含元素但其元素未知的列表。这是一个在典型的面向对象语言中不可能实现的概念。但是我们不知道元素的事实并不意味着那里什么都没有,即列表是空的。

至于 member 调用如何成功:同样,它们不只是检查“XHs 的成员”。它们是问题,意思是“在什么情况下X Hs 的成员?”。同样,Prolog 会为您做一些工作来描述这些情况:

| ?- length(Hs, 5), member(x, Hs).

Hs = [x,_,_,_,_] ? ;

Hs = [_,x,_,_,_] ? ;

Hs = [_,_,x,_,_] ? ;

Hs = [_,_,_,x,_] ? ;

Hs = [_,_,_,_,x]

所以 xHs 的成员,如果 xHs 的第一个元素,或者第二个,或者第三个,等等。在每一种情况下,“描述情况”意味着 Prolog 实际上将列表的适当元素(之前是一个变量)绑定到元素 x。对于这些可能性中的每一种,遵循 member 目标的进一步计算可以进一步实例化列表。这就是构建 Zebra 谜题解决方案的原因:Prolog 构建了一个实际描述解决方案的数据结构,而不是仅仅对“这个谜题有解决方案”的问题回答“是”。