uk
Feedback
ToCode

ToCode

Відкрити в Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Показати більше
1 419
Підписники
-124 години
Немає даних7 днів
-230 день
Архів дописів
ToCode
1 419
# הפתעות עיקרון בשם POLA, ראשי תיבות של principle of least astonishment, טוען שבעיצוב תוכנה אנחנו רוצים לבנות מערכות שיתנהגו בהתאם לציפיות של רוב המשתמשים. אבל יש פה בעיה - אף אחד לא יכול לדעת מה הציפיות של רוב המשתמשים או מי בכלל יהיו המשתמשים האלה. כשבונים שפת תכנות החיים עוד יותר מסובכים, כי כמעט כל מי שמגיע ללמוד את שפת התכנות שבנית מגיע עם ציפיות משפות תכנות אחרות, וכמעט כל החלטה שתעשה תפתיע הרבה אנשים. מטס, היוצר של רובי, טוען בהקשר הזה שעיקרון Pola בכלל לא אומר ששפת התכנות שלו צריכה להיות הכי פחות מפתיעה לרוב האנשים - אלא שהיא צריכה להיות הכי פחות מפתיעה עבורו. אם הגעת משפה אחרת והופתעת מפיצ'ר מסוים זה בסדר גמור. זה פשוט אומר שההבנה שלך של השפה שונה מזו של יוצר השפה. והנה מצאנו עוד זווית שאפשר ללמוד ממנה על הטכנולוגיה. ניקח לדוגמה את טייפסקריפט הפעם ובואו נדבר על Interface-ים ו Type-ים. בשניהם אני יכול להשתמש כדי להגדיר טיפוס חדש ולהרחיב אותו, לדוגמה עבור כלי תחבורה ומכונית:
interface Vehicle {
  goto: (place: Place) => void;
  wheels: number;
}

interface Car extends Vehicle {
  testValidUntil: Date;
}
ואותו דבר עם type-ים:
type Vehicle = {
  goto: (place: Place) => void;
  wheels: number;
};

type Car = Vehicle & {
  testValidUntil: Date;
}
אבל אז אני מנסה להגדיר את אותו שדה ב Vehicle וב Car עם טיפוסים שונים (למשל שדה בשם x שב Vehicle הוא מסוג number וב Car הוא מסוג string) ומופתע מההבדל בין השניים: בשימוש ב interface-ים הקוד לא יסכים להתקמפל בגלל שהוא לא יודע מה סוג השדה ב interface היורש (במקרה שלנו Car). בשימוש ב type-ים הקוד יתקמפל אבל סוג השדה ב interface היורש לא יהיה אף אחד משני הטיפוסים המתנגשים אלא יהיה never, מה שאומר שאין בעיה לקמפל את הקוד כל עוד לא יצרתי אף משתנה מה type הבעייתי. וההפתעה הזאת נפלאה. כי עכשיו היא פותחת את הדלת להמון שאלות חדשות כמו "מה interface יודע ש type לא יודע?", איך interface מצליח לזהות את הבעייה כבר בהגדרה ו type חייב לחכות עד שנגדיר משתנה מה type הזה? האם זה בכוונה? האם זה מאפשר ל type לעשות דברים ש interface לא יכול לעשות? האם זו מגבלה שהיא חלק מהותי מה Design של השפה ויכולה ללמד אותי משהו על הסמנטיקה של interface-ים ו type-ים ומתי כדאי להשתמש בכל אחד? האם זו מגבלה טכנית שאולי תשתנה בעתיד? האם יש מישהו שרוצה בכלל לשנות את זה? נקודת ההתחלה של השאלות היא הפתעה. הפתעה היא כמו מצפן בשבילנו בתהליך הלימוד, היא תמיד מראה את הכיוון כדי להיכנס יותר לעומק.

