עקרונות שפות תכנות אביב 2019. תרגול 11. תכנות לוגי חלק א' נושאים: מבוא לתכנות הלוגי הרלציוני. 1. סמנטיקה: אלגוריתם יוניפיקציה, עצי הוכחה. 2. תכנות לוגי, פנקטורים. 3. רשימות 4. עבודה בפרולוג נעשית באמצעות תוכנת read-evaluate-print בשם.swi ניתן למצוא פרטי התקנה באתר, בעמוד.Software למטרות דיבוג ניתן להשתמש בפרוצדורה,writeln שמקבלת פרמטר אחד תוכן להדפסה. למשל, כדי לדעת מהו הערך של משתנה X בנקודה מסויימת בתוכנית, מוסיפים שם.writeln(X) 0. שימוש ב- SWI פרולוג בחלק הזה של הקורס תשתמשו בתוכנת,SWI אינטרפרטר של שפת תכנות.Prolog תוכלו להוריד אותה מהרשת ולהריץ התקנה אוטומטית. לאחר הפתיחה תראו את המסך הבא: שם ניתן להקליד שאילתות. כדי לטעון קובץ, ניתן להשתמש בתפריט.Consult -< File קיימת גם סביבת עבודה גרפית, אותה ניתן להוריד באתר הקורס. -1-
1. מבוא לתכנות הלוגי הרלציוני. מושגים: פרוצדורה היא קבוצה סדורה של אקסיומות )עובדות וכללים( עם חתימה זהה, הכוללת את שם הפרדיקט ומספר הפרמטרים.(arity) תוכנית לוגית היא קבוצה לא סדורה של פרוצדורות )פרדיקטים(, המגדירות יחסים בתחום ההגדרה של התוכנה. האינטרפרטר של פרולוג עובד בלולאת.read-eval-print בהינתן שאילתה, הוא מנסה להוכיח אותה על סמך התוכנית:.)No )או False אם נכשל, עונה אם הצליח, והשאילתה לא כוללת משתנים, התשובה היא True )או.)Yes )למשל, בשאילתה "טעינת קובץ".( אם הצליח, והשאילתה כוללת משתנים, מציג את כל ההצבות של המשתנים של השאילתה שחושבו בבניית ההוכחה..1.2.3 parent(abraham, isaac). parent(isaac, jacob). parent(sarah, isaac). parent(jacob, joseph). דוגמה 1: תזכורת. נזכר תחילה בדוגמה שראינו בכיתה. נתונה תוכנית male(abraham). male(isaac). male(joseph). female(sarah). mother(x, Y) :- parent(x, Y), female(x). % Signature: ancestor(ancestor, Descendant)/2 % Purpose: Ancestor is an ancestor of Descendant. ancestor(a,d) :- parent(a,d). %1 ancestor(a,d) :- parent(a,p), ancestor(p,d). %2?- parent(abraham, X). X = isaac. נריץ בפרולוג את השאילתות הבאות:?- ancestor(abraham, D) D = isaac; D = jacob; D = joseph; false דוגמה 2: תוכנית למידול מעגלים לוגיים חשמליים. אנו נמדל מעגלים לוגיים באמצעות שימוש בנקודות החיבור כ"אובייקטים" )קבועים ומשתנים( בתוכנית. בתכנות לוגי מטרתנו איננה חישוב תוצאה של פונקציה, אלא בדיקת שאילתות: למשל, האם יש מעגל מסוים במערכת, ואם כן, אילו נקודות הוא כולל. -2-
הגדרות: 1. רכיב חשמלי הוא התקן פרימיטיבי במערכת, שמחבר מספר נקודות. נשתמש בשני סוגי רכיבים חשמליים: נגד וטרנזיסטור. 2. נקודת חיבור היא נקודה שאפשר לחברה לרכיב חשמלי. ישנם שלושה סוגי נקודות חיבור: נקודת הלחמה (שמקשרת שני רכיבים), נקודת מתח ונקודת הארקה. למשל, הציור לעיל מראה נקודות הלחמה n, 5..., n, 1 שתי נקודות מתח ושתי נקודות הארקה. 3. נגד (resistor) הוא רכיב בעל שני חיבורים. הוא סימטרי, לכן אין משמעות לסדר החיבורים. בציור מודגש באליפסה נגד המחבר נקודת מתח ונקודה n. 2 4. טרנזיסטור הוא רכיב בעל שלושה חיבורים, כשלכל חיבור משמעות מיוחדת. אנו נתייחס לחיבורים בסדר ימין-למטה-למעלה בציור. למשל, בטרנזיסטור המוקף בעיגול, הסדר הוא n, 2 אז,ground ובסוף n. 1 5. מעגל לוגי הוא חיבור של כמה רכיבים חשמליים, המממש פונקציה לוגית. כדי למדל את הבעיה באמצעות יחסים בתכנות לוגי, נשתמש בשיטה הידועה כ" modeling."components and connectors בעצם, התוכנית שאנו כותבים תייצג גרף כבסיס נתונים, ללא קשר למשמעות החשמלית של הדוגמה. המערכת כוללת שני סוגי :components נגד וטרנזיסטור. ה- connectors הם,n2 power, ground,,n1,n3.,n4 n5 נגד הוא component שכוללת שני,connectors ואנו מתארים אותה בתור יחס: resistor(end1, End2) טרנזיסטור הוא component המשלב,connectors 3 כשכל אחד משחק תפקיד משלו, ואנו מתארים אותו בתור יחס: transistor(gate, Source, Drain) את כל התמונה ניתן להציג באמצעות תוכנית: % Signature: resistor(end1, End2)/2 % Purpose: resistor gate resistor(power, n1). %1 resistor(power, n2). %2 resistor(n1, power). %3 resistor(n2, power). %4 מכיוון שנגד הוא יחס סימטרי, מייצגים כל נגד באמצעות שתי עובדות, אחת לכל כיוון. % Signature: transistor(gate, Source, Drain)/3 % Purpose: transistor gate transistor(n2, ground, n1). %1 transistor(n3, n4, n2). %2 transistor(n5, ground, n4). %3-3-
תזכורת להגדרות והסברים: כל פרוצדורה מתחילה בחוזה המכיל שני חלקים: חתימה )הכוללת )arity ומטרה. ground n1,,power הם קבועים. עליהם להתחיל באות קטנה. resistor הוא שם הפרדיקט. הוא מגדיר יחס בין שני הארגומנטים שלו. גם הוא קבוע. נוסחה אטומית היא מבנה בצורה (n,predicate(t,1, t כאשר t i הוא term )קבוע או משתנה(. אם 0=n, נוסחה ללא משתנים, לא כותבים גם את הסוגריים. שתי דוגמאות לנוסחה כזאת הן מובנות בשפה :)built-in( true תמיד מצליח, false תמיד נכשל. כלל הוא נוסחה שמגדירה יחס התלוי בתנאים מסוימים, כלומר, הכלל body." "head -: שקול לגרירה.head body עובדה היא הגדרת יחס שנכון ללא כל תנאי. עובדה היא מקרה פרטי של כלל: כאשר הגוף תמיד נכון, הנוסחה האטומית true וסימן הגרירה:- לא נכתבים. שאילתה היא סדרה של נוסחאות אטומיות.?- resistor(power, n1), resistor(n2, power). true ; אין יותר תשובות false משתנים מתחילים באותיות גדולות. משתנה בשם _ הינו משתנה חד פעמי.)wildcard(?- resistor(power, X). X = n1 ; X = n2 "האם קיים X, כך שהזוג (X (power, יכול להתאים ליחס הנגד?".1.2.3.4.5.6.7.8.9 נוסחה בשאילתה יכולה להצליח )אם יש לפחות הצבה אחת שמתאימה להוכחה(, או להיכשל. כל עוד קיימות אפשרויות, פרולוג עונה תשובה אחת. המשתמש יכול ללחוץ ";" כדי להמשיך לתשובה הבאה, או "Enter" כדי לסיים. המשתנים בשאילתה מכומתים כמשתני "קיים" quantified(.)existentially מעגלים לוגיים. 1. חיבור כמה רכיבים בדרך מסוימת יוצר מעגל לוגי. למשל, מעגל לוגי :not קלט פלט הוא נוצר ע"י חיבור נגד עם טרנזיסטור בדרך המצוינת בציור. הנקודה n2 היא קלט, n1 היא פלט. מעגל לוגי מממש פונקציה בוליאנית שערכה נקבע על ידי צירופים של טרנזיסטורים, נגדים ומעגלים. אפשר לתארו כיחס בין נקודות קלט ופלט. ערך נקודת הפלט נקבע על ידי ערך נקודות הקלט, ומבנה הצירופים במעגל. כל מבנה של צירופים קובע סוג של מעגל. לכן, אפשר לתאר כל סוג של מעגל כיחס התלוי במבנה הצירופים. בפרולוג זה מתואר על ידי כללים. אנו מבחינים ביחס חדש בין קלט לפלט: % Signature: not_circuit(input, Output)/2 % Purpose: logic circuit of not. -4-
not_circuit(input, Output) :- %1 Output), transistor(input, ground, גוף הכלל: resistor(power, Output). "," פרושו "וגם" המשתנים בכלל מכומתים על ידי "לכל" quantified( )universally על טווח כל הכלל. ראש הכלל נוסחה אטומית "לכל Input ולכל,Output הזוג Output) (Input, מקיים יחס,not_circuit אם השלישייה Output) (Input, ground, מקיימת יחס טרנזיסטור והזוג Output) (power, מקיים יחס נגד." בציור הראשון קיים רק מעגל אחד כזה:?- not_circuit(x, Y). X = n2, Y = n1 מעגל לוגי nand מתואר בציור הבא )קלטים מימין, פלט משמאל(. power פלט קלט 1 קלט 2-5- ground אנו מבחינים ביחס חדש בין שני קלטים לפלט: % Signature: nand_circuit(input1, Input2, Output)/3 % Purpose: logic circuit of nand nand_circuit(input1, Input2, Output) :- %1 transistor(input1, X, Output), transistor(input2, ground, X), resistor(power, Output). ניתן להתייחס למשתנים המופיעים רק בגוף הכלל כמשתני "קיים" quantified( )existentially "כל Input1, Input2 ו- Output מקיימים יחס של מעגל לוגי nand_circuit אם קיים X כך ש..." נוכל לבדוק האם קיים יחס and במערכת:?- not_circuit(x, Y), nand_circuit(in1, In2, X). X = n2, Y = n1, In1 = n3, In2 = n5 ; false ניתן להריץ בתוכנת SWI סביבת דיבוג גרפי, למשל?- gtrace, not_circuit(x, Y), nand_circuit(in1, In2, X).
?- nodebug. בסיום העבודה ניתן לכבותה באמצעות הפקודה ייתכן כי בעתיד נצטרך להריץ שאילתה כזאת שוב, על קלט אחר. לכן כדאי להפוך אותה לכלל: % Signature: and_circuit(input1, Input2, Output)/3 % Purpose: logic circuit of and and_circuit(input1, Input2, Output) :- %1 not_circuit(x, Output), nand_circuit(input1, Input2, X). 2. סמנטיקה: אלגוריתם יוניפיקציה, עצי הוכחה. 2.1 אלגוריתם יוניפיקציה עבור תכנות לוגי רלציוני. כדי למצוא הוכחה אפשרית לשאילתה, האלגוריתם answer-query עושה ניסיונות מרובים להפעיל כללים על יעד נבחר.)goal( זה נעשה בעזרת אלגוריתם יוניפיקציה,,Unify על ראש הכלל והיעד. הגדרות: X. אינו כולל את t,term הוא t הוא משתנה, X כאשר,X=t הוא ביטוי לא מעגלי מהסוג binding הצבה )substitution( היא פונקציה מקבוצה סופית של משתנים לקבוצה סופית של ביטויים.(terms) )אפשר לחשוב על הצבה כעל קבוצה סופית של bindings ללא חזרות של משתנים(. הפעלה של הצבה על נוסחה אטומית: A = not_circuit(i, I), B = not_circuit(x, Y), s={i=x}: A s = not_circuit(i, I) s = not_circuit(x, X) B s = not_circuit(x, Y) הרכבה של הצבות: s = {I=X} {X=Y} = {I=Y, X=Y} הצבה s נקראת unifier של נוסחאות B, A, אם הפעלתה על שתיהן נותנת תוצאה זהה. לדוגמה: s = {I=Y, X=Y} A s = not_circuit(y, Y) B s = not_circuit(y, Y) אלגוריתם Unify שנלמד בהרצאה, מקבל שתי נוסחאות אטומיות ומחזיר unifier כללי ביותר שלהן unifier(.)mgu, most general -6-
במקרה הנ"ל, נקבל X=Y}.s={I=Y,.s={I=5, X=5, Y=5} קיימות אפשרויות נוספות, כלליות פחות, כמו A = some(b, 3, C), B = some(a, B, D), s={a=3, B=3, C=D}. דוגמה נוספת: יש לש םי לב, שמספר הוא סמל קבוע, ואיננו מחושב כמספר. מבחינת המשמעות, 345 הוא סמל )שם( ולא מספר. 2.2 עצי הוכחה )הפעלת האלגוריתם )answer-query האינטרפרטר מחפש הוכחה לשאילתה נתונה באמצעות בנייה וסקירה של עץ ההוכחה, בו כל האפשרויות נלקחות בחשבון. מבנה העץ תלוי במדיניות בחירת המטרה ובחירת הכלל. תיאור האלגוריתם לראות כאן: מופיע בספר, כשחלקו אפשר proof-tree: if label(node) is?- true,..., true. then 1. Mark node as a Success node (leaf). A success backtracking point. 2. answers := answers {s 1 s 2... s n}, where s 1,..., s n are the substitution labels of the path from the tree-root to node. Mark node with this substitution. else 1. Goal selection: G = Gsel(label(node)). G true since Gsel does not select a true goal. 2. Variables renaming: Rename variables in every rule and fact of P (justified since all variables are universally quantified, i.e., bound). 3. Rule selection: rules = Rsel(G,P) = ( R, σ ) R P, G σ=head(r) σ apply-rules(node, G, rules) end apply-rules: if empty?(rules) then output= node, i.e., the tree rooted at node. A failure backtracking point. else assume that the first element in rules is R, σ. 1. New query construction: new_query = (replace(label(node), G, body(r))) σ. end That is, if label(node) = G 1,...,G n, n 1, G = G i, and body(r) = B 1,..., B m, m 1, then new_query = (G 1,...,G i, B 1,..., B m, G i+1,..., G n) σ. 2. New query expansion: add_branch(node, σ, number(r), proof-tree(make_node(new_query)) 3. Application of other selected rules: apply-rules(node, G, tail(rules)) -7-
?- nand_circuit(in1, In2, Out). דוגמה 3: עצי הוכחה למעגלים לוגיים. {In1=Input1_1, In2=Input2_1, Out=Output_1} {Input1_1=n2, X_1=ground, Output_1=n1} 1 1 nand_circuit(in1, In2, Out) transistor(input1_1, X_1, Output_1), transistor(input2_1, ground, X_1), resistor(power, Output_1) {Input1_1=n3, X_1=n4, Output_1=n2} 2 {Input1_1=n5, X_1=ground, Output_1=n4} 3 transistor(input2_1, ground, ground), resistor(power, n1) fail transistor(input2_1, ground, ground), transistor(input2_1, ground, n4), resistor(power, n4) resistor(power, n2) 3 { Input2_1=n5} resistor(power, n2) fail true 2 כל ענף הצלחה מהווה תשובה, שמתקבלת ע"י הרכבה של כל ההצבות על המסלול המוביל אל העלה, לפי הסדר. בעלה ההצלחה קיבלנו: {In1=Input1_1, In2=Input2_1, Out=Output_1} {Input1_1=n3, X_1=n4, Output_1=n2} {Input2_1=n5} = {In1=n3, In2=Input2_1, Out=n2, Input1_1=n3, X_1=n4, Output_1=n2} {Input2_1=n5} = {In1=n3, In2=n5, Out=n2, Input1_1=n3, X_1=n4, Output_1=n2, Input2_1=n5} בוחרים רק את המשתנים שמופיעים בשאילתה: Out=n2} {In1=n3, In2=n5, היא תשובה אפשרית )ויחידה( לשאילתה. הסבר על הצלעות היוצאות מן השורש: 1. עבור nand_circuit נעשה,rename באמצעות הוספת מספר הגדל בכל שלב: nand_circuit(input1_1, Input2_1, Output_1) :- transistor(input1_1, X_1, Output_1), transistor(input2_1, ground, X_1), resistor(power, Output_1). 2. נעשה Unify של Output_1) nand_circuit(input1_1, Input2_1, עם המטרה הנוכחית Out).nand_circuit(In1, In2, התוצאה היא mgu={in1=input1_1, In2=Input2_1, Out=Output_1} 3. גוף הכלל אחרי ההחלפה התווסף בראש המטרה הנוכחית: transistor(input1_1, X_1, Output1), transistor(input2_1, ground, X_1), resistor(power, Output_1). -8-
2.3 סוגי הרקורסיה. הגדרות: עץ הצלחה הוא עץ הוכחה שיש בו לפחות מסלול הצלחה אחד. עץ כישלון הוא עץ הוכחה שכל המסלולים בו הם מסלולי כישלון. עץ אינסופי הוא עץ הוכחה שיש בו לפחות מסלולי אינסופי אחד. לדוגמה, באמצעות הכלל p(x) :- p(y), q(x, Y).?- p(a). השאילתה תכנס ללולאה אינסופית כתוצאה מכלל הבחירה של סדר ה- goals בפרולוג. p(x) :- q(x, Y), p(y). אחרת מדובר בעץ סופי. ניתן לנסות להימנע מיצירת מסלולים אינסופיים, לדוגמה: המקרה הראשון נקרא "רקורסיה שמאלית". אין ליצור אותה בתוכניות פרולוג. המקרה השני נקרא "רקורסיית זנב". לדוגמה, העץ המצויר לעיל הוא עץ סופי, עץ הצלחה, שיש בו מסלול הצלחה אחד ושני מסלולי כישלון. הערה: מכיוון שלא ניתן לדעת האם כל המסלולים בעץ אינסופי הם מסלולי כישלון, לא קיים עץ כישלון אינסופי. שאלה: איך ישפיע שינוי סדר בחירת הכללים או בחירת ה- goals או הכללים על הרקורסיה הנ"ל? Table name: resistor Schema: End1, End2 Data: (power, n1), (power, n2), SQL Operations: (n1, power), 1) Join (n2, power). חומר השלמה: דוגמה.4 Programming SQL in Relational Logic נציג את יחסי נגד וטרנזיסטור כשתי טבלאות: Table name: transistor Schema: Gate, Source, Drain Data: (n2, ground, n1), (n3, n4, n2), (n5, ground, n4). הפעולה מאחדת שתי טבלאות על סמך עמודות משותפות. % Signature: res_join_trans(end1, X, Source, Drain)/4 % Purpose: join between resistor and transistor according % to End2 of resistor and Gate of transistor. res_join_trans(end1, X, Source, Drain) :- %1 resistor(end1, X), transistor(x, Source, Drain).?- res_join_trans(end1, X, Source, Drain). End1 = power, X = n2, Source = ground, Drain = n1 ; false. -9-
2) Transitive closure of the resistor relation % Signature: res_closure(x, Y)/2 res_closure(x, Y) :- resistor(x, Y). %1 res_closure(x, Y) :- resistor(x, Z), %2 res_closure(z, Y).?- res_closure(x, Y). X = power, Y = n1 ; X = power, Y = n2 ; X = n1, Y = power ; X = n2, Y = power ; X = power, Y = power ; X = power, Y = n1 ; X = power, Y = n2 ; X = power, Y = power ; X = power, Y = n1 ;... קיבלנו את אותן תוצאות אינספור פעמים, בגלל הסימטריות של נגד. 3) Projection. למצוא רק חלק מהמשתנים. % Signature: resistor_end1(end)/1 % Purpose: Find all options of the first resistor's end. resistor_end1(end1) :- resistor(end1, _). %1?- resistor_end1(end1). End1 = power ; End1 = power ; End1 = n1 ; End1 = n2. 4) Selection. למצוא פתרונות כשערכו של אחד מהמשתנים כבר ידוע. % Signature: tr_ground_source(x1, X2, X3)/3 % Purpose: Select all transistors with ground sources. tr_ground_source(x1, ground, X3) :- transistor(x1, X2, X3), X2 = ground. %1-10-
tr_ground_source(x1, ground, X3) :- transistor (X1, ground, X3). או, פשוט:?- tr_ground_source(y1, Y2, Y3). Y1 = n2, Y2 = ground, Y3 = n1 ; Y1 = n5, Y2 = ground, Y3 = n4. הערה: אופרטור שווה מתפקד כאופרטור יוניפיקציה בפרולוג. -11-
Logic Programming.3 Relational logic programming משמש לתיאור טיפוסים אטומיים. על מנת לתאר טיפוסים מורכבים מוסיפים סימני פונקציות, הנקראים פנקטורים.)functors( דוגמה 5: נבנה עץ בינארי ונחפש איבר מסוים בו. נייצג קדקוד בצורה Right).tree(Node, Left, עלה הוא קדקוד ששני הענפים שלו לא קיימים.)nil( % Signature: tree_member(element, Tree)/ 2 % Purpose: Testing tree membership, checks if Element is % an element of the binary tree Tree. tree_member(x, tree(x, _Left, _Right)). %1 tree_member(x, tree(_y, Left, _Right)) :- %2 tree_member(x, Left). tree_member(x, tree(_y, _Left, Right)) :- %3 tree_member(x, Right). כאן tree_member הוא שם הפרדיקט, tree הוא פנקטור המופעל על שלושה ביטויים. הם נראים זהים אך נבדלים במיקום ומטרה: פרדיקט מופיע בראש נוסחה אטומית, פנקטור הוא בנאי ערך המופיע כחלק מהנוסחה. פנקטור יכול להיות מקונן, פרדיקט לא. יכולנו להשתמש באותו שם בדיוק עבור שניהם, וזאת לא הייתה טעות. למשל: in_tree(x, in_tree(x, _Left, _Right)). התפקיד של in_tree נקבע כל פעם לפי מיקומו. א. יוניפיקציה בתכנות הלוגי. עקב קיום הפנקטורים האלגוריתם דורש יותר עבודה. לדוגמה, שלב אחרי שלב: (B,Unify(A, כאשר A = tree_member(tree(x, 10, f(x)), W) B = tree_member(tree(y, Y, Z), f(z)) 1. s={x=y} A s= tree_member(tree(y, 10, f(y)), W) B s= tree_member(tree(y, Y, Z), f(z)) 2. s={x=10, Y=10} A s= tree_member(tree(10, 10, f(10)), W) B s= tree_member(tree(10, 10, Z), f(z)) 3. s={x=10, Y=10, Z=f(10)} A s= tree_member(tree(10, 10, f(10)), W) B s= tree_member(tree(10, 10, f(10)), f(f(10))) 4. s={x=10, Y=10, Z=f(10), W=f(f(10))} A s= tree_member(tree(10,10, f(10)), f(f(10))) B s= tree_member(tree(10,10,f(10)), f(f(10))) במהלך האלגוריתם יש להפעיל,occurs check f(x) X. = אם ננסה להפעיל את האלגוריתם עבור לבדיקה שהקשירות אינן מעגליות, כמו בקשירה A = tree_member(tree(x, Y, f(x)), X) B = tree_member(tree(y, Y, Z), f(z)) נקבל f(f(y)){,}x=y, Z=f(Y), =Y וההצבה לא אפשרית. ב. חישובי שאילתות: -12-
?- tree_member(1, tree(1, nil, nil)). true?- tree_member(2, tree(1, tree(2, nil, nil), tree(3, nil, nil))). true.?- tree_member(1, tree(3, 1, 3)). false.?- tree_member(x, tree(1, tree(2, nil, nil), tree(3, nil, nil))). X=1; X=2; X=3; false tree_member(x, tree(1, tree(2, nil, nil), tree(3, nil, nil))) {X_1=1, X=1, _Left_1=tree(2, nil, nil), _Right_1=tree(3, nil, nil)} 1 {X_1=X, _Y_1=1, Left_1=tree(2, nil, nil), _Right_1=tree(3, nil, nil)} 2 {X_1=X, _Y_1=1, _Left_1=tree(2, nil, nil), Right_1=tree(3, nil, nil)} 3 {X = 1} true tree_member(x, tree(2, nil, nil)) tree_member(x, tree(3, nil, nil)) {X_2=2, X=2, _Left_2=nil, _Right_2=nil} 1 true tree_member(x, nil) {X_2=X, _Y_2=2, Left_2=nil, _Right_2=nil} 2 {X_2=X, _Y_2=2, _Left_2=nil, Right_2=nil} 3 tree_member(x, nil) {X = 2} fail fail {X_2=3, X=3, _Left_2=nil, _Right_2=nil} 1 true {X_2=X,_ Y_2=3, Left_2=nil, _Right_2=nil} 2 tree_member(x, nil) {X_2=X, _Y_2=3, _Left_2=nil, Right_2=nil} 3 tree_member(x, nil) {X = 3}?- tree_member(1, T). T = tree(1, _G445, _G446) ; T = tree(_g444, tree(1, _G449, _G450), _G446) ; T = tree(_g444, tree(_g448, tree(1, _G453, _G454), _G450), _G446) ;... fail fail -13-
נקבל את התשובה התחתונה בעץ המתקבל בעזרת ההרכבה: {X_1=X, _Y_1=1, _Left_1=tree(2, nil, nil), Right_1=tree(3, nil, nil)} {X_2=3, X=3, _Left_2=nil, _Right_2=nil} = {X_1=3, _Y_1=1, _Left_1=tree(2, nil, nil), Right_1=tree(3, nil, nil), X_2=3, X=3, _Left_2=nil, _Right_2=nil} בוחרים את.{X = 3} :X להשוואה, נבדוק את הקוד התואם ב- Scheme. ניתן לראות שנצטרך כמה פרוצדורות שונות כדי לקבל פונקציונליות שקולה. למשל, (define tree-member? (lambda (x t) (cond ((empty? t) #f) ((pair? t) (or (eq? x (car t)) (tree-member? x (cadr t)) (tree-member? x (caddr t)))) (else (eq? x t))))) תטפל רק במקרה ש- x הוא משתנה ו- t נתון. כשאנו מנסים למצוא את כל העלים שכוללים את 1, נקבל עץ הצלחה אינסופי, כשהתשובות ידועות חלקית.)partially instantiated( ניתן לראות את פעולת ה- backtracking שבה האינטרפרטר מוצא פתרון נוסף לבן שמאלי. הערה: קיימים שני סוגי backtracking הצלחה וכישלון. במקרה של הצלחה, האלגוריתם מגיע לעלה הצלחה וחוזר כדי להמשיך בחיפוש הוכחות נוספות. במקרה של כישלון, האלגוריתם לא יכול להמשיך הוכחה כי לא ניתן לעשות יוניפיקציה של ה- goal הנבחר עם אף ראש כלל, והאלגוריתם חוזר לחיפוש נתיבי הוכחה אחרים. שימוש בכלל שדורש קלט מוגדר עם משתנים מצליח לפעמים ליצור את כל התשובות האפשריות. במקרה כזה אנו אומרים כי מדובר בכלל.generator כשאנו נזכרים בקוד של רקורסיה שמאלית p(x) :- p(y), q(x, Y). ניתן להבין כי 1/p למעשה הופך ל- generator, כאשר נקרא באמצעות שאילתה ללא משתנה קשור, אם כי הוא לא מצליח ליצור אפילו את התשובה הראשונה:? - p(a). הערה נוספת: נשים לב לחשיבות פעולת ה- rename. אם היינו בוחרים להריץ את שאילתת ה- generator הנ"ל בצורה הבאה:?- tree_member(1, X). מבלי לבצע,rename לא היינו יכולים לקבל אף הוכחה עקב בעיית יוניפיקציה X היה עץ עם פנקטור tree ומספר 1 בו זמנית. -14-
חומר השלמה: דוגמה 6. בהינתן נוסחה לוגית יש לבדוק האם היא נכונה, כלומר ערכה הוא אמת או שקר. ניתן לייצג נוסחה ע"י פרימיטיביים no,yes )המיוצגות ע"י קבועים(, ופעולות not,or,and )המיוצגות ע"י יחסים(. נקבל % Signature: satisfiable(formula) / 1 % Purpose: There is a true instance % of the Boolean formula. satisfiable(yes). %1 satisfiable(and(x, Y)) :- satisfiable(x), %2 satisfiable(y). satisfiable(or(x, _)) :- satisfiable(x). %3 satisfiable(or(_, Y)) :- satisfiable(y). %4 satisfiable(not(x)) :- wrong(x). %5 % Signature: wrong(formula) / 1 % Purpose: There is a false instance % of the Boolean formula (De Morgan's laws). wrong(no). %1 wrong(or(x, Y)):- wrong(x), %2 wrong(y). wrong(and(x, _)) :- wrong(x). %3 wrong(and(_, Y)) :- wrong(y). %4 wrong(not(x)) :- satisfiable(x). %5 satisfiable(and(not(no), yes)) רקורסיה הדדית: { X_1=not(no), Y_1=yes} 2 satisfiable(not(no)), satisfiable(yes) { X_2= no} 5 1 wrong(no), satisfiable(yes) satisfiable(yes) 1 true -15-