PERLREF(1)

الاسم
perlref - مراجع بيرل وهياكل البيانات المتداخلة

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

الوصف
قبل الإصدار 5 من بيرل، كان من الصعب تمثيل هياكل البيانات المعقدة، لأن جميع المراجع كان يجب أن تكون رمزية - وحتى حينها كان من الصعب الإشارة إلى متغير بدلاً من إدخال في جدول الرموز. الآن لا تكتفي بيرل بجعل استخدام المراجع الرمزية للمتغيرات أسهل فحسب، بل تتيح لك أيضًا امتلاك مراجع "صلبة" (hard) لأي قطعة من البيانات أو الكود. يمكن لأي كمية سلمية (scalar) أن تحمل مرجعًا صلبًا. وبما أن المصفوفات والمصفوفات المجزأة (hashes) تحتوي على سلميات، يمكنك الآن بسهولة بناء مصفوفات من مصفوفات، ومصفوفات من مجزئات، ومجزئات من مصفوفات، ومصفوفات من مجزئات دوال، وما إلى ذلك.

المراجع الصلبة ذكية - فهي تتبع أعداد المراجع لك، وتُحرر الشيء المشار إليه آليًا عندما يصل عدد مراجعة إلى الصفر. (قد لا تصل أعداد المراجع للقيم في هياكل البيانات ذاتية المرجعية أو الدائرية إلى الصفر دون القليل من المساعدة؛ انظر "Circular References" لشرح مفصل.) إذا صادف أن كان ذلك الشيء كائنًا، فسيُدمر الكائن. انظر perlobj لمزيد عن الكائنات. (بمعنى ما، كل شيء في بيرل هو كائن، لكننا عادة ما نحتفظ بالكلمة للمراجع إلى الكائنات التي تم "مباركتها" رسميًا في حزمة فئة.)

المراجع الرمزية هي أسماء لمتغيرات أو كائنات أخرى، تمامًا كما يحتوي الرابط الرمزي في نظام ملفات يونكس على اسم ملف فحسب. تدوين *glob هو نوع من المراجع الرمزية. (تُسمى المراجع الرمزية أحيانًا "مراجع ناعمة"، ولكن يرجى عدم تسميتها بذلك؛ فالمراجع مربكة بما يكفي دون مرادفات عديمة الفائدة.)

في المقابل، المراجع الصلبة تشبه الروابط الصلبة في نظام ملفات يونكس: فهي تُستخدم للوصول إلى كائن أساسي دون الاهتمام باسمه (الآخر). عندما تُستخدم كلمة "مرجع" دون صفة، كما في الفقرة التالية، فإنها عادة ما تتحدث عن مرجع صلب.

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

  إنشاء المراجع
يمكن إنشاء المراجع بعدة طرق.

عامل الخط المائل العكسي

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

    $scalarref = \$foo;
    $arrayref  = \@ARGV;
    $hashref   = \%ENV;
    $coderef   = \&handler;
    $globref   = \*foo;

ليس من الممكن إنشاء مرجع حقيقي لمقبض إدخال/إخراج (مقبض ملف أو مقبض دليل) باستخدام عامل الخط المائل العكسي. أقصى ما يمكنك الحصول عليه هو مرجع لـ typeglob، وهو في الواقع إدخال كامل في جدول الرموز. ولكن انظر شرح بناء الجملة *foo{THING} أدناه. ومع ذلك، لا يزال بإمكانك استخدام type globs و globrefs كما لو كانت مقابض إدخال/إخراج.

الأقواس المربعة

يمكن إنشاء مرجع لمصفوفة مجهولة باستخدام الأقواس المربعة:

    $arrayref = [1, 2, ['a', 'b', 'c']];

هنا أنشأنا مرجعًا لمصفوفة مجهولة من ثلاثة عناصر، عنصرها الأخير هو نفسه مرجع لمصفوفة مجهولة أخرى من ثلاثة عناصر. (يمكن استخدام بناء الجملة متعدد الأبعاد الموصوف لاحقًا للوصول إلى هذا. على سبيل المثال، بعد ما سبق، ستكون لقيمة "$arrayref->[2][1]" القيمة "b".)

أخذ مرجع لقائمة معدودة ليس هو نفسه استخدام الأقواس المربعة - بل هو نفسه إنشاء قائمة من المراجع!

    @list = (\$x, \@y, \%z);
    @list = \($x, @y, %z);      # نفس الشيء!

كحالة خاصة، يعيد "\(@foo)" قائمة مراجع لمحتويات @foo، وليس مرجعًا لـ @foo نفسها. وبالمثل بالنسبة لـ %foo، باستثناء أن مراجع المفاتيح تكون لنسخ (بما أن المفاتيح هي مجرد سلاسل نصية وليست سلميات كاملة).

الأقواس المعقوفة