ToCode
1 419
# מאחורי הקלעים בעבודה עם קוד קיים, אחרי שהצלחתם לגרום לקוד לעשות את מה שאתם רוצים ואתם מתחילים להרגיש בנוח איתו, הרמה הבאה היא להתחיל להסתכל מאחורי הקלעים - מי הדמויות המשפיעות על הקוד? מה מניע אותם? ולמה דברים עובדים כמו שהם עובדים? הרבה פעמים הפער בין שתי הרמות הוא עצום. דוגמה קטנה מהעולם של ריאקט - אחד הפיצ'רים החדשים של ריאקט 17 היה שלא צריך לכתוב:
import React from 'react';
בתחילת כל קובץ JSX. רובנו שמחנו על החיסכון בזמן ועצרנו שם, ובקוד חדש פשוט הפסקנו לכתוב את ה import הזה. זאת הרמה הראשונה. רמת מאחורי הקלעים כבר מדברת על הסיבות בגללן מישהו התאמץ להכניס את הפיצ'ר הזה, והערך הנוסף שהפיצ'ר הזה נותן לאנשים שכותבים כלים בתוך האקוסיסטם של ריאקט. סבסטיאן מרקבג' וונימין קרול כתבו מסמך הסבר די ארוך באותה תקופה על ההגיון ומאחורי הקלעים של ההחלטה כאן: https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md קריאה של אותו מסמך משנה לגמרי את ההבנה של מה קרה שם ועוזרת לגבש תמונה מלאה על הפיצ'ר והקשר שלו עם יכולות אחרות של ריאקט. יש אנשים שההצצה מאחורי הקלעים מלהיבה אותם יותר מההצגה עצמה; אצל רובנו להצליח להסתכל מאחורי הקלעים ולהתעניין במה שיש שם זאת מיומנות שצריך לפתח. והיא לגמרי שווה את ההשקעה.

