תכנות פונקציונלי בג'אווה

1. הקדמה

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

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

2. מהי תכנות פונקציונלי

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

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

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

2.1. חשבון למבדה

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

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

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

2.2. סיווג פרדיגמות תכנות

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

ה גישה חיונית מגדירה תוכנית כרצף של הצהרות המשנות את מצב התוכנית עד שהוא יגיע למצב הסופי. תכנות פרוצדוראלי הוא סוג של תכנות הכרחיות בו אנו בונים תוכניות תוך שימוש בפרוצדורות או תת-דרכים. אחת מפרדיגמות התכנות הפופולריות המכונות תכנות מונחה עצמים (OOP) מרחיבה מושגי תכנות פרוצדורליים.

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

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

2.3. סיווג שפות תכנות

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

שפות פונקציונליות טהורות, כמו Haskell, מאפשרות רק תוכניות פונקציונליות טהורות.

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

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

3. עקרונות ומושגים בסיסיים

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

3.1. פונקציות ממדרגה ראשונה ומסדר גבוה

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

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

באופן מסורתי ניתן היה להעביר פונקציות רק בג'אווה באמצעות מבנים כמו ממשקים פונקציונליים או מחלקות פנימיות אנונימיות. לממשקים פונקציונליים יש בדיוק שיטה מופשטת אחת והם ידועים גם כממשקי Single Abstract Method (SAM Abstract Method).

נניח שעלינו לספק משווה מותאם אישית ל Collections.sort שיטה:

Collections.sort (מספרים, Comparator חדש () {@Override public int השווה (מספר שלם n1, מספר שלם n2) {החזר n1.compareTo (n2);}});

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

בואו נראה כיצד ביטוי למבדה יכול לעזור לנו באותה משימה:

Collections.sort (מספרים, (n1, n2) -> n1.compareTo (n2));

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

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

3.2. פונקציות טהורות

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

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

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

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

סכום שלם (מספרי רשימה) {return numbers.stream (). Collect (Collectors.summingInt (Integer :: intValue)); }

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

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

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

נדון בכמה מהם בסעיפים מאוחרים יותר.

3.3. חוסר שינוי

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

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

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

Class public ImmutableData {גמר פרטי מחרוזת someData; גמר פרטי AnotherImmutableData anotherImmutableData; ImmutableData public (סופי מחרוזת someData, סופי AnotherImmutableData אחרImmutableData) {this.someData = someData; this.anotherImmutableData = אחרImutableData; } מחרוזת ציבורית getSomeData () {להחזיר someData; } public AnotherImmutableData getAnotherImmutableData () {להחזיר AnotherImmutableData; }} מעמד ציבורי AnotherImmutableData {גמר פרטי מספר שלם someOtherData; פומבי AnotherImmutableData (מספר שלם סופי כמה נתונים) {this.someOtherData = someData; } שלם ציבורי getSomeOtherData () {להחזיר someOtherData; }}

שימו לב שעלינו להקפיד על כמה כללים בשקידה:

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

שֶׁלָה לא קל להשיג את זה לגמרי בסדר בכל פעם, במיוחד כאשר מבני הנתונים מתחילים להיות מורכבים. עם זאת, מספר ספריות חיצוניות יכולות להקל על העבודה עם נתונים בלתי ניתנים לשינוי ב- Java. לדוגמה, Immutables ו- Project Lombok מספקים מסגרות מוכנות לשימוש להגדרת מבני נתונים בלתי ניתנים לשינוי ב- Java.

3.4. שקיפות התייחסותית

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

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

מחלקה ציבורית SimpleData {לוגר לוגר פרטי = Logger.getGlobal (); נתוני מחרוזת פרטיים; מחרוזת ציבורית getData () {logger.log (Level.INFO, "קבל נתונים שנקראו עבור SimpleData"); להחזיר נתונים; } ציבורי SimpleData setData (נתוני מחרוזת) {logger.log (Level.INFO, "הגדר נתונים שנקראים SimpleData"); נתונים אלה = נתונים; להחזיר את זה; }}

