المسارات
/
Crystal
Crystal
/
المنهج
/
الإجراءات والكتل
ال

الإجراءات والكتل في Crystal

1 تمرين

نبذة عن الإجراءات والكتل

الكتلة هي قطعة من الكود يمكن تمريرها إلى دالة. وتُعرف هذه في لغات البرمجة الأخرى بالدالة المجهولة أو لامدا. ويمكن للدالة أن تحوّل الكتلة إلى Proc يمثّل كتلة من الكود.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

تُستخدم الكتل وProc كثيرًا جدًا في Crystal، في أمور مثل التكرار ودوال الاستدعاء. وكذلك لإنشاء لغات مخصّصة لمجال معيّن (DSLs)، ومن أمثلة هذه اللغات إطار عمل الويب Kemal أو إطار عمل الاختبار Spec (وهو ما يُستخدم لاختبار حلولك في Crystal).

الكتل

لإنشاء دالة يمكنها استقبال كتلة، عليك إضافة علامة العطف (&) قبل المعامل الأخير. سيحوّل هذا الكتلة إلى Proc ويسنده إلى المعامل. ثم يؤدي استخدام yield إلى تنفيذ الكتلة المُمرَّرة إلى الدالة.

def my_method(&block)
  1 + yield
end

هناك طريقتان لتمرير كتلة: إما باستخدام الكلمتين المفتاحيتين do ... end، أو باستخدام الأقواس المعقوفة ({}). والفرق الرئيسي بينهما هو الأسبقية: فـdo ... end يدعم الكتل متعددة الأسطر، أما الأقواس المعقوفة ({}) فلا تدعمها.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

yield

yield كلمة مفتاحية تنفّذ الكتلة المُمرَّرة إلى الدالة. وهذا يعني أن الكتلة لن تُنفَّذ أبدًا إذا لم يُستدعَ yield. ويمكن أيضًا تمرير وسائط إليها، فتُمرَّر هذه الوسائط إلى الكتلة. ولا يوجد حد لعدد الوسائط التي يمكن تمريرها إلى الكتلة. ولاستخدام الوسائط داخل الكتلة تستعمل صيغة |x, y, ...|، حيث x وy و... هي أسماء الوسائط.

def my_method(&block)
  1 + yield 2
end

# With curly braces:

my_method { |x| x * 3 }
# => 7

# With do ... end:

my_method do |x|
  x * 3
end
# => 7

في المثال أعلاه تُمرَّر إلى الكتلة الوسيطة 2، فتضربها الكتلة في 3 وتُرجع 6. ثم تضيف الدالة 1 إلى الناتج وتُرجع 7.

إذا كان عدد الوسائط المعطاة إلى yield أكثر من عدد الوسائط التي تتوقعها الكتلة، فسيُرفَع خطأ.

def my_method(&block)
  1 + yield
end

my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)

أنواع الكتل

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

def my_method(&block)
  yield "a"
  yield 1
end

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

وعند كتابة الكتل الصريحة يمكنك تحديد أنواع الوسائط ونوع الإرجاع. ويتم ذلك بصيغة Arg1Type, Arg2Type, ... -> ResultType، حيث تُفصل الوسائط بفواصل (,)، ويُفصل نوع النتيجة بـ ->.

def my_method(&block : Int32, Int32 -> Int32)
  1 + yield(2, 3)
end

my_method { |x, y| x * y }
# => 7

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

الصيغة المختصرة

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

def my_method(&block)
  yield "a"
end

# This is: 

my_method { |x| x.upcase }
# => "A"

# Is the same as:

my_method &.upcase
# => "A"

Proc

Proc على صلة قوية جدًا بالكتل. والفرق الجوهري أن Proc نوع، أما الكتلة فليست نوعًا. ويمثّل Proc مؤشرًا إلى دالة، ويمكن تمريره كمتغير.

لتعريف Proc تستخدم صيغة القيمة الحرفية ->(args) { ... }. وتُفصل الوسائط بفواصل (,)، وتُفصل الكتلة بـ { ... }. ولاستدعاء Proc تستخدم الطريقة call وتمرّر إليها الوسائط. وأنواع المعاملات إلزامية. أما نوع الإرجاع فيُستنتج من جسم الـ Proc، لكن يمكن أيضًا تحديده صراحةً.

my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6

التقاط الكتل

التقاط الكتل طريقة لإنشاء Proc من كتلة. ويتم ذلك باستخدام العامل & قبل المعامل الأخير في الدالة. وسيحوّل ذلك الكتلة إلى Proc ويسنده إلى المعامل.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

تمرير Proc إلى الدوال

يمكن تمرير Proc إلى الدوال التي تتوقع كتلة. وقد يكون ذلك مفيدًا عندما تريد تمرير كتل مختلفة بناءً على شرط ما.

def my_method(&block)
  yield 5
end

my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10

تعريف نوع المتغيرات

عند تعريف متغير يمكنك استخدام صيغة : Type لتعريف نوع المتغير. أما بالنسبة للـ Proc فتستخدم صيغة Proc((ArgType, ...), ResultType). وسيكون النوع الأخير دائمًا نوع الإرجاع، وتكون بقية الأنواع أنواع الوسائط. وهذا يعني أنه إذا كان هناك نوع واحد فقط فسيكون نوع الإرجاع، ولن يأخذ Proc أي وسائط.

my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }

تعريف Proc من الدوال

يمكنك أيضًا تعريف Proc من دالة.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم الإجراءات والكتل

التدريب مقفل

افتح 1 تمرين إضافي لممارسة الإجراءات والكتل