איך משגרים טיל בעזרת תכנות פונקציונאלי?

חלק א - תכנות פונקציונלי בjs מהבסיס, pure function - immutable data

מחבר אחיה חביבסקרפינג נעים חברים

שאלה מטרידה

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

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

(ולא, אני לא מתכנן הסבה ממתכנת למדען טילים)

ולבסוף.. מה הקשר לJS, ובפרט אני מקווה לכתוב כמה מילים על הקשר עם ריאקט וVUE.

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

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

אם למדתם תואר וגם אם לא, סביר להניח שאתם מכירים כבר אחת- OOP או בעברית- תכנות מונחה עצמים.

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

את שפת התכנות הותיקה ביותר בתחום התכנות הפונקציונלי  "LISP" המציאו כבר ב1958 עוד לפני שהיה ברור שיש בכלל מחשב שיכול להשתמש בה (מטורף! 🤯)

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

בהמשך אנסה להסביר למה..

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

פונקציה טהורה - pure function
התכנות הפונקציונלי, משתדל ככל הניתן, לכתוב אך ורק פונקציות טהורות.

פונקציות שמקבלות קלט ועל בסיסו בלבד מחזירות פלט, ובדרך, בגוף הפונקציה נמנעות לגמרי מלעשות side effect.

בואו נדגים את זה בjava-script, ניקח לדוגמא את שתי הפונקציות הבאות-

function incrementBy5(num){
   return num+5
}
let resultNumber = incrementBy5(4) // 9

function dividedBy2(){
   return  resultNumber /= 2
}
const finalResult = dividedBy2() // 4.5

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

הפונקציה השנייה לעומתה, עוברת על שני עבירות מחוקי התכנות הפונקציונלי. ראשית, היא לא מקבלת כלל פרמטר ותלויה בסטייט חיצוני. שנית היא משנה את הסטייט החיצוני, ובכך מבצעת בעצם side effect. פונקציה כזאת תקרא אם כן, impure function, פונקציה לא טהורה.

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

זה יכול להיות הדפסה לקונסול, שינוי של הסטייט, וכן, גם שיגור טילים.

 

ניתן דוגמא בקוד, וכדי שהיא תהיה ברורה, אשתמש באותה פונקציה מהדוגמא הראשונה, רק שהפעם אני רוצה שהיא תבצע קצת side effect

 

function incrementBy5(num){
   console.log(num)
   lastNumberInOurFunc = num
   return num+5
}
let lastNumberInOurFunc = null
let resultNumber = incrementBy5(4) // 9

 

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

 

תכנות פונקציונלי, מנסה להימנע ככל הניתן מside effect, ומתבסס כמעט לחלוטין על פונקציות טהורות.

למה?

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

 

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

 

דאטה אימיוטבילי - Immutable data (חחח, אם יש למישהו תרגום יותר מוצלח אשמח להצעות)

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

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

אתן דוגמא
 

// mutate the data
const someArray = [1,2,3]
someArray.push(4)
console.log(someArray) // [1,2,3,4]

// avoiding mutate the data
const newArray = someArray.concat([5])
console.log(newArray) // [1,2,3,4,5]
console.log(someArray) // [1,2,3,4]

 

בדוגמא הראשונה השתמשתי במטודה push, שמשנה את המערך someArray ובדוגמא השנייה השתמשתי בconcat שלא שינתה את המערך אלא החזירה מערך חדש.

 

החברים הטובים של המתכנת הפונקציונאלי

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

  • const

הדבר המתבקש הראשון, הוא להכריז אך ורק על קבועים, שכן, איזה צורך יש לנו במשתנים, כאשר המטרה היא לא לשנות את הדאטה? לכן נמנע משימוש בlet וvar ונכתוב רק const

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

במשתנים אלו גם כאשר אני אכריז עליהם בעזרת const ניתן יהיה לשנות ואף למחוק את הערכים שבתוכם (כמו בדוגמא למעלה)

כאן יכולים המתכנתים הפונקציונאלים להשתמש בשתי גישות

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

בדוגמא שלמעלה, הראתי איך לדוגמא concat מחזירה לי מערך המורכב משני מערכים מבלי לשנות את המערכים
גם הפונקציות הידועות - map, filter וreduce

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

 

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

דוגמא (ניסיון להימנע משימוש בshift)

 

// avoiding mutate the data
const someArray = [1,2,3,4,5]
const newArray = someArray.slice(1)
console.log(someArray) // [1,2,3,4,5]
console.log(newArray) // [2,3,4,5]

// mutate the data
const newSplicyArray = someArray.splice(1)
console.log(someArray) // [1]
console.log(newArray) // [2,3,4,5]
  • חסימה של האפשרות לעריכת המשתנים

הדרך השנייה והמשלימה המאפשרת להימנע משינוי של הסטייט במשתנים מסוג רפרנס

היא שימוש במטודות שמקבעות את המשתנים כדוגמת freeze

 

const array = [1,2,3,4]
array.unshift(0)
console.log(array) // [0,1,2,3,4]

Object.freeze(array)
array.unshift(-1) // error

סיכום ביניים

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

2. אימיוטבל דאטה- המנעות מוחלטת משינוי של הסטייט

תועלת

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

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

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

דברי פרידה

אז..

זהו לבינתיים

בפוסטים הבאים אשתדל לענות על השאלה איך משגרים את הטיל?
מה הקשר לריאקט וvue?
אגע בעוד מושגים כמו closure, recursion, curry

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