การกำหนด“ let expression” ใน Prolog

Oct 15 2020

ในภาษาโปรแกรมที่ใช้งานได้หลายภาษาคุณสามารถ "กำหนดตัวแปรท้องถิ่น" ใหม่ได้โดยใช้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นิพจน์ในลักษณะที่อนุญาตให้มีการกำหนดตัวแปรหลายตัวในเวลาเดียวกัน?

คำตอบ

2 DavidTonhofer Oct 15 2020 at 18:37

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}.
2 jnmonette Oct 18 2020 at 08:38

ปัญหาเกี่ยวกับการกำหนด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
               )).
TA_intern Oct 16 2020 at 08:44

นี่คือวิธีที่คุณจะพิมพ์โดยใช้ไวยากรณ์ Prolog:

example(X, Y) :-
    X = 1,
    succ(X, Y).

หากเป็นอย่างอื่นที่คุณพยายามจะบรรลุคุณต้องอธิบายให้ดีขึ้น "ฉันจะพิมพ์ใน Prolog ได้อย่างไร" ตามหลัง "ฉันกำลังทำอะไร" อย่างเคร่งครัด


หรือว่าคุณต้องการรูปแบบการซ้อนใน Prolog จริงๆ? คุณช่วยยกตัวอย่างที่คุณคิดว่าเป็นประโยชน์ได้ไหม

AndersonGreen Oct 16 2020 at 22:53

เป็นไปได้ที่จะกำหนดเพรดิเคต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สามารถแก้ไขเพรดิเคตเพื่อแทนที่ตัวแปรหลายตัวพร้อมกันได้อย่างง่ายดาย