يمكن إنشاء مرجع لمصفوفة مجزأة مجهولة باستخدام الأقواس المعقوفة:

    $hashref = {
        'Adam'  => 'Eve',
        'Clyde' => 'Bonnie',
    };

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

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

على سبيل المثال، إذا كنت تريد دالة لإنشاء مجزأة جديدة وإعادة مرجع إليها، فلديك هذه الخيارات:

    sub hashem {        { @_ } }   # خاطئة بصمت
    sub hashem {       +{ @_ } }   # جيدة
    sub hashem { return { @_ } }   # جيدة

من ناحية أخرى، إذا كنت تريد المعنى الآخر، يمكنك فعل ذلك:

    sub showem {        { @_ } }   # غامضة (جيدة حاليًا،
                                   # ولكن قد تتغير)
    sub showem {       {; @_ } }   # جيدة
    sub showem { { return @_ } }   # جيدة

تعمل العلامات البادئة "+{" و "{;" دائمًا على إزالة الغموض عن التعبير ليعني إما مرجع HASH أو الكتلة (BLOCK).

الدوال الفرعية المجهولة

يمكن إنشاء مرجع لدالة فرعية مجهولة باستخدام "sub" بدون اسم للدالة:

    $coderef = sub { print "Boink!\n" };

لاحظ الفاصلة المنقوطة. باستثناء أن الكود الموجود بالداخل لا يُنفذ على الفور، فإن "sub {}" ليس تصريحًا بقدر ما هو عامل، مثل "do{}" أو "eval{}". (ومع ذلك، بغض النظر عن عدد المرات التي تنفذ فيها هذا السطر المحدد (إلا إذا كنت في eval("..."))، سيظل $coderef يمتلك مرجعًا لـ نفس الدالة الفرعية المجهولة.)

تعمل الدوال الفرعية المجهولة كإغلاقات (closures) فيما يتعلق بمتغيرات my()، أي المتغيرات المرئية معجميًا داخل النطاق الحالي. الإغلاق هو مفهوم من عالم Lisp يقول إنه إذا قمت بتعريف دالة مجهولة في سياق معجمي معين، فإنها تتظاهر بالعمل في ذلك السياق حتى عندما يتم استدعاؤها خارج السياق.

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

قد تفكر أيضًا في الإغلاق كطريقة لكتابة قالب دالة فرعية دون استخدام eval(). إليك مثال صغير على كيفية عمل الإغلاقات:

    sub newprint {
        my $x = shift;
        return sub { my $y = shift; print "$x, $y!\n"; };
    }
    $h = newprint("Howdy");
    $g = newprint("Greetings");

    # يمر الوقت...

    &$h("world");
    &$g("earthlings");

يطبع هذا

    Howdy, world!
    Greetings, earthlings!

لاحظ بشكل خاص أن $x يستمر في الإشارة إلى القيمة الممررة إلى newprint() على الرغم من خروج "my $x" عن النطاق بحلول الوقت الذي تعمل فيه الدالة الفرعية المجهولة. هذا هو جوهر الإغلاق.

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

المنشئات

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

    $objref = new Doggie( Tail => 'short', Ears => 'long' );

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

    $objref   = Doggie->new(Tail => 'short', Ears => 'long');

    use Term::Cap;
    $terminal = Term::Cap->Tgetent( { OSPEED => 9600 });

    use Tk;
    $main    = MainWindow->new();
    $menubar = $main->Frame(-relief              => "raised",
                            -borderwidth         => 2)

يتوفر بناء جملة الكائن غير المباشر هذا فقط عندما تكون ميزة "use feature "indirect"" فعالة، وهذا ليس هو الحال عند طلب "use v5.36" (أو أعلى)، فمن الأفضل تجنب بناء جملة الكائن غير المباشر تمامًا.

النشوء الذاتي (Autovivification)

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

فتحات Typeglob

يمكن إنشاء مرجع باستخدام بناء جملة خاص، يُعرف بمحبة باسم بناء جملة *foo{THING}. يعيد *foo{THING} مرجعًا لفتحة THING في *foo (وهي إدخال جدول الرموز الذي يحمل كل شيء يُعرف باسم foo).

    $scalarref = *foo{SCALAR};
    $arrayref  = *ARGV{ARRAY};
    $hashref   = *ENV{HASH};
    $coderef   = *handler{CODE};
    $ioref     = *STDIN{IO};
    $globref   = *foo{GLOB};
    $formatref = *foo{FORMAT};
    $globname  = *foo{NAME};    # "foo"
    $pkgname   = *foo{PACKAGE}; # "main"

