问题
In many functional programming languages, it is possible to "redefine" local variables using a let
expression:
let example =
let a = 1 in
let a = a+1 in
a + 1
I couldn't find a built-in Prolog predicate for this purpose, so I tried to define a let
expression in this way:
:- 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)
))
))
)).
This implementation appears to incorrect, since some of the variables are bound before being replaced. I want to define an expression that would allow more than one variable to be "redefined" simultaneously:
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)
))
).
Is it possible to implement a let
expression in a way that allows several variables to be redefined at the same time?
回答1:
let
is essentially a way of creating (inline to the source) a new, local context in which to evaluate functions (see also: In what programming language did “let” first appear?)
Prolog does not have "local contexts" - the only context is the clause. Variables names are only valid for a clause, and are fully visible inside the clause. Prolog is, unlike functional programs, very "flat".
Consider the main
:
main :- let(A = 1,(
writeln(A),
let(A is A+1,(
writeln(A),
let(A is A * 2,(
writeln(A)
))
))
)).
Context being clauses, this is essentially "wrong pseudo code" for the following:
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.
The let
doesn't really bring much to the table here. It seems to allow one to avoid having to manually relabel variables "on the right" of the is
, but that's not worth it.
(Now, if there was a way of creating nested contexts of predicates to organize code I would gladly embrace that!).
Let's probe further for fun (and because I'm currently trying to implement the Monad Idiom to see whether that makes sense).
You could consider creating an explicit representation of the context of variable bindings, as if you were writing a LISP interpreter. This can be done easily with SWI-Prolog dicts, which are just immutable maps as used in functional programming. Now note that the value of a variable may become "more precise" as computation goes on, as long as it has any part that is still a "hole", which leads to the possibility of old, deep contexts getting modified by a current operation, not sure how to think about that.
First define the predicate to generate a new dict from an existing one, i.e. define the new context from the old one, then the code becomes:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.
twice_a(Din,Din.put(a,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,D2), h(D2).
h(D) :- writeln(D.a).
The A
has gone inside the dict D
which is weaved through the calls.
You can now write a predicate that takes a dict and the name of a
context-modifying predicate ModOp
, does something that depends on the context (like calling writeln/1
with the value of a
), then modifies the
context according to ModOp
.
And then deploy foldl/4 working over a list, not of objects, but of operations, or rather, names of operations:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.
twice_a(Din,Din.put(a,X)) :- X is Din.a * 2.
nop(Din,Din).
write_then_mod(ModOp,DictFromLeft,DictToRight) :-
writeln(DictFromLeft.a),
call(ModOp,DictFromLeft,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).
Does it work?
?- main.
1
2
4
true.
Is it useful? It's definitely flexible:
?- express(_{a:1},[inc_a,twice_a,twice_a,inc_a,nop],_DictOut).
1
2
4
8
9
_DictOut = _9368{a:9}.
回答2:
The issue with defining let
as a normal predicate is that you can't redefine variables that appear outside the outermost let
. Here is my attempt at a more correct version, which uses goal expansion. (To me it makes sense, because as far as I know, in lisp-like languages, let
cannot be defined as a function but it could be defined as a macro.)
%goal_expansion(let(Decl,OriginalGoal),Goal) :- %% SWI syntax
goal_expansion(let(Decl,OriginalGoal), _M, _, Goal, []) :- %%SICStus syntax
!,
expand_let(Decl,OriginalGoal,Goal).
expand_let(X, OriginalGoal, Goal) :-
var(X),
!,
replace(X,_Y,OriginalGoal,NewGoal),
Goal=(true,NewGoal).
expand_let(X is Decl, OriginalGoal, Goal) :-
var(X),
!,
replace(X,Y,OriginalGoal,NewGoal),
Goal=(Y is Decl,NewGoal).
expand_let(X = Decl, OriginalGoal, Goal) :-
var(X),
!,
replace(X,Y,OriginalGoal,NewGoal),
Goal=(Y = Decl,NewGoal).
expand_let([],OriginalGoal, Goal) :-
!,
Goal=OriginalGoal.
expand_let([L|Ls],OriginalGoal, Goal) :-
!,
expand_let_list([L|Ls],OriginalGoal,InitGoals,NewGoal),
Goal=(InitGoals,NewGoal).
expand_let((L,Ls),OriginalGoal, Goal) :-
!,
expand_let(Ls,OriginalGoal, SecondGoal),
expand_let(L,SecondGoal, Goal).
expand_let_list([],Goal,true,Goal).
expand_let_list([L|Ls],OriginalGoal,(Init,InitGoals),NewGoal):-
(
var(L)
->
replace(L,_,OriginalGoal,SecondGoal),
Init=true
;
L=(X=Decl)
->
replace(X,Y,OriginalGoal,SecondGoal),
Init=(Y=Decl)
;
L=(X is Decl)
->
replace(X,Y,OriginalGoal,SecondGoal),
Init=(Y is Decl)
),
expand_let_list(Ls,SecondGoal,InitGoals,NewGoal).
This is reusing the replace/4
predicate defined in the question. Note also that the hook predicate differs between Prolog versions. I am using SICStus, which defines goal_expansion/5
. I had a quick look at the documentation and it seems that SWI-Prolog has a goal_expansion/2
.
I introduced a different syntax for multiple declarations in a single let
: let((X1,X2),...)
defines X1
, then defines X2
(so is equivalent to let(X1,let(X2,...))
), while let([X1,X2],...)
defines X1
and X2
at the same time (allowing the swap example).
Here are a few example calls:
test1 :- let(A = 1,(
print(A),nl,
let(A is A+1,(
print(A),nl,
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],let([A=B,B=A],(print(A-B),nl))).
test5 :- let((
[A=1,B=2],
[A=B,B=A]
),(
print(A-B),nl
)).
回答3:
This is how you would type this in using Prolog syntax:
example(X, Y) :-
X = 1,
succ(X, Y).
If it is something else you are trying to achieve, you need to explain better. "How do I type it in Prolog" comes strictly after "What am I doing?"
Or is it that you really want this kind of syntactic nesting in Prolog? Could you provide a couple of examples where you think it is beneficial?
回答4:
It's possible to define a let
predicate that recursively replaces nested let
expressions, so that local variables can be "redefined" without being renamed. This is one way to implement it:
:- 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, Args0, Args),
Term =.. [F|Args]
).
let(A,B) :- replace_let(let(A,B),C),call(C).
main :-
B = 3,
let(A is B+1,(
writeln(A),
let(A is A + 1,(
writeln(A),
C is A + 1,
let(A = C,(
writeln(A)
))
))
)).
This implementation still doesn't work with "simultaneous" variable definitions, but the replace/2
predicate could easily be modified to replace several variables simultaneously.
来源:https://stackoverflow.com/questions/64375536/defining-let-expressions-in-prolog