سويفت الوظيفية سهلة

صورة


تكتب مقالات حول البرمجة الوظيفية الكثير عن كيفية تحسين نهج FP: يصبح من السهل الكتابة والقراءة والدفق والكود والاختبار وبناء بنية سيئة والشعر يصبح ناعم وناعم .


عيب واحد هو عتبة دخول عالية. في محاولة لفهم FP ، صادفت كمية هائلة من النظريات ، والمُسَلِّمات ، والموناديات ، ونظرية الفئات ، وأنواع البيانات الجبرية. وكانت كيفية تطبيق AF في الممارسة العملية غير واضح. بالإضافة إلى ذلك ، تم تقديم أمثلة بلغات غير معروفة بالنسبة لي - Haskell and rock.


ثم قررت اكتشاف FP من البداية. لقد اكتشفت وأبلغت codefest أن FP هو بالفعل مجرد أننا بالفعل استخدامه في سويفت ويمكن استخدامه بشكل أكثر كفاءة.


البرمجة الوظيفية: وظائف نقية ونقص في الحالات


إن تحديد معنى الكتابة في نموذج معين ليس بالمهمة السهلة. تم تشكيل النماذج لعقود من الزمن من قبل أشخاص ذوي رؤى مختلفة ، تم تجسيدهم بلغات بطرق مختلفة ، وتحيط بهم الأدوات. تعتبر هذه الأدوات والمناهج جزءًا لا يتجزأ من النماذج ، لكنها في الواقع ليست كذلك.


على سبيل المثال ، يعتقد أن البرمجة الموجهة للكائنات تقف على ثلاثة أعمدة - الميراث ، التغليف ، وتعدد الأشكال. لكن التغليف وتعدد الأشكال يتم تنفيذهما على وظائف بنفس السهولة الموجودة على الأشياء. أو عمليات الإغلاق - وُلدوا بلغات وظيفية بحتة ، لكنهم هاجروا لفترة طويلة إلى اللغات الصناعية بحيث توقفوا عن الارتباط بـ FP. Monads تشق طريقها أيضًا إلى اللغات الصناعية ، لكنها لم تفقد عضويتها بعد في Haskell الشرطية في عقول الناس.


نتيجةً لذلك ، اتضح أنه من المستحيل تحديد نموذج معين بوضوح. مرة أخرى ، صادفت ذلك في codefest 2019 ، حيث دعا جميع خبراء FP ، متحدثين عن النموذج الوظيفي ، أشياء مختلفة.


شخصيا ، أعجبني التعريف من الويكي:


"البرمجة الوظيفية هي جزء من الرياضيات المنفصلة ونموذج البرمجة التي يتم فيها التعامل مع عملية الحساب كحساب قيم الدوال في الفهم الرياضي للأخير (على عكس الدوال مثل البرامج الثانوية في البرمجة الإجرائية)."


ما هي الوظيفة الرياضية؟ هذه وظيفة تعتمد نتيجتها فقط على البيانات التي يتم تطبيقها عليها.


مثال على دالة رياضية في أربعة أسطر من التعليمات البرمجية يبدو كما يلي:


func summ(a: Int, b: Int) -> Int { return a + b } let x = summ(a: 2, b: 3) 

عند استدعاء الدالة summ مع وسيطات الإدخال 2 و 3 ، نحصل على 5. هذه النتيجة لم تتغير. تغيير البرنامج ، الخيط ، مكان التنفيذ - ستبقى النتيجة كما هي.


والدالة غير الرياضية هي عندما يتم الإعلان عن متغير عمومي في مكان ما.


 var z = 5 

تضيف الدالة sum الآن وسيطات الإدخال وقيمة z.


 func summ(a: Int, b: Int) -> Int { return a + b + z } let x = summ(a: 2, b: 3) 

