Golang: Funksiyalar və qapanmalar

Orkhan Huseynli
6 min readDec 27, 2023

--

Golang — funksiyalar və qapanmalar

Giriş

Funksiyalar müəyyən bir tapşırığı yerinə yetirən təlimatlar toplusudur. Hər bir funksional proqramlaşdırma dilində olduğu kimi funksiyalar Go dilində də xüsusi bir yerə sahibdir. Bu məqalədə biz Go dilində funksiyaları necə yaratmağı və istifadə etməyi öyrənəcək, daha sonra qapanmalar (closures) haqqında müzakirə aparacağıq.

Məqaləyə keçməzdən əvvəl əgər Go dilində yenisinizsə, həmkarım Elnur Məmmədlinin qələmə aldığı Go proqramlaşdırma dilinə giriş, Verilənlərin tipləri, dəyişənlər və sabitlər başlıqlı məqalələri oxumağı təkidlə tövsiyə edirəm.

Funksiyaların yaradılması

Funksiya yaratmaq üçün biz Golangdə “func” açar sözündən istifadə edirik. Daha sonra isə funksiyanın adı, parametrləri, qaytardığı məlumatın növü və funksiyanın gövdəsi gəlir:

func greet(name string, surname string) {
// funksiyanın gövdəsi
}

Diqqət edək ki, parametrin məlumat növü adından sonra gəlir. Əgər bir funksiya bir neçə eyni tipli parametr qəbul edərsə, o zaman biz həmin tipi bir dəfə yazsaq kifayət edər:

func add(a, b int) int {
return a + b;
}

result := add(5, 4)

return” açar sözü isə funksiyadan çıxmaq və geriyə dəyər qaytarmaq üçün istifadə edilir (bildiyiniz kimi). Xatırladım ki, biz bir funksiyanın içərisində çoxlu sayda “return” açar sözündən istifadə edə bilərik:

func isPrime(n int) bool {
sqaureRootOfN := math.Sqrt(float64(n))

for i := 2; i <= int(sqaureRootOfN); i++ {
if n % i == 0 {
return false
}
}

return true
}

Konvensiyaya əsasən Golang funksiyaları “mixedCaps” və ya “MixedCaps” şəklində adlandırılır. Funksiyanın adının baş hərfinin böyük və ya kiçik olması onun paket daxilində fərdi olub-olmamağını müəyyənləşdirir. Əgər funksiyanın adının ilk hərfi böyükdürsə, həmin funksiya digər paketlərdə daxil edilib istifadə oluna bilər.

Funksiyadan çoxlu dəyərlərin qaytarılması

Golang funksiyaları, həmçinin geriyə çoxlu sayda fərqli tipdə dəyərlər qaytara bilər. Deyək ki, bütöv formatda bizə verilmiş bir sətrin (string) içərisindən ad, soyad və yaşı çıxarmaq üçün funksiya yazırıq. Funksiyamız bütov bir sətri parametr olaraq qəbul etməli və geriyə iki sətir (ad, soyad) və bir tam ədəd (yaş) qaytarmalıdır. Oxşar bir şeyi deyək ki, JavaScript ilə etməli olsaq, geriyə massiv qaytarmalı olacağıq.

Golangdə isə aldığımız dəyərlərin hər birini ayrı-ayrılıqda qaytara bilərik. Beləliklə, yazmaq istədiyimiz funksiyanın imzası (function signature) aşağıdakı kimi olacaq:

func extractNameSurnameAndAge(compoundFullName string) (string, string, int) {
// özünüz yazın
}

Gördüyünüz kimi funksiyanın qaytaracağı dəyərlərin tipləri mötərizə içərisində vergüllə ayrılaraq yazılmışdır. Funksiyanı çağırdığımızda isə, onun qaytardığı dəyərləri eyni şəkildə istədiyimiz adlı dəyişənlərə mənimsədə bilərik. Pythonda olduğu kimi:

firstName, lastName, age := extractNameSurnameAndAge("Elnur Məmmədli 18")

