מערכת טיפוסים

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

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

ישנן שפות נטולות טיפוסים, כמו רוב שפות הסף, Forth, BCPL ו-Pawn. פרולוג היא דוגמה נוספת ובולטת לשפה חסרת מערכת טיפוסים.

זמן הבדיקה

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

הסקת טיפוסים 
ישנן שפות בעלות טיפוסיות סטטית המאפשרות למתכנת שלא להצהיר על הטיפוס של המשתנה, והמהדר יודע לגלות זאת בעצמו על סמך הביטויים בהם הוא משתתף. למשל בהצהרה כגון: ;var x = 5, ניתן להבין שהמשתנה x הוא מטיפוס מספר שלם, או מטיפוס כלשהו שניתן להמיר אליו במובלע (ביצוע coercion) מספרים שלמים. ישנן דרכים נוספות לגלות זאת, בהן בדיקה אילו אופרטורים מופעלים על המשתנה, או לאילו פונקציות הוא מועבר כפרמטר.

תכונה זו, הנקראת הסקת טיפוסים (Type Inference) מאפשרת למתכנת להימנע מלהגדיר את הטיפוס שבו הוא עוסק, ואפילו לא לדעת אותו (דבר שעשוי לקרות למשל בעת שימוש ב־Template Metaprogramming ב־++C). יתרון נוסף של תכונה זו הוא הקלות שבה ניתן לכתוב פונקציות פולימורפיות בדרך זו.

השפה הראשונה שניצלה תכונה זאת במלואה היא שפת ML, ובדרך כלל תכונה זו מאפיינת שפות תכנות התומכות בתכנות פונקציונלי. בין השפות המאפשרות זאת ניתן למנות גם את #C (באמצעות המילה השמורה var), ++C החל מתקן C++11 (באמצעות המילה השמורה auto), Haskell, Perl החל מגרסה 6, Visual Basic החל מגרסה 9.0, ושפת D.

האלגוריתם הראשון שהוצע לביצוע בדיקה זאת, והעיקרי שנמצא בשימוש עד היום, נוצר מלכתחילה על ידי האסקל קרי ורוברט פייס בשנת 1958 במסגרת פיתוח תחשיב הלמבדא. רוג'ר הינדלי הרחיב אותו בשנת 1969, והוכיח שהוא מגלה תמיד את הטיפוס הכללי ביותר המתאים. בשנת 1978, בנפרד מעבודתו של הינדלי, הציע רובין מילנר אלגוריתם שקול וקרא לו "אלגוריתם W". בשנת 1982 הוכיח לואיס דאמאס את שלמות האלגוריתם והרחיב אותו על מנת לתמוך בהתייחסות פולימורפית. על אף עבודתו של דאמאס, האלגוריתם נקרא "האלגוריתם של הינדלי-מילנר".

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

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

טיפוסיות ברווז משמשת לרוב בשפות מפורשות מונחות-עצמים. במקרה כזה כל קריאה למתודה של אובייקט בודקת בזמן הקריאה דבר אחד: האם יש לאובייקט המדובר מתודה בשם הזה. אין כל דרישה אחרת על המחלקה אליה שייך האובייקט. לדוגמה, בפייתון הפונקציה len בודקת אורך של רשימות, מחרוזות, וכן כל טיפוס נתונים של איטרטור או פונקציה מחוללת. אבל הפונקציה לא דורשת שהפרמטר שלה יהיה מטיפוס כזה או אחר. כל מה שהיא דורשת היא שתהיה לו מתודה __len__ המקבלת ארגומנט יחיד. כך ניתן להגדיר מחלקה length17, שעבור אובייקטים השייכים לה "האורך שלהם" יהיה תמיד 17:

class length17:
     def __len__(self):
         return 17
 

 x=[1,2]
 print(len(x)) # prints 2
 x=length17()
 print(len(x)) #prints 17

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

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

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

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

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

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

הערות שוליים

מבני נתונים
שפות תכנות