ToCode
1 419
# מה עושים עם חסמים פסיכולוגיים חסמים פסיכולוגיים זה כשאנחנו לא מסוגלים לשקול אפשרות מסוימת, למרות שאותה אפשרות אולי תעזור לנו להתקדם. דוגמה מהחיים שמחוץ לתכנות תהיה בן אדם שיש לו כסף בצד אבל לא מוכן לקנות דירה כי "המחירים מטורפים ואין מצב שאני משתעבד למשכנתא", למרות שהחזר המשכנתא יהיה עבורו זול יותר משכירות (גם בניכוי הריבית שאולי הוא צובר על החיסכון), כי הוא גר באזור שבו מחירי ההשכרה יחסית גבוהים למחירי הקניה. כשניכנס לעולם המקצועי נוכל לדמיין דוגמאות יותר קרובות, לדוגמה אפשר לחשוב על מתכנתת שנשארת בעבודה שהיא לא אוהבת כי היא לא מוצאת אופציה יותר טובה, למרות שהיא יודעת שהסיבה שהיא לא מתקבלת לעבודה טובה יותר היא פערי ידע, והדרך היחידה שלה לסגור את הפערים האלה זה לעזוב את העבודה ולהשקיע כמה חודשים בלשבת ללמוד. אותה מתכנתת עשויה להעדיף להישאר תקועה במקום כי היא לא מסוגלת לשקול אפילו את האפשרות לעזוב לכמה חודשים ולא לדעת מה יקרה אחרי. וכשנצלול לקוד נוכל לדבר על המתכנת שלא מעלה על דעתו לכתוב בדיקות, כי בדיקות זה בזבוז זמן, למרות שהוא מעולם לא השקיע את הזמן בללמוד איך לכתוב בדיקות נכון; או המתכנתת שלא תעלה על דעתה לקחת קורס כי "אם למדריך היה שכל הוא היה כותב קוד ולא מלמד". מחסומים פסיכולוגיים לא נראים כמו מחסום, אלא שהם פשוט מסתירים את האפשרות שמאחוריהם. כשאנחנו בתוך הצומת ומנסים לחשוב מה הצעד הבא, אנחנו לא יכולים לדמיין לעשות את הצעד החסום, ואפילו אם מישהו מציע לנו בדיוק את זה אנחנו ניבהל ונמצא אינסוף תירוצים למה זה לא אפשרי. אבל יש גם חדשות טובות: למרות האימה שהם מעוררים, ולמרות שאי אפשר ממש לראות את הדרך שהם חוסמים, את המחסום הפסיכולוגי עצמו יחסית קל לראות. יושבים עם חבר או חברה, מעלים את הבעיה ומבקשים מאותו חבר להציע כמה שיותר אפשרויות וכמה שיותר הזויות. כשתראו שאחת האפשרויות ממש מבהילה אתכם, יש סיכוי שזיהיתם מחסום פסיכולוגי. אחרי שמזהים מתחילה העבודה הקשה של לפרק את אותו מחסום, לחפש נתונים מדויקים, להבין מה באותה אפשרות כל כך מפחיד אותי ומה צריך לקרות כדי שאוכל לחיות עם הפחד. שווה לדבר עם עצמכם על הפוטנציאל של מה יקרה אחרי. אז אם אני חוזר לאותה מתכנתת שפוחדת לעזוב את העבודה, וכשמישהו מציע לך את זה את מיד נלחצת עם איזה "מה פתאום אבל אני חייבת את הכסף" - שווה לשאול: כמה כסף אני באמת צריכה בחודש? האם אני יכולה לארגן תוכנית חיסכון שתעזור לי לעבור את החודשים הקרובים? שווה גם להזכיר שפעם ישבת שנה שלמה בקורס כדי ללמוד להיות מתכנתת, ועוד שילמת עליו כסף, אז למה זה כל כך מוזר לעשות משהו דומה שוב? והכי חשוב לדבר על המשכורת שתהיה לך אחרי שתשבי ללמוד ותגיעי הרבה יותר מוכנה לראיונות הבאים. יותר מדי פעמים נתקעתי על מחסומים פסיכולוגיים חודשים ואפילו שנים, ואני בטוח שגם לכם יש ניסיון עם כאלה. כל פעם כזאת, כשהמחסום נשבר והצלחתי לזוז קדימה, הצער היחיד היה "איך לא עשיתי את זה קודם". זה יכול לעבוד הוא התמריץ הכי טוב שאפשר לבקש.

ToCode
1 419
# בואו נתקן את JSON.parse ב TypeScript בגלל שהחיים יותר מורכבים מתוכניות מחשב, הרבה פעמים בשלב כתיבת הקוד עדיין לא יהיה לנו את כל המידע שאנחנו צריכים כדי לדעת מה יש בכל משתנה. במיוחד בשפה כמו טייפסקריפט, שמתקמפלת ל JavaScript, אנחנו רגילים לכתוב קוד שמגלה מה לעשות גם בזמן ריצה. הבעיה שגילוי מה לעשות בזמן ריצה הולך נגד הרעיון של Type Safety - כי אם אני לא יודע מה יש לי ביד, איך אני יודע שמותר לי לעשות איתו את מה שרציתי לעשות. לדוגמה נניח שאני קורא אוביקט מ local storage ואני חושב שהוא מכיל מחרוזת שהיא ייצוג JSON-י של אוביקט שיש בו שני שדות מספריים עם המפתחות x ו y. הקוד הבא ב JavaScript קורא את האוביקט הזה ומדפיס את סכום המספרים:
const stringifiedValue = localStorage.getItem('value');
const parsedValue = JSON.parse(stringifiedValue);
const sum = parsedValue.x + parsedValue.y;

