仅使用bagof / 3作为副作用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仅使用bagof / 3作为副作用相关的知识,希望对你有一定的参考价值。
在Prolog中解决一个非常简单的练习:打印从1到100的所有数字,但不是数字,如果数字是3的倍数则打印'Fuzz',如果是5的倍数则打'Buzz',如果两者都是'FizzBuzz'。
我最后做了以下事情:
fizzbuzz :- forall( between(1, 100, X), fizzbuzz(X) ).
fizzbuzz(X) :- ( write_fb(X) ; write_n(X) ), nl.
write_fb(X) :- bagof(_, fb(X), _).
fb(X) :- X rem 3 =:= 0, write('Fizz').
fb(X) :- X rem 5 =:= 0, write('Buzz').
write_n(X) :- write(X).
但是没有任何谓词或控制结构会避免仅使用bagof / 3作为其副作用吗? (我总是有点不确定只使用谓词的副作用)。
作为对现有答案的补充,我想展示一个更多的关系解决方案,我希望能够说明将逻辑编程这样的声明性编程范例应用于这个问题的一些独特优势。
首先,让我们概括一下这个任务:
print all numbers from 1 to 100, but instead of the number, print...
- 'fuzz'如果数字是3的倍数
- 'Buzz'如果是5的倍数
- 如果两者都是'FizzBuzz'。
我认为,默认的假设是数字仅限于整数。
为简单起见,让我们首先将自己限制为一个整数,让我们描述这种整数与所需输出之间的关系。
上面提到的三种情况可以直接翻译成Prolog,使用Prolog系统的CLP(FD)约束进行声明性整数运算:
integer_output(N, 'Fuzz') :- N #= 3*_. integer_output(N, 'Buzz') :- N #= 5*_. integer_output(N, 'FizzBuzz') :- N #= 3*_, N #= 5*_.
但这并不是全部,因为这会产生例如:
?- integer_output(4, N). false.
因此,我们还需要一个案例,我们可以将其表述为:
integer_output(N, N) :- N mod 3 #= 0, N mod 5 #= 0.
这简单地说明,如果没有其他情况适用,我们按原样输出数字。由此产生的关系非常普遍。例如,我们可以将它用于具体的整数:
?- integer_output(1, O). O = 1. ?- integer_output(3, O). O = 'Fuzz' ; false.
我们也可以用它来编写单元测试,例如:
?- integer_output(5, 'Buzz'). true .
这里,已经指定了预期的输出,我们可以使用相同的关系来询问输出是否符合要求。这是关系的一个相当不错的属性,如果我们只在系统终端上编写输出而不是像上面那样将它显式化为谓词参数,那就不会那么容易了。
但还有更多!我们也可以在另一个方向上使用相同的关系,例如我们要求:“哪个整数导致输出Buzz
?”这里是:
?- integer_output(I, 'Buzz'). 5*_680#=I.
这是对早期测试案例的大规模概括,可以作为我们涵盖所有案例的额外保证。事实上,我们甚至可以进一步概括这一点,从而产生最常见的查询,询问答案的一般情况:
?- integer_output(I, O). O = 'Fuzz', 3*_742#=I ; O = 'Buzz', 5*_742#=I ; O = 'FizzBuzz', 5*_1014#=I, 3*_1038#=I.
让我们更多地了解输出。显然,我们期望输出是针对每个可能的整数唯一确定的,对吧?让我们通过询问这个属性的反例来询问Prolog是否是这样的:
?- dif(O1, O2), integer_output(I, O1), integer_output(I, O2). O1 = 'Fuzz', O2 = 'Buzz', 5*_1046#=I, 3*_1070#=I ; O1 = 'Fuzz', O2 = 'FizzBuzz', 5*_1318#=I, 3*_1342#=I, 3*_1366#=I .
现在看起来不太好:从上面看,我们已经怀疑可能存在相同整数I
的情况,产生两个不同的,同样合理的输出O1
和O2
。
事实上,这是一个出现此问题的具体整数:
?- integer_output(15, O). O = 'Fuzz' ; O = 'Buzz' ; O = 'FizzBuzz' ; false.
事实证明,输出并不是唯一确定的!让我们按照自然的直觉立即提出要求:
WHOSE FAULT IS THIS?
CLP(FD)禁止破坏?
事实上,事实证明,使用陈述式公式只是暴露了任务表述中的模糊性。过早地提交其中一个解决方案并不会暴露这个问题。
可能意味着任务描述会导致整数和输出之间的以下关系:
integer_output(N, 'Fuzz') :- N #= 3*_, N mod 5 #= 0. integer_output(N, 'Buzz') :- N #= 5*_, N mod 3 #= 0. integer_output(N, 'FizzBuzz') :- N #= 3*_, N #= 5*_. integer_output(N, N) :- N mod 3 #= 0, N mod 5 #= 0.
这会产生:
?- integer_output(15, O). O = 'FizzBuzz' ; false.
其他测试用例仍然按预期工作。
现在,使用这种关系作为构建块,使用元谓词maplist/3
很容易将其提升为整数列表:
fizz_buzz(Ls) :- numlist(1, 100, Ls0), maplist(integer_output, Ls0, Ls).
示例查询和答案:
?- fizz_buzz(Ls). Ls = [1, 2, 'Fuzz', 4, 'Buzz', 'Fuzz', 7, 8, 'Fuzz'|...] ; false.
请注意,我们自己并没有写任何东西:我们正在使用Prolog toplevel为我们写作,并推理论证。
优点很明显:我们可以再次为这样的谓词编写测试用例。例如,我们希望以下内容成立:它确实如下:
?- Ls = [1,2|_], fizz_buzz(Ls). Ls = [1, 2, 'Fuzz', 4, 'Buzz', 'Fuzz', 7, 8, 'Fuzz'|...] .
到目前为止,一切都是完全纯净的,可用于各个方向。我将这些解决方案格式化为您想要的简单练习。
如果您的Prolog系统不提供numlist/3
,您可以使用bagof/3
获取1到100之间的整数列表,如下所示:
?- bagof(L, (L in 1..100,indomain(L)), Ls). Ls = [1, 2, 3, 4, 5, 6, 7, 8, 9|...].
因此,bagof/3
可用于此任务,但我不建议将其用于副作用。
问题:
打印1到100之间的所有数字,但不是数字,如果数字是3的倍数,则打印'Fuzz',如果是5的倍数,则打印'Buzz',如果两者,则打印'FizzBuzz'。
我认为你遇到这个关于bagof的特性的事实表明你的程序中有“气味”。我发现Prolog在我身上发生了很多事。 Prolog实际上是一个非常小的套件,提供的不是很多。我随着时间的推移已经了解到,如果我遇到需要的东西不在那个最小的工具包中,或者我的用法似乎背叛了内置功能的预期用途,那么几乎总是因为我当前有“气味”做法。
更多关于“气味”的信息:*/
/* -- prolog setup -- */
:- ( op(10'1150,'yfx','forall') ) .
:- ( op(10'1150,'fy','if') ) .
:- ( op(10'1140,'yfx','then') ) .
:- ( op(10'1140,'yfx','else') ) .
(if IF then THEN else ELSE) :- (IF *-> THEN ; ELSE) .
(if IF then THEN) :- (IF *-> THEN ; (throw(false(IF)))) .
term_expansion((if IF then THEN else ELSE),((IF :- THEN *-> (true) ; ELSE))) .
term_expansion((if IF then THEN),((IF :- THEN *-> (true) ; (throw(false(IF)))))) .
/* -- program -- */
if
(
program(_)
)
then
(
(
if
(
generate(NUMBER)
)
then
(
true
)
)
forall
(
if
(
transform(NUMBER,MESSAGE)
)
then
(
if
(
accept(NUMBER,MESSAGE)
)
then
(
if
(
echo(NUMBER)
)
then
(
echo(MESSAGE)
)
)
else
(
true
)
)
)
)
.
if
(
generate(NUMBER)
)
then
(
between(1,100,NUMBER)
)
.
if
(
transform(NUMBER,MESSAGE)
)
then
(
if
(
multiple_of_3(NUMBER)
)
then
(
if
(
multiple_of_5(NUMBER)
)
then
(
MESSAGE='FizzBuzz'
)
else
(
MESSAGE='Fuzz'
)
)
else
(
if
(
multiple_of_5(NUMBER)
)
then
(
MESSAGE='Buzz'
)
else
(
% this contingency is undefined in the problem statement %
true
)
)
)
.
if
(
multiple_of_3(NUMBER)
)
then
(
NUMBER rem 10'3 =:= 10'0
)
else
(
false
)
.
if
(
multiple_of_5(NUMBER)
)
then
(
NUMBER rem 10'5 =:= 10'0
)
else
(
false
)
.
if
(
accept(NUMBER,MESSAGE)
)
then
(
if
(
true
)
then
(
true
)
else
(
false
)
)
else
(
false
)
.
if
(
echo(MESSAGE)
)
then
(
if
(
writeq(MESSAGE)
)
then
(
nl
)
)
.
/*
example query
=============
?-
program(_)
.
3
'Fuzz'
5
'Buzz'
6
'Fuzz'
9
'Fuzz'
12
'Fuzz'
15
'FizzBuzz'
18
'Fuzz'
[ ... and so on as expected ... ]
90
'FizzBuzz'
93
'Fuzz'
95
'Buzz'
96
'Fuzz'
99
'Fuzz'
100
'Buzz'
true.
?-
当我们概述程序目前所需的程序流程的一般草图时,我认为当前方法中的“气味”变得明显:
- 生成
- 打印
- 转变
问题是你在“变换”之前试图“打印”。您希望在“转换”之后“打印”,如下所示:
- 生成
- 转变
- 打印
考虑到这一点,我们可以重写问题陈述:
新问题:
解决所有每个数字:
生成从1到100的每个数字,但是将每个数字转换为每个消息,如果每个数字是3的倍数,则将每个数字转换为每个消息“Fuzz”,如果每个数字是5的倍数,则转换为每个消息“Buzz”每条消息'FizzBuzz'如果每个号码都是,则打印每条消息。
以下是旨在解决上述问题的程序。
程序列表之后是示例查询会话。
% this contingency is undefined in the problem statement %
但是,目前提出的程序并没有完全产生如上所述的预期输出。
在上面的程序中有一个标记:
:- use_module(library(clpfd)) .
:- op(10'1,'yfx','forall') .
:- op(10'1,'fy','once') .
(
program
)
:-
(
(
between(1,100,NUMBER)
)
forall
(
once
(
(
MESSAGE='FizzBuzz'
,
NUMBER rem 10'3 #= 10'0
,
NUMBER rem 10'5 #= 10'0
)
|
(
MESSAGE='Buzz'
,
NUMBER rem 10'5 #= 10'0
)
|
(
MESSAGE='Fuzz'
,
NUMBER rem 10'3 #= 10'0
)
|
(
MESSAGE=_
)
)
,
once
(
(
nonvar(MESSAGE)
,
writeq(NUMBER)
,
nl
,
writeq(MESSAGE)
,
nl
)
|
(
true
)
)
)
)
.
在该计划100%满意之前,需要解决问题。
使用swi-prolog和yap进行测试。
/*
problem statement
solve for all each number :生成每个数字从1到100,但将每个数字转换为
以上是关于仅使用bagof / 3作为副作用的主要内容,如果未能解决你的问题,请参考以下文章
FragmentContainer 与仅作为包装器的另一个 React 组件?