معظم هذه الأشياء تشرح نفسها، لكن *foo{IO} يستحق اهتمامًا خاصًا. فهو يعيد مقبض الإدخال/الإخراج، المستخدم لمقابض الملفات ("open" في perlfunc)، والمقابس (sockets) ("socket" في perlfunc و "socketpair" في perlfunc)، ومقابض الأدلة ("opendir" في perlfunc). للتوافق مع الإصدارات السابقة من بيرل، يعد *foo{FILEHANDLE} مرادفًا لـ *foo{IO}، على الرغم من عدم تشجيعه، للتشجيع على الاستخدام المتسق لاسم واحد: IO. في إصدارات بيرل بين v5.8 و v5.22، سيصدر تحذير بشأن الإهمال، ولكن تم التراجع عن هذا الإهمال منذ ذلك الحين.

يعيد *foo{THING} القيمة undef إذا لم يُستخدم THING المحدد بعد، باستثناء حالة السلميات. يعيد *foo{SCALAR} مرجعًا لسلمية مجهولة إذا لم يُستخدم $foo بعد. قد يتغير هذا في إصدار مستقبلي.

يعد *foo{NAME} و *foo{PACKAGE} الاستثناء، حيث يعيدان سلاسل نصية بدلاً من المراجع. وتعيد هذه الحزمة والاسم الخاص بـ typeglob نفسه، بدلاً من تلك التي عُينت له. لذا، بعد "*foo=*Foo::bar"، سيصبح *foo هو "*Foo::bar" عند استخدامه كسلسلة نصية، لكن *foo{PACKAGE} و *foo{NAME} سيستمران في إنتاج "main" و "foo" على التوالي.

*foo{IO} هو بديل لآلية *HANDLE المذكورة في "Typeglobs and Filehandles" في perldata لتمرير مقابض الملفات إلى أو من الدوال الفرعية، أو تخزينها في هياكل بيانات أكبر. عيبه هو أنه لن ينشئ مقبض ملف جديد لك. ميزته هي أن لديك مخاطرة أقل في تخريب أكثر مما تريد بتعيين typeglob. (إنه لا يزال يخلط بين مقابض الملفات والأدلة، رغم ذلك.) ومع ذلك، إذا قمت بتعيين القيمة الواردة إلى سلمية بدلاً من typeglob كما نفعل في الأمثلة أدناه، فلا يوجد خطر من حدوث ذلك.

    splutter(*STDOUT);          # تمرير الـ glob بالكامل
    splutter(*STDOUT{IO});      # تمرير مقابض الملفات والأدلة معًا

    sub splutter {
        my $fh = shift;
        print $fh "her um well a hmmm\n";
    }

    $rec = get_rec(*STDIN);     # تمرير الـ glob بالكامل
    $rec = get_rec(*STDIN{IO}); # تمرير مقابض الملفات والأدلة معًا

    sub get_rec {
        my $fh = shift;
        return scalar <$fh>;
    }

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

سلمية بسيطة

في أي مكان تضع فيه معرفًا (أو سلسلة من المعرفات) كجزء من اسم متغير أو دالة فرعية، يمكنك استبدال المعرف بمتغير سلمي بسيط يحتوي على مرجع من النوع الصحيح:

    $bar = $$scalarref;
    push(@$arrayref, $filename);
    $$arrayref[0] = "January";
    $$hashref{"KEY"} = "VALUE";
    &$coderef(1,2,3);
    print $globref "output\n";

من المهم فهم أننا لا نقوم بالتحديد بفك إسناد مرجعي لـ $arrayref[0] أو $hashref{"KEY"} هناك. فك إسناد المتغير السلمي يحدث قبل إجراء أي عمليات بحث عن مفاتيح. وأي شيء أكثر تعقيدًا من متغير سلمي بسيط يجب أن يستخدم الطريقتين 2 أو 3 أدناه. ومع ذلك، فإن "السلمية البسيطة" تشمل معرفًا يستخدم هو نفسه الطريقة 1 بشكل متكرر. لذلك، يطبع ما يلي "howdy".

    $refrefref = \\\"howdy";
    print $$$$refrefref;

كتلة

في أي مكان تضع فيه معرفًا (أو سلسلة من المعرفات) كجزء من اسم متغير أو روتين فرعي، يمكنك استبدال المعرف بكتلة (BLOCK) تعيد مرجعًا من النوع الصحيح. بمعنى آخر، يمكن كتابة الأمثلة السابقة على النحو التالي:

    $bar = ${$scalarref};
    push(@{$arrayref}, $filename);
    ${$arrayref}[0] = "January";
    ${$hashref}{"KEY"} = "VALUE";
    &{$coderef}(1,2,3);
    $globref->print("output\n");  # فقط إذا حُمّلت IO::Handle

باعتراف الجميع، من السخف قليلاً استخدام الأقواس المتعرجة في هذه الحالة، ولكن يمكن للكتلة (BLOCK) أن تحتوي على أي تعبير اختياري، ولا سيما التعبيرات المذيلة (subscripted expressions):

    &{ $dispatch{$index} }(1,2,3);      # استدعِ الروتين الصحيح

