الدليل السريع إلى لغة البرمجة Go


إبراهيم البحيصي

أُنشئت لغة البرمجة Go لإنجاز العمل بسهولة، وهي ليست ضمن الاتجاهات الحديثة في علم الحاسوب، ولكنها أحدث وسيلة برمجية لحل المشاكل في الواقع بسرعة.
تمتلك لغة Go مفاهيم مُشابهة للغات البرمجة الإجبارية Imperative Languages بالإضافة لثبات أنواع البيانات Static typing، وتعدّ كذلك سريعة في البرمجة Compilation وسريعة في التشغيل والتنفيذ، ومتوافقة مع المعالجات ذات الأنوية المتعددة بهدف الاستفادة منها بسهولة، كما أنها تمتلك الكثير من المزايا التي تساعد في البرمجة للأنظمة الكبيرة والمعقَّدة.
تتمتع لغة Go بمكتبة معيارية عظيمة ومجتمع برمجي متحمس ونشط.
في هذا المقال، سوف نشرح أساسيات لغة Go بطريقة سهلة وبسيطة، ونُعرج على بعض المفاهيم المهمة. الشفرة البرمجية الموجودة في هذا المقال مترابطة، ولكننا قسّمناها إلى أجزاء ووضعنا عناوين لهذه الأجزاء، كما توجد الكثير من التعليقات المباشرة على الشفرة البرمجية.
المواضيع الأساسية التي يغطيها هذا المقال كالتالي:

  1. كتابة التعليقات.
  2. المكتبات واستيرادها.
  3. الدوال.
  4. أنواع البيانات.
  5. القيم الراجعة المسماة.
  6. المتغيرات والذاكرة.
  7. جمل التحكم.
  8. توليد الدوال.
  9. التنفيذ المؤجل.
  10. الواجهات.
  11. المُدخلات المتعددة.
  12. معالجة الأخطاء.
  13. التنفيذ المتزامن.
  14. الويب

كتابة التعليقات

لكتابة تعليق من سطر واحد

// single line comment

لكتابة تعليق بأكثر من سطر

/* Multi-
 line comment */

المكتبات واستيرادها

يبدأ كل ملف مصدري بالكلمة المفتاحية packag. تُستخدَم الكلمة المفتاحية main لتعريف الملف كملف تشغيلي وليس مكتبة.

package main

لاستيراد حزمة مكتبية في الملف نستخدم التعليمة Import بالطريقة التالية:

import (
    "fmt" // حزمة في المكتبة المعيارية للغة
    "io/ioutil" // تطبق دوال إدخال وإخراج
    m "math"    //نستخدم الحرف m لاختصار اسم مكتبة الدوال الرياضية
    "net/http"  // خادوم ويب
    "os"        // دوال على مستوى نظام التشغيل مثل التعامل مع الملفات
    "strconv"   // تحويلات نصية
)

الدوال Functions

تُعرَّف الدوال باستخدام كلمة func متبوعة باسم الدالة.
تعدّ الدالة main خاصة، وهي المدخل للملف التنفيذي للبرنامج (لغة Go تستخدم الأقواس المزخرفة {} لتحديد الأجزاء/الكتل البرمجية).

func main() {
// لإخراج نص على وحدة الإخراج (العرض) الرئيسية stdout نستخدم الدالة Println الموجودة في مكتبة fmt
 fmt.Println("Hello world!")
// استدعاء دالة من نفس الحزمة الحالية
 beyondHello()
}

تحتاج الدوال لأقواس تستقبل المعاملات Parameters، وحتى في عدم وجود معاملات فإن الأقواس مطلوبة.

func beyondHello() {
// تعريف متغير (لا بد من تعريف المتغير قبل استخدامه)
    var x int 
// إعطاء قيمة للمتغير
    x = 3     
// التعريف القصير باستخدام := ويشمل تعريف المتغير, وتحديد نوعه وإعطاءه قيمة
    y := 4
// دالة ترجع قيمتين منفصلتين
 sum, prod := learnMultiple(x, y)        

// طباعة وإخراج بشكل بسيط ومباشر
    fmt.Println("sum:", sum, "prod:", prod)
    learnTypes()                           
}

يمكن أن توجد في تعريف الدوال معاملات وقيم مرجعة متعددة، فمثلا تأخذ الدالة learnMultiple أدناه معاملين x و y وترجع قيمتين sum و prod من نوع عدد صحيح Int.

