
אתר ללימוד מקיף> פרק 5 - משתנים לקסיקלים
פרק 5 - משתנים לקסיקלים
הנושאים בפרק זה:
5.1.
let ו-*let
5.2.
fluid-let
למשתנים ב-Scheme יש טווח הכרה, כלומר, הם מוכרים לתבניות הנמצאות בקטע מסוים רציף בתוכנית. טווח ההכרה
של משתנים גלובליים הוא כל התוכנית.
טווח ההכרה של משתנים מקומיים הוא גוף הפרוצדורה. המשתנים מהווים פרמטרים של הפרוצדורה lambda הנקשרים
בכל פעם שקוראים לה.
לדוגמא,
(define add2 (lambda (x) (+ x 2)))
(define x 9)
x => 9
(add2 3) => 5
(add2 x) => 11
x => 9
|
בדוגמא שלנו x הוא משתנה כללי וכן ישנו משתנה מקומי x, האחרון הוצג לראשונה ע"י הפרוצדורה add2. המשתנה
הגלובלי ערכו תמיד 9 .המשתנה המקומי x ניקשר לערך 3 בקריאה הראשונה של add2 ובקריאה השניה הוא ניקשר
למשתנה הגלובלי x שערכו 9.
התבנית !set משנה הערך של המשתנה הגלובלי x ע"י:
!set שנתה את ערכו של x ל-20 מכיוון שהוא בטווח ההכרה של !set אך אם !set היתה בתוך גוף הפרוצדורה
add2, היא הייתה משנה את ה-x המקומי.
(lambda (x)
(define add2
(set! x (+ x 2))
x))
|
בדוגמא הזו !set מוסיפה 2 למשנה המקומי x ומחזירה את הערך. (למעשה הפרוצדורה הזו לא שונה מהפרוצדורה
add2 הקודמת.) אנו יכולים לקרוא ל-add2 על המשתנה הגלובלי x:
(המשתנה הגלובלי x הוא כעת 20 ולא 9!!! )
הפרוצדורה !set שנמצאת בתוך add2 משפיעה רק על המשתנה המקומי ש add2 משתמשת בו. המשתנה הגלובלי x
לא מושפע מהפרוצדורה !set.
יש לשים לב שדנו בנושא זה משום שכינינו את שני המשתנים באותו שם. כאשר אנו עושים זאת אנו מאפילים על
משתנה אחר .בדוגמא שלנו המשתנה המקומי x מאפיל על המשתנה הגלובלי.
גוף הפרוצדורה יכול לגשת ולשנות את המשתנים בטווח ההכרה שלו, בתנאי שמשתנים אחרים לא מאפילים עליהם
- תופעה זו יכולה לתת תוצאות מעניינות .
(define counter 0)
(define bump-counter
(lambda ()
(set! counter (+ counter 1))
counter))
|
הפרוצדורה bump-counter נקראת thunk. הפונקציה יכולה לשנות רק משתנה גלובלי ולא מקומי, במקרה שלנו היא
מקדמת את ה- counter ב-1.
נתבונן בכמה דוגמאות:
(bump-counter) => 1
(bump-counter) => 2
(bump-counter) => 3
|
5.1 'let' ו-'*let'
משתנים מקומיים ניתנים להצגה מבלי ליצור פרוצדורה. התבנית של let מאפשרת להציג רשימה של משתנים מקומיים,
כלומר:
(let ((x 1)
(y 2)
(z 3))
(list x y z))
=> (1 2 3)
|
כמו ב-lambda בגוף ה-let המשתנה המקומי מאפיל על המשתנה הגלובלי.
בדוגמא שלנו האיתחולים של x ל-1, y ל-2 ו-z ל-3 לא נחשבים לגוף הפרוצדורה. לכן התייחסות ל-x באיתחול
תתייחס למשתנה הגלובלי ולא למקומי.
(let ((x 1)
(y x))
(+ x y))
=> 21
|
זאת מכיוון ש-x מאותחל ב-1 ו-y מאותחל ל-x הגלובלי (שהוא 20).
לעתים יותר נוח לאתחל את המשתנים ברצף, כך שהאתחול של המשתנה האחרון תתרחש בטוח ההכרה של שאר המשתנים
הקודמים התבנית *let עושה זאת:
(let* ((x 1)
(y x))
(+ x y))
=> 2
|
האתחול של x ו-y מתייחסים ל-x המקומי. הדוגמא הקודמת היא למעשה קיצור של התוכנית הנ"ל:
(let ((x 1))
(let ((y x))
(+ x y)))
=> 2
|
הערכים שקשורים למשתנים יכולים להיות פרוצדורות. לדוגמא,
(let ((cons (lambda (x y) (+ x y))))
(cons 1 2))
=> 3
|
בגוף הפרוצדורה let, cons מוסיפה לארגומנטים שלה. מחוץ לפרוצדורה cons ממשיכה לייצר dotted - pairs.
5.2 fluid - let
משתנה מילולי מוכר בכל טווח ההכרה שלו בתנאי שמשתנה אחר לא מאפיל עליו. לעתים זה מועיל להציב במשתנה
המילולי ערך מסוים באופן זמני ,לשם כך נשתמש בתבנית *fluid-let
(fluid-let ((counter 99))
(display (bump-counter)) (newline)
(display (bump-counter)) (newline)
(display (bump-counter)) (newline))
|
ישנו דמיון ל-let אך ישנו הבדל משמעותי, במקום להאפיל על המשתנה הגלובלי counter, מציבים בו באופן זמני
99, לפני שממשיכים עם הפרוצדורה, ולכן התוצאה תהיה:
לאחר שהפרוצדורה סיימה את תפקידה הערך של המשתנה הכללי חוזר לקדמותו.
כלומר:
יש לשים לב של-fluid-let יש אפקט שונה מ-let. fluid-let לא מגדירה משתנים חדשים כמו let. היא רק משנה את
הקישורים של משתנים קיימים והשינוי תקף כל עוד הפרוצדורה מתבצעת. על מנת להשתכנע נתבונן בתוכנית הבאה:
(let ((counter 99))
(display (bump-counter)) (newline)
(display (bump-counter)) (newline)
(display (bump-counter)) (newline))
|
בתוכנית זו החלפנו את fluid-let ב-let ותוצאת הריצה היא:
כלומר, המשתנה הגלובלי counter שאותחל ב-3 מעודכן בכל קריאה ל-bump-counter.
המשתנה counter השני שאותחל ל - 99 ,לא מושפע מהקריאות ל- bump-counter, למרות שהקריאות הן בטווח ההכרה
של המשתנה המקומי ,הגוף של bump-counter לא נמצא בטווח זה .הפרוצדורה מתייחסת ל-counter הגלובלי
שערכו הסופי הוא 6.
fluid-let - היא תבנית מיוחדת במינה. ניתן לעיין בפרק 8.3 בהגדרה שלה.