بسبب القدرة على حذف الأقواس المتعرجة في الحالة البسيطة لـ $$x، غالبًا ما يخطئ الناس في اعتبار رموز فك المرجعية (dereferencing) كمعاملات حقيقية، ويتساءلون عن أسبقيتها. لو كانت كذلك، لأمكنك استخدام الأقواس العادية بدلاً من الأقواس المتعرجة. الأمر ليس كذلك. انظر إلى الفرق أدناه؛ الحالة 0 هي نسخة مختصرة من الحالة 1، وليس الحالة 2:

    $$hashref{"KEY"}   = "VALUE";       # الحالة 0
    ${$hashref}{"KEY"} = "VALUE";       # الحالة 1
    ${$hashref{"KEY"}} = "VALUE";       # الحالة 2
    ${$hashref->{"KEY"}} = "VALUE";     # الحالة 3

الحالة 2 خادعة أيضًا من حيث أنك تصل إلى متغير يسمى %hashref، بدلاً من فك المرجعية عبر $hashref إلى الهش الذي يُفترض أنه يشير إليه. سيكون ذلك هو الحالة 3.

تدوين السهم

تحدث استدعاءات الروتين الفرعي والبحث عن عناصر المصفوفة الفردية بشكل متكرر لدرجة أنه يصبح من المرهق استخدام الطريقة 2. كشكل من أشكال التجميل النحوي، يمكن كتابة أمثلة الطريقة 2 كما يلي:

    $arrayref->[0] = "January";   # عنصر مصفوفة
    $hashref->{"KEY"} = "VALUE";  # عنصر هش
    $coderef->(1,2,3);            # استدعاء روتين فرعي

يمكن أن يكون الجانب الأيسر من السهم أي تعبير يعيد مرجعًا، بما في ذلك عملية فك مرجعية سابقة. لاحظ أن $array[$x] ليس نفس الشيء مثل "$array->[$x]" هنا:

    $array[$x]->{"foo"}->[0] = "January";

هذه إحدى الحالات التي ذكرناها سابقًا حيث يمكن للمراجع أن تنبثق إلى الوجود عندما تكون في سياق القيمة اليسارية (lvalue). قبل هذه العبارة، قد يكون $array[$x] غير معرف. إذا كان الأمر كذلك، فإنه يُعرف آليًا بمرجع هش حتى نتمكن من البحث عن "{"foo"}" بداخله. وبالمثل، سيُعرف "$array[$x]->{"foo"}" آليًا بمرجع مصفوفة حتى نتمكن من البحث عن "[0]" بداخله. تسمى هذه العملية التنشيط التلقائي (autovivification).

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

    $array[$x]{"foo"}[0] = "January";

والذي، في الحالة المبسطة لاستخدام المصفوفات العادية فقط، يمنحك مصفوفات متعددة الأبعاد تمامًا مثل لغة C:

    $score[$x][$y][$z] += 42;

حسنًا، ليس تمامًا مثل مصفوفات C في الواقع. لغة C لا تعرف كيف تزيد حجم مصفوفاتها عند الطلب، بينما تفعل لغة بيرل ذلك.

الكائنات

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

استخدامات متنوعة

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

    if ($ref1 == $ref2) {  # مقارنة عددية بسيطة للمراجع
        print "المرجعان 1 و 2 يشيران إلى نفس الشيء\n";
    }

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

يمكن استخدام معامل bless() لربط الكائن الذي يشير إليه المرجع بحزمة تعمل كفئة (class) كائن. انظر perlobj.

يمكن فك مرجعية typeglob بنفس الطريقة التي يفك بها المرجع، لأن بنية فك المرجعية تشير دائمًا إلى نوع المرجع المطلوب. لذا فإن "${*foo}" و "${\$foo}" كلاهما يشيران إلى نفس المتغير السلمي (scalar).

إليك خدعة لإقحام استدعاء روتين فرعي داخل سلسلة نصية:

    print "أعاد الروتين الفرعي @{[mysub(1,2,3)]} تلك المرة.\n";

طريقة عملها هي أنه عندما تظهر "@{...}" في سلسلة نصية بين علامتي اقتباس مزدوجتين، يتم تقييمها ككتلة (block). تنشئ الكتلة مرجعًا لمصفوفة مجهولة تحتوي على نتائج استدعاء "mysub(1,2,3)". وبذلك تعيد الكتلة بأكملها مرجعًا إلى مصفوفة، ثم يتم فك مرجعيتها بواسطة "@{...}" وإقحامها في السلسلة النصية. هذه المناورة مفيدة أيضًا للتعبيرات الاختيارية:

    print "ينتج عن ذلك @{[$n + 5]} من الأدوات\n";

وبالمثل، يمكن فك مرجعية تعبير يعيد مرجعًا إلى متغير سلمي (scalar) عبر "${...}". وبالتالي، يمكن كتابة التعبير أعلاه كما يلي:

    print "ينتج عن ذلك ${\($n + 5)} من الأدوات\n";

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

    my $foo = {};
    my $bar = { foo => $foo };
    $foo->{bar} = $bar;

