meta class 2

לאחר הבעיה להסביר את meta class בפעם הראשונה, זהו עוד סיבוב שמנסה להסביר את הפעולה.

אז נתחיל מההתחלה: מה זה meta class ?

ובכן בשביל זה נבין מה היא מאפשרת קודם. כאשר אני מדבר על meta class אני מדבר על כך שאני מספק טיפוס נתונים שמאפשר למשתנה להחזיק מחלקה. הכוונה היא לא להחזיק ערך או אתחול של מחלקה (אובייקא/מופע) או כל דבר אחר שקשור למחלקה, אלא את המחלקה עצמה. ניתן גם להעביר כל מחלקה שיורשת מהמחלקה שאותה לקחנו בתור meta class.

כלומר אני יכול להעביר בתור פרמטר מסויים מחלקה במקום מופע שלה.

עכשיו נשאלת השאלה למה זה טוב ?

ובכן  האפשרות להעביר מחלקה יכולה לספק לנו יכולות דינמיות מגוונות בזמן ריצה, אשר המטרה העיקרית שלה זה עיקרון ה DRY, והיכולת גם לעבוד עם יורשים אשר אינם ידועים לנו בזמן כתיבת הקוד (כדוגמת ספריות דינמיות שאנחנו טוענים בזמן ריצה).

שימושים עיקריים הינם:

  1. יצירת פונקציה אשר תיצור מופע מחלקה חדש ללא צורך בקוד מיוחד עבור כל סוג המחלקות שקיבלנו עבורינו. זהו קוד מאוד נפוץ ומוכר כאשר מדובר בשימוש ב meta class בכל השפות
  2. עוד סוג של עבודה עם תבניות, כלומר אפשרות להשתמש בתכונות של מחלקה ספציפית, גם כאשר המופע שלנו מכיל תכונות ומתודות אחרות, ובכך להתנהג במתודות למשל, כאילו היו מתודות סטטיות שלא דרשו מאיתנו איתחול של המחלקה, ואף להפעיל דברים שלא קיימות במופע הקיים שלנו. אפשרות זו נקראת class reference.

בשביל האפשרות הראשונה ניצור את הקוד הבא (יש קוד כזה בלזרוס ודלפי, אבל את הקוד הבא אני כותב כאן ללא בדיקה אמיתית אם הוא ירוץ או לא) :

1 procedure Application.CreateForm(AFrom : TFromClass; var AInstance : TFrom);
2 var
3   i, max, num, len : integer
4 begin
5   AInstance := AForm.Create(nil);
6   max := 0;
7   len := Length(AForm.ClassName) +1;
8   for i := 0 to Screen.ComponentCounts -1 do
9    begin
10    if not Screen.Component[i] = AForm then continue;
11    try
12      num := StrToInt(Copy(Screen.Component[i], len, length(Screen.Component[i]) - len));
13      if num > max then
14        max := num;
15    except
16      continue;
17    end;
18   end;
19   inc(max);
20   AInstance.Name := AForm.ClassName + IntToStr(max);
21 end;

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

שימו לב שהקוד כאן הוא רק לצורך הדגמה, ולא בהכרח יעבוד ויעשה את העבודה.

באפשרות השנייה דיברתי על שימוש במחלקה בשביל להריץ מתדות שבצורה אחרת הייתי חייב לקבל instance מוכן שלהם במחלקה:

1 procedure UseMethod(AClass : TMyMetaClass; AInstance : AClass);
2 begin
3  (AInstance as AClass).Method;
4 end;

מה בעצם השורה של (AInstance as AClass) עושה ? ובכן בד"כ בפסקל אנחנו מבצעים casting בצורה הבאה:

Integer(3.14);

