פרק מספר 504 של רברס עם פלטפורמה, שהוקלט ב-11 בנובמבר 2025 - פרק ראשון אחרי כנס Reversim 2025, שהיה מוצלח מאוד (גם בסטנדרטים הגבוהים ממילא). בזמן הקלטת הפרק עדיין לא פורסמו ההקלטות, אבל עכשיו כבר כן - Reversim Summit 2025 - YouTube - שווה לפנות כמה שעות טובות. בינתיים, ואורי ורן (וניק!) מארחים את דניאל בסקין כדי לדבר על תכנות פונקציונלי. 🎗️
[01:27] דניאל
- (דניאל) אז אני כבר שנים עם תכנות פונקציונלי - הוא רודף אותי, אני רודף אותו.
- בזמן האחרון אני עצמאי ומנסה להתפתח בכיוון של לייעץ, מתכנת - למכור לארגונים וחברות איכות קוד, בגדול.
- ותכנות פונקציונלי זה הדרך שבה אני בוחר לעשות את זה - ואני גם מאוד אוהב ללמד אותו.
- לכן אני פה - הייתי רוצה למצוא קהל יעד רחב לזה, ואני מקווה שהציבור שמקשיב ומאזין לפודקאסט הוא חד-משמעית קהל שיכול להתאים לזה.
- ואני עצמי, אם ניקח קצת אחורה לאיך שהגעתי לכל העולם הזה, אז בכלל התחלתי כמישהו שלמד פיזיקה, ותוכנה היה ממני והלאה.
- ועל הדרך לקחתי כמה קורסים באוניברסיטה בתכנות - וחשבתי שיום אחד אולי זה יהיה שימושי, משהו שם בפיזיקה, אני לא יודע.
- מפה לשם, גיליתי שאני ממש נהנה מזה - נהנה בעיקר מלסדר קוד, לעצב קוד, לעשות אותו ליפה ואלגנטי - ומשם התפתחתי לעולם התוכנה, עשיתי Java מספר שנים . . .
- ואז בטעות, סתם ניגררתי לאיזה בלוג שהראה “איזה שפה מוזרה עם חיצים”, ולא הבנתי מה זה . . .
- וזה כזה הציק לי, ואמרתי “טוב, יאללה, אני אשב רגע, אני אלמד מה זה החצים האלה ומשם נראה אם זה מעניין”.
- הסתבר שזו השפה שקוראים לה Scala, שרצה על-JVM - ומשם נבלעתי לחור שחור של תכנון פונקציונלי, ומאז זה הדביק אותי.
- הייתי עושה Java בסגנון הזה - כשזה עוד היה מאוד קשה ולא נוח.
- היום Java הרבה יותר “נחמדה” לזה, אבל עכשיו למעלה מעשור אני כבר מתכנת וכותב קוד בסגנון הזה
- ועוזר לאנשים אחרים לעשות את זה, מרצה ברחבי העולם גם, בכנסים וכדומה.
- ועכשיו הייתי רוצה - אז הייתי בעיקר בעולמות של Scala - עכשיו הייתי רוצה קצת להרחיב את קהל היעד, ולראות איך אני יכול לעזור לאנשים ב-JVM, Python, JavaScript, TypeScript, בכל מקום.
- להפעיל את אותם עקרונות שבעצם הם אוניברסליים יחסית - תכנון פונקציונלי זה לא משהו מאוד פרטני לשפה ספציפית, והייתי רוצה להרחיב את קהל היעד.
- לראות ולהביא לאנשים את הידע הזה עד כמה שאפשר.
[03:32] פונקציונליות טהורה - למה?
(רן) לפני שבועיים פחות או יותר, יונתן ממן מ-Teads [PKA Outbrain] פרסם Twit ושאל “מהי השפה שלמדתם ואף פעם לא השתמשתם?” . . . . והיו כל מיני תשובות משעשעות. וסתם סקרן אותי, האמת שלא עשיתי סטטיסטיקה, אבל כמה מבין השפות האלה הן פונקציונליות או “Pure Functional” ואחרות. אני אישית רשמתי שפות אחרות, האמת שגם ML . . . כן, אבל טוב, פשוט לא משתמשים בה הרבה בתעשייה.
(רן) Logo, נכון . . . היו כאלה שרשמו “חזקאל” [hasql], אבל גם היו Prolog ואחרים, כלומר שפות אחרות שהן אולי פחות פרקטיות. יפות, אבל פחות פרקטיות ויותר נישתיות.
אבל יש לא מעט שפות שמתהדרות במינוח “Pure Functional”, נכון? - ואולי כמעט כל השפות, יש בהן איזשהו אספקט פונקציונלי . . . .
- (דניאל) כן, חד משמעית. אני חושב שתכנות פונקציונלי הצליח . . .
(רן) . . . .אולי חוץ מ-C, אני לא יודע - אבל כמעט כל השפות היותר-מודרניות, יש בהן איזשהו אספקט פונקציונלי.
(אורי) האמת שבסוף ה-Processor עובד ככה - יש Interrupt-ים, מגיבים ל-Interrupt-ים . . . .
(רן) כן, למרות שאני חושב שאם אתה רוצה להיות קרוב לחומרה, אתה כנראה תהיה כמה שיותר אימפרטיבי (Imperative) - שפות כמו C או ++C - והפונקציונלי, דווקא הוא קצת מתמטי, הוא קצת יותר אבסטרקטי.
- (דניאל) אם כי פעם היה איזה ניסיון היסטורי לעשות Lisp Machine, אני חושב . . . מכונה שספציפית הייעוד שלה היה לעבוד בצורה ש-Lisp עובד.
- לא תפס, לצערנו, אבל כן - קרוב לחומרה אנחנו פחות . . .
(רן) כן, אז אולי בתור התחלה נבוא ונגיד שתכנון פונקציונלי זה לא רק Lisp ולא רק Clojure ולא רק ML, אלא זה כנראה נמצא כמעט בכל השפה שאתם משתמשים בה היום. זאת אומרת, הזכרת את Scala שהיא חזקה בזה, אבל זה גם קיים ה-Java, זה גם קיים ב-Python וזה קיים הרבה מאוד ב-JavaScript וב-Ruby וכנראה בכל שפה אחרת שנמצאת פה באזור.
- (דניאל) תנחומיי. אני לא יודע אם זה . . .
(אורי) . . . עוד פעם, באמת, ממש לקבל Interrupt-ים מכרטיס רשת וכאלה.
(רן) אבל אז נשאלת השאלה: למה? זאת אומרת, כי זה יפה? כי זה טוב? כי זה עוזר לנו במשהו? . . . זאת אומרת, למה בכלל?
- (דניאל) התשובה היא מבחינתי היא “גם וגם וגם וגם” . . . . בערך לכל דבר שתשאל.
(רן) אז בוא נפרט . . .
- (דניאל) אז קודם כל, אני חושב שאנחנו מתכנתים - באנו לכתוב תוכנה, באנו לכתוב אותה טוב
- ואני חושב שתכנון פונקציונלי מביא לנו, “שם על השולחן”, מלא מלא דברים שקשורים בדיוק לזה.
- כל מיני מילים שנגמרות במילה “Ability”, כמו Maintainability, Testability, Debuggability ועוד “Abilities” למיניהם.
- וזה לא סתם במקרה, אלא זה משהו מאוד אינהרנטי (Inherent) בתכנון פונקציונלי - שזה ההתרכזות ב”פונקציות טהורות”
- פונקציות עם קלט ופלט.
- זה משהו שהוא הרבה יותר נוח לעבודה, אם אפשר למסגר את הבעיה בצורה הזאת.
- נגיד Testability - אם יש לי איזה Side Effect מלחיץ שעושה Mutation על איזה משתנה, לא יודע איפה
- כשאני רוצה לעשות טסט על הדבר הזה, אני צריך עכשיו, לפני שאני מריץ את הטסט, לעשות Setup ועוד פה ושם וזה . . .
- ואז בסוף, אחרי שעשיתי את ה-Setup המורכב והמתוסבך הזה, אז אני עושה טסט.
- אם אני עובד בעיקר עם פונקציות טהוריות, עם קלט ופלט - איתחלתי את הקלט, הסתכלתי על הפלט, סיימתי.
- אז טסטביליות (Testability) בסגנון הזה זה משהו שבא כמעט “בחינם”, ברגע שנכנסים לעולם הזה.
- אגב, אנחנו אולי קפצנו קדימה, כי לא ניסינו להגדיר מה זה “תכנות פונקציונלי” . . .
(רן) כן, אבל זה בסדר לקפוץ קדימה, כי לומדים מהדוגמאות . . . אז דיברת על האספקט של Purity, אוקיי - מה זה אומר?
- (דניאל) אז Purity - יש כל מיני דרכים להגדיר אותה, ולא כולן חופפות במאה אחוז, אבל אני אוהב להסתכל על זה בתור “השימוש בפונקציות שהן מתמטיות”, במובן הפורמלי של המילה.
- “מתמטית” - כי יש קלט, יש פלט, ואין שום דבר אחר.
- מה ההפוך של זה? נגיד, יש לי פונקציה שמה שהיא עושה זה שהיא מקבלת “שום קלט” ומוציאה Void, אין לה כלום . . . .
- אז זה נגיד לא פונקציה מתמטית - אין לה קלטים, אין לה פלטים, וכל פעם שאני אריץ אותה, אני לא יודע מה יקרה . . . .
- יקרה משהו, אבל לא רואים את זה מגולם בקלט והפלט.
- (רן) נגיד מדפיסה על המסך . . . דוגמה קלאסית.
- (דניאל) מדפיסה על המסך, כן. אז קשה לכתוב לזה טסט, קשה לתפוס ברקע את ההדפסות האלה, קשה להבין מה קורה שם - לי, ל-Compiler, ל-AI . . .
- אם אנחנו חייבים לדבר על AI, אז גם ל-AI יהיה קשה לתפוס מה קורה שם.
- אז פונקציה טהורה זה פונקציה שהפוך מזה - פונקציה שכל מה שיש לה לעשות זה לקחת את הקלטים שלה ולהוציא פלטים.
- עכשיו, אני לא טוען שתמיד אפשר למסגר כל בעיה בצורה הזאת - וזה מאוד תלוי בסביבה, ויש שפות שפחות טובות או יותר טובות.
- אבל אם הצלחתי לכתוב את הבעיה שלי - לנסח אותה בצורה הזאת - אני מוצא את עצמי מפרק את הבעיה למלא אבני-בניין קטנות של פונקציות טהרות.
- וכל פונקציה - אפשר לקחת אותה בנפרד: לשחק איתה, להריץ אותה בנפרד, לעשות את הטסטים בנפרד - ואז זה Mix & Match בין הדברים האלה.
- אז זה מביא לנו יותר טסטביליות (Testability), זה מביא לנו מודולריות שהרבה פעמים אנחנו שואפים אליה
- דברים כמו Reusability, שזה גם משהו שתמיד מבטיחים לנו בכל פרדיגמה חדשה שמגיעה, מבטיחים לנו את זה.
- ותכנון פונקציונלי זו לא פרדיגמה חדשה - היא התחילה אי-שם בשנות ה-60, והיא אף פעם לא הבטיחה לנו את זה אפילו - אבל עם זאת, אם הקומפוננטה (Component) זה פונקציה טהורה, קטנה, וחמודה, שאני יודע בדיוק מה הקלטים הפלטיים שלה, הרבה יותר קל למחזר אותה.
- כי אני לא צריך לחפש את התלויות הבלתי-נראות ברקע.
(רן) האם, דרך אגב, אתה מכיר את המושג של Purity בפרדיגמות אחרות? זאת אומרת, בשפות אימפרטיביות (Imperative) אני מניח שהתשובה היא, באופן ריק, “לא” . . . אבל מה עם השפות הלוגיות?
- (דניאל) אז יש שפות לוגיות, למשל, Prolog היא שפה לוגית שעד כדי כל מיני דברים של יעילות היא יחסית טהורה.
- אבל אפילו בשפות אימפרטיביות (Imperative) יש משהו שיכול להיות טהור - אבל זה לוקח אותנו ממש לאיזוטריקה . . .
- יש שפות שיש בהן . . . משהו בסגנון Rust [דותן חש הפרעה בכוח], שיש Borrow Checker - לוקחים את זה עוד כמה רמות למעלה.
- יש לנו מה שנקרא Linear Typing, ואז אומנם אני יכול לעשות שינויים במערכים in place, אבל אני אף פעם לא יכול לגשת לדבר ששיניתי בעבר, אני יכול רק להתקדם קדימה.
- ואז במקום מסוים אני אהיה Pure, בזה שאני לא יכול לחזור לדברים ש”זיבלתי” מאחורי.
- אבל זה ממש איזוטרי ולא קשור לעולם ה-Mainstream-י שבו אנחנו עובדים.
(רן) לא, אבל זה כן מעלה נקודה מעניינת, כל הנושא של Purity.
[10:00] יעילות / מבני-נתונים גדולים ושינוי קטן
(רן) יצא לי לעבוד לא מעט, נגיד ב-Clojure, אוקיי? ונגיד שם עובדים הרבה פעמים על Map-ים, על רשימות . . . ועכשיו, נגיד שיש לך רשימה מאוד מאוד מאוד ארוכה - ואתה רוצה לשנות אותה. כיוון שהפונקציה היא Pure, היא לא תשנה את הרשימה אלא תייצר איזשהו עותק - ופה אני שואל את השאלה של יעילות.
אז איך השפות האלה מתמודדות עם הצורך לעבוד, נגיד, עם מבני-נתונים גדולים ושינוי קטן?
- (דניאל) אז קודם כל, אם ממש-ממש רוצים, רוב השפות ייתנו איזשהו Escape Patch - בין אם הוא יותר Escape-י או פחות Escape-י, שמאפשר אולי אפילו לעשות משהו Mutable.
- אבל אם לא, אז יש פה איזשהו Trade-off: קודם כל, להשוות מבנה נתונים Mutable למבנה נתונים Immutable, זה השוואה של “תפוחים ותפוזים”.
- כי למבנה Immutable יש “כוח-על”, במירכאות - שגרסאות-העבר שלו נשמרות, וזה אומר שאני יכול להמשיך להשתמש בהן.
- זאת אומרת שיש לי “Snapshot-ים חינם” לאורך כל השינויים שלי.
- אז להשוות אותו אחד לאחד ל-Mutable-Immutable זה כבר קצת מסתיר מאיתנו חלק מהיתרונות.
- אבל אם אני רגע אפילו מתעלם מזה - יש, ברגע שאני נכנס לעולם של Immutable, אני כן צריך לחשוב מחדש על ביצועים והעלויות האסימפטוטיות של כל דבר שאני עושה.
- ויש שני דברים, שתי תשובות מרכזיות -
- אחת - להתחכם במבנה נתונים: אז במקום לעבוד עם מערך שהוא Immutable, עובדים עם איזה עץ, עם איזה פיצול 32 שעושה משהו שהוא לוגריתמי ולא O(1), אבל לכל או להרבה צרכים פרקטיים מאוד מאוד מהיר.
- זה מצד אחד.
- ומצד שני, יש אספקט אחר של Immutable או Purity, וזה שאפשר לעשות Sharing, כן?
- אם יש לי חתיכה של מבנה-נתונים שלא השתנתה בין לבין, אז אין שום בעיה לשתף אותה בין כל העותקים השונים.
- ופה אני חוסך על אלוקציה (Allocation), GC, ועוד מלא דברים אחרים שברקע יכולים להיות בעייתיים.
- ואז, אולי, לקבל חזרה חלק ממה שאיבדתי.
- פרקטית, אם אני כותב איזה Backend שרובו חי על Database-ים ותקשורת-רשת, כנראה שזה לא הדבר שיעסיק אותי.
- פרקטית, הניסיון שלי עם נגיד Scala - עבדתי בשרת, מבנה נתונים - רוב הזמן זה לא היה.
- היו מקרים, אגב, אבל רוב הזמן, המבנה נתונים זה לא מה שעיכב אותי בחיים, כי שתי התכונות האלה כנראה מפצות על הפערי ביצועים.
- (רן) כן.
[12:08] העדפות לגבי Typing
(רן) תכונה נוספת שאני חושב שמאוד מאפיינת, אולי אפילו מבדילה, בין שפות פונקציונליות שונות, זה Typing, משמע - Strong Typing versus No Typing, או Weak Typing.
אז נגיד שפות כמו JavaScript - לפחות הגרסאות הראשונות היו ללא Typing, שפות כמו Clojure ו-Lisp - אין להן Typing. לעומת זאת, שפות כמו ML, הזכרת את Scala, הזכרת את Rust - יש להן Typing מאוד מאוד חזק.
איפה אתה? איפה העדפות שלך?
- (דניאל) אז אני חד משמעית “מכור ל-Type-ים” . . . . אני מתקשה לדמיין את עצמי חוזר חזרה לעולם הדינמי.
- כי אני חושב שהשריר הזה שמצליח להחזיק את כל ה-Context מסביב כשהשפה דינמית הלך והתנוון עם השנים, כי אני פשוט הפסקתי להשתמש בו . . . .
- ה-Complier כל הזמן מלווה אותי, הוא החבר הכי טוב שלי - וככה אני מתייחס אליו.
- הוא החבר הכי טוב שלי - אני צריך לטפח אותו, לעזור לו, לא לשקר לו . . . . יש לנו הרבה דברים שכדאי לעשות עם חברים טובים.
- (דניאל) אז אני חד-משמעית הולך לכיוון ה-Type-ים - בעיקר כי לי זה מרגיש נוח.
- זה מאפשר לי לחשוב ברמה High-Level-ית על הקוד שלי, בלי להיכנס לפרטים בצורה שהיא לא קשקוש על הלוח, אלא ממש דברים שאני יכול אחרי זה להתחיל לקמפל (Compile) ולעקוב אחרי התוצאות שלו.
- אז אני כן יותר אוהב לעבוד בעולם של Type-ים - Scala זו מן הסתם שפה שהיא Typed, ולא במקרה נשארתי איתה.
- אבל אני חושב שהרבה מהיתרונות של תכנות פונקציונלי אפשר גם להוציא גם ללא Type-ים - אבל שוב, אני אישית חד משמעית על ה-Type-ים.
[13:36] אז למה?
(רן) אוקיי, אז בוא נחזור רגע - עשינו איזשהו מעבר קצר, ובוא נחזור ל”למה?” אז אמרת והזכרת דברים כמו Testability ו-Maintainability וכו’ . . .
- (דניאל) כן, אז דבר שני שאני חושב שהוא אמור להיות מאוד אינטואיטיבי לרוב האנשים שאי פעם נגעו בקוד, זה מושג שקוראים לו “Spooky action at a distance”
- מושג שהגיע אי-שם מפיזיקה, איפשהו אינשטיין זרק איזה משהו [יש מצב שזה ה-TL;DR ל-Quantum Entanglement הכי חזק אי פעם . . . .], אבל...
- (דניאל) At a Distance, כן. אז זה סופר-אינטואיטיבי . . . . כאילו, מילים גדולות כאלה, אבל זה דבר מאוד אינטואיטיבי.
- דמיינו: אני כותב קוד - כמה זמן - “פיפס! משהו נשבר שם” - מקום לא קשור, “מה אני קשור?!”
- (רן) הפרפר, “משק כנפי-הפרפר” . . . .
- (דניאל) כן, בדיוק.
- אז ה-Spooky Action פה זה שכאילו עשיתי משהו, לא ידעתי על מה השפעתי - ומשהו במרחק גדול מהקוד שלי הלך ונשבר.
- אז אם אני מתחיל להסתכל מה הסיבות לדברים כאלה בקוד, אז הרבה פעמים הסיבות יהיו דברים כמו Side-Effect-ים:
- יש לי משתנה Mutable-י שהשתנה פה, ואז הוא השפיע על משהו שם - ואיזו שרשרת של דברים Mutable-יים שקרו ברקע.
- או שכתבתי משהו לאיזה Database וקראתי אותו במקום אחר, וכדומה.
- עכשיו, Database-ים - אולי חבל שנגעתי בהם, אני לא רוצה ללכת ולהגיד מה אני עושה מול Database, אבל יכול להיות פחות משתנים, או דברים כאלה שהם Side-Effect-ים שאני עושה בתוך הקוד שלי, וזה משהו שתכנון פונקציונלי מנסה להסתיר מאיתנו.
- אז אם אני עובד עם פונקציות טהורות, עם קלט ופלט, אחד הדברים שאני לא עושה זה משנה משתנים - כי זה לא חלק מהקלט ופלט.
- בעצם, פונקציה שעושה Side-Effect-ים, יש לה כאילו ערוץ תקשורת נוסף, שהוא כזה בלתי נראה, שהולך וברקע עושה כל מיני דברים.
- אז התופעה הזאת - Spooky Action at a Distance - זה משהו שאני בטוח שכל מי שנתקל בו לא רוצה שזה יקרה.
- זה כאילו סבל כזה של “אני מפחד לגעת בקוד, כי אין לי מושג איפה הוא ישפיע בהמשך”.
- ותכנון פונקציונלי לא מסיר את זה לחלוטין, וכמובן שבסוף גם שם יש באגים - אבל לדעתי אם אני עובר למצב שבו אני Immutable ומפסיק להשתמש במשתנים, אז לא יודע מה, 80% מה-Spooky Action at a Distance הביזאריים וכל מיני Race Conditions מוזרים ולא יודע מה, פשוט נעלמים.
- (דניאל) בהקשר של קוונטים וחוסר לוקאליות של אינטראקציה בין דברים . . .
(רן) הוא לא אהב את הקוונטים, איינשטיין . . .
- (דניאל) הוא כנראה קצת ראה קדימה, מבחינת מה שהוא דיבר עליו. אבל כן, היה שם איזה קונפליקט לגבי מה אנחנו חווים לעומת מה שיש.
- אבל שם זה גם, זה כאילו משהו שמאוד הפריע רעיונית - אנחנו חושבים על דברים, אנחנו עושים פעולות פה - ואנחנו מצפים ששם לא יקרה כלום. זה מוזר . . .
- אז כמתכנתים, אני חושב שאנחנו חווים את זה על ימין ועל שמאל.
- ותכנון פונקציונלי לוקח ומצמצם את זה משמעותית.
- כאילו Immutability פלוס ”להפסיק להשתמש ב-Var”, וכבר אתם במצב הרבה הרבה יותר טוב.
- וזה מבחינתי סיבה מספר 2, או אפילו 1, ללכת לכיוון הזה.
- וסיבה מספר 3, שאולי אפילו מעניין להסתכל על זה מחדש, אולי אני אשים את זה מספר 1 - זה תכל’ס כיף . . . .
- כי אני מצאתי את עצמי כשהגעתי לעולם הזה, אז הייתי כותב Java, וזה היה כזה מאוד וורבוזי (Verbose) ועם מלא מלל על דברים מאוד פשוטים.
- אז התחלתי ללמוד לחשוב פונקציונלית וללמוד על פונקציות כמו Map ו-Filter וכדומה - וב-Java זה עוד לא היה משהו מובנה, היו כל מיני ספריות-צד-ג', אבל לא היו פונקציות אנונימיות.
- אז מצאתי את עצמי שהקוד שאני כותב שם, ושלוקח לי “קילומטרים של טקסט” לכתוב, פתאום זה נהיה כזה קליל וכיפי ואלגנטי.
- וגם תחום פונקציונלי הרבה פעמים נוטה להיות מה שנקרא דקלרטיבי (Declarative) - אני מצהיר יותר “מה” אני רוצה לעומת “איך”.
- אני לא מדבר על הצעדים הקטנים של “איך” לעשות כל פקודה, אלא אני אומר “אני רוצה בגדול את הדבר הזה”, ולא נכנס לפרטים.
- והרבה פעמים התיאור של “מה” הוא הרבה יותר פשוט, הרבה יותר קריא, הרבה יותר נוח להבין, מאשר התיאור של צעדים, איך צעד אחר צעד לעשות דברים.
- ואם נגיד אני מסתכל על לולאות For לעומת Map, אין להשוות - הקוד הזה בעיניי הרבה יותר קריא, הרבה יותר כיף לעבוד איתו.
- ואז לאט לאט, ככל שמתקדמים בעולם הזה, אז במיוחד אם זה הולך עם Type-ים וכדומה, פתאום העולם מתחיל להיראות כמו איזה חתיכות לגו.
- זה כזה “אני מחפש את האבני-לגו הנכונות”, ואז אני מקליק אותן - ופתאום הכול פשוט נבנה מעצמו לאט -לאט.
[17:42] פרקליט השטן (הפונקציונלי)
(רן) פה אני רוצה רגע לשחק את פרקליט השטן - אני חושב שזה Skill שצריך לבנות. זאת אומרת - היכולת לבוא ולראות את “הלגו” הזה, היכולת לראות את המודולריות הזאת, היכולת לבוא ולקרוא קוד שמכיל Map-ים
ו-Filter-ים וכו', ולהבין היטב מה הוא עושה - זה Skill שצריך לפתח, ואני חושב שזה יכול להיות מאוד מאתגר.
זאת אומרת, הניסיון שלי - גם לי אישית, וגם יצא לי לעבוד בחברות שבהן יש קוד די משמעותי שהוא פונקציונלי -
זה Skill שלהרבה מפתחים קשה לפתח.
עכשיו, יכול להיות, אפשר לבוא ולטעון, שזה בגלל הדרך שבה למדנו מדעי המחשב, בגלל הדרך שבה יש הרבה שפות אחרות שכתובות. זאת אומרת, מאוד יכול להיות שזה איכשהו “ככה האנושות לקחה אותנו לשם”, אולי.
אבל אני כן חושב שזה מצב נתון - לטעמי האישי, ללא מחקר אקדמי - שלמפתחים יותר קל עם קוד אימפרטיבי (Imperative), יותר קל להבין קוד אימפרטיבי, מקוד פונקציונלי, אף על פי שהוא יותר קצר בדרך כלל, ואני מסכים איתך שהוא באמת נראה יותר אלגנטי. אני חושב שהוא מחביא בתוכו הרבה הבנה שהיא Implicit, שזה Skill שצריך לפתח - ולא לכולם יש אותו.
- (דניאל) אני לא מתווכח עם זה - בגלל זה בא לי להציע סדנאות לאנשים, שמישהו יבוא ויביא אותי ויעזור לי ללמד את זה.
- ואני גם חוויתי את זה שכשאני מלמד אנשים, ולפעמים לוקח זמן קצת למחוק את התפיסות שהם הגיעו איתן מניסיון קודם, ולהמיר אותן למשהו שהוא יותר מתאים לזה.
- אבל - ואני מסכים, חד משמעית -אם אני מוציא עכשיו, מוציא סטודנט מהאוניברסיטה, שם אותו מול מסך, סביר להניח . . .
- יצא לי לא מזמן להיות מתרגל בקורס של hasql באוניברסיטה - והסטודנטים היו בשוק.
- זה היה שנה ג', ולא היה להם מושג על מה נפלתי עליהם - והם סבלו, כולם . . .
- חד משמעית יש את זה גם עם אנשים יותר מנוסים.
- זה מצד אחד.
- מצד שני, אני חושב שיש פה איזושהי אשליה מסוימת - אז נכון שלמדתי לקרוא יותר, אולי אני יותר מורגל לקרוא לולאות, לראות צעד-צעד ולעקוב אחרי משתנים [הי בגרות במדמ”ח ב-2000!] - אבל זה מאוד לא סקיילבילי (Saleable).
- אני יכול להסתכל על עשר שורות קוד, להבין בדיוק מה שקורה שם בכל המשתנים.
- ויותר מזה, אני יכול להגיד שגם בתכנות פונקציונלי, לפעמים אנחנו נחביא מאחורי ממשק “טהור” משהו שהוא, מטעמי ביצועים, עוד משהו עם טיפה משתנים, בסדר.
- אבל אם אנחנו עושים לזה Scale-out - בונים קוד יותר ויותר גדול - אז כל “התלויות הבלתי נראות האלה”, כל המשתנים וכל הדברים שקורים באופן בלתי נראה, הם דברים שהולכים וגדלים.
- כמו שאם לפני כמה עשורים מישהו היה אומר “אבל Go-To! - אנשים יודעים לקרוא Go-To יותר טוב!”
- ואז הייתם כאילו אומרים “נכון, אבל Go-To מכניס מלא בלאגן . . . “.
- אז באותה צורה אני רואה תכנות אימפרטיבי (Imperative) - לא באופן גורף, לא במאה אחוז, אבל הרבה פעמים לאט לאט בונה לנו תלויות בלתי נראות בתוך הקוד, כך שהאנתרופיה של הקוד גדלה עם הזמן.
- ותכנות פונקציונלי, לדעתי, בולם את זה.
- עכשיו, יש פה מחיר - יש פה מחיר של ללמוד, להבין, להכיר את הקונספטים החדשים.
- אבל חוץ מזה שהוא בולם את זה - אני חושב שהוא גם פותח דלתות חדשות, כן? מאפשר לכתוב קוד שהוא עושה דברים יותר סתם . . . .
- נגיד, פתאום מקביליות נהית עולם אחר לחלוטין, שאולי אפילו יותר קל - במובנים מסויימים - לנהל, מאשר עם משתנים.
- ומנעולים, ו-Deadlocks, ולא יודע מה . . . .
(רן) לא ניכנס שם לעומק, אבל אם יש State משותף, מאוד קשה לנהל מקביליות. אם אין State - דיברנו על פונקציות טהורות - אם אין State, אז יותר קל.
(אורי) אבל בסוף ה-State הוא העולם שאנחנו רוצים לשנות, שהקוד שלנו ישנה . . . . זה נחמד מאוד להיות “טהור” [היי וולדמורט], אבל אתה צריך להזיז משהו בעולם.
(רן) כן, אבל פה - סליחה, אני אנסה לענות בשמך, דניאל, ואתה תתקן אותי - אני חושב שהחוכמה זה לדעת להבדיל בין קוד טהור לקוד לא-טהור. זאת אומרת, לשים את הדברים המסובכים במקום אחד - ואת הדברים הפשוטים יותר במקום אחר, וככה יותר קל לשלוט ב-Blast Radius, או מה שקראת לזה “Spooky Action at a Distance”, . . . .
- (דניאל) בגדול כן. אני אמרתי שאני, בתור מישהו שבא מרקע של פיזיקה - מבחינתי שהמחשבים יהיו קופסאות שמתחממות ולא לעשות שום דבר מועיל לאף אחד, סבבה.
- אבל אני מכיר בזה שבעולם התוכנה אנחנו רוצים . . .
(אורי) בפיזיקה יודעים שהן לא רק מתחממות, אלא מחממות . . .
- (דניאל) כן, אבל אני מכיר בזה שעולם התוכנה לרוב רוצה גם תוצרים שבאמת עושים משהו, ובסוף באמת הסוד הוא איך לחלק את הדברים לכאלה שאני יכול לשלוט עליהם כך לבין דברים שאני לא יכול.
- ובסוף, לרוב בכל עולם של תכנון פונקציונלי שאני אנסה להיכנס אליו ברצינות, תיהיה בסוף איזשהו דרך לנהל State.
- אז זה לא משהו שאני משמיד אותו - אבל אני הופך אותו למנוהל, אני הופך אותו ליותר Explicit, אני הופך אותו למשהו שיותר קל לעקוב אחריו.
- בין אם זה עם Type-ים ובין אם זה מבחינת ה-Flow של הקוד.
- והרבה פעמים מגיעים נגיד ל-Design שקוראים לו Imperative Shell - Functional Core.
- אז אני באמצע - הליבה, ה-Business Logic שלי, הדברים שמאוד חשוב שנכתוב עליהם טסטים ושהגיוני שזה יהיה קל - אז הם יהיו בסגנון הפונקציונלי.
- וכל זה יהיה מחובר למעטפת קלילה יחסית, בתקווה כמה שיותר קטנה, שהיא התקשורת עם העולם החיצון - Database-ים וכדומה - שהם מזינים את הליבה הפונקציונלית.
- אז כן, ברור שאנחנו לא נשמיד Side-Effect-ים לחלוטין, אבל אנחנו נמצא דרכים לנהל אותם בצורה יותר מסודרת, וזה מבחינתי הדרך באמת למקסם את התועלת מתכנות פונקציונלי, ואשכרה לעשות משהו מועיל למישהו.
[23:01] פיל בחדר של אינטליגנציה מלאכותית / מי עוזר למי?
(רן) סבבה, אז דיברנו על מספר יתרונות של תכנות פונקציונלי, וגם אמרנו ש... זאת אומרת, לא חייבים להיות All-in - זה נמצא בהרבה מאוד שפות שהן סופר-Mainstream היום, הזכרנו Java ו-Python ו-JavaScript, you name it - אבל לא דיברנו על AI [משחק שתייה?].
ועכשיו, השאלה היא . . . . הרגע שאורי חיכה לו, כן - השאלה: האם אתה רואה פה יחסי גומלין או אדישות
בין תכנות פונקציונלי ל-AI? או במילים אחרות, האם פונקציונלי עוזר ל-AI? האם AI עוזר לפונקציונלי? או שהם פשוט אדישים אחד לשני?
- (דניאל) אז אני חושב. . . . אני לוקח את זה לכיוון של “פונקציונלי עוזר ל-AI”.
- לא מזמן אפילו הוצאתי קליפ ל-YouTube לראשונה, בניסיון להדגיש את הנקודה הזאת [Make Illegal AI Edits Unrepresentable - YouTube].
- חד-משמעית עוזר - ולא רק כי זה נוח לי מטעמי מכירות, אלא בצורה מאוד קונקרטית.
- אם אני הולך לכיוון של תכנות פונקציונלי עם Type-ים, שבעצם אם נפתח את זה יותר טכנית, אז נראה שבעצם יותר תכנות פונקציונלי הופך את ה-Type-ים ליותר “מלאים” - תיאוריים, יותר מרחיבים מה שקורה במציאות.
- לצורך העניין, אם לפני זה דיברנו על חתימה של “מקבלת כלום - מחזירה Void”, בתכנון פונקציונלי זו לא תהיה חתימה כזאת - היא תהיה חתימה עם input, Output ומשהו באמצע.
- אז ברגע שיש לי את הדבר הזה, אני בעצם מערב את ה-Complier פנימה - ה-Complier נהיה משהו הרבה יותר חזק.
- עכשיו, אם נסתכל על AI - או על בני אדם לצורך העניין, ו-AI הם פשוט, נכון לכרגע, גרסה קצת מוזרה של בני אדם: גם חכמים יותר, גם טיפשים יותר, איכשהו בו-זמנית - אז אם נסתכל על AI . . .
(אורי) . . . כמו בני אדם . . .
- (דניאל) כן, גם בני אדם, כמו בני אדם, כן . . . אז אם נסתכל על איך שה-AI או בני אדם מסתכלים על קוד, אז אנחנו לרוב מסתכלים בצורה מאוד מאוד מקומית.
- יש לנו איזשהו Scope כזה של כמה שורות קוד, אנחנו מבינים מה קורה שם פלוס-מינוס, ומעבר לזה, כאילו, כן - אנחנו יכולים להחזיק עוד את כל ה-Class, שלושה Calss-ים, אולי אני עבדתי על הפיצ'ר איזה שבוע אז אני יודע טיפה יותר.
- אבל בסוף יש איזשהו תחום סופי שבו אני מסוגל להחזיק ידע.
- וכך גם ה-AI - החלונות שלהם גדלים, אבל בסוף הם סופיים.
- כשאני מסתכל על Codebase גדול, אז באיזשהו שלב, אם יש לי איזה Invariant או משהו בקוד שצריך להיאכף לאורך כל הקוד, שהוא יותר גדול מה-Scope שה-AI מצליח לתפוס, אז אני אהיה בברוך.
- כי ה-AI יעשה את מה שהוא עושה - נגיד שהוא עשה את זה טוב - אז יעשה מה שהוא עושה בתוך ה-Scope הקטן הזה, אבל אז הוא שבר את האינווריאנטה (Invariant) שם בפינה של הקוד.
- מצד שני, אם אני הלכתי לכיוון התכנות הפונקציונלי, אם מקסמתי את היכולת של ה-Complier . . . .
(רן) כלומר, אתה מדבר על, נגיד, חתיכת-קוד שנמצאת מחוץ לחלון ה-Context . . .
- (דניאל) כן, לחלון ה-Context . . . . אז אם הלכתי לכיוון של פונקציונלי, הלכתי יותר All-in על ה-Complier שיעזור לי.
- אז ה-Complier, להבדיל מבני אדם או AI, מסתכל ב-Scope גלובלי.
- אם יש לי איזושהו Variant או משהו שקודדתי בצורה של Type -ים, הוא יאכוף את זה על כל הקוד, לאורך כל ה-Codebase כולו.
- ואז אם ה-AI שבר לי משהו פה, ה-Complier יכול להגיד “אה, שם גם נשבר משהו!" - וזה מבחינתי משהו מאוד מאוד משלים.
- גם לבני אדם - מבחינתי זה טכניקות שהיו רלוונטיות לבני אדם עשורים.
- וגם עכשיו עוד יותר ל-AI - כי AI זה פשוט מייצר לנו עוד ועוד קוד, והרבה יותר קשה לעקוב אחרי זה, אז...
(רן) כן, אבל אני אהיה קטנוני ואני אגיד שאתה מדבר על Complier ועל Typing - אבל אתה לא אומר “פונקציונלי”.
כלומר, יש שפות פונקציונליות שהן לא Typed, יש שפות שהן אימפרטיביות (Imperative), שהן Strongly-Typed,
זה לא אותו דבר.
- (דניאל) נכון, זה לא אותו דבר. אז תפסת אותי על . . . אני כזה עושה קצת סוויץ' בין שני הנושאים.
- אז קודם כל, אני באמת נוטה לכיוון של ה-Typed, אבל זה עובד בשני הכיוונים.
- א' - ה-Spooky Action at a Distance רלוונטי גם בעולם דינמי.
- אם ה-Python או ה-JavaScript שלי כתובים בצורה שיש הרבה פחות השלכות בלתי-נראות ברחבי הקוד, אז הסיכוי שה-AI ישבש לי משהו על ידי איזשהו Shift פה, ישבש משהו שם - יותר נמוך.
- ומצד שני, אני באמת מעדיף לעבוד בצורה שהיא Typed - והקשר בין זה לתכנון פונקציונלי זה שתכנון פונקציונלי, בגלל שהוא מכריח אותי להיות מאוד Explicit לגבי מה שאני עושה, הוא בין השאר הופך את ה-Type-ים שלי ליותר Explicit.
- עכשיו, אני לא חייב, ואני יכול לעבוד עם Type-ים פרימיטיביים יחסית ועדיין להיחשב לפונקציונלי על הנייר.
- אבל פה הייתי מוסיף עוד איזה עיקרון משלים, שזה משהו שמאוד פופולרי בעולם של תכנון פונקציונלי, אבל הוא לא מחויב לשם, וזה עיקרון שקוראים לו “Make your legal states unrepresentable”.
- המגמה של לנסות לקחת כל מיני Variant-ות בקוד ולקודד כ-Type-ים.
- ואז זה נכון שאפשר לעשות את זה מחוץ לתכנון פונקציונלי - אבל פרקטית, תרבותית, זה הרבה יותר מושרש בעולם של תכנון פונקציונלי.
- לרוב ה-Type System של שפות פונקציונליות הן יותר גמישות ויותר מתאימות לפרדיגמה הזאת של make your legal states . . . .
(רן) (רן) בוא רגע נגיד . . . . ניתן דוגמה למה זה “Make your legal states unrepresentable”: נגיד יש לך Class שיש בו משתנה, סליחה שאני בערך לא אירופי, אם המשתנה הזה אסור שיהיה NULL, אז אתה חייב לאתחל אותו ב-Constructor, נכון? אסור לעשות לו Set. אז זאת אומרת שאם ה-Class הזה קיים, אז המשתנה הזה יש לו ערך. אם הוא לא קיים, אז זה בסדר.
(אורי) נראה לי שהתייחסתם ל-AI כמשהו שעוזר לנו בכתיבה - של שפות כאלה ושפות כאלה - אני רוצה להתייחס לשאלה של “אוקיי, נניח שאנחנו כותבים מערכת Agent או משהו שמשחק בעולם של AI, שהוא עולם פחות צפוי ב-Inputs וב-Outputs שלו, האם עבודה בשפה פונקציונלית תעזור לנו פה? מבחינת, למשל, Testing? דיברת על זה בהתחלה - Testing הוא יותר קל כי אני יכול לדעת מה Input, מה ה-Output וכאלה, אבל פתאום יש לי עולם אחר,
שהוא פחות צפוי, כשאני עובד מול LLM.
- (דניאל) אז שוב - גם לפני LLM-ים העולם היה לא צפוי . . . . יש לי Database, יש תקשורת-רשת, יש לי Timeout-ים, דברים מפה ועד להודעה חדשה שאני לא צופה אותם.
- וזה קיים וזה לא נעלם משום מקום בין כה וכה.
- השאלה היא איך אני מגדר את העולם שבו אני רוצה להבין דברים.
- אז אוקיי, LLM עשה משהו - הוציא לי פלט.
- הפלט הזה זה פיסת-דאטה - אני יכול לפרמל מה אני מצפה שיהיה בדאטה הזה.
- ומשם והלאה אני יכול להמשיך בגישה הפונקציונלית - לאסוף טרנזקציות ועוד ועוד, ואז להחזיר את זה חזרה “אל הלא נודע של ה-LLM” או מי שזה לא יהיה [סנדרסון?]
- השאלה זה לא כמה לא נודע יש לי בחוץ, אלא כמה מתוך זה אני יכול למסגר בשביל לעבוד איתו.
- עכשיו, האם ה-Agent עושה “הכל מהכל”, וכל Input הוציא כל Output והוא החליף אותי? אוקיי, אז אני לא יודע.
- אני מקווה שה-Agent-ים יום אחד יגלו תכנות פונקציונלי בעצמם ויחליטו לעבוד ככה.
- אבל מבחינתי, כל עוד אני עדיין צריך להבין מה קורה בקוד, ואני רוצה שגם מי שעובד על הקוד, כולל AI, יצליח להבין מה קורה בו, הייתי רוצה למסגר כמה שיותר מהקוד שלי ולהפריד אותו מהבלגן.
- אז יש את “התוהו ובוהו שקורה מחוץ ל-Scope שלי” - ויש את הסדר שאני מנסה להתוות בתוך הקוד שאני שולט עליו.
- שם אני אנסה להיות פונקציונלי.
- אז כמה יש בלגן בחוץ וכמה LLM-ים יותר או פחות צפויים? מבחינתי זה כאילו לא משנה את הפרדיגמה של איך שאני מנסה לנהל את הקוד שאני כותב בעצמי
(רן) אני יכול דרך אגב, אורי, לענות לך דווקא מהעולם שבו אני נמצא, ושבו אני בין השאר גם עובד על איך בודקים Agent-ים - הזכרת Agent. איך יודעים שה-Agent עושה את הדבר הנכון? איך יודעים שהוא קרא לפונקציה הנכונה? איך יודעים שהוא העביר לה את הפרמטרים הנכונים וכו'?
עכשיו - זה אתגר, כן? זה אתגר שקיים בעולם ויש לזה כל מיני גישות.
בגדול יש שתי גישות בולטות, שהן אחד - זה להסתכל על לאיזה פונקציות הוא קורא, ואם הוא קרא לפונקציות הנכונות והעביר להן את הפרמטרים הנכונים, אז הוא בסדר. נקרא לזה “הגישה הפונקציונלית".
הגישה השנייה היא להסתכל על ה-Database. זאת אומרת, פשוט לא להסתכל על לאיזה פונקציות הוא קרא, אלא להסתכל על ה-Database ולבדוק מה ה-State לפני ומה ה-State אחרי. נגיד, אמרתי לו “תזמין לי טיסה” - אם בסוף הטיסה הוזמנה אז ה-State הוא בסדר.
עכשיו, הגישה של להסתכל על ה-Database היא הרבה יותר מורכבת. למה? כי צריך לסמלץ (Simulate) את ה-Database, צריך לעשות פה הרבה יותר חיווטים - וגם בחיווטים האלה כמובן יכולים להיות באגים - אבל צריך לעשות פה הרבה הרבה יותר חיווטים, צריך לעשות הרבה יותר מאמצים כדי לבדוק את השיטה האימפרטיבית (Imperative) הזאת, מאשר לבדוק את השיטה הפונקציונלית. [Lenny’s Podcast - Building eval systems that improve your AI product]
אז בהקשר הזה אני חושב שכן - זאת אומרת, לעשות נגיד ולידציה (Validation) ל-Agent-ים תחת הנחות פונקציונליות זה הרבה יותר פשוט מאשר תחת הנחות אימפרטיביות (Imperative).
אז כן - אפילו מהאספקט הזה, תכנון פונקציונלי הוא משהו שיותר קל לעבודה איתו, אוקיי? כי יש, נקרא לזה “חוזים”, אוקיי? חוזים - ואנחנו מבקשים מה-Agent לעמוד ב... “לממש” את החוזים האלה.
[31:45] סדנאות לסיכום
(רן) אז זהו, אנחנו כבר ממש מגיעים לקראת הסוף. ואמרת, הזכרת שאתה קצת - שאתה מלמד, שאתה עושה סדנאות, אז אולי כמה מילים על זה?
- (דניאל) אז בגדול, אני הייתי רוצה לקחת את מיטב העקרונות שאפשר להפיק מהעולם הזה של תכנון פונקציונלי - זרקתי כמה Buzz-words כאלה, כמו “Spooky Action at a Distance” ו“Make your legal states unrepresentable”, ולהפוך אותם לסדנה שזמינה לאנשים שעובדים בכל שפת תוכנה שהיא, בגדול - עד כדי C, שבאמת אמרנו ששם לא יהיה הרבה מה לתרום לצערי.
- אז הסדנה היא שאני לוקח איזשהו Syllabus כזה שבגדול מה-Basics של מה זה תכנון פונקציונלי ועד יישומים שהם או Design או ארכיטקטורה ומנסה להתאים אותם לקהל היעד שרוצה להשתתף בסדנה ולהפוך את זה למשהו שאפשר לקחת באמת פרקטית ליומיום.
- זאת אומרת, כבר היום אני יכול להגיד הנה - “קח תעיף את כל ה-Var-ים מהקוד שלך, תראה מה יקרה”, כן?
- צעדים כאלה, מאוד, כביכול, קטנים ופשוטים - אבל פתאום ההשלכות מרחיקות לכת.
- גם אם אני לא עושה את זה ממש תכל'ס עכשיו, אני יכול לחשוב מה ההשלכות.
- אז אני רוצה להפוך את זה לסדנה ולהנגיש את זה “להמונים” - לכל מי שבעצם גם לא נגע בתכנון פונקציונלי או שהסתקרן או כדומה.
- וזה לא חייב להיות שפה כמו Scala או hasql או דומיהן, שהן מאוד נישתיות, אלא אפשר לעשות את זה ב-Python.
- אני אישית, כמו שאמרתי, מעדיף Types - אבל גם איכשהו Typing מגיע יחסית פנימה לכל השפות העולם.
- אז Python, Java, TypeScript, JavaScript והכול.
- ובעצם לקחת את העקרונות האלה ולהנגיש אותם לכולם.
- ואשמח אם יפנו אליי [הנה, כאן - The Functional Edge for Modern Software Engineers
- (דניאל) כן - LinkedIn, מה שזה לא יהיה, אני זמין.
- יש אתר איפשהו, אפשר לקשר אותו מאיפשהו.
(רן) מעולה.
(רן) אז שיהיה בהצלחה, תודה רבה!
(אורי) זה היה פודקאסט מאוד פונקציונלי . . . (רן) כן, גם. אימון פונקציונלי . . .
(דניאל) כן, מלא בדיחות בתחום הזה, חד-משמעית.
(רן) טוב, תודה, נתראה!
האזנה נעימה ותודה רבה לעופר פורר על התמלול!
אין תגובות:
הוסף רשומת תגובה