يمكنك أيضًا إنشاء مرجع دائري باستخدام متغير واحد:

    my $foo;
    $foo = \$foo;

في هذه الحالة، لن يصل عدد المراجع للمتغيرات أبدًا إلى 0، ولن تُجمع المراجع أبدًا بواسطة جامع القمامة (garbage collector). يمكن أن يؤدي هذا إلى تسرب في الذاكرة.

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

يمكنك كسر المراجع الدائرية عن طريق إنشاء "مرجع ضعيف". المرجع الضعيف لا يزيد عدد المراجع للمتغير، مما يعني أن الكائن يمكن أن يخرج عن النطاق ويتم تدميره. يمكنك إضعاف مرجع باستخدام وظيفة "weaken" المصدرة بواسطة وحدة Scalar::Util، أو المتوفرة كـ "builtin::weaken" مباشرة في إصدار بيرل 5.35.7 أو أحدث.

إليك كيف يمكننا جعل المثال الأول أكثر أمانًا:

    use Scalar::Util 'weaken';

    my $foo = {};
    my $bar = { foo => $foo };
    $foo->{bar} = $bar;

    weaken $foo->{bar};

أُضعف المرجع من $foo إلى $bar. عندما يخرج المتغير $bar عن النطاق، سيُجمع بواسطة جامع القمامة. في المرة القادمة التي تنظر فيها إلى قيمة مفتاح "$foo->{bar}"، ستكون "undef".

يمكن أن يكون هذا الفعل عن بُعد مربكًا، لذا يجب أن تكون حذرًا في استخدامك لـ weaken. يجب عليك إضعاف المرجع في المتغير الذي سيخرج عن النطاق أولاً. وبهذه الطريقة، سيحتوي المتغير الأطول عمرًا على المرجع المتوقع حتى يخرج هو عن النطاق.

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

كثيرًا ما يتوقع الناس أن تعمل بهذه الطريقة. وهي كذلك بالفعل.

    $name = "foo";
    $$name = 1;                 # يضبط $foo
    ${$name} = 2;               # يضبط $foo
    ${$name x 2} = 3;           # يضبط $foofoo
    $name->[0] = 4;             # يضبط $foo[0]
    @$name = ();                # يمسح @foo
    &$name();                   # يستدعي &foo()
    $pack = "THAT";
    ${"${pack}::$name"} = 5;    # يضبط $THAT::foo بدون eval

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

    use strict 'refs';

وبعد ذلك لن يُسمح إلا بالمراجع الصلبة فيما تبقى من الكتلة المحيطة. ويمكن لكتلة داخلية أن تلغي ذلك بـ

    no strict 'refs';

متغيرات الحزمة فقط (العالمية، حتى لو كانت محلية) هي المرئية للمراجع الرمزية. المتغيرات اللفظية (المصرح عنها بـ my()) ليست في جدول رموز، وبالتالي فهي غير مرئية لهذه الآلية. على سبيل المثال:

    local $value = 10;
    $ref = "value";
    {
        my $value = 20;
        print $$ref;
    }

سيظل هذا يطبع 10، وليس 20. تذكر أن local() تؤثر على متغيرات الحزمة، والتي تكون جميعها "عالمية" بالنسبة للحزمة.

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

    $push = "pop on ";
    print "${push}over";

كانت تعني دائمًا طباعة "pop on over"، على الرغم من أن push كلمة محجوزة. تم تعميم هذا ليعمل بنفس الطريقة بدون علامات الاقتباس المزدوجة المحيطة، بحيث أن

    print ${push} . "over";

وحتى

    print ${ push } . "over";

سيكون له نفس التأثير. هذا البناء لا يعتبر مرجعًا رمزيًا عندما تستخدم strict refs:

    use strict 'refs';
    ${ bareword };      # حسنًا، تعني $bareword.
    ${ "bareword" };    # خطأ، مرجع رمزي.

وبالمثل، نظرًا لكل عمليات التذييل (subscripting) التي تتم باستخدام كلمات مفردة، تنطبق نفس القاعدة على أي كلمة مجردة (bareword) تُستخدم لتذييل الهش. لذا الآن، بدلاً من كتابة

    $hash{ "aaa" }{ "bbb" }{ "ccc" }

يمكنك كتابة فقط

    $hash{ aaa }{ bbb }{ ccc }

ولا تقلق بشأن ما إذا كانت المذيلات كلمات محجوزة. في الحالة النادرة التي ترغب فيها فعلاً في القيام بشيء مثل

    $hash{ shift }

يمكنك فرض التفسير ككلمة محجوزة بإضافة أي شيء يجعلها أكثر من كلمة مجردة:

    $hash{ shift() }
    $hash{ +shift }
    $hash{ shift @_ }

