الكتاب الأساسي

قواعد فواصل الأسطر في Go

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

قواعد إدراج الفاصلة المنقوطة

من القواعد التي كثيرًا ما نلتزم بها عمليًا ألّا نضع القوس المعقوف الافتتاحي ({) لأي كتلة شيفرة صريحة في سطر جديد. على سبيل المثال، تفشل كتلة شيفرة حلقة for التالية في التصريف.


	for i := 5; i > 0; i--
	{ // سطر جديد غير متوقع، كان يتوقع { بعد عبارة for
	}

ولجعلها تُصرَّف بنجاح، يجب ألّا يوضع القوس المعقوف الافتتاحي في سطر جديد، كما يلي:


	for i := 5; i > 0; i-- {
	}

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


	for
	{
		// افعل شيئًا ...
	}

إذن، ما القواعد الأساسية لكسر الأسطر في برمجة Go؟ قبل الإجابة عن هذا السؤال، ينبغي أن نعرف حقيقة مفادها أن قواعد Go النحوية الرسمية تستخدم الفواصل المنقوطة ; كعلامات إنهاء لأسطر الشيفرة. لكننا نادرًا ما نستخدم الفواصل المنقوطة في شيفرة Go لدينا. لماذا؟ السبب هو أن معظم الفواصل المنقوطة اختيارية ويمكن حذفها. وستدرج المصرِّفات الفواصل المنقوطة المحذوفة عنا تلقائيًا عند التصريف.

على سبيل المثال، الفواصل المنقوطة العشر في البرنامج التالي كلها اختيارية.


package main;

import "fmt";

func main() {
	var (
		i   int;
		sum int;
	);
	for i < 6 {
		sum += i;
		i++;
	};
	fmt.Println(sum);
};

افترض أن البرنامج أعلاه مخزّن في ملف اسمه semicolons.go، يمكننا تشغيل go fmt semicolons.go لإزالة جميع الفواصل المنقوطة غير الضرورية من ذلك الملف. وستعيد المصرِّفات إدراج الفواصل المنقوطة المُزالة (في الذاكرة) تلقائيًا عند تصريف الشيفرة المصدرية.

ما قواعد إدراج الفواصل المنقوطة في Go؟ لنقرأ قواعد الفواصل المنقوطة المدرجة في مواصفات Go.

تستخدم القواعد النحوية الرسمية الفواصل المنقوطة ";" كعلامات إنهاء في عدد من القواعد الإنتاجية. ويمكن لبرامج Go حذف معظم هذه الفواصل المنقوطة باستخدام القاعدتين التاليتين:

  1. عند تقسيم المدخل إلى رموز (tokens)، تُدرج فاصلة منقوطة تلقائيًا في تدفق الرموز مباشرة بعد الرمز الأخير في السطر إذا كان ذلك الرمز - معرّفًا
  • أو قيمة حرفية لعدد صحيح أو عدد عشري أو عدد تخيلي أو rune أو نص
  • أو إحدى الكلمات المفتاحية break أو continue أو fallthrough أو return
  • أو أحد المعاملات وعلامات الترقيم ++ أو -- أو ) أو ] أو }
  1. للسماح للجمل المركّبة بأن تشغل سطرًا واحدًا، يمكن حذف الفاصلة المنقوطة قبل قوس الإغلاق ) أو }.

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

تعني القاعدة الثانية أن الفاصلة المنقوطة الأخيرة في تعريف متعدد العناصر قبل علامة الإغلاق )، والفاصلة المنقوطة الأخيرة داخل كتلة شيفرة أو تعريف نوع (بنية أو واجهة) قبل علامة الإغلاق }، اختياريتان. وإذا كانت الفاصلة المنقوطة الأخيرة غائبة، فستعيد المصرِّفات إدراجها تلقائيًا.

تتيح لنا القاعدة الثانية كتابة الشيفرة الصحيحة التالية.


import (_ "math"; "fmt")
var (a int; b string)
const (M = iota; N)
type (MyInt int; T struct{x bool; y int32})
type I interface{m1(int) int; m2() string}
func f() {print("a"); panic(nil)}

ستدرج المصرِّفات الفواصل المنقوطة المحذوفة عنا تلقائيًا، كما تُظهر الشيفرة التالية.


var (a int; b string;);
const (M = iota; N;);
type (MyInt int; T struct{x bool; y int32;};);
type I interface{m1(int) int; m2() string;};
func f() {print("a"); panic(nil);};

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


var a = 1; var b = true
a++; b = !b
print(a); print(b)

من القاعدتين، نعلم أنه لن تُدرج فاصلة منقوطة أبدًا مباشرة بعد الكلمة المفتاحية for. ولهذا السبب يكون مثال حلقة for المجرّدة المعروض أعلاه صحيحًا.

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


func f() {
	a := 0
	// يفشل السطران التاليان في التصريف.
	println(a++) // ++ غير متوقع، كان يتوقع فاصلة أو )
	println(a--) // -- غير متوقع، كان يتوقع فاصلة أو )
}

السبب في أن الشيفرة أعلاه غير صحيحة هو أن المصرِّفات ستنظر إليها كما يلي


func f() {
	a := 0;
	println(a++;);
	println(a--;);
}

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


	anObject.
		MethodA().
		MethodB().
		MethodC()