وأضاف الاعتماد على الدولة العالمية. الآن لا يمكن للمرء التنبؤ بشكل لا لبس فيه بقيمة x. سوف تتغير باستمرار اعتمادا على عندما تم استدعاء وظيفة. نسميها الوظيفة 10 مرات على التوالي ، وفي كل مرة يمكننا الحصول على نتيجة مختلفة.


نسخة أخرى من الوظيفة غير الرياضية:


 func summ(a: Int, b: Int) -> Int { z = b - a return a + b } 

بالإضافة إلى إرجاع مجموع وسائط الإدخال ، تقوم الدالة بتغيير المتغير العمومي z. هذه الميزة لها تأثير جانبي.


البرمجة الوظيفية لها مصطلح خاص للوظائف الرياضية - الوظائف الخالصة. الوظيفة البحتة هي دالة تقوم بإرجاع نفس النتيجة لنفس مجموعة قيم الإدخال وليس لها آثار جانبية.


الوظائف الخالصة هي حجر الزاوية في FP ، وكل شيء آخر ثانوي. من المفترض ، بعد هذا النموذج ، أننا نستخدمهم فقط. وإذا كنت لا تعمل مع دول عالمية أو قابلة للتغيير ، فلن تكون في التطبيق.


الطبقات والهياكل في نموذج وظيفي


في البداية ، اعتقدت أن FP يتعلق فقط بالوظائف ، ويتم استخدام الفئات والهياكل فقط في OOP. لكن اتضح أن الطبقات تتناسب أيضًا مع مفهوم FP. فقط يجب أن يكونوا ، دعنا نقول ، "نظيف".


الفئة "البحتة" هي فئة ، وجميع طرقها وظائف خالصة ، والخصائص غير قابلة للتغيير. (هذا مصطلح غير رسمي ، تم إعداده استعدادًا للتقرير).


ألق نظرة على هذا الفصل:


 class User { let name: String let surname: String let email: String func getFullname() -> String { return name + " " + surname } } 

يمكن اعتباره تغليف بيانات ...


 class User { let name: String let surname: String let email: String } 

وظائف للعمل معهم.


 func getFullname() -> String { return name + " " + surname } 

من وجهة نظر FP ، فإن استخدام فئة المستخدم لا يختلف عن العمل مع الأوليات والوظائف.


أعلن قيمة - المستخدم فانيا.


 let ivan = User( name: "", surname: "", email: "ivanov@example.com" ) 

قم بتطبيق دالة getFullname عليها.


 let fullName = ivan.getFullname() 

نتيجة لذلك ، نحصل على قيمة جديدة - اسم المستخدم الكامل. نظرًا لأنه لا يمكنك تغيير معلمات خاصية إيفان ، فإن نتيجة استدعاء getFullname لم تتغير.


بالطبع ، يمكن للقارئ اليقظ أن يقول: "انتظر لحظة ، تُرجع طريقة getFullname نتيجة بناءً على قيم عمومية لها - خصائص فئة ، وليس وسيطات". لكن في الحقيقة ، الطريقة هي مجرد وظيفة يتم فيها تمرير كائن كوسيطة.


يدعم Swift حتى هذا الإدخال بشكل صريح:


 let fullName = User.getFullname(ivan)() 

إذا كنا بحاجة إلى تغيير بعض قيمة الكائن ، على سبيل المثال البريد الإلكتروني ، فسيتعين علينا إنشاء كائن جديد. يمكن القيام بذلك بالطريقة المناسبة.


 class User { let name: String let surname: String let email: String func change(email: String) -> User { return User(name: name, surname: surname, email: email) } } let newIvan = ivan.change(email: "god@example.com") 

السمات الوظيفية في سويفت


لقد كتبت بالفعل أن العديد من الأدوات والتطبيقات والمناهج التي تعتبر جزءًا من النموذج يمكن استخدامها فعليًا في نماذج أخرى. على سبيل المثال ، تعتبر الموناد وأنواع البيانات الجبرية والاستدلال التلقائي للنوع والطباعة الصارمة والأنواع التابعة والتحقق من صحة البرنامج أثناء التجميع جزءًا من FP. ولكن العديد من هذه الأدوات يمكن أن نجدها في سويفت.