console.log(`sum = ${sum}`);
אבל כשאדון טייפסקריפט קורא את הקוד הזה הוא חוטף חום: איך אתה יודע, הוא צועק עליי, שבאמת יש ב local storage אוביקט עם המפתח שכתבת? ואיך אתה יודע שהערך הוא באמת מחרוזת שתצליח לתרגם אותה ל JSON? ואם כבר פיענחת אותה ל JSON, איך אתה יודע שלאוביקט שקיבלת יש שדות בשם x ו y והם מספרים? בתרגום ל TypeScript אני צריך להרגיע את כל הדאגות. ניסיון ראשון שלא עובד עשוי להיראות כך:
type TwoNumbers = { x: number, y: number };

const stringifiedValue = localStorage.getItem('value');
const parsedValue: TwoNumbers = JSON.parse(stringifiedValue);
const sum = parsedValue.x + parsedValue.y;

console.log(`sum = ${sum}`);
זה לא עובד בגלל שטייפסקריפט מספיק חכם בשביל להבין ש getItem לא תמיד יחזיר מחרוזת ו JSON.parse לא יכול לעבוד על null-ים, אבל אם אני מתקן את הבעיה הזאת אני מגיע לקוד הבא שדווקא מתקמפל יופי:
import './style.css'

type TwoNumbers = { x: number, y: number };

const stringifiedValue = localStorage.getItem('value');
if (stringifiedValue) {
  const parsedValue: TwoNumbers = JSON.parse(stringifiedValue);
  const sum = parsedValue.x + parsedValue.y;
  
  console.log(`sum = ${sum}`);
}
וזאת בעיה - כי לטייפסקריפט אין באמת דרך לדעת שמה שהיה ב LocalStorage מתאים לטיפוס שהגדרתי, ובכל זאת הוא לא התרגז. מבחינתו אם אני מפענח JSON אני בטח יודע מה אני אמור לקבל. עבודה חכמה יותר עם טיפוסים תכריח אותנו לבדוק מה באמת פירססנו ושהוא באמת מתאים לטיפוס שלנו. בשביל זה הכי קל לעטוף את JSON.parse:
import './style.css'

type TwoNumbers = { x: number, y: number };

function safeJsonParse(text: string): unknown {
  return JSON.parse(text)
}

const stringifiedValue = localStorage.getItem('value');
if (stringifiedValue) {
  const parsedValue: TwoNumbers = safeJsonParse(stringifiedValue);
  const sum = parsedValue.x + parsedValue.y;
  
  console.log(`sum = ${sum}`);
}
כבר התקדמנו! עכשיו טייפסקריפט כבר כועס על ההשמה מ JSON.parse ל parsedValue בגלל שלא בדקנו שהאוביקטים מתאימים. בשביל הבדיקה אני יכול להגדיר פונקציה נפרדת:

type TwoNumbers = { x: number, y: number };

function safeJsonParse(text: string): unknown {
  return JSON.parse(text)
}

function isTwoNumbers(value: any): value is TwoNumbers {
  return (
    'x' in value &&
    typeof value.x === 'number' &&
    'y' in value &&
    typeof value.y === 'number'
  )
}

const stringifiedValue = localStorage.getItem('value');
if (stringifiedValue) {
  const parsedValue = safeJsonParse(stringifiedValue);
  if (isTwoNumbers(parsedValue)) {
    const sum = parsedValue.x + parsedValue.y;
    console.log(`sum = ${sum}`);
  }
}
הקוד האחרון כבר מבצע את כל הבדיקות, ובאופן כללי קוד שישתמש בפונקציה העוטפת safeJsonParse תמיד יהיה חייב לבדוק את האוביקט שהוא מקבל בעזרת פונקציית בדיקה לפני שאפשר יהיה להשתמש בערך.

ToCode
1 419
למי שפספסו הקלטה של וובינר ריאקט נייטיב שהתקיים ביום חמישי האחרון: https://www.youtube.com/watch?v=ou_9QtBC2_M