func learnMultiple(x, y int) (sum, prod int) {
// نفصل بين القيم المُرجعة بفاصلة عادية
    return x + y, x * y 
}

أنواع البيانات Data Types

func learnTypes() {
//التعريفات القصيرة عادة تؤدي الغرض المطلوب
// تعريف متغير نصي باستخدام علامة التنصيص المزدوجة   
 str := "Learn Go!" 

// تعريف متغير نصي باستخدام علامة التنصيص المنفردة
 s2 := `A "raw" string literal can include line breaks.` 

// تعريف متغير من نوع rune وهو عبارة عن مسمى آخر لنوع int32 ويحتوي المتغير من هذا النوع على يونيكود      
g := 'Σ' 

// تعريف عدد عشري Float
f := 3.14195 

// تعريف عدد مركب (عقدي)Complex
c := 3 + 4i  
// تعريف المتغيرات باستخدام var
var u uint = 7 //عدد طبيعي (صحيح موجب)
var pi float32 = 22. / 7 //عدد عشري من 32 بت

// طريقة التحويل باستخدام التعريف القصير (byte تعتبر مسمى اخر لنوع uint8)
    n := byte('\n')
// المصفوفات لها حجم محدد وثابت في وقت الترجمة
 // تعريف مصفوفة من نوع int بحجم 4 عناصر وبقيمة أولية تساوي صفر
    var a4 [4]int

// تعريف مصفوفة بحجم 3 عناصر بالقيم 3 و 1 و 5 
    a3 := [...]int{3, 1, 5} 

يقدّم Go نوع بيانات يُسمّى الشرائح Slices. الشرائح (Slices) لها حجم ديناميكي. المصفوفات والشرائح لها مميزات ولكن حالات الاستخدام للشرائح شائعة أكثر.
تعرّف التعليمة التالية شريحة من النوع int

// لاحظ الفرق بين تعريف المصفوفة والشريحة، حيث عند تعريف الشريحة لا يوجد رقم يحدد حجمها
    s3 := []int{4, 5, 9}    

//تعريف شريحة من نوع int بأربعة عناصر بقيم صفرية   
 s4 := make([]int, 4)   

// تعريف فقط، ولا يوجد تحديد
 var d2 [][]float64      

 // طريقة تحويل النوع من نص لشريحة
bs := []byte("a slice") 

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

    s := []int{1, 2, 3}     
    s = append(s, 4, 5, 6)  
// ستُطبَع شريحة بالمحتويات التالية [1 2 3 4 5 6]
    fmt.Println(s) 

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

    s = append(s, []int{7, 8, 9}...)  
// سيتم طباعة شريحة بالمحتويات التالية [1 2 3 4 5 6 7 8 9]
    fmt.Println(s)  

تعرّف التعليمة التالية متغيرين p وq ليكونا مؤشّريْن Pointers على متغيّرين من نوع int يحويان قيمتين مُرجعتيْن من الدالة learnMemory:

    p, q := learnMemory() 

عندما تسبق النجمة مؤشرا فإن ذلك يعني قيمة المتغير الذي يحيل إليه المؤشر، أي في المثال التالي قيمتا المتغيريْن اللذيْن ترجعهما الدالة learnMemory:

    fmt.Println(*p, *q)   

الخرائط Maps في Go هي مصفوفات ترابطية يمكن التعديل عليها ديناميكيا وهي تشابه نوع القاموس او الهاش في اللغات الأخرى.

/* هنا نعرف خريطة يكون مفتاحها من نوع نصي، وقيم العناصر رقمية. */
    m := map[string]int{"three": 3, "four": 4}
    m["one"] = 1

تعدّ لغة Go المتغيرات غير المستخدمة خطأ. التسطير السفلي بالطريقة التالية يجعلك تستخدم المتغير ولكن تتجاهل قيمته في نفس الوقت:

    _, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a3, s4, bs

تُستخدَم هذه الطريقة عادة لتجاهل قيمة راجعة من دالة، فمثلا تستطيع تجاهل رقم الخطأ الراجع من دالة إنشاء الملف os.Create والذي يفيد بأن الملف موجود مسبقا، وتفترض دائما أن الملف شيُنشَأ:

    file, _ := os.Create("output.txt")
    fmt.Fprint(file, "بالمناسبة، هذه هي دالة الكتابة في ملف")
    file.Close()

    fmt.Println(s, c, a4, s3, d2, m)

    learnFlowControl()
}