זהו שיעור POJO טיפוסי ב- Java, אך אנו מעוניינים לברר אם זה מספק שקיפות התייחסותית. בואו נציין את ההצהרות הבאות:

נתוני מחרוזת = SimpleData חדש (). SetData ("Baeldung"). GetData (); logger.log (Level.INFO, SimpleData חדש (). setData ("Baeldung"). getData ()); logger.log (Level.INFO, נתונים); logger.log (Level.INFO, "Baeldung");

שלושת הקריאות ל כּוֹרֵת עֵצִים שקולים סמנטית אך אינם שקופים בהתייחסות. השיחה הראשונה אינה שקופה בהתייחסות מכיוון שהיא מייצרת תופעת לוואי. אם נחליף את השיחה הזו לערכה כמו בשיחה השלישית, אנחנו נפספס את היומנים.

השיחה השנייה גם אינה שקופה בהתייחסות SimpleData ניתן לשינוי. קריאה ל data.setData כל מקום בתוכנית יקשה על החלפתו לערכו.

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

4. טכניקות תכנות פונקציונליות

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

4.1. הרכב פונקציות

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

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

רבים ממשקים פונקציונליים אלה מספקים תמיכה בהרכב פונקציות מבחינת בְּרִירַת מֶחדָל ו סטָטִי שיטות. בוא נבחר את פוּנקצִיָה ממשק כדי להבין זאת טוב יותר. פוּנקצִיָה הוא ממשק פונקציונלי פשוט וגנרי שמקבל טיעון אחד ומייצר תוצאה.

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

יומן פונקציות = (value) -> Math.log (value); פונקציה sqrt = (value) -> Math.sqrt (value); פונקציה logThenSqrt = sqrt.compose (log); logger.log (Level.INFO, String.valueOf (logThenSqrt.apply (3.14))); // פלט: 1.06 פונקציה sqrtThenLog = sqrt.andThen (log); logger.log (Level.INFO, String.valueOf (sqrtThenLog.apply (3.14))); // תפוקה: 0.57

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

יש כמה ממשקים פונקציונליים אחרים שיטות מעניינות לשימוש בהרכב פונקציות, כגון שיטות ברירת המחדל ו, או, ו לִשְׁלוֹל בתוך ה לְבַסֵס מִמְשָׁק. אמנם ממשקים פונקציונליים אלה מקבלים טיעון יחיד, אך ישנם התמחויות דו-ארציות, כמו BiFunction ו BiPredicate.

4.2. מונאדים

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

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

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

Optional.of (2) .flatMap (f -> Optional.of (3) .flatMap (s -> Optional.of (f + s)))

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

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

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

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

4.3. קארי

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

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

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

פוּנקצִיָה משקל = מסה -> כוח משיכה -> מסה * כוח משיכה; פונקציה weightOnEarth = weight.apply (9.81); logger.log (Level.INFO, "המשקל שלי על כדור הארץ:" + weightOnEarth.apply (60.0)); פונקציה weightOnMars = weight.apply (3.75); logger.log (Level.INFO, "המשקל שלי במאדים:" + weightOnMars.apply (60.0));

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

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

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

פונקציה סטטית פרטית weightOnEarth () {כוח משיכה סופי כפול = 9.81; מסת החזרת -> מסה * כוח משיכה; }

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

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

4.4. רקורסיה

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

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

גורם שלם (מספר שלם) {החזר (מספר == 1)? 1: מספר * עובדתי (מספר - 1); }

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

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

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

גורם שלם (מספר שלם, תוצאה שלמה) {חזרה (מספר == 1)? תוצאה: עובדה (מספר - 1, תוצאה * מספר); }

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

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

5. מדוע ענייני תכנות פונקציונליים?

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

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

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

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

לכן, לפני שנתחיל להשתמש בתכנות פונקציונאלי, עלינו להכשיר את עצמנו לחשוב על התוכניות שלנו מבחינת פונקציות.

6. האם ג'אווה מתאימה?

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

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

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

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

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

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

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

7. מסקנה

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

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

קוד המקור של המאמר זמין באתר GitHub.