يمكننا الآن تحسين مشروع سطر الأوامر الذي نفذناه في فصل سابق بعنوان كتابة برنامج سطر أوامر Command Line بلغة رست Rust بعد تعرُّفنا على المكرّرات في المقال السابق معالجة سلسلة من العناصر باستخدام المكررات iterators، إذ سنستخدمها لجعل شيفرتنا البرمجية أكثر وضوحًا واختصارًا. دعنا نلقي نظرةً على طريقة تطبيق المكررات في مشروعنا والتي ستجعل منه إصدارًا محسنًا خاصةً على الدالتين Config::build
و search
.
إزالة clone باستخدام المكرر
أضفنا في الشيفرة 6 (من فصل كتابة برنامج سطر أوامر بلغة رست: إعادة بناء التعليمات البرمجية لتحسين النمطية Modularity والتعامل مع الأخطاء) شيفرةً برمجية تأخذ شريحةً slice من القيم ذات النوع String
وأنشأنا بها نسخةً instance من الهيكل Config
بالمرور على الشريحة ونسخ القيم والسماح بالهيكل Config
بامتلاك هذه القيم. سنُعيد كتابة التطبيق ذاته الخاص بالدالة Config::build
في الشيفرة 17 كما كانت في الشيفرة 23 (الفصل 12):
اسم الملف: src/lib.rs
impl Config { pub fn build(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let file_path = args[2].clone(); let ignore_case = env::var("IGNORE_CASE").is_ok(); Ok(Config { query, file_path, ignore_case, }) } }
[الشيفرة 17: إعادة بناء الدالة Config::build
من الشيفرة 23 (الفصل 12)]
ذكرنا وقتها أنه ليس علينا القلق بخصوص استدعاءات clone
غير الفعالة لأننا سنزيلها في المستقبل. حسنًا، أتى الوقت الآن.
نحتاج clone
هنا لوجود شريحة بعناصر String
في المعامل args
، إلا أن الدالة build
لا تمتلك args
، ولإعادة ملكية نسخة Config
، اضطررنا لنسخ القيم من الحقول query
و file_path
الموجودة في الهيكل Config
بحيث تمتلك نسخة Config
القيم الموجودة في حقوله.
أصبح بإمكاننا -بمعرفتنا الجديدة بخصوص المكررات- التعديل على الدالة build
لأخذ ملكية مكرر مثل وسيط لها بدلًا من استعارة شريحة، وسنستخدم وظائف المكرر بدلًا من الشيفرة البرمجية التي تتحقق من طول السلسلة النصية وتمرّ على عناصرها في مواقع محددة، وسيوضِّح ذلك استخدام Config::build
لأن المكرر يستطيع الحصول على هذه القيم.
بعد أخذ الدالة Config::build
لملكية المكرر وتوقف استخدامها لعمليات الفهرسة indexing التي تستعير القيم، أصبح بإمكاننا نقل قيم String
من المكرر إلى الهيكل Config
بدلًا من استدعاء clone
وإنشاء تخصيص allocation جديد.
إزالة المكرر المعاد مباشرة
افتح ملف src/main.rs الخاص بمشروع الدخل والخرج، والذي يجب أن يبدو مماثلًا لما يلي:
اسم الملف: src/main.rs
fn main() { let args: Vec<String> = env::args().collect(); let config = Config::build(&args).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {err}"); process::exit(1); }); // --snip-- }
سنعدّل أولًا بداية الدالة main
التي كانت موجودة في الشيفرة 24 من فصل سابق لتكون الشيفرة 18 التي تستخدم مكررًا، إلا أنها لن تُصرَّف بنجاح إلا بعد تعديلنا للدالة Config::build
أيضًا.
اسم الملف: src/main.rs
fn main() { let config = Config::build(env::args()).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {err}"); process::exit(1); }); // --snip-- }
[الشيفرة 18: تمرير القيمة المعادة من env::args
إلى Config::build
]
تُعيد الدالة env::args
مكررًا، إذ يمكننا تمرير ملكية المكرّر المُعاد من env::args
إلى Config::build
الآن مباشرةً بدلًا من تجميع قيم المكرر في شعاع ثم تمرير شريحة إلى الدالة Config::build
.
نحتاج إلى تحديث تعريف الدالة Config::build
في ملف src/lib.rs الخاص بمشروعنا. دعنا نغير بصمة الدالة Config::build
لتبدو على نحوٍ مماثل للشيفرة 19، إلا أن هذا لن يُصرَّف بنجاح لأننا بحاجة لتحديث متن الدالة أيضًا.
اسم الملف: src/lib.rs
impl Config { pub fn build( mut args: impl Iterator<Item = String>, ) -> Result<Config, &'static str> { // --snip--
[الشيفرة 19: تحديث بصمة الدالة Config::build
بحيث تتوقع تمرير مكرر]
يوضّح توثيق المكتبة القياسية بالنسبة للدالة env::args
بأن نوع المكرر المُعاد هو std::env::Args
وأن هذا النوع يطبّق السمة Iterator
ويُعيد قيمًا من النوع String
.
حدّثنا بصمة الدالة Config::build
بحيث يحتوي المعامل args
على نوع معمم generic type بحدّ السمة trait bound impl Iterator<Item = String>
بدلًا من &[String]
. ناقشنا الصيغة impl Trait
سابقًا وهي تعني أن args
يمكن أن يكون أي نوع يطبّق النوع Iterator
ويُعيد عناصرًا من النوع String
.
يمكننا إضافة الكلمة المفتاحية mut
إلى توصيف المعامل args
لجعله متغيّرًا mutable بالنظر إلى أننا نأخذ ملكية args
وسنعدّل args
بالمرور ضمنه.
استخدام توابع السمة Iterator بدلا من الفهرسة
سنصحح متن الدالة Config::build
، فنحن نعلم أنه بإمكاننا استدعاء التابع next
على args
لأنها تطبّق السمة Iterator
. نحدّث في الشيفرة 20 الشيفرة البرمجية التي كانت موجودة في الشيفرة 23 بحيث نستخدم التابع next
:
اسم الملف: src/lib.rs
impl Config { pub fn build( mut args: impl Iterator<Item = String>, ) -> Result<Config, &'static str> { args.next(); let query = match args.next() { Some(arg) => arg, None => return Err("Didn't get a query string"), }; let file_path = match args.next() { Some(arg) => arg, None => return Err("Didn't get a file path"), }; let ignore_case = env::var("IGNORE_CASE").is_ok(); Ok(Config { query, file_path, ignore_case, }) } }
[الشيفرة 20: تعديل محتوى الدالة Config::build
بحيث نستخدم توابع المكرر]
تذكّر أن أول قيمة من القيم المُعادة من الدالة env::args
هي اسم البرنامج، لذا نريد تجاهلها والحصول على القيمة التي تليها، ولذلك نستدعي next
أولًا دون فعل أي شيء بالقيمة المُعادة، ثم نستدعي next
مرةً أخرى للحصول على القيمة التي نريد وضعها في حقل query
من الهيكل Config
. إذا أعادت next
القيمة Some
، سنستخدم match
لاستخلاص القيمة، أما إذا أعادت None
، فهذا يعني عدم وجود وسطاء كافية من المستخدم وعندها نُعيد من الدالة القيمة Err
مبكرًا، ونفعل ذلك مجددًا للتعامل مع القيمة file_path
.
جعل الشيفرة البرمجية أكثر وضوحا باستخدام محولات المكرر
يمكننا أيضًا استغلال ميزة من مزايا المكررات في الدالة search
ضمن مشروعنا، وهي موضّحة في الشيفرة 21 والشيفرة 19 (من فصل سابق):
اسم الملف: src/lib.rs
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results }
[الشيفرة 21: تطبيق الدالة search
من الشيفرة 19 (الفصل 12)]
يمكننا كتابة هذه الشيفرة البرمجية بطريقة مختصرة باستخدام توابع محولات المكرر، إذ يسمح لنا ذلك أيضًا بوجود شعاع وسيط متغيّر نسميه results
. يفضِّل أسلوب لغات البرمجة العمليّة تقليل كمية الحالات المتغيّرة لجعل الشيفرة البرمجية أكثر وضوحًا، إذ قد تفتح لنا إزالة الحالة المتغيّرة فرصةً لجعل عمليات البحث تُنفَّذ بصورةٍ متزامنة، لأنه لن يكون علينا حينها إدارة الوصول المتتالي إلى الشعاع results
. توضح الشيفرة 22 هذا التغيير:
اسم الملف: src/lib.rs
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents .lines() .filter(|line| line.contains(query)) .collect() }
[الشيفرة 22: استخدام توابع محول المكرر في تطبيق الدالة search
]
تذكر أن الهدف من الدالة search
هو إعادة جميع الأسطر الموجودة في contents
التي تحتوي على query
. تستخدم هذه الشيفرة البرمجية -بصورةٍ مشابهة لمثال filter
في الشيفرة 16- محوّل filter
للمحافظة على السطور التي يُعيد فيها التعبير line.contains(query)
القيمة true
. يمكننا تجميع الأسطر الناتجة في شعاع آخر باستخدام collect
.
هذه الطريقة أبسط بكثير. جرّب تنفيذ التعديلات ذاتها بحيث تستخدم توابع المكرر في الدالة search_case_insensitive
بصورةٍ مشابهة.
ترجمة -وبتصرف- لقسم من الفصل Functional Language Features: Iterators and Closures من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.