القيم الراجعة المسماة Named return values

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

func learnNamedReturns(x, y int) (z int) {
    z = x * y
//هنا كتبنا كلمة return فقط وضمنيا نعني إعادة قيمة المتغير z
    return 
}

ملاحظة: تعتمد لغة Go كثيرا على جمع الموارد غير المستخدمة Garbage collection. توجد في Go مؤشّرات لكن بدون إجراء عمليات حسابية عليها (تستطيع الخطأ في استخدام مؤشر فارغ ولكن لا تستطيع الزيادة على المؤشر).

المتغيرات والذاكرة

المتغيّران p وq أدناه هما مؤشّران على النوع int ويمثّلان قيمتين راجعتين في الدالة. يكون المؤشّران عند تعريفهما فارغين؛ إلا أن استخدام الدالة المُضمّنة new يجعل قيمة المتغيّر العددي الذي يحيل إليه المؤشّرp مساوية للصفر، وبالتالي يأخذ حيزا من الذاكرة؛ أي أن المؤشر p لم يعد فارغا.

func learnMemory() (p, q *int) {
    p = new(int) 
// تعريف شريحة من 20 عنصر كوحدة واحدة في الذاكرة
s := make([]int, 20)

// إعطاء قيمة لأحد العناصر
s[3] = 7             
// تعريف متغير جديد محلي على مستوى الدالة
r := -2              
// إرجاع قيمتين من الدالة هما عبارة عن عناوين الذاكرة للمتغيرات s و r على الترتيب.
return &s[3], &r     
}

func expensiveComputation() float64 {
    return m.Exp(10)
}

جمل التحكم

تتطلّب الجمل الشرطية وجود أقواس مزخرفة ولا تتطلب وجود أقواس هلالية.

func learnFlowControl() {
    if true {
        fmt.Println("told ya")
    }

    if false {
        // Pout.
    } else {
        // Gloat.
    }

نستخدم جملة switch في حال حاجتنا لكتابة أكثر من جملة شرطية متتابعة.

    x := 42.0
    switch x {
    case 0:
    case 1:
    case 42:
    case 43:
    default:        
    }

كما الجملة الشرطية، فإن جملة for لا تأخذ أقواس هلالية.المتغيرات المعرفة في جملة for تكون مرئية على مستوى الجملة.

    for x := 0; x < 3; x++ {         fmt.Println("iteration", x)
    }

جملة for هي جملة التكرار الوحيدة في لغة Go ولها شكل آخر بالطريقة التالية:

    for { // تكرار لا نهائي  
 // نستطيع استخدام break لوقف التكرار
        break    
// نستطيع استخدام continue للذهاب للتكرار القادم
        continue 
    }

تستطيع استخدام range للمرور على عناصر مصفوفة، شريحة، نص، خريطة أو قناة Channel تـعيد range قيمة واحدة عند استخدام قناة، وقيمتين عند استخدام شريحة أو مصفوفة أو نص أو خريطة.

// مثال:
    for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// نطبع قيمة كل عنصر في الخريطة
        fmt.Printf("key=%s, value=%d\n", key, value)
    }

استخدم علامة التسطير السفلي مقابل القيمة الراجعة إذا كنت تريد الحصول على القيمة فقط، كالتالي:

    for _, name := range []string{"Bob", "Bill", "Joe"} {
        fmt.Printf("Hello, %s\n", name)
    }

نستطيع استخدام التعريف القصير مع الجملة الشرطية بحيث يُعرف متغير ومن ثم يُفحَص في جملة الشرط.
نعرّف في ما يلي متغيرًا y ونقوم بإعطائه قيمة ومن ثم نقوم بوضع شرط الجملة بحيث يتم فصلهما ب فاصلة منقوطة.

    if y := expensiveComputation(); y > x {
        x = y
    }

نستطيع تعريف دوال وهمية anonymous مباشرة في الشفرة البرمجية”

    xBig := func() bool {
// عرّفنا المتغيّر x التالي قبل جملة switch السابقة
        return x > 10000 
    }
    x = 99999
// ترجع الدالة xBig الآن القيمة true
    fmt.Println("xBig:", xBig()) 
    x = 1.3e3                    