Bu, Go proqramlaşdırma dilində geniş yayılmış bir yanaşmadır. Nümunə üçün “strconv” moduluna baxa bilərik. Əgər biz hər hansı bir sətri tam ədədə çevirmək istəsək “strconv.ParseInt” funksiyasından istifadə etməliyik. ParseInt funksiyası isə geriyə bir tam ədəd və bir xəta qaytaracaq. Qaytarılan xəta, əgər mövcuddursa, funksiyanın sətri ədədə niyə çevirə bilmədiyini bizə izah edəcək:

age, err := strconv.ParseInt("21", 10, 64)

if err != nil {
fmt.Println(err.Error())
}

fmt.Println(age)

Adlandırılmış dəyərlərin qaytarılması (named return values)

Golang bizə funksiyadan qaytaracağımız dəyərləri aşağıdakı şəkildə təyin etməyə imkan verir:

func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}

func main() {
x, y := split(17)

fmt.Println(x, y)
}

Burada biz x və y dəyişənlərini elə funksiyanın imzasında elan etmişik və bu dəyişənlər funksiyanın geri qaytarmalı olduğu dəyərlərdir. Biz onlara funksiyanın içərisində dəyər mənimsədə və daha sonra boş “return” bəyanatı yaza bilərik. Bu yanaşmaya “naked return” deyilir.

“naked return” ilk baxışdan qeyri-intuitiv olduğundan və uzun funksiyalarda oxunaqlığı azaltdığından dolayı tövsiyə edilən yanaşma deyil.

Birinci sinif vətəndaşlar

Funksiyalar Go dilində birinci sinif vətəndaşlardır. Yəni onlar istənilən bir dəyişən kimi davrana bilərlər. Biz bir funksiyanı hər hansı bir dəyişənə mənimsədə, onu başqa funksiyaya parametr kimi ötürə və qaytara bilərik.

Nümunə üçün funksional proqramlaşdırmada məşhur olan “filter” funksiyasına baxa bilərik. Filter funksiyası verilən massivi filtrləmək üçün istifadə olunur. Lakin bunu edərkən verilən massivi dəyişməməli və filtrləmə funksiyaya ötürülən “predicate” funksiyanın köməyilə aparılmalıdır.

Predicate funksiya geriyə “true” və ya “false” qaytaran funksiyalara deyilir.

Aşağıda verilən nümunədə filter funksiyası verilən massivin içərisindən cüt ədədləri seçib bizə qaytarır. Ədədləri seçmək üçün isə “isEven” funksiyasından istifadə edir:

func isEven(number, index int) bool {
return number%2 == 0
}

func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}

evenNumbers := filter(numbers, isEven)

fmt.Println(evenNumbers) // [2, 4, 6, 8]
}

Bu nümunədə “isEven” funksiyası “filter” funksiyasına parametr kimi ötürülmüşdür.

Anonim funksiyalar

Əgər siz “isEven” funksiyasını başqa yerdə istifadə etməyəcəksinizsə, onu bir başa “filter” funksiyasına ötürdüyümüz yerdə də təyin edə bilərsiniz:

func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8}

evenNumbers := filter(numbers, func(number, index int) bool {
return number%2 == 0
})

fmt.Println(evenNumbers)
}

Bu cür təyin olunan funksiyalara anonim funksiyalar deyilir. Çünki funksiyanın adı yoxdur. Bunu Python və Javada mövcud olan lambdalar ilə müqayisə edə bilərsiniz.

“filter” funksiyasının implementasiyası isə aşağıdakı kimidir:

func filter(data []int, predicate func(int, int) bool) []int {
result := make([]int, 0, len(data))

for index, element := range data {
if predicate(element, index) {
result = append(result, element)
}
}

return result
}

Burada funksiyanın ikinci parametri iki tam ədəd qəbul edən və “bool” tipində dəyər qaytaran bir funksiyadır. Biz bu funksiya tipini kənarda ləqəb kimi təyin edə və onu istifadə edə bilərik:

type PredicateFn = func(int, int) bool

func filter(data []int, predicate PredicateFn) []int {
result := make([]int, 0, len(data))

for index, element := range data {
if predicate(element, index) {
result = append(result, element)
}
}

return result
}

