And*_*een 5 prolog let lexical-scope
在许多函数式编程语言中,可以使用let
表达式“重新定义”局部变量:
let example =
let a = 1 in
let a = a+1 in
a + 1
Run Code Online (Sandbox Code Playgroud)
为此我找不到内置的 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)
))
))
)).
Run Code Online (Sandbox Code Playgroud)
此实现似乎不正确,因为某些变量在被替换之前已绑定。我想定义一个表达式,允许同时“重新定义”多个变量:
main :- let((A = 1, B = 2), % this will not work with the let/2 predicate that I defined
let((A=B,B=A),(
writeln(A),
writeln(B)
))
).
Run Code Online (Sandbox Code Playgroud)
是否可以以let
允许同时重新定义多个变量的方式实现表达式?
let
本质上是一种创建(内联到源)新的本地上下文的方法,在其中评估函数(另请参阅:\xe2\x80\x9clet\xe2\x80\x9d 第一次出现在哪种编程语言中?)
Prolog 没有“本地上下文”——唯一的上下文是子句。变量名称仅对子句有效,并且在子句内完全可见。与函数式程序不同,Prolog 非常“扁平”。
\n考虑main
:
main :- let(A = 1,(\n writeln(A),\n let(A is A+1,(\n writeln(A),\n let(A is A * 2,(\n writeln(A)\n ))\n ))\n )).\n
Run Code Online (Sandbox Code Playgroud)\n上下文是子句,这本质上是以下内容的“错误伪代码”:
\nmain :- f(1).\nf(A) :- writeln(A), B is A+1, g(B).\ng(A) :- writeln(A), B is A*2, h(B).\nh(A) :- writeln(A).\n
Run Code Online (Sandbox Code Playgroud)\n?- main.\n1\n2\n4\ntrue.\n
Run Code Online (Sandbox Code Playgroud)\n这let
并没有真正带来太多好处。它似乎允许人们避免必须手动重新标记“右侧”的变量is
,但这不值得。
(现在,如果有一种方法可以创建谓词的嵌套上下文来组织代码,我会很乐意接受!)。
\n让我们进一步探索一下乐趣(因为我目前正在尝试实现 Monad Idiom 来看看这是否有意义)。
\n您可以考虑创建变量绑定上下文的显式表示,就像编写 LISP 解释器一样。这可以使用 SWI-Prolog dicts轻松完成,它们只是函数式编程中使用的不可变映射。现在请注意,随着计算的进行,变量的值可能会变得“更精确”,只要它的任何部分仍然是“洞”,这会导致旧的深层上下文可能被当前操作修改,不知道如何思考这一点。
\n首先定义谓词以从现有字典生成新字典,即从旧字典定义新上下文,然后代码变为:
\ninc_a(Din,Din.put(a,X)) :- X is Din.a + 1.\ntwice_a(Din,Din.put(a,X)) :- X is Din.a * 2.\n\nmain :- f(_{a:1}).\nf(D) :- writeln(D.a), inc_a(D,D2), g(D2).\ng(D) :- writeln(D.a), twice_a(D,D2), h(D2).\nh(D) :- writeln(D.a).\n
Run Code Online (Sandbox Code Playgroud)\n已进入通过调用编织的A
字典内部。D
现在,您可以编写一个谓词,该谓词采用字典和上下文修改谓词的名称ModOp
,执行一些取决于上下文的操作(例如使用writeln/1
的值进行调用a
),然后根据 修改上下文ModOp
。
然后foldl/4
在列表上部署工作,不是对象列表,而是操作列表,或者更确切地说,操作名称列表:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.\ntwice_a(Din,Din.put(a,X)) :- X is Din.a * 2.\nnop(Din,Din).\n\nwrite_then_mod(ModOp,DictFromLeft,DictToRight) :-\n writeln(DictFromLeft.a),\n call(ModOp,DictFromLeft,DictToRight).\n\nmain :- \n express(_{a:1},[inc_a,twice_a,nop],_DictOut).\n\nexpress(DictIn,ModOps,DictOut) :-\n foldl(\n write_then_mod, % will be called with args in correct order\n ModOps,\n DictIn,\n DictOut).\n
Run Code Online (Sandbox Code Playgroud)\n有效吗?
\n?- main.\n1\n2\n4\ntrue.\n
Run Code Online (Sandbox Code Playgroud)\n有用吗?它绝对是灵活的:
\n?- express(_{a:1},[inc_a,twice_a,twice_a,inc_a,nop],_DictOut).\n1\n2\n4\n8\n9\n_DictOut = _9368{a:9}.\n
Run Code Online (Sandbox Code Playgroud)\n