تشريح الدالة
الشكل العام لدالة Zero:
fun name(param1: Type1, param2: Type2) -> ReturnType {
// الجسم
return value
}
القطع:
fun— الكلمة المفتاحية التي تُقدّم الدالة.name— اسم الدالة.(param1: Type1, ...)— قائمة المعاملات. كل معامل له نوع صريح.-> ReturnType— نوع الإرجاع.{ ... }— الجسم، كتلة من العبارات.return value— يُنهي الدالة معvalue.
مثال صغير ملموس:
fun double(value: i32) -> i32 {
return value * 2
}
هذه الدالة كاملة. تأخذ i32 واحدًا، تُعيد آخر، ولا تُنفّذ أيّ إدخال/إخراج. من داخل الجسم، value ربط من نمط let — تستطيع استخدامه مثل أي ربط محلي آخر.
استدعاء الدوال
الاستدعاءات تبدو تمامًا كما تتوقّع:
let result = double(21)
الوسيط يجب أن يطابق نوع المعامل. النتيجة تُربط إلى result، والذي يستنتج المترجم نوعه i32 لأن double تُعيد i32.
مثال متكامل يربط دالة مساعدة وmain معًا — اضغط Run لرؤيته يعمل:
يجب أن تحصل على math works\n على stdout.
pub والرؤية
افتراضيًا، الدالة المُعلَنة في ملف تكون خاصّة بذلك الملف (أو الوحدة — قواعد الرؤية تُشدَّد مع نمو المشاريع). لكشف دالة خارج وحدتها، اسبقها بـ pub:
pub fun greet() -> String {
return "hello\n"
}
بدون pub، لا تستطيع الشيفرة في وحدات أخرى استدعاء greet. يحتاج زمن التشغيل لاستدعاء main من خارج أي وحدة يُعرّفها المستخدم، ولهذا تكون main دائمًا pub.
قاعدة الخصوصية الافتراضية جيّدة للاعتماد عليها. علّم فقط ما تنوي أن يكون واجهة؛ والباقي يبقى داخل الوحدة.
أنواع الإرجاع
كل دالة تُعلن نوع إرجاعها بعد ->. أنواع إرجاع شائعة:
fun answer() -> i32 { return 42 }
fun ok() -> bool { return true }
fun label() -> String { return "ready\n" }
fun nothing() -> Void { }
Void هو نوع الإرجاع للدالة التي تُؤدّي عملها عبر تأثيرات جانبية بدلًا من إنتاج قيمة. دالة Void لا تحتاج return صريحة — الوصول إلى نهاية الجسم يكفي.
fun log(world: World, message: String) -> Void raises {
check world.out.write(message)
}
استدعاءات الدوال التي تتجاهل قيمة
إن أعادت دالة قيمة ولا تهتمّ بها، لا يزال يجب عليك الإقرار بالعائد. الأسلوب المعتاد هو ربطها بـ let:
ignored ربط لا تقرأه بقية الدالة. اتّفاق استخدام اسم ignored (أو _) يُشير إلى أن التجاهل متعمّد. هذا احتكاك أكثر من إسقاط قيمة العائد بصمت، وهذا هو المقصود: في لغة يقرأ ويُولّد الوكلاء فيها الشيفرة، القيمة غير المقروءة كثيرًا ما تكون خللًا يستحقّ إبرازه.
دور raises
الدالة التي قد تفشل تُعلن ذلك في توقيعها. رأينا هذا على main:
pub fun main(world: World) -> Void raises {
check world.out.write("hello\n")
}
عبارة raises قد تكون مجرّدة (أي خطأ) أو محدّدة:
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
raises { InvalidInput } تعني "هذه الدالة يمكن أن تفشل بـ InvalidInput، ولا شيء غيره." يجب على المستدعين استخدام check (أو شكل معالجة أكثر تفصيلًا) لنشر الخطأ أو معالجته.
Raises و Check يتعمّق في هذا، بما في ذلك ما يحدث مع أنواع أخطاء متعدّدة وكيف تتفاعل check مع عبارة raises للمستدعي.
الدوال العامّة (Generic)
عندما تريد دالة تعمل على أكثر من نوع واحد، أعلن معاملات النوع بين قوسَين زاويين:
fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
return Pair { left: left, right: right }
}
T وU معاملات نوع — المستدعي يُقرّر ما هما. استدعاء makePair(40, 2_u8) يُعطيك Pair<i32, u8>. راجع التعميم Generics للقصة الكاملة، بما في ذلك البنى (shapes) العامّة والقيود.
أين تعيش الدوال
في برنامج صغير، تكتب الدوال مباشرة في ملف .0. في حزمة، تُوزّع الدوال عبر ملفات تحت src/ ويحلّ المترجم المراجع المتقاطعة بينها نيابةً عنك. الأساسيات تبقى كما هي — fun، والمعاملات، ونوع الإرجاع، والجسم — بغضّ النظر عن مكان وجود الدالة فعليًا.
ملاحظات أسلوبية
بعض الاتّفاقات التي ستراها في الأمثلة الرسمية:
- أسماء دوال بأحرف صغيرة، كلمات متّصلة (
makePair) أو مفصولة بأسلوب camelCase. المكتبة القياسية تميل إلى camelCase. - قيمة إرجاع واحدة لكل دالة. إن احتجت إعادة عدّة أشياء، ابنِ
shapeصغيرة لها — هذا أوضح من إرجاع tuple-of-pair-of-tuple. - دوال
Voidتُنفّذ استدعاءاتcheckفقط؛ والدوال التي تحسب قيمة تتجنّب الإدخال/الإخراج عندما تستطيع. هذا الفصل ثقافي جزئيًا ومفروض جزئيًا — دالة حساب نقية لا تأخذworldولذلك لا تستطيع حرفيًا تنفيذ إدخال/إخراج.
النقطة الأخيرة تستحقّ التوقّف. لأن الإدخال/الإخراج يعيش خلف قدرة World وتُمرَّر World صراحة، فإن توقيع الدالة يُخبرك إن كان يمكن أن تُنفّذ إدخال/إخراج. الدوال التي لا تذكر توقيعاتها World نقية بالنسبة للعالم الخارجي. هذه خاصية يستطيع الوكلاء (والبشر) الاعتماد عليها دون قراءة الجسم.
التالي: If/Else
رأيت if يظهر عرضًا — التوثيق التالي يُغطّي تعابير if/else بالتفصيل، بما في ذلك كيف تتفاعل مع الروابط وما هو غائب عمدًا (لا تحويل ضمني لقيم الصدق، ولا عامل ثلاثي).
الأسئلة الشائعة
كيف تُعلن دالة في Zero؟
استخدم fun: fun name(param: Type) -> ReturnType { body }. أضف pub أمامها لتجعل الدالة مرئية خارج وحدتها. أضف raises بعد نوع الإرجاع إن كانت الدالة يمكن أن تفشل. مثلًا: pub fun double(value: i32) -> i32 { return value * 2 }.
ماذا تفعل كلمة pub؟
pub؟pub تجعل التعريف عامًا — مرئيًا للشيفرة خارج وحدته الحالية. بدون pub، تكون الدالة خاصّة بالملف (أو الحزمة) التي أُعلنت فيها. نقطة الدخول المعتادة pub fun main يجب أن تكون عامة حتى يستطيع زمن التشغيل العثور عليها واستدعاؤها.
كيف تُعيد قيمة من دالة في Zero؟
اكتب return value داخل جسم الدالة. التعبير يجب أن يطابق نوع الإرجاع المُعلَن. دالة بنوع إرجاع Void لا تُعيد شيئًا ولا تحتاج عبارة return صريحة — الوصول إلى نهاية الجسم يكفي.
هل يمكن لدوال Zero أن تأخذ معاملات متعدّدة؟
نعم. اذكرها بين قوسين مفصولة بفواصل، كلٌّ باسمه ونوعه: fun add(a: i32, b: i32) -> i32 { return a + b }. كل معامل يصبح ربطًا من نمط let في جسم الدالة. تتطلّب Zero أنواعًا صريحة على المعاملات — لا يوجد استنتاج لنوع المعامل عند إعلان الدالة.
ماذا تعني raises في توقيع الدالة؟
raises في توقيع الدالة؟raises تُعلن أن الدالة يمكن أن تفشل. raises المجرّدة تسمح بأي نوع خطأ؛ أمّا raises { InvalidInput } فتُقيّدها بأخطاء مُسمّاة محدّدة. على المستدعين استخدام check (أو شكل آخر صريح للفشل) ليُقرّوا بإمكانية الفشل — لا يمكنهم تجاهلها بصمت.