Qapanmalar

Golangdə funksiyaların başqa funksiyalara parametr kimi ötürülməsinin şahidi olduq. Qeyd etdiyimiz kimi biz həm də funksiya daxilində digər funksiyanı geri qaytara bilərik:

func makeGreeting() func() {
return func() {
fmt.Println("Eşq olsun!")
}
}

greet := makeGreeting()

greet() // Eşq olsun!
greet() // Eşq olsun!

Burada “makeGreeting” funksiyası geriyə “Eşq olsun!” mətnini çap edən bir anonim funksiya qaytarır. Biz “makeGreeting” funksiyasını çağırmaqla onun qaytardığı funksiyanı “greet” adlı dəyişənə mənimsədirik.

İndi isə gəlin bu nümunəni daha maraqlı hala gətirək:

func makeGreeting(message string) func(string) {
return func(person string) {
fmt.Printf("%s, %s!\n", message, person)
}
}

func main() {
greetWell := makeGreeting("Salam")
greetWell("Elnur") // Salam, Elnur!
greetWell("Pərvin") // Salam, Pərvin!

greetBad := makeGreeting("Xoş getdin")
greetBad("Murad") // Xoş getdin, Murad!
greetBad("Səttar") // Xoş getdin, Səttar!
}

Burada “makeGreeting” funksiyası bir növ fabrik (factory) rolunu oynayır. Bu fabrik bizə istədiyimiz cür salamlama funksiyası yaratmağa imkan verir.

Bəs yaşam dövrü?

Bildiyimiz kimi, Golangdə dəyişənin yaşam dövrü onun əhatə dairəsi (scope) ilə əlaqədardır. Yəni dəyişənin həyatı onu əhatə edən dairə bitdikdə bitir. Bu isə o deməkdir ki, “makeGreeting” funksiyası bitdikdə onun parametrləri və içərisində elan olunmuş bütün dəyişənlərin həyatı da sonlanmalıdır. Yuxarıdakı nümunə isə bununla ziddiyyət təşkil edir.

Belə ki, “makeGreeting” funksiyası fəaliyyətini dayandırmağına baxmayaraq ona ötürülən “message” parametri hələ də yaddaşdan təmizlənməmişdir. Funksiyadan törəyən “greetWell” və “greetBad” funksiyaları onun parametrini olduğu kimi yadda saxlayıb, zibil yığanın həmin dəyərləri təmizləməsinə imkan vermir.

Fərqli bir nümunəyə baxaq:

func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}

func main() {
count := makeCounter()
count() // 1
count() // 2
count() // 3
}

Eynilə burada da “makeCounter”in əhatə dairəsində olan “count” dəyişəni olduğu kimi qalır və funksiyanın qaytardığı anonim funksiya hər dəfə onu bir-bir artırır.

Bu funksiyanın öz əhatə dairəsini yadında saxlamağından irəli gəlir və buna funksional proqramlaşdırmada qapanma (closure) deyilir.

Dərhal çağırılan anonim funksiyalar

Golang, həmçinin bizə anonim funksiyaları elə təyin etdiyimiz yerdəcə çağırmağa imkan verir. Bu cür ifadələrə “Immediately Invoked Function Expression” və ya qısaca IIFE deyilir. Əgər siz JavaScript ilə uzun müddət məşğul olmusunuzsa, bu ifadə sizə doğma gələ bilər:

func main() {
func() {
fmt.Println("Bu funksiya çox tez işə düşdü")
}()
}

Xülasə

Bu məqalədə biz Golangdə funksiyalar haqqında öyrəndik. Go dilində funksiyalar birinci sinif vətəndaş olduğundan onlara istədiyimiz bir dəyişən kimi davrana bildiyimizi gördük. Bir funksiyadan digər funksiyanı qaytardığımız zaman isə qapanmaların necə yaranmasını da təcrübə etdik.

Son olaraq, funksiyalar haqda bəzi resurları sizinlə paylaşıram. Oxuduğunuz üçün təşəkkürlər!

--

--

No responses yet