Prolog截断符(cut)未按预期阻止回溯,代码问题求助
为什么Scryer Prolog中的cut截断符未按预期工作?
我用Scryer Prolog编写了一套日期计算谓词,原本想通过在date_end_of_month的第一个分支中加入cut截断符,实现逻辑:若为闰年2月,则月末是29号;否则使用months列表定义的天数。但实际调试时发现,闰年2月的28号被错误判定为月末,而把cut替换为第二个分支中对leap_year的否定判断后,逻辑恢复正常。
原问题代码及查询结果
:- use_module(library(lists)). :- use_module(library(clpz)). months([ january-31, february-28, march-31, april-30, may-31, june-30, july-31, august-31, september-30, october-31, november-30, december-31 ]). nth1_month_days(N, Days) :- months(Ms), nth1(N, Ms, _-Days). date_end_of_month(date(Year, 2, _), 29) :- leap_year(Year), !. date_end_of_month(date(Year, M, _), Days) :- nth1_month_days(M, Days). leap_year(Y) :- Diff #= Y - 2000, 0 #= Diff rem 4. date_is_end_of_month(date(Year, Month, Day)) :- date_end_of_month(date(Year, Month, Day), Day).
关键查询结果
- 预期成功的查询(符合预期):
?- Year = 2024, Date = date(Year, 2, 29), date_end_of_month(Date, D), date_is_end_of_month(Date). %@ Year = 2024, Date = date(2024,2,29), D = 29. - 预期失败但实际成功的查询(不符合预期):
?- Year = 2024, Date = date(Year, 2, 28), date_end_of_month(Date, D), date_is_end_of_month(Date). %@ Year = 2024, Date = date(2024,2,28), D = 29.
问题根源:cut的触发条件被忽略
cut的作用是提交当前分支的选择,阻止回溯到同谓词的后续分支,但它只有在当前分支完全成功匹配时才会触发。在错误的代码中,当查询date_is_end_of_month(date(2024,2,28))时,它会调用date_end_of_month(date(2024,2,28), 28):
- 第一个分支
date_end_of_month(date(Year,2,_),29)的第二个参数是固定的29,和目标的28无法统一,因此第一个分支直接失败,cut根本没有被执行。 - 由于第一个分支失败,Prolog会回溯到第二个分支,
nth1_month_days(2,28)成功匹配,导致date_is_end_of_month错误地返回真。
你的预期是cut能阻止第二个分支执行,但实际上cut从未被触发——因为第一个分支的参数匹配就没通过。
修正后的代码及验证
将cut替换为第二个分支中对闰年的否定判断,确保闰年时第二个分支直接失效:
date_end_of_month(date(Year, 2, _), 29) :- leap_year(Year). date_end_of_month(date(Year, M, _), Days) :- \+ leap_year(Year), nth1_month_days(M, Days). % 其余代码保持不变
修正后的查询结果
- 原本预期失败的查询现在正确失败:
?- Year = 2024, Date = date(Year, 2, 28), date_end_of_month(Date, D), leap_year(Year), date_is_end_of_month(Date). %@ false. - 闰年2月29号的查询正常成功(回溯时第二个分支因
\+ leap_year(2024)失败):?- Year = 2024, Date = date(Year, 2, 29), date_end_of_month(Date, D), leap_year(Year), date_is_end_of_month(Date). %@ Year = 2024, Date = date(2024,2,29), D = 29 %@ ; false.
内容的提问来源于stack exchange,提问作者Jake
相关产品推荐
相关产品推荐

