כדי להבין כיצד עובד האבלואציה של ביטויים בOCaml. לאחר מכן נבין כיצד נכנסים closures לתמונה הזאת בשביל לאפשר תכונות מסוימות.
נסתכל על קטע הקוד הפשוט הבא
let x = 1 in
if true then 2 else x
אנחנו יודעים שהמנגנון של פירוש הסמנטיקה של OCaml הוא מבוסס על substitutions כלומר הinterpreter יראה את קטע הקוד הבא ויבצע החלפה של כל המקומות שבהם מופיע x בתוך קטע הקוד בערך שלו 1 ונקבל if true then 2 else 1. המנגנון הזה טוב אבל הוא לא מושלם, שכן היינו יכולים לדעת מניתוח קוד בצורה אחרת ש if true תמיד מתקיים ופשוט להחזיר 2 על כל הביטוי. בדוגמה הזאת זה נראה משהו זניח, אבל אם זה היה קורה בתוך קוד שיש לו לולאה מאוד ארוכה המנגנון הזה היה יכול לעזור מאוד מבחינת יעילות.
נרצה לממש אינטרפטציה עצלה בדומה לשפת while. כלומר מבוססת מצבים.
סביבה:
היא רשימה מהצורה
הגדים:
יחס ה evaluation מסומן כך
נסמן-
דוגמה:
נראה ש let x = 3 in x
איך לוקחים את הנ״ל לעולם של פונקציות.
כדי לעשות זאת ננסה להוסיף כלל חדש של application
במילים, כדי שההפעלה של
נשמע הגיוני, אבל נראה ש הכלל הזה לא עובד באמצעות הניסיון שלנו לבצע את הevaluation על הביטוי הבא
קל לראות שיש כאן בעיה של scopes. שכן איזה x חוזר מהפונקציה f, החיצוני או הפנימי? אם נריץ את הקוד הזה בOCaml נקבל שהפלט לדבר הזה הוא 1.
כעת ננתח לפי הכלל החדש APP שהגדרנו , אנחנו נראה שבסביבה הריקה הביטוי הנ״ל (נסמנו e) יוערך ל 2. לשם הנוחות נסמן את הביטוי שאחרי ה in הראשון כ
כמעט סיימנו, הגענו ל
ניתן לראות שבסוף קיבלנו ש x נגזר ל2 בגלל הכלל VAR כיוון ש
מה הבעיה בכלל הזה?
ההבדל העיקרי הוא ש OCaml עובד לפי static scoping כלומר ההפנייה למשתנה היא תמיד ה top level scope ובהגדרה שלנו השתמשנו ב dynamic scoping כלומר לוקחים את הערך העדכני ביותר מהסביבה (E(x)).
נגדיר את זה-
סקופ דינמי : הערכים של המשתנים בגוף של הפונקציה מקושר לפי הסביבה שבה היא רצה
סקופ סטטי : הערכים של המשתנים בגוף של הפונקציה מקושר לפי הסביבה שבה היא הוגדרה.
כדי לטפל בבעייה הזאת, נרצה להגדיר מבנה שונה מהסביבה שלא מתייחס לפונקציה כערך ומאפשר לנו לדעת לאן ממופים כל המשתנים הלא קשורים שמופיעים בה מראש, לפני הפעלת הפונקציה.
כמעט ואין שפות היום שמשתמשות בסקופ דינמי וגם אלו מאפשרות גם סקופ סטטי למשל lisp, racket ו perl
כאן נכנס הclosure לתמונה. נגדיר אותו כזוג סדור מהצורה <env,exp> כך ש
כעת נוכל לנסח את הכלל APP מחדש
אז כעת השוני העיקרי הוא שאנחנו עובדים עם
כדי לוודא שניתן לגזור פונקציה רק בסביבה שבה היא הוגדרה נשתמש בכלל FUN :
זה בעצם כלל שדומה ל const רק שהפעם זה אומר שבמקום להגיד שזה ממופע לעצמו זה ממופע לזוג שהגדרנו וככה מחזיקים את הסביבה שהייתה בזמן שהפונקציה הוגדרה.
אם נחזור כעת, לדוגמה שלנו נוכל להראות ש e ממופה ל1 כמו ב OCaml