ToCode
1 419
# איך להוריד את הדולר בפייתון שאלה מעניינת שפורסמה בפורום פייתון הזכירה לי שהכיף בתכנות הוא לא למצוא את התשובה. בסיפור הפעם מתכנת כתב תוכנית שמבקשת מחיר בדולרים, ורצה לעשות איזשהו חישוב על המחיר. בואו נניח שאנחנו רוצים להציע 10% הנחה. הבעיה היא שחלק מהמשתמשים הכניסו את המחיר עם התחילית $, והיה צריך למצוא דרך להיפטר ממנה. הנה קוד שהיה יכול להיות נקודת ההתחלה:
price = float(input("Original price is: "))
price_after_discount = price * 0.9

print(f"After coupon discount you'll pay {price_after_discount}")
והפעלה לדוגמה תקינה היא:
$ python removedollar.py
Original price is: 15
After coupon discount you'll pay 13.5
וכמובן שכשמכניסים את המטבע בהתחלה הכל נשבר:
Original price is: $10
Traceback (most recent call last):
  File "/Users/ynonp/tmp/blog/removedollar.py", line 1, in <module>
    price = float(input("Original price is: "))
ValueError: could not convert string to float: '$10'
מה עושים? ## אופציה 1: למחוק את הדולר בהתחלה אם מכניסים אותו גישה ראשונה לפיתרון יכולה להתבסס על זה שכשמשהו נשבר אנחנו מתקנים אותו. אז אם שמנו לב שמשתמשים מכניסים דולר בתחילת המחיר, פשוט נמחק את הדולר:
user_price = input("Original price is: ")
price = float(user_price.lstrip("$"))
price_after_discount = price * 0.9

print(f"After coupon discount you'll pay {price_after_discount}")
כמובן שהחיסרון הוא שאנחנו מוחקים רק דולר ורק מההתחלה. אם מישהו החליט להכניס מטבע שונה או בסוף במקום בהתחלה המעקף שלנו לא יעבוד. ## אופציה 2: לנקות את הקלט עם ביטוי רגולארי גישה יותר מתוחכמת היא לזהות את כל השטויות משני צידי המחיר עם ביטוי רגולארי ולקחת רק את המספרים:
import re

price = input("Original price is: ")
match = re.search(r'([\d.]+)', price)
if match:
    price = float(match.group(1))
    price_after_discount = price * 0.9

    print(f"After coupon discount you'll pay {price_after_discount}")
else:
    print(f"Invalid price {price}")
גישה זו יותר איטית ממחיקת רק התו הראשון, אבל גם יותר גמישה. ## אופציה 3: לתפוס את השגיאה ולהציע למשתמש להכניס מחדש את הקלט בחזרה לפשטות, אפשר להיות ישירים עם המשתמשים ולבקש מהם להקליד מספר בלי סימן מהצדדים. אם זה לא קרה נוכל לבקש מהם להקליד שוב:
while True:
    try:
        price = float(input("Original price is: "))
        price_after_discount = price * 0.9

        print(f"After coupon discount you'll pay {price_after_discount}")
        break
    except ValueError:
        print("Please type the price as number only (no $ sign)")
## אופציה 4: להגביל את התווים שמשתמש יכול להקליד וריאנט יותר מתוחכם על 3 יהיה להגביל ממש את המשתמש כך שאפשר יהיה להקליד רק ספרות או סימן נקודה. המודול readchar של פייתון עושה בדיוק את זה:
import sys, string
from readchar import readkey, key

def read_only_numbers(prompt):
    value = ""
    print(prompt, end="")
    sys.stdout.flush()

    while True:
        k = readkey()
        if k == key.ENTER:
            print()
            return float(value)
        if k not in string.digits and k != ".":
            continue

        print(k, end="")
        sys.stdout.flush()
        value += k


price = float(read_only_numbers("Original price is: "))
price_after_discount = price * 0.9

