البرمجة الوظيفية في Swift: المستوى المتقدم
\n\nالبرمجة الوظيفية (FP) هي نموذج ينظر إلى الحسابات على أنها تقييم للدوال الرياضية ويتجنب تغيير الحالة والبيانات القابلة للتغيير. في Swift، وهي لغة متعددة النماذج، لا يتم دعم الإمكانيات الوظيفية فحسب، بل إنها مدمجة بعمق في اللغة والمكتبة القياسية. إذا كنت قد أتقنت بالفعل المفاهيم الأساسية مثل map وfilter وreduce، فقد حان الوقت للتعمق في التقنيات المتقدمة التي ستساعدك على كتابة كود أكثر أمانًا وقابلية للتنبؤ والاختبار لتطبيقات iOS وmacOS. في هذه المقالة، سنستعرض الفونكتورات، المونادات، الحسابات الكسولة، وتركيب الدوال مع أمثلة عملية.
1. الفونكتورات: حاويات قابلة للتحويل
\n\nالفونكتور هو نوع بيانات يقوم بتنفيذ دالة fmap (في Swift هي map). جوهر الفونكتور هو أنه يمثل حاوية يمكن تحويل محتواها باستخدام دالة مُمررة، دون الخروج من حدود الحاوية. المثال الكلاسيكي هو Optional.
لنرى كيف يعمل الفونكتور على مثال Optional:
let maybeNumber: Int? = 5\nlet result = maybeNumber.map { $0 * 2 }\nprint(result) // Optional(10)\n\nlet nilValue: Int? = nil\nlet nilResult = nilValue.map { $0 * 2 }\nprint(nilResult) // nil\n\nهنا، Optional هو فونكتور. إذا كانت هناك قيمة بداخله، يقوم map بتطبيق الإغلاق ويعيد Optional جديدة. إذا لم تكن هناك قيمة، لا يتم تنفيذ العملية، ونحصل على nil. هذا يخلصنا من الإنشاءات المرهقة مثل if let.
فونكتور مهم آخر في Swift هو Array. يقوم map على المصفوفة بتطبيق دالة على كل عنصر ويعيد مصفوفة جديدة:
let numbers = [1, 2, 3, 4]\nlet doubled = numbers.map { $0 * 2 }\nprint(doubled) // [2, 4, 6, 8]\n\nإنشاء فونكتور خاص بك. يمكنك تنفيذ فونكتور لأي نوع مخصص عن طريق تعريف دالة map. على سبيل المثال، غلاف بسيط لتسجيل العمليات:
struct Logged<T> {\n let value: T\n let log: [String]\n \n func map<U>(_ transform: (T) -> U) -> Logged<U> {\n let newValue = transform(value)\n return Logged<U>(value: newValue, log: log + ["تم تحويل \\(value) إلى \\(newValue)"])\n }\n}\n\nlet logged = Logged(value: 10, log: ["القيمة الأولية"])\nlet transformed = logged.map { $0 + 5 }\nprint(transformed.log) // ["القيمة الأولية", "تم تحويل 10 إلى 15"]\n\n2. المونادات: سلاسل من الحسابات مع السياق
\n\nالموناد هو فونكتور يقوم بالإضافة إلى ذلك بتنفيذ دالة flatMap (أو bind). إذا كان map يسمح بتطبيق دالة تُرجع قيمة عادية، فإن flatMap يسمح بتطبيق دالة تُرجع هي نفسها قيمة في نفس السياق (مثل Optional أخرى). هذه أداة أساسية لبناء سلاسل من العمليات مع الحفاظ على السياق.
المثال الأكثر وضوحًا هو العمل مع القيم الاختيارية. لنفترض أن لدينا دالة تستخرج رقمًا من سلسلة نصية، ودالة تقسم رقمًا على رقم آخر، لكن كلاهما قد يُرجع nil:
func parseNumber(_ string: String) -> Int? {\n return Int(string)\n}\n\nfunc divideSafely(_ a: Int, by b: Int) -> Int? {\n guard b != 0 else { return nil }\n return a / b\n}\n\n// بدون flatMap نضطر لاستخدام if let المتداخلة\nif let num1 = parseNumber("10") {\n if let result = divideSafely(num1, by: 2) {\n print(result) // 5\n }\n}\n\n// مع flatMap يصبح كل شيء أكثر أناقة\nlet result = parseNumber("10").flatMap { divideSafely($0, by: 2) }\nprint(result) // Optional(5)\n\nlet failed = parseNumber("abc").flatM