البرمجة الوظيفية في Rust: من الألف إلى الياء
لغة Rust، المعروفة بسلامة التعامل مع الذاكرة والأداء العالي، تقدم أيضًا أدوات قوية للبرمجة الوظيفية. على عكس اللغات الوظيفية البحتة (Haskell)، تسمح Rust بالجمع بمرونة بين الأساليب الأمرية والوظيفية. في هذه المقالة، سنشرح المفاهيم الأساسية للبرمجة الوظيفية في Rust: من الإغلاقات الأساسية إلى الأنماط المونادية المتقدمة. ستكون المادة مفيدة لكل من المبتدئين والمطورين ذوي الخبرة الذين يرغبون في كتابة كود أكثر إيجازًا وموثوقية.
1. الأساسيات: الثبات والمتغيرات
تبدأ البرمجة الوظيفية بالثبات (عدم القابلية للتغيير). في Rust، المتغيرات غير قابلة للتغيير افتراضيًا. هذا فرق رئيسي عن العديد من اللغات.
// متغير غير قابل للتغيير (افتراضيًا)let x = 10;// x = 20; // خطأ في الترجمة!
// متغير قابل للتغيير (بشكل صريح)let mut y = 5;y = 15; // يعملاستخدام let بدون mut يضمن عدم تغيير القيمة بعد التهيئة. هذا يقلل من عدد الآثار الجانبية ويجعل الكود أكثر قابلية للتنبؤ. في الأسلوب الوظيفي، يُوصى بتفضيل المتغيرات غير القابلة للتغيير، وإنشاء قيم جديدة بدلاً من تعديل القيم القديمة.
2. الإغلاقات (Closures) — الدوال المجهولة
الإغلاقات هي دوال مجهولة يمكنها التقاط المتغيرات من بيئتها. في Rust، يتم تنفيذها من خلال السمات Fn و FnMut و FnOnce.
2.1. بناء الجملة والتقاط المتغيرات
// إغلاق بسيطlet add_one = |x: i32| -> i32 { x + 1 };println!("{}", add_one(5)); // 6
// التقاط متغير من البيئةlet base = 10;let add_base = |x| x + base; // يتم التقاط base بالمرجع (&)println!("{}", add_base(20)); // 30
// التقاط مع التغيير (FnMut)let mut counter = 0;let mut increment = || { counter += 1; };increment();increment();println!("{}", counter); // 2
// التقاط مع الاستهلاك (FnOnce)let name = String::from("Rust");let consume = || { drop(name); };consume();// println!("{}", name); // خطأ: تم نقل name2.2. الإغلاقات كوسائط للدوال
غالبًا ما يتم تمرير الإغلاقات إلى دوال الترتيب الأعلى، مثل map و filter و fold.
let numbers = vec![1, 2, 3, 4, 5];
// استخدام إغلاق مع filterlet evens: Vec<i32> = numbers .into_iter() .filter(|&x| x % 2 == 0) .collect();
println!("{:?}", evens); // [2, 4]3. المكررات وسلاسل التحويلات
المكررات هي قلب البرمجة الوظيفية في Rust. إنها كسولة (تتم العمليات الحسابية فقط عند استدعاء collect() أو for) وتسمح ببناء سلاسل من التحويلات.
3.1. محولات المكررات الأساسية
let data = vec![1, 2, 3, 4, 5, 6];
// سلسلة: filter -> map -> take -> collectlet result: Vec<i32> = data .iter() .filter(|&x| x % 2 == 0) // نترك الأعداد الزوجية .map(|x| x * 2) // نضاعف .take(2) // نأخذ أول اثنين .collect();
println!("{:?}", result); // [4, 8]3.2. الطي (fold) والاختزال
fold هو نظير reduce في اللغات الأخرى. يقوم بتجميع النتيجة أثناء المرور على جميع العناصر.
let numbers = [1, 2, 3, 4, 5];
// مجموع كل الأرقامlet sum = numbers.iter().fold(0, |acc, &x| acc + x);println!("Sum: {}", sum); // 15
// المضروبlet factorial = (1..=5).fold(1, |acc, x| acc * x);println!("5! = {}", factorial); // 1203.3. المكررات الكسولة اللانهائية
// مكرر لانهائي، نأخذ أول 10let first_ten: Vec<i32> = (0..) .filter(|x| x % 2 == 0) .map(|x| x * x)