การกำหนด“ let expression” ใน Prolog
ในภาษาโปรแกรมที่ใช้งานได้หลายภาษาคุณสามารถ "กำหนดตัวแปรท้องถิ่น" ใหม่ได้โดยใช้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(A),
writeln(B)
))
).
เป็นไปได้หรือไม่ที่จะใช้letนิพจน์ในลักษณะที่อนุญาตให้มีการกำหนดตัวแปรหลายตัวในเวลาเดียวกัน?
คำตอบ
letโดยพื้นฐานแล้วเป็นวิธีการสร้าง (อินไลน์ไปยังแหล่งที่มา) บริบทใหม่ในท้องถิ่นที่ใช้ในการประเมินฟังก์ชัน (ดูเพิ่มเติม: ภาษาโปรแกรมใดที่ "ให้" ปรากฏขึ้นก่อน )
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-เปิดฉากdictsซึ่งเป็นแผนที่เพียงแค่เปลี่ยนรูปที่ใช้ในการเขียนโปรแกรมการทำงาน ตอนนี้โปรดทราบว่าค่าของตัวแปรอาจ "แม่นยำมากขึ้น" เมื่อการคำนวณดำเนินต่อไปตราบใดที่ยังมีส่วนใดส่วนหนึ่งที่ยังคงเป็น "รู" ซึ่งนำไปสู่ความเป็นไปได้ที่บริบทที่เก่าและลึกจะถูกแก้ไขโดยการดำเนินการปัจจุบัน ไม่แน่ใจว่าจะคิดได้อย่างไร
ขั้นแรกให้กำหนดเพรดิเคตเพื่อสร้างคำสั่งใหม่จากที่มีอยู่กล่าวคือกำหนดบริบทใหม่จากอันเก่าจากนั้นโค้ดจะกลายเป็น:
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).
Aได้หายไปภายใน Dict Dซึ่งเป็นทอผ่านสาย
ตอนนี้คุณสามารถเขียนคำกริยาที่ใช้ Dict และชื่อของภาคบริบทการปรับเปลี่ยนที่ModOpไม่ได้เป็นสิ่งที่ขึ้นอยู่กับบริบท (ชอบเรียกร้องwriteln/1ที่มีค่าของa) ModOpแล้วปรับเปลี่ยนบริบทตาม
จากนั้นปรับใช้foldl/4การทำงานกับรายการไม่ใช่ของวัตถุ แต่เป็นการดำเนินการหรือชื่อของการดำเนินการ:
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).
ได้ผลหรือไม่?
?- main.
1
2
4
true.
มีประโยชน์หรือไม่ มีความยืดหยุ่นแน่นอน:
?- express(_{a:1},[inc_a,twice_a,twice_a,inc_a,nop],_DictOut).
1
2
4
8
9
_DictOut = _9368{a:9}.
ปัญหาเกี่ยวกับการกำหนดletเป็นเพรดิเคตปกติคือคุณไม่สามารถกำหนดตัวแปรที่ปรากฏภายนอกสุดletใหม่ได้ นี่คือความพยายามของฉันในเวอร์ชันที่ถูกต้องมากขึ้นซึ่งใช้การขยายเป้าหมาย (สำหรับฉันแล้วมันสมเหตุสมผลแล้วเพราะเท่าที่ฉันรู้ในภาษาที่คล้ายเสียงกระเพื่อมletไม่สามารถกำหนดเป็นฟังก์ชันได้ แต่สามารถกำหนดเป็นมาโครได้)
%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).
นี่คือการนำเพรดิเคตที่replace/4กำหนดไว้ในคำถามกลับมาใช้ โปรดทราบว่าเพรดิเคตของ hook จะแตกต่างกันระหว่างเวอร์ชัน 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,
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
)).
นี่คือวิธีที่คุณจะพิมพ์โดยใช้ไวยากรณ์ 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, 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)
))
))
)).
การใช้งานนี้ยังใช้ไม่ได้กับการกำหนดตัวแปร "พร้อมกัน" แต่replace/2สามารถแก้ไขเพรดิเคตเพื่อแทนที่ตัวแปรหลายตัวพร้อมกันได้อย่างง่ายดาย