Menu
CoddyTech

מטא־מתודת __newindex

חלק מהיחידה לוגיקה וזרימת התוכנית במסלול ה-Lua של Coddy. שיעור 19 מתוך 54.

למדת איך __index קובע מה קורה כשמנסים לקרוא מפתח שלא קיים בטבלה. עכשיו תגלו את המקבילה שלו: __newindex, שקובע מה קורה כשמשייכים ערך למפתח שעדיין לא קיים בטבלה.

מטא־מתודת __newindex מופעלת בכל פעם שמנסים להקצות ערך למפתח שאינו קיים כרגע בטבלה. כך אפשר ליירט שינויים ולשלוט בהם לפני שהם מתבצעים.

הנה דוגמה פשוטה שמונעת הוספה של מפתחות חדשים לטבלה:

local readOnly = {}
local meta = {
    __newindex = function(table, key, value)
        print("Cannot add key: " .. key)
    end
}

setmetatable(readOnly, meta)

readOnly.name = "Alice"  -- פלט: Cannot add key: name
print(readOnly.name)     -- פלט: nil

כשאתה מנסה להגדיר readOnly.name = "Alice", Lua מזהה ש-name לא קיים בטבלה. במקום להוסיף אותו, Lua קוראת לפונקציה __newindex עם שלושה ארגומנטים: הטבלה עצמה, המפתח שמוגדר והערך שמוקצה.

בדוגמה הזאת, הפונקציה פשוט מדפיסה הודעה ולא באמת שומרת שום דבר.

אפשר גם להשתמש ב-__newindex כדי לאמת נתונים לפני שמאפשרים להכניס אותם לטבלה שלך:

local player = {}
local meta = {
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            rawset(t, key, value)
        end
    end
}

setmetatable(player, meta)

player.health = "high"  -- פלט: Health must be a number!
player.health = 100     -- מותר, נשמר באמצעות rawset
player.health = "high"  -- אין הודעה: health קיים כעת

שימו לב לשימוש ב־rawset() בדוגמה השנייה. הפונקציה הזאת עוקפת את המטא־טבלה ומגדירה את הערך ישירות בטבלה.

בלעדיו, הגדרת הערך תפעיל שוב את __newindex, וכך תיווצר לולאה אינסופית.

מכיוון ש־__newindex מופעל רק עבור מפתחות שהטבלה לא מכילה, הבדיקה הזו מגינה על ההשמה הראשונה של כל מפתח. לאחר ש־rawset שמר את health, השמות מאוחרות יותר משנות אותו ישירות. כדי לבדוק כל השמה, השאר את הטבלה עצמה ריקה ושמור את הערכים בטבלה שנייה, שממנה היא קוראת באמצעות __index:

local data = {}
local player = setmetatable({}, {
    __index = data,
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            data[key] = value
        end
    end
})

player.health = 100
player.health = "high"  -- פלט: Health must be a number!
print(player.health)    -- פלט: 100

ל־player אף פעם אין מפתח משלו, ולכן כל השמה עוברת דרך __newindex, וכל קריאה עוברת דרך __index אל data.

challenge icon

אתגר

קל

כתבו פונקציה createValidatedTable שמקבלת key ו-value ומחזירה טבלה שמקבלת רק ערכי מחרוזת עבור מפתחות חדשים.

צרו טבלה ריקה והצמידו לה מטא-טבלה עם __newindex שבודקת את סוג הערך. אם מישהו מנסה להוסיף ערך שאינו מחרוזת, השתמשו ב-rawset() כדי לאחסן במקומו את המחרוזת "invalid". עבור ערכי מחרוזת, אחסנו אותם כרגיל באמצעות rawset().

לוגיקה:

  • צרו טבלה ריקה
  • צרו מטא-טבלה עם __newindex שמקבלת את הטבלה, המפתח והערך
  • בתוך __newindex, בדקו אם הערך הוא מחרוזת באמצעות type(value) == "string"
  • אם הערך הוא מחרוזת, השתמשו ב-rawset(t, key, value) כדי לאחסן אותו
  • אם הערך אינו מחרוזת, השתמשו ב-rawset(t, key, "invalid") כדי לאחסן במקומו את "invalid"
  • הצמידו את המטא-טבלה לטבלה באמצעות setmetatable()
  • השתמשו ב-rawset() כדי להוסיף לטבלה את זוג המפתח-ערך ההתחלתי (תוך עקיפת המטא-טבלה)
  • החזירו את הטבלה

פרמטרים:

  • key (מחרוזת): המפתח ההתחלתי שיש להוסיף לטבלה
  • value (מחרוזת): הערך ההתחלתי שיש להוסיף לטבלה

ערך מוחזר: טבלה עם בדיקה שמקבלת רק ערכי מחרוזת, המכילה את זוג המפתח-ערך ההתחלתי (טבלה)

נסו בעצמכם

function createValidatedTable(key, value)
    -- כתבו כאן את הקוד
end
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה לוגיקה וזרימת התוכנית

תרגלו בעצמכם: קומפיילר Lua אונליין