(השימוש בcasting להמיר מספר ממשי למספק שלם לא מומלץ, זה רק לצורך הדוגמא), אבל כאן בשורה של (AInstance as AClass) עשינו סוג אחר של casting בפסקל. הפעולה מוכרת בתור checked typecasts, שבה אנחנו גורמים למופע שלנו להתנהג כאילו היה מופע המחלקה של AClass, למרות שהוא לא. העניין הוא שאי אפשר לעשות את זה עם כל מחלקה, אלא רק מחלקה שהיא זהה למחלקה שהמופע שלנו שייך אליה, או למחלקה שיורשת מהמחלקה שהמופע שלנו שייך אליה, וזה ההבדל בין ה casting הרגיל לבין הסוג הזה של casting.

8 מחשבות על “meta class 2

  1. Shai

    ואי, עידו, איזה בלגן.

    קודם כל, אני לא מבין למה אתה קורא (או קהילת הפסקל קוראת) לדברים האלה metaclass. כלומר, אני כן מבין: יש לך סוג (type) שאובייקטים ששייכים אליו הם class-ים, ולכן, על-פי ההגדרה היבשה, זה metaclass. אבל כל עוד אין לך אפשרות לכתוב metaclass משלך, אין כאן הרבה יותר מאשר reflection באמת (ואני לא מדבר על הדוגמאות שלך; קראתי את התיעוד).

    לגבי הטענה מהפוסט הקודם שלך, על "רק פסקל ו-objective c", בדוק שוב את מנגנוני ה-reflection של Java ו-#C. בג'אוה, אם T היא מחלקה, אז יש ביטוי T.class שנותן את מופע ה-metaclass עבורה; עד 1.4, ה-metaclass של כולם היה Java.lang.Class, ומאז 1.5, עם הגנריות, ה-metaclass של T הוא

    Java.lang.Class
    

    (אני מקווה שהפירמוט יעבוד…), שמתנהג באופן דומה מאד ל-"class of …" של פסקל (למערכת הסוגים של Java יש קצת יותר תיאוריה מאחוריה, ולכן אפשר שם לדבר גם על סוג מקסימלי ולא רק על סוג מינימלי כמו כאן). בדו-דיאז, כמובן, יש מנגנונים דומים אך שונים.

    וכדי להבהיר: מה שיש בשפות הדינמיות — יכולת לכתוב metaclass משלך, ולא רק לגשת לזה שהשפה מייצרת עבורך — פותח אפשרויות שפסקל בכלל לא מעלה על דעתה. אתה יכול לקבוע מה המשמעות של ירושה, למשל — איך מתייחסים למחלקת-אב; אתה יכול לקבוע מה המשמעות של זה שהוגדרו אברים מסויימים (למשל, להחליט שכל מתודה ששמה מתחיל ב-C היא class-method); אתה מקבל שליטה על מנגנון בניית ה-class (לא ה-instance; ה-class). זה המצב בפייתון, ואם אני לא טועה גם ב-lisp. מה שאתה מקבל בפסקל הוא צל חיוור של היכולות האלו.

  2. Shai

    … הפירמוט לא עבד, כמובן (למה אין preview…)

    לגבי הדוגמה השניה — אתה באמת צריך לבדוק בזמן ריצה את הסוג? התחביר של הגדרת הפרוצדורה נראה כאילו תהיה בדיקה בזמן קומפילציה.

  3. ik_5 מאת

    שי, לדעתי אתה מערבב כאן 2 מושגים שונים. אני יכול בזמן ריצה ליצור תוספות ולבצע שינויים למחלקה בפסקל, אבל שים לב שהיא לא שפה דינמית (לפחות לא מה שאני עובד עליו) ולכן יש איתה הגבלות מסויימות בנושא בגלל זה.

    עוד נושא, הוא כל ה RTTI, אשר לא נכנסתי אליו, שמאפשר לי לבצע את מה שדיברת עליו בנושא של ג'אווה. ראה את הכתובת הבאה:
    http://freepascal.org/docs-html/rtl/system/tobject.classparent.html
    בשביל לקבל את האב של המחלקה שלך (שגם משתמש ב meta data).

    אני לא מבין אבל את ההגדרה שלך של "אבל כל עוד אין לך אפשרות לכתוב metaclass משלך, אין כאן הרבה יותר מאשר reflection באמת (ואני לא מדבר על הדוגמאות שלך; קראתי את התיעוד)".

  4. Shai

    לא, אני חצי-מערבב מושגים, לא ממש, ולא את אלה שאתה מדבר עליהם. אני לא מדבר על שינוי המחלקה בזמן ריצה, לאחר שהיא נוצרה; אני מדבר על משהו ששקול להתערבות בתהליך הקומפילציה. זה לא לגמרי מחייב שהשפה תהיה דינמית, אבל זה באמת קצת קשה אחרת (Boo היא שפה סטאטית שנותנת לך להתערב בקומפילציה, אבל למיטב ידיעתי אין הרבה כאלה).

    כתיבת metaclass משלך היא סוג של meta-programming שפסקל פשוט לא תומכת בו. הרעיון הכללי הוא כזה: תשכח לרגע מה-metaclass-ים של פסקל, ותחשוב על הקונספט של מחלקה בתור class. איך נראה ה-class הזה?

    אז ככה: לכל class יש קבוצת אבות, קבוצת איברי-נתונים, וקבוצת מתודות; יש פרוצדורה שמחליטה, עבור אובייקט ושם של איבר, לאיזה איבר באמת לגשת (למשל, לאיזה אב). יש פרוצדורה של אתחול class — משהו שמאגד את אוסף ההגדרות של איברי-נתונים ופרוצדורות, סדרת האבות, והשם, ל"אובייקט" אחד, ומייצר עבורו מחלקה-בת של TClass ואובייקט מהמחלקה הזו; יש כנראה עוד כמה דברים, פחות קרדינליים. בפסקל, כל הפרוצדורות הנ"ל מקודדות בתוך הקומפיילר, ואין לך שום דבר להגיד בעניין. בפייתון, אתה יכול להגדיר class משלך שמממש את הממשקים הנכונים, ואז להגדיר מחלקות שעובדות לפי הגדרת ה-class שלך.

    למשל: ב-++C, יש שלושה סוגי התנהגות ב-override של פונקציות: פונקציות רגילות מחליפות את הפונקציה של האב, בנאים "מתווספים" לפונקצית האב אחריה, ומחריבים "מתווספים" לפומנקצית האב לפניה. אם אתה רוצה ליצור פונקציה שהירושה שלה מתנהגת כמו של בנאי או של מחריב (והרבה פעמים היית רוצה, למשל עם אובייקטים שמאותחלים מחוץ לבנאי) — אין לך דרך לעשות זאת; הפונקציה היורשת צריכה לקרוא לפונקצית האב במפורש. metaclass שאתה כותב מאפשר לך לעשות דברים כאלה — הוא יכול לשנות את המשמעות של "ירושה".

    למשל: בהרבה סביבות ושפות, מקובל לייצר "class-עזר" ל-class נתון — לצורכי סריאליזציה, או לצרכי UI, או לצרכי DB. הרבה פעמים מחלקות כאלה נוצרות ע"י מחוללי קוד, ואח"כ קשה לשמור עליהן מסונכרנות עם הקוד שלך; או שאתה כותב אותם בעצמך ואומר שהיית יכול לחולל אותם. metaclass מאפשר לך לחולל אותם תוך כדי קומפילציה — את ה-class-ים ישר, בלי source — כי הוא מטפל בתהליך יצירת ה-class.

    (ואם הצלחתי להסביר את עצמי, עכשיו הדוגמה בפייתון בוויקיפדיה אמורה להיות הרבה יותר ברורה לך).

  5. ik_5 מאת

    יש לי הרגשה שאני אל מבין אותך, אבל בפסקל דבר ראשון אני יכול לרשת בנאים והורסים. יותר מזה, אני יכול לספק שם שונה לגמרי לבנאי מהשם של Create כאשר מדובר במחלקה (לא כאשר מדובר באובייקט).

    דוגמא לכך היא http://ik.homelinux.org/projects/files/constructor_test.pp

    אבל סביר להניח שאני לא מבין כאן משהו בהסבר שלך.

  6. Shai

    לא, נראה שאתה לא מבין. אבל קח את הדוגמה שלך; לפי מה שכתוב ב-http://www.drbob42.com/delphi4/d4constr.htm (תוצאה ראשונה בגיגול AfterConstruction), הקוד ב-test שלך הוא לא לגמרי תקין — AfterConstruction לא טורחת לקרוא למתודה הנורשת (ברור לי שבהקשר הזה זו סתם קטנוניות, כי המתודה ב-TObject בטח לא עושה שום דבר, וכמובן גם ה-test לא). אבל מה אם היית יכול לקבוע בהגדרה של class שלך, נקרא לו TMyAfter: "בכל העץ של class-ים שיורשים (ישירות או עקיפות) מ-TMyAfter, הפונקציות AfterConstruction של ה-class-ים נקראות לפי סדר הירושה, מ-TMyAfter עד לעלה". המשתמש לא צריך לקרוא לפונקציה הנורשת במפורש; המערכת כבר עושה את זה בשבילו, לפני הכניסה לקוד שלו. הממשק היה נעשה יותר בטוח. הסיבה היחידה לא לעשות את זה כך היא שיכול להיות שהבנים ירצו לעשות משהו לפני ה-AfterConstruction של האב; אבל סביר בהחלט שיהיה עץ שלם שבו זה לא הגיוני, והקריאות ל-Inherited בתחילת כל AfterConstruction הן סתם RY מיותר (ומועד לשכחה, כמו כל RY מיותר).

    פסקל לא נותנת לך להגדיר כזה דבר בכלל. ב-CLOS (מערכת האוביקטים של LISP) יש לך תכונה כזאת מובנית (Before-methods). בפייתון, אין תכונה כזו מובנית, אבל בעזרת meta-class זה לא קשה לסדר את זה — עבור מתודות ספציפיות, או עבור כל המתודות, או מה שתרצה.

    כלומר, שוב: meta-class "אמיתי" מגדיר מהי המשמעות של להיות (סוג של) class, כמו ש-class של חלון מגדיר מהי המשמעות של להיות חלון. בדוגמה הספציפית הזאת, ה-meta-class משנה קצת את מנגנון הירושה, עבור ה-class-ים ששייכים אליו.

    בכל השפות התומכות ב-class-ים, המערכת מספקת לך לפחות סוג אחד של meta-class; שפות אקספרסיביות יותר מפסקל, כמו פייתון, נותנות לך גם להגדיר אחרים משלך, ולשייך אליהם class-ים, כך שכתיבת ה-class-ים הללו נעשית קלה ופשוטה יותר.

  7. ik_5 מאת

    אני חושב שאני מבין.

    הסיבה שלא קראתי לא inherited היא בגלל שזו סתם דוגמא לצורך העניין, ולא בגלל שאין מה לרשת (אני צריך לבדוק פיזית אם יש מה לרשת שם).
    אבל אחד הדברים שאני מאוד אוהב בפסקל (ולא קשור לדיון) זו האפשרות שלי לרשת נגיד את Create למרות שלבנאי שלי קראתי init.
    ולא בג'אווה ולא ב ++C אפשר לעשות דברים כאלו.

    אבל אתה צודק ולפחות עד כמה שאני יודע, אי אפשר להגדיר ממש לעומק את ה meta class אז אני מבין את הכוונה שלך לגבי זה שהיא בסיסית ביותר.

  8. פינגבק: מערך כתכונה | לראות שונה

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s