Prolog 和列表统一
Prolog and List Unification
我正在努力加深对 Prolog 的理解,以及它如何处理合一。在这种情况下,它如何处理与列表的统一。
这是我的知识库;
member(X, [X|_]).
member(X, [_|T]):- member(X, T).
如果我理解正确的话。如果 member(X, [X|_])
不成立,则进入递归规则,如果 X
在列表 T
中,则 [_|T]
与 T
统一。
那么我的递归谓词中的匿名变量会发生什么?它会被丢弃吗?我很难理解列表的确切统一过程,因为 [_|T]
是两个变量,而不是一个。我只是想弄清楚统一过程如何精确地与列表一起工作。
列表只是具有 '.'
仿函数的复合术语:
1 ?- [_|T] = .(_,T).
true.
2 ?- [_|T] =.. X.
X = ['.', _G2393, T].
复合词结构统一的通常过程适用:
3 ?- [A|T] = .(B,R).
A = B,
T = R.
[A|T]
实际上是 .(A,T)
所以函子 (.
) 和元数(两项都是二元数,元数为 2)匹配,因此相应的成分也匹配.
是的,为了报告统一结果,匿名变量_
被忽略了。否则它只是一个新的唯一命名变量。
it moves into the recursive rule, and if X is in list T, then [_|T] is unified with T.
不完全是。作为子句 selection 的一部分,统一发生在 在 之前 "moving on"。将列表 L
与 [_|T]
统一就是 select 它的 "tail" 并让 T
引用它。例如
4 ?- L = [1,2,3], L = [_|T].
L = [1, 2, 3],
T = [2, 3].
(_
是 1
但未报告)。
假设 _
是 Y
member(X, [Y|T]):- member(X, T).
那么这就是True
不管Y
。现在你是 "returning" member(X, T)
。换句话说,您正在丢弃 Y
和 "returning" member(X, T).
_
意味着,不管它是什么,忽略那个变量。
The _ is just like any other variable, except that each one you see is
treated as a different variable and Prolog won't show you what it
unifies with. There's no special behavior there; if it confuses you
about the behavior, just invent a completely new variable and put it
in there to see what it does.
在您的例子中,您的函数检查给定元素是否存在于列表中,因此,您获取列表的第一个元素,检查是否相等,如果不相等,则丢弃该元素并继续。
[A|B]
表示一个列表,其中 A 是 Head 元素,B 是整个其余列表。
所以向您解释一下算法:
- 子句:如果 X 是列表的第一个元素,则谓词成功。
- 子句:如果不是这样,我们尝试在列表的尾部找到 X。因此,我们递归地调用 member 但不是传递整个列表,我们现在传递除了 head 元素之外的列表。换句话说,我们一步一步地遍历列表,始终首先查看头部元素。如果那不是我们的元素,我们会进一步挖掘。
将匿名变量_
视为以后不需要的变量。如果您将 _
替换为大写字母,该算法也会起作用,但是它会警告您您命名了一个您从未使用过的变量。
我认为您关于列表如何表示为变量的主要问题已得到充分回答,但我感觉 Prolog 还有一些其他方面需要澄清。
要理解 Prolog 谓词和子句,最好不要将它们视为 "functions" 那 "return" 的东西,即使是比喻。它可以引导你在 Prolog 中走上命令式思维的黑暗道路。 :)
在考虑谓词时:
(1) member(X, [X|_]).
(2) member(X, [_|T]) :- member(X, T).
将 member/2
视为 关系 ,它描述元素 X
何时是列表 L
的成员,子句是确定何时为真的规则。
我假设您已经根据其他答案(例如,Will Ness 的详细答案)了解 Prolog 中列表的表示方式。
第一个子句说:
(1) X
是 [X|_]
的成员,无论列表 [X|_]
的尾部是什么
在该表示法中,变量 _
表示列表 [X|_]
的尾部,而 X
表示该列表的第一个元素。 X
是这个列表中的一个成员,这是很正常的,所以 member(X, [X|_]).
是事实。不管列表的尾部是什么,它都是真的,所以我们只使用 _
(一个匿名变量),因为这条规则不需要这些信息。 Prolog 在技术上没有 "throw the value away" 但程序员将其丢弃,因为程序员没有使用它。相反,如果我们说 member(X, [X|T]).
可以正常工作,但我们没有使用 T
。 Prolog 可能会实例化它,但它不会被使用。这就像在 C 中分配 x = 3
但不使用它的值。在这种情况下,Prolog 将指示有关 "singleton" 变量的警告。注意那些,因为这通常意味着您拼错了某些内容或忘记了某些内容。 :)
下一个规则是递归的。它说:
(2) X
是列表的成员 [_|T]
if X
是尾部的成员该列表的 (T
),无论列表的第一个元素是什么
这里我们考虑的是不那么简单的情况,即列表中的第一个元素可能与 X
不匹配,因此 member(X, L)
的真值取决于,在此规则中, member(X, T)
的真值,其中 T
是 L
的尾部(除第一个元素外的所有元素)。规则不统一member(X, [_|T])
和member(X, T)
,所以它不统一T
和[_|T]
如您所想。 :-
运算符定义规则或蕴涵(注意规则描述中的 if)。 [N.B.,如果你要统一这些术语,那么统一 运算符,=/2
: member(X, [_|T]) = member(X, T)
].
在递归查询member(X, T)
上,Prolog回到顶部,第一条规则,并试图统一第一个参数X
和第二个参数的头部(这是原来的list 减去它的第一个元素,或 head) 并且,如果它不匹配,则再次转到规则 #2,并不断检查尾部,直到它可以统一。如果到达尾部为空 ([]
) 且无法将 X
与任何元素统一的点,则谓词失败,因为没有匹配 [=54] 的事实或规则=].但是,如果它确实 X
与一个元素统一,它 成功 (它 不 “return 一个值*在某种意义上,一个函数会在其他语言中)并揭示它在过程中的参数中实例化的任何变量的值,或者如果传递的所有参数都已经实例化,则简单地会成功。如果成功后有更多规则要检查(有一个所谓的选择点),它会(如果你告诉它)返回并检查更多的解决方案,如果找到它们,也会显示它们。或者显示no
或 false
如果没有更多。
查看示例查询,是 b
[a,b,c]
的成员吗?
member(b, [a,b,c]).
Prolog 将首先尝试将查询与 fact 或谓词的 head 统一起来。它找到的第一个是:
member(X, [X|_]).
试图统一,X = b
,但[a,b,c]
(或者,头尾表示法中的[a|[b,c]]
)没有与[b|_][=63统一=]aand
b` 不匹配)。 Prolog 然后继续下一个子句:
member(X, [_|T]) :- member(X, T).
将member(b, [a,b,c])
与head统一起来,得出:
member(b, [_|[b,c]]) :- member(b, [b,c]).
它现在有递归查询来追查:member(b, [b,c])
。由于这是一个新查询,它再次从顶部开始并尝试将其与 member(X, [X|_])
统一。现在,它成功了,因为 member(b, [b,c])
(或者,member(b, [b|[c]])
)匹配这个模式:member(b, [b|_])
.
因此,member(b, [a,b,c]).
成功并且 Prolog 将 return "true"。但是,它还没有完成,因为它留下了所谓的选择点。即使它匹配了 member(b, [b,c])
与第一个子句,它仍然会想要返回并找到更多使它成功的案例,并且还有另一个子句要追求。因此,Prolog 将返回并针对第二个子句尝试 member(b, [b,c])
,将 member(b, [b|[c]])
匹配到 member(b, [_|[c]])
并进行另一个递归查询,member(b, [c])
依此类推,直到最终无法找到另一种解决方案。这就是查询看起来像这样的原因:
| ?- member(b, [a,b,c]).
true ? ;
no
| ?-
它首先成功了,但随后我们要求更多(;
),然后失败(no
)。这让一些 Prolog 初学者感到困惑,但这是因为我们要求 Prolog 得到另一个解决方案,它说有 none.
因为 Prolog 会继续尝试寻找解决方案(根据要求),您还可以在查询中使用变量:
member(E, [a,b,c]).
此查询的运行方式与前面的示例相同,但现在我有一个变量作为第一个参数。 Prolog 将成功地将其与第一个子句匹配:member(E, [a,b,c])
通过 E = a
与 member(X, [X|_])
统一。所以你会看到类似的东西:
| ?- member(E, [a,b,c]).
E = a ?
如果我们现在用 ;
要求更多的解决方案,Prolog 返回并尝试第二个子句,统一 member(E, [a|[b,c]])
和 member(X, [_|T])
产生 _ = a
(被忽略在谓词中)和 T = [b,c]
。然后它递归地查询 member(E, [b,c])
并且,因为它是一个新查询,所以返回顶部并再次匹配 member(X, [X|_])
,这次是 E = b
。所以我们看到:
| ?- member(E, [a,b,c]).
E = a ? ;
E = b ?
等等。 member(E, [a,b,c])
将找到 E
的所有值,这些值使 member(E, [a,b,c])
为真,然后在用尽 [a,b,c]
).
的所有元素后最终失败
我正在努力加深对 Prolog 的理解,以及它如何处理合一。在这种情况下,它如何处理与列表的统一。
这是我的知识库;
member(X, [X|_]).
member(X, [_|T]):- member(X, T).
如果我理解正确的话。如果 member(X, [X|_])
不成立,则进入递归规则,如果 X
在列表 T
中,则 [_|T]
与 T
统一。
那么我的递归谓词中的匿名变量会发生什么?它会被丢弃吗?我很难理解列表的确切统一过程,因为 [_|T]
是两个变量,而不是一个。我只是想弄清楚统一过程如何精确地与列表一起工作。
列表只是具有 '.'
仿函数的复合术语:
1 ?- [_|T] = .(_,T).
true.
2 ?- [_|T] =.. X.
X = ['.', _G2393, T].
复合词结构统一的通常过程适用:
3 ?- [A|T] = .(B,R).
A = B,
T = R.
[A|T]
实际上是 .(A,T)
所以函子 (.
) 和元数(两项都是二元数,元数为 2)匹配,因此相应的成分也匹配.
是的,为了报告统一结果,匿名变量_
被忽略了。否则它只是一个新的唯一命名变量。
it moves into the recursive rule, and if X is in list T, then [_|T] is unified with T.
不完全是。作为子句 selection 的一部分,统一发生在 在 之前 "moving on"。将列表 L
与 [_|T]
统一就是 select 它的 "tail" 并让 T
引用它。例如
4 ?- L = [1,2,3], L = [_|T].
L = [1, 2, 3],
T = [2, 3].
(_
是 1
但未报告)。
假设 _
是 Y
member(X, [Y|T]):- member(X, T).
那么这就是True
不管Y
。现在你是 "returning" member(X, T)
。换句话说,您正在丢弃 Y
和 "returning" member(X, T).
_
意味着,不管它是什么,忽略那个变量。
The _ is just like any other variable, except that each one you see is treated as a different variable and Prolog won't show you what it unifies with. There's no special behavior there; if it confuses you about the behavior, just invent a completely new variable and put it in there to see what it does.
在您的例子中,您的函数检查给定元素是否存在于列表中,因此,您获取列表的第一个元素,检查是否相等,如果不相等,则丢弃该元素并继续。
[A|B]
表示一个列表,其中 A 是 Head 元素,B 是整个其余列表。
所以向您解释一下算法:
- 子句:如果 X 是列表的第一个元素,则谓词成功。
- 子句:如果不是这样,我们尝试在列表的尾部找到 X。因此,我们递归地调用 member 但不是传递整个列表,我们现在传递除了 head 元素之外的列表。换句话说,我们一步一步地遍历列表,始终首先查看头部元素。如果那不是我们的元素,我们会进一步挖掘。
将匿名变量_
视为以后不需要的变量。如果您将 _
替换为大写字母,该算法也会起作用,但是它会警告您您命名了一个您从未使用过的变量。
我认为您关于列表如何表示为变量的主要问题已得到充分回答,但我感觉 Prolog 还有一些其他方面需要澄清。
要理解 Prolog 谓词和子句,最好不要将它们视为 "functions" 那 "return" 的东西,即使是比喻。它可以引导你在 Prolog 中走上命令式思维的黑暗道路。 :)
在考虑谓词时:
(1) member(X, [X|_]).
(2) member(X, [_|T]) :- member(X, T).
将 member/2
视为 关系 ,它描述元素 X
何时是列表 L
的成员,子句是确定何时为真的规则。
我假设您已经根据其他答案(例如,Will Ness 的详细答案)了解 Prolog 中列表的表示方式。
第一个子句说:
(1) X
是 [X|_]
的成员,无论列表 [X|_]
的尾部是什么
在该表示法中,变量 _
表示列表 [X|_]
的尾部,而 X
表示该列表的第一个元素。 X
是这个列表中的一个成员,这是很正常的,所以 member(X, [X|_]).
是事实。不管列表的尾部是什么,它都是真的,所以我们只使用 _
(一个匿名变量),因为这条规则不需要这些信息。 Prolog 在技术上没有 "throw the value away" 但程序员将其丢弃,因为程序员没有使用它。相反,如果我们说 member(X, [X|T]).
可以正常工作,但我们没有使用 T
。 Prolog 可能会实例化它,但它不会被使用。这就像在 C 中分配 x = 3
但不使用它的值。在这种情况下,Prolog 将指示有关 "singleton" 变量的警告。注意那些,因为这通常意味着您拼错了某些内容或忘记了某些内容。 :)
下一个规则是递归的。它说:
(2) X
是列表的成员 [_|T]
if X
是尾部的成员该列表的 (T
),无论列表的第一个元素是什么
这里我们考虑的是不那么简单的情况,即列表中的第一个元素可能与 X
不匹配,因此 member(X, L)
的真值取决于,在此规则中, member(X, T)
的真值,其中 T
是 L
的尾部(除第一个元素外的所有元素)。规则不统一member(X, [_|T])
和member(X, T)
,所以它不统一T
和[_|T]
如您所想。 :-
运算符定义规则或蕴涵(注意规则描述中的 if)。 [N.B.,如果你要统一这些术语,那么统一 运算符,=/2
: member(X, [_|T]) = member(X, T)
].
在递归查询member(X, T)
上,Prolog回到顶部,第一条规则,并试图统一第一个参数X
和第二个参数的头部(这是原来的list 减去它的第一个元素,或 head) 并且,如果它不匹配,则再次转到规则 #2,并不断检查尾部,直到它可以统一。如果到达尾部为空 ([]
) 且无法将 X
与任何元素统一的点,则谓词失败,因为没有匹配 [=54] 的事实或规则=].但是,如果它确实 X
与一个元素统一,它 成功 (它 不 “return 一个值*在某种意义上,一个函数会在其他语言中)并揭示它在过程中的参数中实例化的任何变量的值,或者如果传递的所有参数都已经实例化,则简单地会成功。如果成功后有更多规则要检查(有一个所谓的选择点),它会(如果你告诉它)返回并检查更多的解决方案,如果找到它们,也会显示它们。或者显示no
或 false
如果没有更多。
查看示例查询,是 b
[a,b,c]
的成员吗?
member(b, [a,b,c]).
Prolog 将首先尝试将查询与 fact 或谓词的 head 统一起来。它找到的第一个是:
member(X, [X|_]).
试图统一,X = b
,但[a,b,c]
(或者,头尾表示法中的[a|[b,c]]
)没有与[b|_][=63统一=]aand
b` 不匹配)。 Prolog 然后继续下一个子句:
member(X, [_|T]) :- member(X, T).
将member(b, [a,b,c])
与head统一起来,得出:
member(b, [_|[b,c]]) :- member(b, [b,c]).
它现在有递归查询来追查:member(b, [b,c])
。由于这是一个新查询,它再次从顶部开始并尝试将其与 member(X, [X|_])
统一。现在,它成功了,因为 member(b, [b,c])
(或者,member(b, [b|[c]])
)匹配这个模式:member(b, [b|_])
.
因此,member(b, [a,b,c]).
成功并且 Prolog 将 return "true"。但是,它还没有完成,因为它留下了所谓的选择点。即使它匹配了 member(b, [b,c])
与第一个子句,它仍然会想要返回并找到更多使它成功的案例,并且还有另一个子句要追求。因此,Prolog 将返回并针对第二个子句尝试 member(b, [b,c])
,将 member(b, [b|[c]])
匹配到 member(b, [_|[c]])
并进行另一个递归查询,member(b, [c])
依此类推,直到最终无法找到另一种解决方案。这就是查询看起来像这样的原因:
| ?- member(b, [a,b,c]).
true ? ;
no
| ?-
它首先成功了,但随后我们要求更多(;
),然后失败(no
)。这让一些 Prolog 初学者感到困惑,但这是因为我们要求 Prolog 得到另一个解决方案,它说有 none.
因为 Prolog 会继续尝试寻找解决方案(根据要求),您还可以在查询中使用变量:
member(E, [a,b,c]).
此查询的运行方式与前面的示例相同,但现在我有一个变量作为第一个参数。 Prolog 将成功地将其与第一个子句匹配:member(E, [a,b,c])
通过 E = a
与 member(X, [X|_])
统一。所以你会看到类似的东西:
| ?- member(E, [a,b,c]).
E = a ?
如果我们现在用 ;
要求更多的解决方案,Prolog 返回并尝试第二个子句,统一 member(E, [a|[b,c]])
和 member(X, [_|T])
产生 _ = a
(被忽略在谓词中)和 T = [b,c]
。然后它递归地查询 member(E, [b,c])
并且,因为它是一个新查询,所以返回顶部并再次匹配 member(X, [X|_])
,这次是 E = b
。所以我们看到:
| ?- member(E, [a,b,c]).
E = a ? ;
E = b ?
等等。 member(E, [a,b,c])
将找到 E
的所有值,这些值使 member(E, [a,b,c])
为真,然后在用尽 [a,b,c]
).