الكتابة القوية واستنتاج الكتابة جزء من Swift. لا يحتاج الأمر إلى فهمه أو إدخاله في المشروع ، بل لدينا فقط.


لا توجد أنواع تابعة ، على الرغم من أنني لا أرفض التحقق من السلسلة بواسطة المترجم ، فهي عبارة عن بريد إلكتروني ، أو صفيف ، أو أنه غير فارغ ، أو قاموس ، أو أنه يحتوي على مفتاح apple. بالمناسبة ، لا توجد أنواع تابعة في Haskell أيضًا.


تتوفر أنواع البيانات الجبرية ، وهذا شيء رائع ، ولكن يصعب فهمه من الناحية الرياضية. الجمال هو أنه لا يحتاج إلى أن يكون مفهوما رياضيا من أجل استخدامه. على سبيل المثال ، Int ، enum ، اختياري ، Hashable هي أنواع جبرية. وإذا كانت كثافة العمليات (Int) بعدة لغات ، وكان البروتوكول في الهدف (C) ، فسيكون التعداد مع القيم المرتبطة ، والبروتوكولات ذات التطبيق الافتراضي والأنواع المرتبطة بها بعيدة عن كل مكان.


غالبًا ما يشار إلى التحقق من التجميع عند التحدث عن لغات مثل الصدأ أو الهاسكل. من المفهوم أن اللغة معبرة جدًا بحيث تتيح لك وصف جميع حالات الحافة بحيث يتم التحقق منها بواسطة المترجم. لذلك ، إذا تم تجميع البرنامج ، فسوف ينجح بالتأكيد. لا أحد يعارض أنه قد يحتوي على أخطاء في المنطق ، لأنك قمت بتصفية البيانات بطريقة غير صحيحة لعرضها على المستخدم. لكنه لن يسقط ، لأنك لم تتلق بيانات من قاعدة البيانات ، أو أن الخادم يعرض الإجابة الخاطئة التي كنت تحسبها ، أو أدخل المستخدم تاريخ ميلاده كسلسلة ، وليس كرقم.


لا أستطيع أن أقول أن تجميع رمز سريع يمكن أن يلحق كل الأخطاء: على سبيل المثال ، من السهل منع تسرب الذاكرة. لكن الكتابة القوية والاختيارية تحمي من الكثير من الأخطاء الغبية. الشيء الرئيسي هو الحد من الاستخراج القسري.


Monads: ليس جزءًا من نموذج FP ، ولكنه أداة (اختياري)


في كثير من الأحيان ، يتم استخدام FPs و monads في نفس التطبيق. في وقت واحد ، حتى ظننت أن monads هي البرمجة الوظيفية. عندما فهمتهم (لكن هذا غير دقيق) ، خلصت إلى عدة استنتاجات:


  • إنها بسيطة ؛
  • انهم مرتاحون
  • فهمهم اختياريا ، وهو ما يكفي لتكون قادرة على تطبيق ؛
  • يمكنك بسهولة الاستغناء عنها.

سويفت بالفعل اثنين من monads القياسية - اختياري والنتيجة. كلاهما ضروري للتعامل مع الآثار الجانبية. اختياري يحمي من لا شيء ممكن. النتيجة - من حالات استثنائية مختلفة.


النظر في المثال جلبت إلى حد السخافة. افترض أن لدينا وظائف ترجع عددًا صحيحًا من قاعدة البيانات ومن الخادم. قد ترجع الثانية صفر ، لكننا نستخدم الاستخراج الضمني للحصول على سلوك وقت الهدف.


 func getIntFromDB() -> Int func getIntFromServer() -> Int! 

نستمر في تجاهل اختياري وتنفيذ وظيفة لجمع هذه الأرقام.


 func summInts() -> Int! { let intFromDB = getIntFromDB() let intFromServer = getIntFromServer()! let summ = intFromDB + intFromServer return summ } 