print(f"After coupon discount you'll pay {price_after_discount}")
## סיכום התרגיל הזה הוא אחת הסיבות שאני לא אוהב לחלק פיתרונות עם התרגילים פה באתר. לא משנה כמה פיתרונות ראיתם, תמיד אפשר לחשוב על עוד פיתרון והצורך לחפש את "הפיתרון הנכון" הוא בדיוק מה שעוצר אותנו מלהתקדם. המשחק כשרואים בעיה הוא לא לחפש איך "צריך" לפתור אותה, אלא איך "אפשר" לפתור אותה, ומה היתרונות והחסרונות של כל גישה. נ.ב. כל הגישות כוללות טעות חישוב בגלל ש float-ים הם לא דרך טובה לייצג כסף. נסו למצוא איזה ערכים נותנים תוצאות לא נכונות, ולתקן באמצעות החלפת החישוב למספרים שלמים שמייצגים סנטים.

ToCode
1 419
# לא חשבתי על זה לא חשבתי על זה שכשמשנים כתובת מייל צריך לבקש סיסמה, כי ברוב האתרים אפשר ללחוץ "שכחתי סיסמה" ולקבל קישור לאיפוס סיסמה למייל. לא חשבתי על זה שאלמנט יכול להתכווץ אם הוא בתוך flex-box כי ברירת המחדל של flex-shrink היא 1. לא חשבתי על זה שכמות השאילתות שכתבתי תלויה במספר השורות בטבלה. לא חשבתי שקובץ הלוג יגיע לכזה גודל. כל מנגנון שאנחנו מכניסים למערכת יכול להישבר בדרכים שלא חשבנו עליהן. גישה אחת שלא עובדת להתמודד עם הפחד היא לנסות כן לחשוב על הכל מראש, לתכנן עד לפרטים הכי קטנים ולתת לאינסוף אנשי מקצוע לעבור על התוכניות לפני שמעבירים אותן לפיתוח. זה לא עובד כי גם אנשי מקצוע טובים מפספסים וכי עולם התוכנה משתנה כל הזמן. גישה יותר טובה משלבת בין מחקר טוב מראש לבין נכונות לתקן בזמן ריצה: אנחנו לא מוותרים על תכנון. כשנעלה מערכת אנחנו נבדוק איפה הלוגים שלה נשמרים, איפה הגיבויים נשמרים, נפעיל כלים אוטומטיים שיעזרו לנו למצוא בעיות נפוצות בקוד ונכיר את הקוד והכלים שלנו מספיק טוב כדי לא לפשל בדברים ברורים. באותו זמן, כשמישהו מדווח על תקלה מתכנתים טובים לא לוקחים ללב או מתווכחים. אנחנו משתמשים בזה כהזדמנות ללמוד "על מה לא חשבתי הפעם" ומוסיפים לצ'ק ליסט לקראת הפעם הבאה.

ToCode
1 419
# להבין מה חשוב אם יש מטרה אחת שבגללה שווה לקחת קורס על נושא ולא להסתפק בקריאת תיעוד זה לימוד נכון של סדר הדברים והבנה של מה חשוב ועל מה אפשר לוותר. ולכן הטעות הכי נפוצה כשלוקחים קורס היא להשוות אורך או היקף שלו. אני יכול להעביר חיים שלמים בכתיבת יישומי ריאקט בלי להשתמש ב Context, בלי Code Splitting, בלי Class Components, בלי CSS in JS ובלי עוד עשרות פיצ'רים שלו, וכך עם עוד טכנולוגיות רבות נוספות. הפער בין קורס לתיעוד הוא תמיד גדול וכך הוא צריך להיות. קורס טוב עוזר להבין מה חשוב, להתמקד בדבר החשוב הבא ולתת לי את המיומנות לעשות משהו מועיל עם מה שלמדתי. אני לא מחפש קורס שיחליף תיעוד אלא כזה שאחריו אהיה מוכן לשמוע עוד ולקרוא את התיעוד. כי התוצאה של ללמוד יותר מדי חומר היא לא שיודעים יותר מדי, אלא שהכל מתערבב ולא מצליחים לכתוב אפילו את הדברים הבסיסיים כמו שצריך.

