在 Prolog 中定义“let 表达式”

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允许同时重新定义多个变量的方式实现表达式?

Dav*_*fer 2

let本质上是一种创建(内联到源)新的本地上下文的方法,在其中评估函数(另请参阅:\xe2\x80\x9clet\xe2\x80\x9d 第一次出现在哪种编程语言中?

\n

Prolog 没有“本地上下文”——唯一的上下文是子句。变量名称仅对子句有效,并且在子句内完全可见。与函数式程序不同,Prolog 非常“扁平”。

\n

考虑main

\n
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

上下文是子句,这本质上是以下内容的“错误伪代码”:

\n
main :- 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

(现在,如果有一种方法可以创建谓词的嵌套上下文来组织代码,我会很乐意接受!)。

\n
\n

让我们进一步探索一下乐趣(因为我目前正在尝试实现 Monad Idiom 来看看这是否有意义)。

\n

您可以考虑创建变量绑定上下文的显式表示,就像编写 LISP 解释器一样。这可以使用 SWI-Prolog dicts轻松完成,它们只是函数式编程中使用的不可变映射。现在请注意,随着计算的进行,变量的值可能会变得“更精确”,只要它的任何部分仍然是“洞”,这会导致旧的深层上下文可能被当前操作修改,不知道如何思考这一点。

\n

首先定义谓词以从现有字典生成新字典,即从旧字典定义新上下文,然后代码变为:

\n
inc_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

\n

现在,您可以编写一个谓词,该谓词采用字典和上下文修改谓词的名称ModOp,执行一些取决于上下文的操作(例如使用writeln/1的值进行调用a),然后根据 修改上下文ModOp

\n

然后foldl/4在列表上部署工作,不是对象列表,而是操作列表,或者更确切地说,操作名称列表:

\n
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