سيقوم pragma الاستخدام "use warnings" أو مفتاح -w بتحذيرك إذا فسر كلمة محجوزة كسلسلة نصية. لكنه لن يحذرك بعد الآن بشأن استخدام كلمات صغيرة، لأن السلسلة تكون مقتبسة فعليًا.

  أشباه الهش: استخدام مصفوفة كهش
أُزيلت أشباه الهش من بيرل. لا يزال pragma 'fields' متاحًا.

  قوالب الدوال
كما هو موضح أعلاه، فإن الدالة المجهولة التي لها حق الوصول إلى المتغيرات اللفظية المرئية عند تجميع تلك الدالة، تنشئ إغلاقًا (closure). وهي تحتفظ بالوصول إلى تلك المتغيرات حتى لو لم يتم تشغيلها إلا لاحقًا، كما هو الحال في معالج الإشارات أو رد نداء Tk.

يسمح لنا استخدام الإغلاق كقالب دالة بتوليد العديد من الدوال التي تعمل بشكل متشابه. لنفترض أنك أردت دوالاً مسماة بأسماء الألوان تقوم بتوليد تغييرات خطوط HTML لمختلف الألوان:

    print "Be ", red("careful"), "with that ", green("light");

ستكون دالتا red() و green() متشابهتين. لإنشاء هذه، سنقوم بتعيين إغلاق لـ typeglob باسم الدالة التي نحاول بناءها.

    @colors = qw(red blue green yellow orange purple violet);
    for my $name (@colors) {
        no strict 'refs';       # السماح بالتلاعب بجدول الرموز
        *$name = *{uc $name} = sub { "<FONT COLOR='$name'>@_</FONT>" };
    }

الآن تبدو كل تلك الدوال المختلفة موجودة بشكل مستقل. يمكنك استدعاء red() و RED() و blue() و BLUE() و green() وما إلى ذلك. توفر هذه التقنية كلاً من وقت التجميع واستخدام الذاكرة، وهي أقل عرضة للخطأ أيضًا، لأن فحص البنية يتم في وقت التجميع. من الضروري أن تكون أي متغيرات في الروتين الفرعي المجهول لفظية (lexicals) من أجل إنشاء إغلاق سليم. وهذا هو سبب وجود "my" على متغير تكرار الحلقة.

هذا أحد الأماكن القليلة التي يكون فيها إعطاء نموذج مبدئي (prototype) للإغلاق منطقيًا. إذا أردت فرض سياق سلمي (scalar context) على وسائط هذه الدوال (ربما ليست فكرة حكيمة لهذا المثال بالذات)، كان بإمكانك كتابتها بهذه الطريقة بدلاً من ذلك:

    *$name = sub ($) { "<FONT COLOR='$name'>$_[0]</FONT>" };

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

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

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

تتمكن الروتينات الفرعية المجهولة من الالتقاط في كل مرة تنفذ فيها معامل "sub"، حيث يتم إنشاؤها على الطاير. إذا كنت معتادًا على استخدام الروتينات الفرعية المتداخلة في لغات برمجة أخرى مع متغيراتها الخاصة، فسيتعين عليك العمل بجهد أكبر قليلاً في بيرل. الترميز البديهي لهذا النوع من الأشياء يؤدي إلى تحذيرات غامضة حول "will not stay shared" (لن تبقى مشتركة) بسبب الأسباب الموضحة أعلاه. على سبيل المثال، هذا لن يعمل:

    sub outer {
        my $x = $_[0] + 35;
        sub inner { return $x * 19 }   # خطأ
        return $x + inner();
    }

الحل البديل هو ما يلي:

    sub outer {
        my $x = $_[0] + 35;
        local *inner = sub { return $x * 19 };
        return $x + inner();
    }

الآن لا يمكن استدعاء inner() إلا من داخل outer()، بسبب التعيينات المؤقتة للروتين الفرعي المجهول. ولكن عندما يحدث ذلك، يكون له حق الوصول الطبيعي إلى المتغير اللفظي $x من نطاق outer() في الوقت الذي يتم فيه استدعاء outer.

لهذا تأثير مثير للاهتمام يتمثل في إنشاء دالة محلية لدالة أخرى، وهو أمر لا يدعمه بيرل عادةً.

  صيغة فك المراجع اللاحقة (Postfix)
بدءًا من الإصدار v5.20.0، أصبحت صيغة اللاحقة (postfix) لاستخدام المراجع متاحة. وهي تعمل كما هو موصوف في "استخدام المراجع"، ولكن بدلاً من استخدام رمز (sigil) سابِق، يُستخدم رمز ونجمة لاحقين.

على سبيل المثال:

    $r = \@a;
    @b = $r->@*; # يكافئ @$r أو @{ $r }

    $r = [ 1, [ 2, 3 ], 4 ];
    $r->[1]->@*;  # يكافئ @{ $r->[1] }