في حين تفشل الشيفرة التالية في التصريف.


	anObject
		.MethodA()
		.MethodB()
		.MethodC()

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


	anObject;
		.MethodA();
		.MethodB();
		.MethodC();

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


package main

import "fmt"

func alwaysFalse() bool {return false}

func main() {
	for
	i := 0
	i < 6
	i++ {
		// استخدم i ...
	}

	if x := alwaysFalse()
	!x {
		// افعل شيئًا ...
	}

	switch alwaysFalse()
	{
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}
}

كتل التحكم في التدفق الثلاث كلها صحيحة. وستدرج المصرِّفات فاصلة منقوطة في نهاية كل من الأسطر 9 و10 و15 و20.

يُرجى ملاحظة أن كتلة switch-case في المثال أعلاه ستطبع true بدلًا من false. وهي مختلفة عن


	switch alwaysFalse() {
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}

إذا استخدمنا الأمر go fmt لتنسيق الأولى، فستُضاف فاصلة منقوطة تلقائيًا بعد استدعاء alwaysFalse()، فتصبح


	switch alwaysFalse();
	{
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}

النسخة المعدّلة مكافئة للنسخة التالية.


	switch alwaysFalse(); true {
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}

ولهذا السبب ستطبع true.

من العادات الجيدة تشغيل go fmt وgo vet باستمرار على شيفرتك.

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


func f(x int) {
	switch x {
	case 1:
	{
		goto A
		A: // يُصرَّف بنجاح
	}
	case 2:
		goto B
		B: // خطأ نحوي: جملة مفقودة بعد التسمية
	case 0:
		goto C
		C: // يُصرَّف بنجاح
	}
}

تشير رسالة خطأ التصريف إلى أنه يجب أن توجد جملة تلي تعريف التسمية. لكن يبدو أنه لا توجد تسمية في المثال أعلاه يتبعها جملة. فلماذا يكون تعريف التسمية B: وحده غير صحيح؟ السبب هو أنه بموجب قاعدة إدراج الفاصلة المنقوطة الثانية المذكورة أعلاه، ستدرج المصرِّفات فاصلة منقوطة قبل كل محرف } يلي تعريفي التسميتين A: وC:، كما تُظهر الشيفرة التالية.


func f(x int) {
	switch x {
	case 1:
	{
		goto A
		A:
	;} // تُدرج فاصلة منقوطة هنا
	case 2:
		goto B
		B: // خطأ نحوي: جملة مفقودة بعد التسمية
	case 0:
		goto C
		C:
	;} // تُدرج فاصلة منقوطة هنا
}

تمثّل الفاصلة المنقوطة المنفردة جملة فارغة في الواقع، ولهذا يكون تعريفا التسميتين A: وC: صحيحين كلاهما. ومن ناحية أخرى، يلي تعريف التسمية B: التعبير case 0:، وهو ليس جملة، لذا فإن تعريف التسمية B: غير صحيح.

يمكننا إدراج فاصلة منقوطة يدويًا (جملة فارغة) في نهاية تعريف التسمية B: لجعله يُصرَّف بنجاح.

الفاصلة (,) لن تُدرج تلقائيًا

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

على سبيل المثال، مقتطف الشيفرة التالي صحيح.


func f1(a int, b string,) (x bool, y int,) {
	return true, 789
}
var f2 func (a int, b string) (x bool, y int)
var f3 func (a int, b string, // الفاصلة الأخيرة مطلوبة
) (x bool, y int,             // الفاصلة الأخيرة مطلوبة
)
var _ = []int{2, 3, 5, 7, 9,} // الفاصلة الأخيرة اختيارية
var _ = []int{2, 3, 5, 7, 9,  // الفاصلة الأخيرة مطلوبة
}
var _ = []int{2, 3, 5, 7, 9}
var _, _ = f1(123, "Go",) // الفاصلة الأخيرة اختيارية
var _, _ = f1(123, "Go",  // الفاصلة الأخيرة مطلوبة
)
var _, _ = f1(123, "Go")
// الأمر نفسه ينطبق على التحويلات الصريحة.
var _ = string(65,) // الفاصلة الأخيرة اختيارية
var _ = string(65,  // الفاصلة الأخيرة مطلوبة
)

لكن مقتطف الشيفرة التالي غير صحيح، لأن المصرِّفات ستدرج فاصلة منقوطة في كل سطر من الشيفرة، باستثناء السطر الثاني. فهناك ثلاثة أسطر ستسبب أخطاء نحوية unexpected newline.


func f1(a int, b string,) (x bool, y int // خطأ
) {
	return true, 789
}
var _ = []int{2, 3, 5, 7 // خطأ: سطر جديد غير متوقع
}
var _, _ = f1(123, "Go" // خطأ: سطر جديد غير متوقع
)

كلمة أخيرة

في الختام، لنصف قواعد فواصل الأسطر في Go وفقًا للشروح أعلاه.

في Go، يكون فاصل السطر مقبولًا (ولن يؤثر على سلوك الشيفرة) إذا:

  • حدث مباشرة بعد كلمة مفتاحية غير break وcontinue وreturn، أو بعد أي من الكلمات المفتاحية الثلاث التي لا تليها تسميات أو نتائج إرجاع؛
  • حدث مباشرة بعد فاصلة منقوطة، سواء أُدرجت الفاصلة المنقوطة صراحةً أم ضمنيًا؛
  • لم يؤدِّ إلى إدراج فاصلة منقوطة ضمنية.

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