• בלוג
  • קצת כמו כלב רק עם כנפיים

קצת כמו כלב רק עם כנפיים

29/09/2022

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

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

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

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

type Upsert<FullType, T> = T extends { id: number }
    ? Partial<FullType>
    : Omit<FullType, "id">;

מאוד רציתי לקחת טיפוס כזה:

type MyStuff = {
    id: number,
    foo: number,
    bar: number,
}

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

function upsert<T>(thing: Upsert<MyStuff, T>) {}

// I wanted these to compile:
upsert({ id: 10, foo: 5 });
upsert({ foo: 10, bar: 20 });

// And this to not compile:
upsert({ foo: 5 });

וכמובן שזה לא עובד.

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

הקוד הזה כבר עובד:

type Upsert<FullType, T> = T extends { id: number }
    ? T & Partial<FullType>
    : T & Omit<FullType, "id">;

type MyStuff = {
    id: number,
    foo: number,
    bar: number,
}

function upsert<T>(thing: Upsert<MyStuff, T>) {}

// Compiles
upsert({ id: 10, foo: 5 });
upsert({ foo: 10, bar: 20 });

// Doesn't compile - missing "id" or "bar"
upsert({ foo: 5 });

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

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