ToCode
1 419
הי חברים עוד 15 דקות (בשעה עשר) נתחיל וובינר היכרות עם ריאקט נייטיב. מוזמנים להצטרף בקישור: https://us06web.zoom.us/j/85267945481?pwd=NDNPSUNuQlVocHBaZlBrdElLYkFZUT09

ToCode
1 419
# גיט אליאסים לא יהפכו אתכם לפרודוקטיביים יותר (אולי אפילו הפוך) אליאסים בגיט יכולים לעזור לנו להקליד פחות ולחסוך מאיתנו את הצורך לזכור רצפים מסובכים של פקודות, אבל רוב הזמן הגדרת אליאסים לא רק שלא תעזור לנו ללמוד גיט אלא אפילו תפריע. במקום להכיר את הכלים הבסיסיים ולקבל ידע שממשיך איתנו לכל מקום, אנחנו מתרגלים לקיצור דרך ולא מצליחים להתמצא כשהקיצור נעלם. הנה כמה אליאסים שראיתי שנופלים למלכודת הזאת ולכן לא הייתי מוסיף אותם לקיצורים שלי, וכמה אליאסים שכן יכולים לעזור: ## לא משתמש - הצגת כל הענפים האליאס הזה מציג את כל הענפים עם מידע על כל ענף:
git config --global alias.bra "branch -a  --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(contents:subject) %(color:blue)(%(committerdate:short)) [%(authorname)]' --sort=-committerdate"
זה נראה יפה אבל אני מעדיף את ההתנהגות הרגילה של git branch. כשאני בודק ענפים אני כן רוצה לדעת ולהבדיל בין ענפים מקומיים למרוחקים ואני תמיד רוצה לדעת איזה סוג ענף אני מחפש. ## לא משתמש - ביטול קומיט האליאס הזה מבטל את הקומיט האחרון:
git config --global alias.uncommit 'reset --soft HEAD^'
לא הייתי משתמש בו כי הוא לא חוסך הקלדה (להקליד את ה reset זה לא הרבה יותר ארוך), אלא משנה מילים ומפריע לי להבין איך גיט עובד. במקום להסתכל על reset כפקודה שמזיזה את הבראנץ, אני רואה את המילה uncommit וחושב שעשיתי משהו עם הקומיט. ## לא משתמש - הוספה וקומיט באותו משפט האליאס הזה מוסיף את כל הקבצים מתיקיית העבודה ל Staging Area וגם פותח את ה Editor כדי לעשות קומיט:
git config --global alias.ac '!git add -A && git commit
בעבודה שלי אני תמיד מפעיל status לפני ואחרי ה add כדי לראות מה בדיוק התווסף ולוודא שכל קומיט נשמר בדיוק עם הקבצים שמתאימים לו. ## משתמש - קיצורי דרך הסוג היחיד של אליאסים שכן שווה להשתמש בהם הוא קיצורי דרך, לדוגמה:
git config --global alias.st 'status -sb'
מציג סטטוס באמצעות כתיבת git s במקום כל המילה status, וגם מציג גירסה מקוצרת שלו. או עוד אחד נחמד הוא:
git config --global alias.ll 'log --oneline --graph'
שמציג לוג מקוצר עם git ll. אליאסים טובים הם אלה שחוסכים לנו עבודה אבל לא משאירים אותנו עם מושגים שגויים לגבי מה שבאמת קורה בגיט ולא מפריעים לנו לזכור גם את הפקודה המקורית.

ToCode
1 419
# היום למדתי: רקוקרסיה וטייפסקריפט טייפסקריפט יכולה להיות פשוטה וכיפית כשאנחנו נשארים על השבילים המוכרים, אבל גם אפלה ומעניינת כשאנחנו יורדים מהשביל ומנסים לשלב את התחביר שלה בצורות לא צפויות. היום הופתעתי לגלות שאפשר לכתוב קוד שבכלל לא חשבתי שיעבוד אבל בסוף הוא עושה בדיוק את מה שרצית ועוד קצת. הנה ההדבקה והסבר אחריה:
type Letter<Word extends string, R extends string[] = []> = 
  Word extends `${infer H}${infer T}` ? Letter<T, [...R, H]> : R[number];