نسمي الوظيفة النهائية ونستخدم النتيجة.


 let result = summInts() print(result) 

هل سينجح هذا المثال؟ حسنًا ، إنه يجمع بالتأكيد ، ولكن ما إذا كنا قد حصلنا على التعطل في وقت التشغيل أم لا ، فهو غير معروف لأحد. هذا الرمز جيد ، فهو يُظهر نوايانا تمامًا (نحتاج إلى مجموع بعض الأرقام) ولا يحتوي على أي شيء لا لزوم له. لكنه خطير. لذلك ، فقط الصغار والأشخاص الواثقين يكتبون بهذه الطريقة.


تغيير المثال لجعلها آمنة.


 func getIntFromDB() -> Int func getIntFromServer() -> Int? func summInts() -> Int? { let intFromDB = getIntFromDB() let intFromServer = getIntFromServer() if let intFromServer = intFromServer { let summ = intFromDB + intFromServer return summ } else { return nil } } if let result = summInts() { print(result) } 

هذا الكود جيد ، إنه آمن. باستخدام استخراج صريح ، دافعنا ضد لا شيء ممكن. لكنها أصبحت مرهقة ، وبين الشيكات الآمنة من الصعب بالفعل تمييز نيتنا. ما زلنا بحاجة إلى مجموع عددين ، وليس إلى فحص أمني.


في هذه الحالة ، يحتوي اختياري على طريقة خريطة ، موروثة من نوع ربما من هاسكل. نحن نطبقها ، وسوف يتغير المثال.


 func getIntFromDB() -> Int func getIntFromServer() -> Int? func summInts() -> Int? { let intFromDB = getIntFromDB() let intFromServer = getIntFromServer() return intFromServer.map { x in x + intFromDB } } if let result = summInts() { print(result) } 

أو حتى أكثر إحكاما.


 func getIntFromDB() -> Int func getintFromServer() -> Int? func summInts() -> Int? { return getintFromServer().map { $0 + getIntFromDB() } } if let result = summInts() { print(result) } 

استخدمنا الخريطة لتحويل intFromServer إلى النتيجة التي نحتاجها دون الاستخراج.


لقد تخلصنا من الشيك بداخل summInts ، لكن تركناه في المستوى الأعلى. يتم ذلك عن عمد ، لأنه في نهاية سلسلة الحساب يجب أن نختار طريقة لمعالجة نقص النتيجة.


مقتطف


 if let result = summInts() { print(result) } 

استخدم القيمة الافتراضية


 print(result ?? 0) 

أو عرض تحذير في حالة عدم تلقي البيانات.


 if let result = summInts() { print(result) } else { print("") } 

الآن لا يحتوي الكود في المثال على الكثير ، كما في المثال الأول ، وهو آمن ، كما في المثال الثاني.


لكن الخريطة لا تعمل دائمًا كما يجب


 let a: String? = "7" let b = a.map { Int($0) } type(of: b)//Optional<Optional<Int>> 

إذا مررنا إحدى الوظائف إلى الخريطة ، وكانت النتيجة اختيارية ، فسنحصل على اختياري مزدوج. لكننا لسنا بحاجة إلى حماية مزدوجة ضد الصفر. واحد يكفي. تسمح طريقة flatMap بحل المشكلة ، فهي تناظري للخريطة بفارق واحد ، فهي تنشر matryoshkas.


 let a: String? = "7" let b = a.flatMap { Int($0) } type(of: b)//Optional<Int>. 

مثال آخر حيث الخريطة و flatMap ليست مريحة للغاية للاستخدام.


 let a: Int? = 3 let b: Int? = 7 let c = a.map { $0 + b! } 