// بعد تعديل قيمة x إلى 1.3e3 التي تساوي 1300 (أي أكبر من 1000) فإن الدالة xBig ترجع false
    fmt.Println("xBig:", xBig()) 

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

    fmt.Println("Add + double two numbers: ",
        func(a, b int) int {
            return (a + b) * 2
        }(10, 2))    
    goto love
love:

    learnFunctionFactory() // دالة ترجع دالة
    learnDefer()      // التأجيل
    learnInterfaces() // التعامل مع الواجهات
}

توليد الدوال

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

func learnFunctionFactory() {

تعدّ الطريقتان التاليتان في طباعة الجملة متماثلتين، إلا أن الطريقة الثانية أوضح ومقروءة أكثر وهي الشائعة.

    fmt.Println(sentenceFactory("summer")("A beautiful", "day!"))

    d := sentenceFactory("summer")
    fmt.Println(d("A beautiful", "day!"))
    fmt.Println(d("A lazy", "afternoon!"))
}

المزخرفات Decorators موجودة في بعض لغات البرمجة، وموجودة بنفس المفهوم في لغة Go بحيث نستطيع تمرير معطيات إلى الدوال.

func sentenceFactory(mystring string) func(before, after string) string {
    return func(before, after string) string {
        return fmt.Sprintf("%s %s %s", before, mystring, after)
    }
}

التنفيذ المؤجل

نستطيع استخدام خاصية التأجيل في الدوال بحيث ننفّذ إجراءً قبل إعادة القيمة المرجعة، وفي حال كتابة أكثر من إجراء، فإن تنفيذ هذه الإجراءات يكون بطريقة عكسية، كما في وظيفة learnDefer:

func learnDefer() (ok bool) {
// تُنفَّذ التعليمات المؤجلة قبل أن ترجع الوظيفة  النتيجة.   
    defer fmt.Println("deferred statements execute in reverse (LIFO) order.")
    defer fmt.Println("\nThis line is being printed first because")
// يُستخدَم تأجيل التنفيذ عادة لإغلاق ملف بعد فتحه.
    return true
}

الواجهات Interfaces

نعرّف في ما يلي دالة باسم Stringer تحتوي على دالة واحدة باسم String ؛ ثم نعرّف هيكلا Struct من خانتين نوع int باسم x وy.

type Stringer interface {
    String() string
}
type pair struct {
    x, y int
}

نعرّف في ما يلي دالة String على النوع pair، ليصبح pair تطبيقا Implementation للواجهة Stringer. يُسمى المتغيّرp أدناه بالمُستقبل. لاحظ كيفية الوصول لحقول الهيكل pair وذلك باستخدام اسم الهيكل متبوعا بنقطة ثم اسم الحقل.

