חמישה דברים שהייתי משנה בקלוז'ר

10/08/2023

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

אני כתבתי פוסט מבוא לקלוז'ר בעבר כאן: https://www.tocode.co.il/blog/2019-11-hello-clojure

וגם העברתי יחד עם חבר וובינר על קלוז'ר שמוקלט כאן: https://www.tocode.co.il/past_workshops/95

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

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

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

היום אנחנו יודעים שקומפיילר יכול להסיק המון מסקנות (Type Inference) גם בשפות דינמיות.

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

הבחירה של קלוז'ר להישאר מחוץ למשחק הטיפוסים היא כרגע המחסום המשמעותי ביותר בין קלוז'ר ליישומים גדולים.

2. אקוסיסטם

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

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

3. קשה לדבג קוד פונקציונאלי

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

(defn is-valid-email [user]
    (let [email (:email user)]
        (re-matches #".+\@.+\..+" email)))

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

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

4. צ'ט ג'י-פי-טי לא ממש יכול לעזור

בדומה לאקוסיסטם, גם התיעוד על קלוז'ר ברשת עדיין לא מספיק משמעותי ולכן כשאתם נתקעים קשה למצוא תשובות ב Stack Overflow ו Chat GPT לא ממש מצליח לעזור.

זה אולי לא ממש אשמת קלוז'ר אבל זה בהחלט משהו לקחת בחשבון לפני שבוחרים בה לפרויקט גדול.

5. רצפים עצלים

אחת התכונות היפות של קלוז'ר היא גם אחת המבלבלות - וזה ה Lazyness של השפה. שימו לב לתוכנית הבאה:

(defn sum-numbers [nums]
  (map println nums) ;; Let's print each number for debugging.
  (reduce + nums))

(sum-numbers (range 10))

;; => 45
;; But where are the printlns?

הקוד מחזיר 45 כצפוי, אבל לא מדפיס את כל המספרים שקיבל כי map מחזירה Lazy Sequence שאף אחד לא משערך. גם התוכנית הזאת יכולה להפתיע כי למרות ה catch היא תזרוק Exception:

(defn safe-invert-seq [s]
  (try (map #(/ 1 %) s)
       ;; For the sake of the example, let's return an empty list if we
       ;; encounter a division by zero.
       (catch ArithmeticException _ ())))

(safe-invert-seq (range 10))

שוב בגלל שהשיערוך של המידע קורה אחרי שיצאנו מה try. כאן יש מאמר מאוד מקיף עם עוד הרבה דוגמאות כאלה.

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