// works! h is one of the letters in "hello"
const x: Letter<"hello"> = "h";

// compilation error! x is not one of the letters in "hello"
const y: Letter<"hello"> = "x";
בקצרה הטיפוס Letter שהגדרתי מקבל מחרוזת בתור Generics ומייצר ממנה טיפס שהוא איחוד של כל האותיות במחרוזת. בואו נפרק את זה כדי להבין איך ולמה זה עובד: הטיפוס הוא גנרי ומקבל שני פרמטרים: Word ו R, שזה קיצור של Result. ל R יש ערך ברירת מחדל אז אני לא מעביר אותו כשאני משתמש בטיפוס. הטיפוס מוגדר בתור Conditional Type. זה אומר שאם Word באמת מתאים לצורה:
`${infer H}${infer T}`
אז התנאי אמיתי ונלך לסימן שאלה, ואם לא אז התנאי שלילי ונלך לנקודותיים. אותה תבנית מוזרה נקראת Template Literal והיא מגדירה מחרוזת שמורכבת משירשור של שני טיפוסים. בגלל שאני יודע ש Word הוא מחרוזת, אז H ו T הם חלקים של אותה מחרוזת. החלק H הוא האות הראשונה, ו T זה המשך המילה. צריך לזכור שבטייפסקריפט כל אות היא טיפוס שמתאים רק לאותה אות, כלומר אני יכול לכתוב:
// compiles OK
const a: "a" = "a";

// compilation error - expecting only the letter "a"
const b: "a" = "b";
לכן H יהיה טיפוס של האות הראשונה במחרוזת, ו T יהיה טיפוס של מחרוזת שמורכבת מכל האותיות פרט לראשונה. המילה hello מתאימה לזה ועבורה ה H יהיה האות h קטנה וה T יהיה המחרוזת ello. בגלל שהמחרוזת מתאימה אנחנו הולכים לחלק שאחרי הסימן שאלה והוא אומר שהגדרת הטיפוס היא רקורסיבית:
Letter<T, [...R, H]>
הטיפוס מוגדר להיות Letter של T, שזה רק הסוף של המילה, אבל הפעם הפרמטר השני R מקבל ערך התחלתי - כל מה שהיה בו קודם פלוס הטיפוס H כלומר האות h הקטנה. לכן השלב הראשון ברקורסיה היה המעבר:
Letter<"hello", []> => Letter<"ello", ["h"]>
בגלל שזו רקורסיה טייפסקריפט ימשיך לפענח עוד ועוד צעדים שלה עד שהמחרוזת תתרוקן ואז נקבל:
Letter<"", ["h", "e", "l", "l", "o"]>
ואז מגיעים לחלק שאחרי הנקודותיים:
R[number]
בגלל ש R הוא מערך עם אותיות שידועות כולן בזמן קומפילציה, אנחנו יכולים לדבר על הטיפוס R[0] שהוא האות h, או הטיפוס R[1] שהוא האות e. אנחנו גם יכולים לדבר על הטיפוס R[number] שהוא איחוד של כל האותיות במערך, כי number יכול להתאים לכל מספר שהוא אינדקס חוקי במערך. התוצאה כמו שכבר בטח הבנתם היא הטיפוס שמזהה שמשתנה קיבל אות מתוך המילה. את הרעיון קיבלתי מוויליאם קנדילון, ואתם מוזמנים להמשיך לראות אותו גם בוידאו בו הוא בונה משחק וורדל שלם בעזרת טיפוסים בטייפסקריפט: https://www.youtube.com/watch?v=JT30j4nhej4