في إصداري بيرل 5.20 و 5.22، يجب تفعيل هذه الصيغة باستخدام use feature 'postderef'. وبدءًا من بيرل 5.24، لا يلزم التصريح عن أي ميزة لجعلها متاحة.

ينبغي أن يعمل فك المراجع اللاحق في جميع الظروف التي يعمل فيها فك المراجع الكتلي (المحيط)، ويجب أن يكون مكافئًا له تمامًا. تسمح هذه الصيغة بكتابة وقراءة فك المراجع بالكامل من اليسار إلى اليمين. عُرّفت المكافئات التالية:

  $sref->$*;  # يماثل  ${ $sref }
  $aref->@*;  # يماثل  @{ $aref }
  $aref->$#*; # يماثل $#{ $aref }
  $href->%*;  # يماثل  %{ $href }
  $cref->&*;  # يماثل  &{ $cref }
  $gref->**;  # يماثل  *{ $gref }

لاحظ بشكل خاص أن "$cref->&*" ليس مكافئًا لـ $cref->()، ويمكن أن يخدم أغراضًا مختلفة.

يمكن استخراج عناصر النوع العام (Glob) عبر ميزة فك المراجع اللاحقة:

  $gref->*{SCALAR}; # يماثل *{ $gref }{SCALAR}

يمكن استخدام فك مراجع المصفوفات والمتغيرات اللاحقة في استكمال السلاسل النصية (علامات الاقتباس المزدوجة أو معامل "qq")، ولكن فقط في حال تفعيل ميزة "postderef_qq". كما يُدعم استكمال الوصول إلى أعلى فهرس للمصفوفة اللاحقة ("->$#*") عند تفعيل ميزة "postderef_qq".

  تشريح المراجع اللاحقة
يمكن أيضًا أخذ شرائح القيم للمصفوفات والمجموعات المترابطة (hashes) باستخدام تدوين فك المراجع اللاحق، مع المكافئات التالية:

  $aref->@[ ... ];  # يماثل @$aref[ ... ]
  $href->@{ ... };  # يماثل @$href{ ... }

تشريح أزواج المفاتيح/القيم اللاحق، الذي أُضيف في 5.20.0 ووُثق في قسم Key/Value Hash Slices من perldata، يعمل أيضًا كما هو متوقع:

  $aref->%[ ... ];  # يماثل %$aref[ ... ]
  $href->%{ ... };  # يماثل %$href{ ... }

كما هو الحال مع المصفوفة اللاحقة، يمكن استخدام فك مراجع تشريح القيم اللاحق في استكمال السلاسل النصية (علامات الاقتباس المزدوجة أو معامل "qq")، ولكن فقط في حال تفعيل ميزة "postderef_qq".

  الإسناد إلى المراجع
بدءًا من v5.22.0، يمكن الإسناد إلى معامل المراجعة. وهو يؤدي عملية استعارة (aliasing)، بحيث يصبح اسم المتغير المشار إليه في الجانب الأيسر مستعارًا للشيء المشار إليه في الجانب الأيمن:

    \$x = \$y; # يشير $x و $y الآن إلى نفس المتغير
    \&foo = \&bar; # تعني foo() الآن bar()

يجب تفعيل هذه الصيغة باستخدام "use feature 'refaliasing'". وهي تجريبية، وستُصدر تحذيرًا بشكل مبدئي ما لم يُفعّل no warnings 'experimental::refaliasing'.

يمكن الإسناد إلى هذه الأشكال، وتؤدي إلى تقييم الجانب الأيمن في سياق مفرد (scalar):

    \$scalar
    \@array
    \%hash
    \&sub
    \my $scalar
    \my @array
    \my %hash
    \state $scalar # أو @array، إلخ.
    \our $scalar   # إلخ.
    \local $scalar # إلخ.
    \local our $scalar # إلخ.
    \$some_array[$index]
    \$some_hash{$key}
    \local $some_array[$index]
    \local $some_hash{$key}
    condition ? \$this : \$that[0] # إلخ.

تؤدي عمليات التشريح والأقواس إلى تقييم الجانب الأيمن في سياق قائمة (list):

    \@array[5..7]
    (\@array[5..7])
    \(@array[5..7])
    \@hash{'foo','bar'}
    (\@hash{'foo','bar'})
    \(@hash{'foo','bar'})
    (\$scalar)
    \($scalar)
    \(my $scalar)
    \my($scalar)
    (\@array)
    (\%hash)
    (\&sub)
    \(&sub)
    \($foo, @bar, %baz)
    (\$foo, \@bar, \%baz)

