问题描述
在许多函数式编程语言中,可以使用let
表达式“重新定义”局部变量:
let example =
let a = 1 in
let a = a+1 in
a + 1
为此,我找不到内置的Prolog谓词,因此我尝试以这种方式定义let
表达式:
:- initialization(main).
:- set_prolog_flag(double_quotes,chars).
replace(Subterm0,Subterm,Term0,Term) :-
( Term0 == Subterm0 -> Term = Subterm
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],maplist(replace(Subterm0,Subterm),Args0,Args),Term =.. [F|Args]
).
let(A,B) :-
((D,D1) = (A1 is B1,C is B1);
(D,D1) = (A1=B1,C=B1)),subsumes_term(D,A),D=A,replace(A1,C,B,B2),call((D1,B2)).
main :- let(A = 1,(
writeln(A),let(A is A+1,(
writeln(A),let(A is A * 2,(
writeln(A)
))
))
)).
此实现似乎不正确,因为某些变量在替换之前已绑定。我想定义一个表达式,该表达式允许同时“重新定义”多个变量:
main :- let((A = 1,B = 2),% this will not work with the let/2 predicate that I defined
let((A=B,B=A),writeln(B)
))
).
是否可以以允许同时重新定义多个变量的方式实现let
表达式?
解决方法
let
本质上是一种(在源代码中)创建一个新的本地上下文以评估函数的方法(另请参见:In what programming language did “let” first appear?)
Prolog没有“本地上下文”-唯一的上下文是子句。变量名称仅对子句有效,并且在子句中完全可见。与功能程序不同,Prolog非常“扁平”。
考虑main
:
main :- let(A = 1,(
writeln(A),let(A is A+1,(
writeln(A),let(A is A * 2,(
writeln(A)
))
))
)).
上下文是子句,对于以下情况,这本质上是“错误的伪代码”:
main :- f(1).
f(A) :- writeln(A),B is A+1,g(B).
g(A) :- writeln(A),B is A*2,h(B).
h(A) :- writeln(A).
?- main.
1
2
4
true.
let
在这里并没有带来太多好处。似乎可以避免在is
的右侧“手动”重新标记变量,但这是不值得的。
(现在,如果有一种方法可以创建谓词的嵌套上下文来组织代码,我很乐意接受!)。
让我们进一步探索乐趣(因为我目前正在尝试实现Monad Idiom,以查看是否有意义)。
您可以考虑创建变量绑定上下文的显式表示形式,就像在编写LISP解释器一样。使用SWI-Prolog dicts可以轻松完成此操作,SWI-Prolog foldl/4
只是函数编程中使用的不可变映射。现在请注意,变量的值可能会随着计算的进行而变得“更精确”,只要它的任何部分仍然是“空洞”,就可能导致旧的深层上下文被当前操作修改,不知道该怎么想。
首先定义谓词以从现有的谓词生成新的dict,即从旧的谓词定义新的上下文,然后代码变为:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.
twice_a(Din,X)) :- X is Din.a * 2.
main :- f(_{a:1}).
f(D) :- writeln(D.a),inc_a(D,D2),g(D2).
g(D) :- writeln(D.a),twice_a(D,h(D2).
h(D) :- writeln(D.a).
A
进入了通过调用编织的字典D
内。
您现在可以编写一个带字典和谓词名称的谓词
上下文修改谓词ModOp
,根据上下文执行某些操作(例如使用值为writeln/1
调用a
),然后修改
ModOp
中的上下文。
然后部署perldoc perlrun
: how to execute the Perl interpreter: command line switches处理列表,而不是对象列表,而是操作列表,或更确切地说是操作名称:
inc_a(Din,X)) :- X is Din.a * 2.
nop(Din,Din).
write_then_mod(ModOp,DictFromLeft,DictToRight) :-
writeln(DictFromLeft.a),call(ModOp,DictToRight).
main :-
express(_{a:1},[inc_a,twice_a,nop],_DictOut).
express(DictIn,ModOps,DictOut) :-
foldl(
write_then_mod,% will be called with args in correct order
ModOps,DictIn,DictOut).
行得通吗?
?- main.
1
2
4
true.
有用吗?绝对灵活:
?- express(_{a:1},inc_a,_DictOut).
1
2
4
8
9
_DictOut = _9368{a:9}.
,
将let
定义为常规谓词的问题在于,您无法重新定义最外面的let
之外的变量。这是我尝试使用目标扩展的更正确版本的尝试。 (对我来说,这很有意义,因为据我所知,在类似Lisp的语言中,let
不能定义为函数,而可以定义为宏。)
%goal_expansion(let(Decl,OriginalGoal),Goal) :- %% SWI syntax
goal_expansion(let(Decl,_M,_,Goal,[]) :- %%SICStus syntax
!,expand_let(Decl,OriginalGoal,Goal).
expand_let(X,Goal) :-
var(X),!,replace(X,_Y,NewGoal),Goal=(true,NewGoal).
expand_let(X is Decl,Y,Goal=(Y is Decl,NewGoal).
expand_let(X = Decl,Goal=(Y = Decl,NewGoal).
expand_let([],Goal) :-
!,Goal=OriginalGoal.
expand_let([L|Ls],expand_let_list([L|Ls],InitGoals,Goal=(InitGoals,NewGoal).
expand_let((L,Ls),expand_let(Ls,SecondGoal),expand_let(L,SecondGoal,Goal).
expand_let_list([],true,Goal).
expand_let_list([L|Ls],(Init,InitGoals),NewGoal):-
(
var(L)
->
replace(L,Init=true
;
L=(X=Decl)
->
replace(X,Init=(Y=Decl)
;
L=(X is Decl)
->
replace(X,Init=(Y is Decl)
),expand_let_list(Ls,NewGoal).
这将重用问题中定义的replace/4
谓词。还要注意,钩子谓词在Prolog版本之间是不同的。我正在使用SICStus,它定义了goal_expansion/5
。我快速浏览了一下文档,似乎SWI-Prolog中有一个goal_expansion/2
。
我为单个let
中的多个声明引入了不同的语法:let((X1,X2),...)
定义了X1
,然后定义了X2
(因此等效于let(X1,let(X2,...))
) ,而let([X1,X2],...)
同时定义X1
和X2
(允许使用交换示例)。
以下是一些示例调用:
test1 :- let(A = 1,(
print(A),nl,(
print(A),let(A is A + 1,(
print(A),nl
))
))
)).
test2 :- A=2,let([A=B,B=A],(print(B),nl)).
test3 :- A=1,let((
A is A * 2,A is A * 2,A is A * 2
),(
print(A),nl
)),print(A),nl.
test4 :- let([A=1,B=2],(print(A-B),nl))).
test5 :- let((
[A=1,[A=B,B=A]
),(
print(A-B),nl
)).
,
这是您使用Prolog语法键入的方式:
example(X,Y) :-
X = 1,succ(X,Y).
如果您要实现其他目标,则需要更好地说明。严格在“我在做什么?”之后出现“我如何在Prolog中键入它”?
还是您真的想要在Prolog中进行这种语法嵌套?您能提供一些您认为有益的示例吗?
,可以定义一个let
谓词,该谓词以递归方式替换嵌套的let
表达式,以便可以在不重命名的情况下“重新定义”局部变量。这是实现它的一种方法:
:- initialization(main).
:- set_prolog_flag(double_quotes,chars).
replace(Subterm0,Subterm,Term0,Term) :-
( Term0 == Subterm0 -> Term = Subterm
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],maplist(replace(Subterm0,Subterm),Args0,Args),Term =.. [F|Args]
).
replace_let(Term0,Term) :-
( [Term0,Term1] = [A,(A2 is B1,C2)],(Pattern = (A1 is B1);Pattern = (A1 = B1)),P1 = let(Pattern,C1),subsumes_term(P1,A),P1=A,replace(A1,A2,C1,C2),replace_let(Term1,Term)
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],maplist(replace_let,Term =.. [F|Args]
).
let(A,B) :- replace_let(let(A,B),C),call(C).
main :-
B = 3,let(A is B+1,(
writeln(A),C is A + 1,let(A = C,(
writeln(A)
))
))
)).
此实现仍然不能与“同时”变量定义一起使用,但是可以很容易地修改replace/2
谓词以同时替换多个变量。