اذهب إلى المحتوى

استخدام المكررات Iterators في تطبيق سطر أوامر بلغة رست


Naser Dakhel

يمكننا الآن تحسين مشروع سطر الأوامر الذي نفذناه في فصل سابق بعنوان كتابة برنامج سطر أوامر 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.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...