يجب أن يكون كل عنصر في الجانب الأيمن مرجعًا لبيانات من النوع الصحيح. الأقواس التي تحيط بمصفوفة مباشرة (وربما أيضًا "my"/"state"/"our"/"local") ستجعل كل عنصر في المصفوفة مستعارًا للمتغير المقابل المشار إليه في الجانب الأيمن:

    \(@x) = \(@y); # أصبح لـ @x و @y نفس العناصر الآن
    \my(@x) = \(@y); # وبالمثل
    \(my @x) = \(@y); # وبالمثل
    push @x, 3; # ولكن الآن @x بها عنصر إضافي تفتقر إليه @y
    \(@x) = (\$x, \$y, \$z); # تحتوي @x الآن على $x و $y و $z

يُحظر دمج هذا الشكل مع "local" ووضع أقواس مباشرة حول المجموعات المترابطة (hashes) (لأنه من غير الواضح ما الذي ينبغي أن تفعله):

    \local(@array) = foo(); # خطأ
    \(%hash)       = bar(); # خطأ

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

    (my $tom, \my $dick, \my @harry) = (\1, \2, [1..3]);
    # $tom هو الآن \1
    # $dick هو الآن 2 (للقراءة فقط)
    # @harry هي (1,2,3)

    my $type = ref $thingy;
    ($type ? $type eq 'ARRAY' ? \@foo : \$bar : $baz) = $thingy;

إسناد المراجع في سياق قائمة يعيد قائمة من المراجع لكل قيمة في الجانب الأيسر. فعلى سبيل المثال

    @b = ((\$l1, \$l2, \(@a)) = (\$r1, \$r2, $\r3, \$4));

يكافئ

    (\$l1, \$l2, \(@a)) = (\$r1, \$r2, $\r3, \$4);
    @b = (\$l1, \$l2, \$a[0], \$a[1]);

يمكن لحلقة "foreach" أيضًا أن تأخذ منشئ مرجع لمتغير الحلقة الخاص بها، وإن كانت الصيغة تقتصر على أحد ما يلي، مع إمكانية استخدام "my" أو "state" أو "our" بعد الخط المائل العكسي:

    \$s
    \@a
    \%h
    \&c

لا يُسمح باستخدام الأقواس. هذه الميزة مفيدة بشكل خاص لمصفوفات المصفوفات، أو مصفوفات المجموعات المترابطة (arrays-of-hashes):

    foreach \my @a (@array_of_arrays) {
        frobnicate($a[0], $a[-1]);
    }

    foreach \my %h (@array_of_hashes) {
        $h{gelastic}++ if $h{type} eq 'funny';
    }

تنبيه: الاستعارة (Aliasing) لا تعمل بشكل صحيح مع الإغلاقات (closures). إذا حاولت استعارة متغيرات معجمية من دالة فرعية داخلية أو "eval"، فسيكون الاستعارة مرئيًا فقط داخل تلك الدالة الفرعية، ولن يؤثر على الدالة الخارجية التي صُرّح عن المتغيرات فيها. هذا السلوك الغريب عرضة للتغيير.

  التصريح عن مرجع لمتغير
بدءًا من v5.26.0، يمكن أن يأتي معامل المراجعة بعد "my"، أو "state"، أو "our"، أو "local". يجب تفعيل هذه الصيغة باستخدام "use feature 'declared_refs'". وهي تجريبية، وستُصدر تحذيرًا بشكل مبدئي ما لم يُفعّل "no warnings 'experimental::refaliasing'".

هذه الميزة تجعل هذه:

    my \$x;
    our \$y;

تكافئ:

    \my $x;
    \our $x;

الغرض منها رئيس هو الاستخدام في الإسنادات إلى المراجع (انظر "الإسناد إلى المراجع" أعلاه). كما أنها تسمح باستخدام الخط المائل العكسي على بعض العناصر فقط في قائمة المتغيرات المُصرح عنها:

    my ($foo, \@bar, \%baz); # يكافئ:  my $foo, \my(@bar, %baz);

تحذير: لا تستخدم المراجع كمفاتيح لمجموعة مترابطة (hash)
لا يمكنك (بشكل مفيد) استخدام مرجع كمفتاح لمجموعة مترابطة. سيتم تحويله إلى سلسلة نصية:

    $x{ \$x } = $x;

إذا حاولت فك مراجع المفتاح، فلن يؤدي ذلك إلى فك مراجع حقيقي، ولن تحقق ما تحاول القيام به. قد ترغب في القيام بشيء أقرب إلى

    $r = \@a;
    $x{ $r } = $r;

وبعد ذلك، يمكنك على الأقل استخدام values()، والتي ستكون مراجع حقيقية، بدلاً من keys()، التي لن تكون كذلك.

توفر وحدة Tie::RefHash القياسية حلاً مناسبًا لهذا الأمر.

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

انظر أيضًا perldsc و perllol لمعرفة كيفية استخدام المراجع لإنشاء هياكل بيانات معقدة، و perlootut و perlobj لمعرفة كيفية استخدامها لإنشاء كائنات.

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.