ماذا لو كانت الوظيفة تأخذ وسيطين وكلاهما اختياري؟ بالطبع ، لدى FP حلاً - هذا هو عامل تطبيق وكاري. لكن هذه الأدوات تبدو محرجة إلى حد ما دون استخدام عوامل تشغيل خاصة ليست في لغتنا ، وتعتبر كتابة العوامل المخصصة سيئة. لذلك ، فإننا نعتبر طريقة أكثر سهولة: نكتب وظيفة خاصة.


 @discardableResult func perform<Result, U, Z>( _ transform: (U, Z) throws -> Result, _ optional1: U?, _ optional2: Z?) rethrows -> Result? { guard let optional1 = optional1, let optional2 = optional2 else { return nil } return try transform(optional1, optional2) } 

يستغرق اثنين من القيم الاختيارية كوسائط ووظيفة مع اثنين من الوسائط. إذا كان كلا الخيارين له قيم ، فسيتم تطبيق دالة عليهما.
الآن يمكننا العمل مع العديد من الخيارات دون نشرها.


 let a: Int? = 3 let b: Int? = 7 let result = perform(+, a, b) 

يحتوي monad الثاني ، النتيجة ، أيضًا على أساليب الخريطة و flatMap. لذلك ، يمكنك العمل معها بنفس الطريقة تمامًا.


 func getIntFromDB() -> Int func getIntFromServer() -> Result<Int, ServerError> func summInts() -> Result<Int, ServerError> { let intFromDB = getIntFromDB() let intFromServer = getIntFromServer() return intFromServer.map { x in x + intFromDB } } if case .success(let result) = summInts() { print(result) } 

في الواقع ، هذا هو ما يجمع بين monads معا - القدرة على العمل مع القيمة داخل الحاوية دون إزالتها. في رأيي ، هذا يجعل الشفرة موجزة. ولكن إذا كنت لا تحب ذلك ، فما عليك سوى استخدام مقتطفات صريحة ، وهذا لا يتعارض مع نموذج FP.


مثال: تقليل عدد الوظائف القذرة


لسوء الحظ ، في البرامج الحقيقية ، توجد حالات عالمية وتأثيرات جانبية في كل مكان - طلبات الشبكة ومصادر البيانات وواجهة المستخدم. والوظائف الخالصة فقط لا يمكن الاستغناء عنها. ولكن هذا لا يعني أنه يتعذر علينا الوصول إلى FP تمامًا: يمكننا محاولة تقليل عدد الوظائف القذرة ، والتي عادة ما تكون كثيرة جدًا.


دعونا نلقي نظرة على مثال صغير قريب من تطوير الإنتاج. بناء واجهة مستخدم ، وتحديدا نموذج دخول. يحتوي النموذج على بعض القيود:


1) تسجيل الدخول لا يقل عن 3 أحرف
2) كلمة المرور لا يقل عن 6 أحرف
3) زر "تسجيل الدخول" نشط إذا كان كلا الحقلين صالحين.
4) يعكس لون إطار الحقل حالته ، أسود - صالح ، أحمر - غير صالح


قد يبدو الرمز الذي يصف هذه القيود كما يلي:


التعامل مع أي إدخال المستخدم


 @IBAction func textFieldTextDidChange() { // 1.     // 2.   guard let login = loginView.text, let password = passwordView.text else { // 3. - loginButton.isEnabled = false return } let loginIsValid = login.count > constants.loginMinLenght if loginIsValid { // 4. - loginView.layer.borderColor = constants.normalColor } let passwordIsValid = password.count > constants.passwordMinLenght if passwordIsValid { // 5. - passwordView.layer.borderColor = constants.normalColor } // 6. - loginButton.isEnabled = loginIsValid && passwordIsValid } 

معالجة إتمام تسجيل الدخول:


 @IBAction func loginDidEndEdit() { let color: CGColor // 1.     // 2.   if let login = loginView.text, login.count > 3 { color = constants.normalColor } else { color = constants.errorColor } // 3.   loginView.layer.borderColor = color } 

معالجة إتمام كلمة المرور:


 @IBAction func passwordDidEndEdit() { let color: CGColor // 1.     // 2.   if let password = passwordView.text, password.count > 6 { color = constants.normalColor } else { color = constants.errorColor } // 3. - passwordView.layer.borderColor = color } 

الضغط على زر الإدخال:


 @IBAction private func loginPressed() { // 1.     // 2.   guard let login = loginView.text, let password = passwordView.text else { return } auth(login: login, password: password) { [weak self] user, error in if let user = user { /*  */ } else if error is AuthError { guard let `self` = self else { return } // 3. - self.passwordView.layer.borderColor = self.constants.errorColor // 4. - self.loginView.layer.borderColor = self.constants.errorColor } else { /*   */ } } } 

قد لا يكون هذا الكود هو الأفضل ، لكن بشكل عام هو جيد ويعمل. صحيح ، لديه عدد من المشاكل:


  • 4 مقتطفات صريحة ؛
  • 4 التبعيات على الدولة العالمية ؛
  • 8 آثار جانبية.
  • حالات نهاية غير واضحة ؛
  • التدفق غير الخطي.

المشكلة الرئيسية هي أنه لا يمكنك تحمل قول ما يحدث في شاشتنا. بالنظر إلى طريقة واحدة ، نرى ما الذي يحدث مع دولة عالمية ، لكننا لا نعرف من وأين ومتى تمس الدولة. نتيجة لذلك ، لفهم ما يحدث ، تحتاج إلى العثور على جميع نقاط العمل مع وجهات النظر وفهم ما يحدث التأثيرات. حفظ كل هذا في الاعتبار أمر صعب للغاية.


إذا كانت عملية تغيير الحالة خطية ، فيمكنك دراستها خطوة بخطوة ، مما يقلل العبء المعرفي على المبرمج.


دعنا نحاول تغيير المثال ، وجعله أكثر وظيفية.


أولاً ، نحدد نموذجًا يصف الحالة الحالية للشاشة. سيتيح لك ذلك معرفة المعلومات الضرورية للوظيفة بالضبط.


 struct LoginOutputModel { let login: String let password: String var loginIsValid: Bool { return login.count > 3 } var passwordIsValid: Bool { return password.count > 6 } var isValid: Bool { return loginIsValid && passwordIsValid } } 

نموذج يصف التغييرات المطبقة على الشاشة. هناك حاجة لمعرفة بالضبط ما سوف نتغير.


 struct LoginInputModel { let loginBorderColor: CGColor? let passwordBorderColor: CGColor? let loginButtonEnable: Bool? let popupErrorMessage: String? } 

الأحداث التي قد تؤدي إلى حالة شاشة جديدة. لذلك سوف نعرف بالضبط الإجراءات التي تغير الشاشة.


 enum Event { case textFieldTextDidChange case loginDidEndEdit case passwordDidEndEdit case loginPressed case authFailure(Error) } 

الآن نحن تصف الطريقة الرئيسية للتغيير. تقوم هذه الوظيفة الخالصة ، استنادًا إلى حالة الحالة الحالية ، بجمع حالة جديدة من الشاشة.


 func makeInputModel( event: Event, outputModel: LoginOutputModel?) -> LoginInputModel { switch event { case .textFieldTextDidChange: let mapValidToColor: (Bool) -> CGColor? = { $0 ? normalColor : nil } return LoginInputModel( loginBorderColor: outputModel .map { $0.loginIsValid } .flatMap(mapValidToColor), passwordBorderColor: outputModel .map { $0.passwordIsValid } .flatMap(mapValidToColor), loginButtonEnable: outputModel?.passwordIsValid ) case .loginDidEndEdit: return LoginInputModel(/**/) case .passwordDidEndEdit: return LoginInputModel(/**/) case .loginPressed: return LoginInputModel(/**/) case .authFailure(let error) where error is AuthError: return LoginInputModel(/**/) case .authFailure: return LoginInputModel(/**/) } } 

الشيء الأكثر أهمية هو أن هذه الطريقة هي الوحيدة المسموح لها بالمشاركة في بناء دولة جديدة - وهي نظيفة. يمكن دراستها خطوة بخطوة. انظر كيف تحول الأحداث الشاشة من النقطة أ إلى النقطة ب. إذا حدث شيء ما ، فالمشكلة هنا بالضبط. ومن السهل اختباره.


أضف خاصية مساعدة للحصول على الحالة الحالية ، وهذه هي الطريقة الوحيدة التي تعتمد على الحالة العامة.


 var outputModel: LoginOutputModel? { return perform(LoginOutputModel.init, loginView.text, passwordView.text) } 

أضف طريقة "قذرة" أخرى لإنشاء تأثيرات جانبية لتغيير الشاشة.


 func updateView(_ event: Event) { let inputModel = makeInputModel(event: event, outputModel: outputModel) if let color = inputModel.loginBorderColor { loginView.layer.borderColor = color } if let color = inputModel.passwordBorderColor { passwordView.layer.borderColor = color } if let isEnable = inputModel.loginButtonEnable { loginButton.isEnabled = isEnable } if let error = inputModel.popupErrorMessage { showPopup(error) } } 

على الرغم من أن طريقة updateView ليست نظيفة ، إلا أنها المكان الوحيد الذي تتغير به خصائص الشاشة. العنصر الأول والأخير في سلسلة العمليات الحسابية. وإذا حدث خطأ ما ، فستكون نقطة التوقف هذه.


يبقى فقط لبدء التحويل في الأماكن الصحيحة.


 @IBAction func textFieldTextDidChange() { updateView(.textFieldTextDidChange) } @IBAction func loginDidEndEdit() { updateView(.loginDidEndEdit) } @IBAction func passwordDidEndEdit() { updateView(.passwordDidEndEdit) } 

طريقة loginPressed جاءت فريدة بعض الشيء.


 @IBAction private func loginPressed() { updateView(.loginPressed) let completion: (Result<User, Error>) -> Void = { [weak self] result in switch result { case .success(let user): /*  */ case .failure(let error): self?.updateView(.authFailure(error)) } } outputModel.map { auth(login: $0.login, password: $0.password, completion: completion) } } 

الحقيقة هي أن النقر على زر "تسجيل الدخول" يطلق سلسلتين من العمليات الحسابية ، وهو أمر غير محظور.


استنتاج


قبل دراسة FP ، ركزت بشدة على نماذج البرمجة. كان من المهم بالنسبة لي أن يتبع الرمز OOP ، لم يعجبني الوظائف الثابتة أو الكائنات عديمة الحالة ، ولم أكتب وظائف عامة.


الآن يبدو لي أن كل تلك الأشياء التي اعتبرتها جزءًا من نموذج تعسفي إلى حد ما. الشيء الرئيسي هو رمز نظيف ومفهوم. لتحقيق هذا الهدف ، يمكنك استخدام كل ما هو ممكن: الوظائف الخالصة ، الطبقات ، الموناديات ، الميراث ، التركيب ، الاستدلال النوعي. يحصلون جميعًا جيدًا على الكود ويجعلون الكود أفضل - فقط قم بتطبيقه على المكان.


ماذا تقرأ عن هذا الموضوع


تعريف البرمجة الوظيفية من ويكيبيديا
كتاب هاسكل
شرح functor ، monads و functor تطبيقها على الأصابع
كتاب هاسكل عن ممارسات استخدام ربما (اختياري)
كتاب عن الطبيعة الوظيفية للسويفت
تحديد أنواع البيانات الجبرية من الويكي
مقال عن أنواع البيانات الجبرية
مقال آخر عن أنواع البيانات الجبرية
تقرير ياندكس عن البرمجة الوظيفية على سويفت
تطبيق مكتبة بريلود القياسية (هاسكل) على سويفت
مكتبة مع الأدوات الوظيفية على سويفت
مكتبة أخرى
واحد آخر

Source: https://habr.com/ru/post/ar455359/


All Articles