Menu

אופרטור טרנרי ב-Golang: למה אין ב-Go ובמה להשתמש

ל-Go אין אופרטור טרנרי (cond ? a : b). הדף מסביר למה, מראה את התחליף האידיומטי עם if/else, את cmp.Or לערכי ברירת מחדל, ופונקציית עזר גנרית יחד עם המלכודת שמבדילה אותה מטרנרי אמיתי.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

ל-Go אין אופרטור טרנרי. אין cond ? a : b. התחליף האידיומטי הוא פקודת if:

הצהירו על המשתנה עם ערך ברירת המחדל, ואז שנו אותו ב-if. כששני הענפים צריכים עבודה, השתמשו ב-if/else:

var fee int
if member {
	fee = 0
} else {
	fee = computeFee(order)
}

למה Go השאירה אותו בחוץ

ה-FAQ של Go עונה על זה ישירות: המתכננים ראו את האופרטור ?: משמש לעיתים קרובות מדי ליצירת ביטויים מסובכים ובלתי חדירים, והחליטו ש-if/else, אף שהוא ארוך יותר, ברור יותר ללא ספק. שפה צריכה רק מבנה אחד של בקרת זרימה מותנית.

בפועל האופרטור החסר עולה שתיים או שלוש שורות לכל שימוש, ומבטל קטגוריה שלמה של ביטויים מקוננים כמו a ? b ? c : d : e.

חזרה מוקדמת במקום זה

בתוך פונקציה, הצורה הנקייה ביותר מדלגת לעיתים קרובות על המשתנה לגמרי:

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

ערכי ברירת מחדל עם cmp.Or (Go 1.22)

הטרנרי הנפוץ ביותר בשפות אחרות הוא "השתמש בערך הזה, או בחלופה אם הוא ריק": name ? name : "anonymous", או name || "anonymous" ב-JavaScript. Go 1.22 הוסיפה את cmp.Or בדיוק לשם כך. היא מחזירה את הארגומנט הראשון שאינו ערך האפס של הטיפוס שלו:

היא מתייחסת לערך האפס כאל "חסר", ולכן היא לא יכולה להבחין בין "הפורט הוא 0 בכוונה" לבין "הפורט לא הוגדר". כשאפס הוא ערך תקין, השתמשו במצביע, ב-bool נפרד או ב-if מפורש.

פונקציית If גנרית, והמלכודת שלה

עם generics (Go 1.18) אפשר לכתוב פונקציה שנראית כמו טרנרי:

func If[T any](cond bool, a, b T) T {
	if cond {
		return a
	}
	return b
}

status := If(ok, "pass", "fail")

היא עובדת לערכים פשוטים, ויש בסיסי קוד שמשתמשים בה. אבל היא לא אופרטור טרנרי, וההבדל חשוב. Go מעריכה את כל הארגומנטים לפני הקריאה לפונקציה, כך שגם a וגם b תמיד מחושבים:

הקריאה הראשונה מחשבת גם את "a" וגם את "b", והשנייה גורמת ל-panic עם nil pointer dereference, למרות שהתנאי false. טרנרי אמיתי היה מעריך רק את הצד שנבחר. לכן:

  • לעולם אל תשתמשו בפונקציה כזאת כשאחד הצדדים מבצע dereference למצביע, ניגש לאינדקס של slice או קורא רשומה ב-map שאולי לא קיימת.
  • לעולם אל תשתמשו בה כשאחד הצדדים יקר או שיש לו תופעות לוואי.
  • לערכים זולים ובטוחים היא עובדת, אבל הרבה סוקרי קוד ב-Go עדיין יבקשו if פשוט.

אפשר להפוך את הענפים לעצלים באמצעות העברת פונקציות, If(ok, func() string { return a }, func() string { return b }), אבל בשלב הזה פקודת ה-if קצרה יותר.

טריקים אחרים, ולמה להימנע מהם

חיפוש ב-map. map[bool]string{true: "yes", false: "no"}[ok] עובד, בונה map ומבצע hashing לתוכו בכל הערכה, ומעריך את שני הערכים. זה קוריוז, לא ניב.

פונקציה שנקראת מיד. פונקציה אנונימית שנקראת במקום נותנת ביטוי:

label := func() string {
	if n > 0 {
		return "positive"
	}
	return "non-positive"
}()

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

בחירת תחליף

מצבבמה להשתמש
בחירה בין שני ערכיםערך ברירת מחדל, ואז if
שני הענפים צריכים חישובif/else
הערך קובע את התוצאה של הפונקציהreturn מוקדם
חלופה כשערך ריקcmp.Or(value, fallback)
שלושה מקרים או יותרswitch
תנאי חוזר עם משמעותפונקציה קטנה עם שם

הצעות להוסיף ביטוי מותנה עולות באופן קבוע ב-issue tracker של Go ונדחו עד עכשיו. כתבו את ה-if.

שאלות נפוצות

האם יש ל-Go אופרטור טרנרי?

לא. ל-Go אין ביטוי cond ? a : b. ה-FAQ של Go אומר שהמתכננים השאירו אותו בחוץ כי משתמשים בו לעיתים קרובות מדי כדי לבנות ביטויים שקשה לקרוא. השתמשו במקום זה בפקודת if/else.

איך כותבים הצבה מותנית בשורה אחת ב-Go?

הציבו קודם את ברירת המחדל, ואז דרסו אותה:

label := "odd"
if n%2 == 0 {
	label = "even"
}

זה התחליף האידיומטי. gofmt לא ישים גוף של if בשורה אחת, כך שזה תמיד לוקח לפחות שלוש שורות.

אפשר לכתוב פונקציה טרנרית גנרית ב-Go?

כן: func If[T any](cond bool, a, b T) T { if cond { return a }; return b }. הבעיה היא ש-Go מעריכה כל ארגומנט לפני הקריאה לפונקציה, כך שגם a וגם b מחושבים. If(p != nil, p.Name, "") עדיין מבצע dereference ל-p שהוא nil וגורם ל-panic. טרנרי אמיתי היה מעריך רק צד אחד.

איך קובעים ערך ברירת מחדל אם מחרוזת ריקה ב-Go?

מאז Go 1.22, cmp.Or(name, "anonymous") מחזירה את הארגומנט הראשון שאינו ערך האפס, כך שהיא נותנת את name אלא אם name הוא "". היא עובדת לכל טיפוס comparable, וכמו כל קריאה לפונקציה היא מעריכה את כל הארגומנטים שלה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל