החל עקרונות SOLID - אחריות מבוססת על Open / Closed, Liskov Substitution, Interface Segregation ו-תלויה - בתכנות מוכוונת האובייקט (OOP) מתקבלת באופן נרחב כפרקטיקה הטובה ביותר לבניית מקורות מכובדים והיקף.עם זאת, תכנות פונקציונלי (FP) שפות כגון Haskell, Scala, Elixir, ו- Clore פועלים באופן בסיסי תחת מושגים שונים: פונקציות שונות, כאשר הן פועלות לפונקציות שונות, כדי ליצור נתונים פונקציונליות, כאשר הן פועלות באופן עצמאיות, כאשר הן פועלות לפונקציות שונות, ופעולות שונות, כאשר הן פועלות.

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

הבנת עקרונות SOLID בקונטקסט

לפני צלילה לקשיים, מומלץ לזכור מה כל עקרון SOLID נועד להשיג ב-OOP:

  • (ב) 1:1: שיעור צריך רק סיבה אחת לשנות, כלומר, צריך לבודד אחריות אחת.
  • (FLT:0) Open/Closed Principle (OCPOVA)FLT:1: ישויות תוכנה צריכות להיות פתוחות להרחבה אך סגורות לשינוי.
  • (ב) ⁇ :0) ,LSP) ,FLT:1: חומרים חייבים להיות חלק עבור סוגי הבסיס שלהם מבלי לשנות את הנכונות של התוכנית.
  • (ב) .0.10.10.10.10.10.1: לקוחות לא צריכים להיות מחויבים להיות תלויים בממשקים שהם אינם משתמשים בהם.זה מוביל לממשקים ספציפיים בעלי ערך מוסף.
  • (FLT:0) עקשנות דחייה Principle (DIR)FLT:1: מודולים ברמה גבוהה לא צריכים להיות תלויים במודולים ברמה נמוכה; שניהם צריכים להיות תלויים בהפשטות.

ב-OOP, עקרונות אלה מתמזגים הדוקים עם כיתות, ירושה, ממשקים והתנהגות פולימורפית. תכנות פונקציונלי מחליף מנגנונים אלה עם פונקציות, סוגים של נתונים אלגבריים (ADTs), כיתות מסוג (ב Haskell) או פרוטוקולים (ב Clojure), ותפקודו.

האתגרים הספציפיים של SOLID בשפות פונקציונליות

עקרון אחריות יחיד (SRP)

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

לדוגמה, בצינור פונקציונלי כמו FLT:0 (באמצעות מס צינור), כל צעד הוא פונקציה טהורה.אבל הצינור עצמו הוא שילוב של אחריות.ה-SRP עבור הצינור הוא מעורפל: האם הצינור יש אחריות אחת של "מעבדת שיא", או האם לכל פונקציה יש צינורות ארוכים משלה או פונקציות שמקבלות טיעונים רבים יכולים לסמן SRP (כמו מפתחי מעגל) הוא לא חייב להיות בעל שם של קבוצה על תפקודים על בסיס קבוע (מתאים) או על בסיס קבוע).

Open/Closed Principle (OCP)

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

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

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

Liskov Substitution Principle (LSP)

(ב) ב-OOP, אם יש לך מעמד בסיס (FLT:1) עם שיטה:2, ו- subclassFLT 3 אשר לא יכול לעוף, החלפתם של 4 עבור FLT:5 לשבור את התוכנית.

(ב) שפות פונקציונליות לעיתים רחוקות יש ניתוק במובן OOP, במקום זאת, הן מסתמכות על פולימורפיזם, סוגי נתונים אלגבריים, ותבניות התאמה ל-LSP הופכות רלוונטיות כאשר משתמשים במודולים או בפרוטוקולים מסוגים מסוגים.לדוגמה, פונקציה המצפה שבדיקת נתונים מסוג AFLT 6 תתבסס על תקפים (סעיף 9) ו-(FLT) אינה נכונה (הת) של תקפים, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, אם ניתן לקרוא ל-(FLT) ל-(FLT) FPD) ל-(FLT) FPD) ל-(FLT) , לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, על-(FLT) FP) ל-(FLT) FPD) , לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, לדוגמה, על פי חוק מסוג זה יכול להיות מסתמך על פי חוק מסוג זה יכול להיות מסתמך על פי חוק מסוג זה, לדוגמה, לדוגמה,

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

Interface Segregation Principle (ISP)

ISP מעודד ממשקים קטנים וממוקדים.ב-OOP, אתה שובר ממשק גדול לקטנים יותר, כך שלקוחות תלויים רק במה שהם צריכים. ב-FP, המקבילה לממשק היא חתימה או תיעוד של פונקציות (למשל, מילון של שיטות במבנה של אלקסייר עם שיחות).העיקרון עדיין בתוקף: פונקציה לא צריכה לדרוש יותר מפרמטרים, ולא צריך לחשוף מורכבות מיותרת.

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

(ב) FP מעודד שימוש בשיעורים מסוג זה קיימים כמו: FP מעודד שימוש בקטגוריות מסוגים קיימים כגון: (FLT:12), אשר מחסנים (FLT:13), אשר הם חלק מ-FLT:17), באמצעות שימוש ב-FLT 18 כהקשר מפר את ISP: הפונקציה יש תלות בלתי תלויה ב-מוגבלות ב-FLTF:19 אף אם כי אין צורך ב-FV).

הסתברות להורדת Principle (DIP)

DIP קובע כי שני מודולים ברמה גבוהה ונמוכים צריכים להיות תלויים בהפשטות, לא על יישום קונקרטי. in OOP, אתה משתמש ממשקים או שיעורים מופשטים כדי למנוע תלותים. in FP, תלות מועברת בדרך כלל כפרמטרים פונקציונליים או כתיעוד תצורה.זה נקרא לעתים קרובות "זריקת תלות באמצעות טיעוני תפקוד", והוא משיג באופן טבעי את העיוות: הפונקציה ברמה גבוהה אינה מיידית; היא מקבלת אותם.

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

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

אסטרטגיות להתאמה SOLID לתכנות פונקציונליות

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

Embrace Pure Functions ו- Clear Data Flow

SRP הוא כמובן מרוצה כאשר כל פונקציה עושה בדיוק דבר אחד: להפוך נתונים קלט נתונים לתוך נתונים פלט ללא תופעות לוואי. כדי להימנע מלהצית צינורות מונוליטיים, לשבור את הופך למודולים נפרדים בשם: השתמש במודולים (למשל, FLT:23, FLT:23, FLT:24) לפונקציות הקשורות לקבוצה תחת אחריות אחת.

לדוגמה, במקום פונקציה אחת הקוראת קובץ, pars JSON, ומאמת אותו, יש פונקציות טהורות נפרדות -FLT:25 (impure, עטוף ב IO / Effect), LT:26 (pure), FLT:27 (pure), FLT:27 (pure) - ולהלחין אותם בתפקיד תזמורת יחיד.

מינוף מערכת סוג עבור OCP ו- LSP

סוגי נתונים אלגבריים עם דפוס התאמה יכולים להשיג OCP בשילוב עם בדיקת מיצוי.כאשר אתה מוסיף גרסה חדשה לסוג של סכום, כוחות המדור שאתה יכול לעדכן את כל ההתאמות דפוס.זה ההפך של OCP - זה דורש שינוי - כך עדיף להשתמש סוגים פתוחים של נתונים (למשל, FLT:28 ב Haske) או פרוטוקולים כאמור, עבור OCP, מעדיף שימוש בפונקציות פתורות כגון סוג של שימוש; במקום שימוש טיעון יעיל; השתמש בתוכנות , במקום שימוש , במקום שימוש , במקום שימוש בתוכנות טיעון טיעון , או , כגון , כגון פונקציות , כגון טיעון , כגון פונקציות , כגון ⁇ ⁇ , או ⁇ ⁇ ⁇ ⁇ ⁇ , במקום שימוש , במקום שימוש ⁇ ⁇ , במקום שימוש ⁇ ⁇ ⁇ , במקום שימוש ⁇ ⁇ , השתמש , במקום שימוש , במקום שימוש , במקום שימוש , במקום שימוש , השתמש בתוכנות , במקום שימוש , ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ , השתמש בתוכנות ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇

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

השתמש בפונקציות סדר גבוה יותר ומבנה ISP

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

לדוגמה, בסקאלה, במקום:

def process(config: Config): Result // Config has many fields

מעדיף:

def process(database: String => IO[Data], logger: String => IO[Unit]): IO[Result]

זה הופך את התלויים האמיתיים מפורשים ומפורשים.

הזרקת התלות הפורמלית באמצעות פרדוקסים עבור DIP

הצורה הפשוטה ביותר של DIP ב FP היא לעשות את כל התלויים החיצוניים באופן מפורש כטיעוני תפקוד.זה מתאים באופן מושלם עם העיקרון כי לוגיקה ברמה גבוהה תלויה בהפשטות (חתימות הפונקציה) והטלפון מספק יישום קונקרטי.עבור גרפים מורכבים יותר, לשקול שימוש בדפוס Reader (inkell: FLT) או מערכת השפעה כמו ZIO יש סוג של סביבה מורכבת יותר של 3:31.

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

טיפים מעשיים לאימוץ SOLID ב FP

  • (FLT:0)עיצוב פועל עם קידוד ברור / קידוד חוזה: להימנע מפונקציות המותרות את טענותיהם או להסתמך על המדינה העולמית.זה תומך ישירות ב-SRP והופך את החלפתם לקלה יותר.
  • (FLT:0) מודולים קטנים, כפיים על פני גדולי סימול 1 (כל מודול צריך לייצא סט של פונקציות המשרתות מטרה אחת.זה SRP מוחל ברמת המודול.
  • (ב) ,0) שיעורים או פרוטוקולים מסוג זה כדי להשיג פולימורפיליזם ללא ירושהFillo:1 ; חוקי Define עבור שיעורים מסוג זה ולבחון אותם כדי לספק את LSP.
  • (ב) אם יש צורך ב-[[1924]], [[1924]], [[1924]], [[1924]], [[1924]]]], [[1924]], [[1924]]]]]], [[1924]]]]]]
  • (ב) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
  • (FLT:0) בדיקה מבוססת נכסים 1IRFLT כדי לאמת כי קוד פולימורפילי פועל נכון עבור כל המימושים.
  • (ב) ,0) ירארכיות מורשת עמוקה אפילו בשפות עם תכונות דמויות OOPFIRLT:1 במקום זאת, השתמש בהרכב ובפונקציות סדר גבוה יותר, אשר באופן טבעי לשמור קוד סגור לשינוי.
  • (FLT:0) מספק על ידי הפקת פונקציות זעירות של עוזרות זעירות 1 ; כאשר פונקציה גדלה מעבר למספר קווים.זה ישפר באופן אוטומטי תאימות עם SRP.

משאבים חיצוניים

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

  • (ב) SOLID Principlesofph:1 ; סקירה יסודית של עקרונות OOP-מקדו.
  • מרטין פולר: דחייה של מברשות הבקרה והזריקת התלות של דפוסים של ההרחבה:1 - מאמר קלאסי על DIP ו- DI, החל משני פרדיגמות.
  • (FLT:0) דוח שפה של Haskell 2010: סוג של כיתות ההרחבה 1) - פרטים על האופן שבו כיתות מסוג מאפשרות לפולימורפיזם אד-הוק ועיצוב ידידותי ל-OCP.
  • (ב) ⁇ :0Cats Typemia ClassFLT:1 - דוגמאות של איך סקאלה פונקציונלית משתמשת בכיתות מסוג מבוזרות כדי להשיג מהירויות דמויות ISP.
  • (ב) ⁇ :0) פרוטוקולים של קלודור 1 (FLT:0) מפגינים הרחבה פתוחה/סגורה ללא ירושה בשפה FP דינמית.

מסקנה

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

האתגרים המתוארים במאמר זה - כגון ambiguity SRP צינורות, מורכבות OCP עם סוגים של פוליסות LSP באמצעות חוקים, ISP עם שיעורים מסוג גנרי, ו DIP חוטים במערכות אפקט - יכול להיות להתגבר עם עיצוב זהיר ונכונות לחשוב במונחים של שינויים ופשטות ולא אובייקטים. על ידי התאמת SOLID חשיבה במקום העתק קשיח, לבנות מערכות פונקציונליות כי הם מסוגלים לשמור על היתרונות של קוד פתוח ושמירה על יעילות גבוהה יותר כמו גם כן, כמו גם כן, כמו גם על היתרונות של שקיפות, כמו גם כן.