المسارات
/
Julia
Julia
/
المنهج
/
تركيب الدوال
تر

تركيب الدوال في Julia

1 تمرين

نبذة عن تركيب الدوال

تشجع Julia المبرمجين على وضع أكبر قدر ممكن من الكود داخل دوال يمكن ترجمتها في وقت التنفيذ (JIT)، كما أن إنشاء دوال صغيرة كثيرة فعّال بحكم التصميم.

وهذا يميل إلى ترك العديد من الدوال الصغيرة البسيطة، التي تحتاج إلى تركيبها لتنفيذ مهام غير بسيطة.

أحد الأساليب الواضحة هو تداخل استدعاءات الدوال. المثال التالي متكلف جدًا، لكنه يوضح الفكرة.

julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

عيب هذا الأسلوب هو أن قابلية القراءة تنخفض بسرعة كلما زاد عمق التداخل.

نحتاج إلى أسلوب أبسط وأكثر مرونة.

التركيب

هذا هو الأسلوب المحبوب لدى علماء الرياضيات، وJulia تحاكي الصياغة الرياضية.

يمكن تركيب أي عدد من الدوال معًا باستخدام عوامل ∘ (وتُدخل بكتابة \circ ثم Tab)، كما في composed. ويمكن استخدام الناتج كدالة واحدة.

julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse

julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])

هناك نقطتان جديرتان بالملاحظة:

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

التدفق عبر الأنابيب

قد يُفكَّر في البديل على أنه أسلوب المبرمجين، لا الرياضيين.

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

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

عامل الأنبوب هو |> (كما في F# وإصدارات R الحديثة)، غير أن Julia لديها أيضًا نسخة بث .|>.

julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

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

القيود والحلول البديلة وخيارات أخرى

ليس من قبيل المصادفة أن الدوال المستخدمة لتوضيح التركيب والتدفق عبر الأنابيب تأخذ جميعها وسيطًا واحدًا.

بعض اللغات الوظيفية البحتة تمرر الوسيط الأول إلى دالة، لكنها تسمح بتضمين وسائط أخرى.

في المقابل، لا تتوقع Julia في خط الأنابيب سوى أسماء الدوال (أو ما يعادلها)، دون أي وسائط إضافية.

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

الحلول البديلة

نحتاج إلى دوال ذات وسيط واحد تفعل ما يلزم. لحسن الحظ، تعريف دوال جديدة في Julia سهل.

ببساطة، يمكننا استخدام anonymous function. على سبيل المثال، إذا كانت لدينا سلسلة نصية واحدة كدخل وأردنا التقسيم عند الشرطات السفلية:

julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

يمكن بعد ذلك تمرير هذا المتجه إلى دوال أخرى، كما في السابق.

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

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

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

julia> function makesplit(sep)
           fs(str) = split(str, sep)
           fs
       end
makesplit (generic function with 1 method)

julia> f_us = makesplit('_')
(::var"#fs#32"{Char}) (generic function with 1 method)

julia> "my_test_strings" |> f_us
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

# alternatively:
julia> "my_test_strings" |> makesplit('_')
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

بمجرد تعريف makesplit()، يمكن استخدامها للعمل مع أي فاصل. لاحظ أن makesplit('_') هو استدعاء دالة يُقيَّم إلى دالة أخرى، وهذه بدورها تستقبل المدخل من الأنبوب.

إذا بدا هذا مربكًا، فهذا طبيعي في البداية (لكنه يصبح أوضح مع الممارسة).

خيارات أخرى

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

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

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم تركيب الدوال