func (p pair) String() string { 
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

تُستخدَم الأقواس الهلالية لإنشاء عنصر من الهياكل Structs. نستخدم التعريف القصير (باستخدام := ) في المثال أدناه لإنشاء متغير باسم p وتحديد نوعه بالهيكل pair .

func learnInterfaces() {
    p := pair{3, 4}
// نستدعي الدالة String الخاصة بالنوع pair     
fmt.Println(p.String()) 
// نُعرف متغيرًا باسم i من نوع الواجهة المعرفة سابقا Stringer
var i Stringer          
// هذه المساواة صحيحة، لأن pair تُطبق Stringer
i = p                   
/* نستدعي الدالة String الخاصة بالمتغير i من نوع Stringer ونحصُل على نفس النتيجة السابقة */
fmt.Println(i.String())

/* عند تمرير المتغيرات السابقة مباشرةإلى دوال الحزمة fmt الخاصة بالطباعة والإخراج، فإن هذه الدوال تستدعي الدالة String لطباعة التمثيل الخاص بالمتغير.  */
// يعطي السطران التاليان نفس النتيجة السابقة للطباعة   
 fmt.Println(p) 
 fmt.Println(i) 
    learnVariadicParams("great", "learning", "here!")
}

المدخلات المتعددة

من الممكن أن نمرر معطيات متغيرة العدد للدوال.

func learnVariadicParams(myStrings ...interface{}) {
/* تمرّ جملة التكرار التالية على عناصر المعطيات المدخلة للدالة. التسطير السفلي هنا نعني به تجاهل المؤشر الخاص بالعنصر الذي نمر عليه. */
    for _, param := range myStrings {
        fmt.Println("param:", param)
    }

/* هنا نمرّر مدخلات الدالة ذات العدد المتغير كمعامل لدالة أخرى (للدالة Sprintln) */
fmt.Println("params:", fmt.Sprintln(myStrings...))

    learnErrorHandling()
}

معالجة الأخطاء Errors Handling

تُستخدَم الكلمة المفتاحية “ok,” لمعرفة صحة عبارة من عدمها. في حال حدوث خطأ فيمكننا استخدام err لمعرفة تفاصيل أكثر عن الخطأ.

func learnErrorHandling() {
    m := map[int]string{3: "three", 4: "four"}
    if x, ok := m[1]; !ok { 
// ok هنا ستكون false لأن رقم 1 غير موجود في الخريطة m
        fmt.Println("no one there")
    } else {
// x ستكون القيمة الموجودة في map
        fmt.Print(x)
    }
/* هنا نحاول أن نقوم بعمل تحويل لقيمة نصية إلى عدد مما سينتج عنه خطأ, ونقوم بطباعة تفاصيل الخطأ في حالة أن err ليست nil */
    if _, err := strconv.Atoi("non-int"); err != nil {         
        fmt.Println(err)
    }
    learnConcurrency()
}
// المعطى c هنا من نوع قناة، وهو كائن لتأمين الاتصالات المتزامنة
func inc(i int, c chan int) {
// عندما يظهر عنصر من نوع قناة على الشمال، فإن العملية  <- تعني إرسال
    c <- i + 1 
}

التنفيذ المتزامن Concurrency

نستخدم الدالة السابقة لعمل إضافة عددية على بعض الأرقام بالتزامن. نستخدم make كما فعلنا في بداية المقال لإنشاء متغير دون تحديد قيمة له.

func learnConcurrency() {
// هنا نقوم بإنشاء متغير من نوع قناة وباسم c
    c := make(chan int)
/* نبدأ بإنشاء ثلاثة دوال متزامنة للغة Go. الأعداد سيتم الزيادة عليهابالتزامن (وبالتوازي في حال كان الجهاز مُهيئاً لذلك). */
// كافة الإرسالات ستتجه لنفس القناة 
// كلمة go هنا تعني بدء دالة أو وظيفة جديدة
    go inc(0, c) 
    go inc(10, c)
    go inc(-805, c)
// ثم نقوم بعمل ثلاثة قراءات من نفس القناة وطباعة النتائج.
/* لاحظ أنه لا يوجد تحديد ترتيب لوصول القراءات من القناة، ولاحظ أيضا أنه عند ظهور القناة على يمين العملية  <-  فهذا يعني أننا نقوم بقراءة واستقبال من القناة. */  
  fmt.Println(<-c, <-c, <-c) 

// قناة جديدة تحتوي على نص
cs := make(chan string)       
// قناة تحتوي على قنوات نصية
ccs := make(chan chan string) 

// إرسال قيمة 84 إلى القناة c
go func() { c <- 84 }()       
// إرسال كلمة wordy للقناة cs
go func() { cs <- "wordy" }() 

/* جملة Select تشبه جملة switch ولكنها في كل حالة تحتوي على عملية خاصة بقناة جاهزة للتواصل معها. */ 
 select {
// القيمة المُستلمة من القناة من الممكن أن تُحفظ في متغير.
    case i := <-c:
        fmt.Printf("it's a %T", i)
    case <-cs:
        fmt.Println("it's a string")
// قناة فارغة ولكنها جاهزة للتواصل   
 case <-ccs: 
        fmt.Println("didn't happen.")
    }
// برمجة الويب
    learnWebProgramming()
}

الويب

نستطيع بدء خادوم ويب باستخدام دالة واحدة من حزمة http. نمرّر في المعامل الأول للدالة ListenAndServe عنوان TCP للاستماع له، والمعامل الثاني واجهة عبارة عن معالج http.

func learnWebProgramming() {
    go func() {
        err := http.ListenAndServe(":8080", pair{})
// نطبع الأخطاء في حال وجودها.        
fmt.Println(err) 
    }()

    requestServer()
}
// اجعل pair معالج http وذلك بواسطة تطبيق دالته الوحيدة المسماة ServeHTTP
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// تتبع الدالة Write للحزمة  http.ResponseWriter ونستخدمها لإرجاع رد لطلب http
w.Write([]byte("You learned Go in Y minutes!"))
}

func requestServer() {
    resp, err := http.Get("http://localhost:8080")
    fmt.Println(err)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Printf("\nWebserver said: `%s`", string(body))
}

ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=Go.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن