Я оставлю кому-то еще объяснять опасности головных объединений вывода аргументов при наличии порезов. Вместо этого давайте рассмотрим случаи, когда предикату не удается объединить третий аргумент с максимумом первых двух аргументов. Мы можем определить следующее свойство, которому должен удовлетворять предикат max/3
:
property(M,N,Max) :-
( max(M,N,Max) ->
Max >= M, Max >= N
; nonvar(Max)
).
Т.е. в случае успеха max/3
значение Max
должно быть равным или превышать первые два аргумента. Если max/3
терпит неудачу, это должно произойти из-за связанного третьего аргумента, который не может объединиться с максимумом из двух входных аргументов.
Давайте QuickCheck это с помощью реализации lgtunit
Logtalk (которую вы можете запустить с большинством Prolog системы; здесь я буду использовать SWI-Prolog):
$ swilgt
...
?- {lgtunit(loader)}.
...
% (0 warnings)
true.
?- [user].
|: max(M,N,M):-M >= N,!.
|: max(M,N,N).
Warning: user://1:102:
Warning: Singleton variables: [M]
|: property(M,N,Max) :-
|: ( max(M,N,Max) ->
|: Max >= M, Max >= N
|: ; nonvar(Max)
|: ).
|: ^D% user://1 compiled 0.01 sec, 3 clauses
true.
Обратите внимание, что мы хотим создать случайные тесты, в которых первые два аргумента являются целыми числами, а третий аргумент может быть либо переменной, либо целым числом:
?- lgtunit::quick_check(property(+integer, +integer, ?integer), [n(20000)]).
* quick check test failure (at test 5484 after 9 shrinks):
* property(1,0,0)
false.
Как видно из сообщения об ошибке, определение предиката max/3
может завершиться неудачно при вызове со всеми связанными аргументами. Цель max(1,0,0)
не объединяется с главой первого предложения. Таким образом, используется второе предложение, приводящее к успешной цели, где ожидается сбой.
Мы можем исправить ошибку (выставленную QuickCheck), переместив объединение с выходным аргументом в после сокращение:
max(M,N,Max):- M >= N,!, Max = M.
max(_,N,N).
Но правильно ли это определение? Быстрый тест с приведенным выше случаем показывает, что ошибка исправлена:
?- max(1,0,0).
false.
Давайте также QuickCheck (используя то же свойство):
?- lgtunit::quick_check(property(+integer, +integer, ?integer), [n(20000)]).
% 20000 random tests passed
true.
Выглядит хорошо, но помните: QuickCheck может выявлять ошибки, но не может доказать, что они не существуют. Фактически, если бы мы использовали количество тестов по умолчанию (100), мы могли бы пропустить ошибку. В некоторых случаях это помогает проверить с узкими аргументами. Например, используя оригинальное, глючное, определение max/3
:
?- lgtunit::quick_check(
property(
+between(integer,-2,2),
+between(integer,-2,2),
?between(integer,-2,2)
)
).
* quick check test failure (at test 23 after 0 shrinks):
* property(1,-1,-1)
false.