נזכיר שהsyntax של פונקציה רקורסיבית הוא מהצורה
הבעיה בהגדרה ריקורסיבית עם מה שעשינו זה שאנחנו צריכים להגדיר לאן כל ערך בתוך הפונקציה f ממופה בסביבה שבה הפונקציה הוגדרה, הבעיה עם זה היא שf מכיל את עצמו וזה עלול מיפוי מהצורה:

כלל ההיסק של let rec יהיה
בעצם אומרים שהביטוי התחתון בסביבה E ימופה ל v אם
ניקח את הדוגמה הבאה
נסמן את הביטוי עד ה in ב e. כמו כן נגדיר את שתי הסביבות הבאות
אם כן נתחיל לפתח את כללי הגזירה כמה שאפשר עם הנתונים שיש ברשותנו
כדי לפתוח את הכלל המסומן באדום, נצטרך להגדיר מספר כללים נוספים שכן e הוא ביטוי מהצורה if else. כמו כן יש לנו בפנים ביטויים אריתמטיים.

כעת עם הכללים האלה נוכל להמשיך לפתח את הגזירה
כעת נפתח גם הביטוי המסומן
ונפתח את הביטוי האחרון (באדום)
וסיימנו.
כדי לסכם:
כדי לחשב את let x = e1 in e2 בסביבה כלשהי env
1) Evaluate - נחשב את
2) Extend - הרחבה של הסביבה כך שהיא מכילה את ההצמדה של המשתנה
3) Evaluate - נחשב שוב פעם , את
4) מחזירים את
נדגים על הביטוי let f= fun x -> x in f 0.
הסביבה היא
בעצם יצרנו ״סביבת עבודה״ או closure שבה התוצאה x ממופה לערך מסויים. נדגים על קוד קצת יותר מורכב כדי להבין מה הכוונה.
let x = 1 in
let f = fun y -> x in
let x = 2 in
f 0
הפונקציה הזאת באוקמל מחזירה 1 (בגלל הסקופ הסטטי שדיברנו עליו, כלומר הערך של x בזמן הגדרת הפונקציה הוא הקובע)
אם ניקח את קוד c השקול נקבל
{
int x = 1
{
int f(int y)
{
return x;
}
{
int x = 2;
printf("%d", f(0));
}
}
}
בקוד הזה, יוחזר 2 כי הscope הוא דינמי ומה שחשוב זה מה הערך של x בזמן הקריאה לפונקציה.
כפי שאמרנו אנחנו נגדיר את הפונקציות שלנו כclosure כלומר מכילות גם את קטע הקוד של הפונקציה וגם את הסביבה שנשמרת בזמן הגדרת הפונקציה.
דוגמה 1
let x = 1 in
let f = fun y -> x in
let x = 2 in
let z = f 0 in z
שורה 2 יוצרת את הclosure :
בשורה 4 קוראים ל closure עם הארגומנט 0 אבל הראנו בכללי הגזירה שמסתכלים על הסביבה שבה הפונקציה הוגדרה ולכן z=1.
דוגמה 2
let x = 1 in
let f y = x + y in
let x = 3 in
let y = 4 in
let z = f(x+y) in z
בשורה 2 יצרנו closure של
בסקופ דינמי- היינו מחשבים את z לפי הערך של x כאשר הפונקציה נקראה ולכן היינו מקבלים
נשים לב שאם שפה מסויימת לא מוצאת בscope מסויים משתנה היא בדר״כ כלל תלך לscope שמעליה כדי לחפש את ההצהרה , רק אם היא לא תמצא בscope הגלובלי תזרק שגיאה. התנהגות כזאת נקראת scope chain search