היום למדתי: מאקרו החץ ב Clojure

10/11/2019

באליקסיר יש אופרטור שנקרא Pipe Operator שתפקידו מאוד פשוט, הוא הופך קוד שנראה ככה:

foo(bar(baz(new_function(other_function()))))

לקוד שנראה ככה:

other_function() |> new_function() |> baz() |> bar() |> foo()

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

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

"Elixir rocks" 
|> String.upcase()
|> String.split()

מה שמחזיר את התוצאה:

["ELIXIR", "ROCKS"]

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

%{"blah" => "blah", "vtha" => "blah"}
|> Enum.filter(fn {k, v} -> k != v end)
|> Enum.map(fn {k, v} -> {k, v} end)
|> Map.new
|> IO.inspect

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

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

הנה הדוגמא האחרונה בתרגום לקלוז'ר:

(->>
  {"blah" "blah" "vtha" "blah"}
  (filter (fn [[k v]] (not= k v)))
  (map (fn [[k v]] [v k]))
  (into {})
  (println))

אגב השמות הרשמיים של המאקרו-אים בקלוז'ר הם Thread first macro לקצר ו Thread last macro לארוך. בדף התיעוד הזה תמצאו הסבר יותר מקיף עליהם כמו גם עוד כמה מאקרו-אים לטיפול יותר עדין באותו נושא https://clojure.org/guides/threading_macros.