FREE

ΕΝΟΤΗΤΑ 12 GO - Functions (Part 2)

Στο σημερινό δωρεάν μάθημα GO θα συνεχίσουμε την αναφορά μας στα functions και θα δούμε κάποιες έξτρα ιδιότητες τους όπως anonymous functions και closures. Ας ξεκινήσουμε πρώτα από τα anonymous functions.

Μέχρι τώρα, όλα τα functions που δημιουργήσαμε είχανε όνομα. Μπορούμε όμως στην GO να ορίσουμε functions που δεν έχουν όνομα. Εκτός από την παράλειψη του ονόματος όλες οι άλλες λειτουργίες και ιδιότητες του function παραμένουν οι ίδιες. Δηλαδή μπορούμε να ορίσουμε input παραμέτρους και να λαμβάνουμε πίσω τιμές μέσω των returning values. Επίσης μπορούμε να ορίσουμε μια anonymous function μέσα σε μια κανονική function.

Η γενική δομή των anonymous functions είναι η εξής:

main.go


package main

import "fmt"

func main() {
    func(){
        fmt.Println("Welcome to Kassapoglou.github.io/main site")
    }()
}

Output


Welcome to Kassapoglou.github.io/main site

Η πρώτη μας παρατήρηση είναι ότι μέσα στην main( ) function έχουμε ορίσει μια anonymous function. Η δήλωση του anonymous function ξεκινάει με την λέξη func και μετά, εφόσον δεν χρησιμοποιούμε input παραμέτρους, ακολουθούν κενές παρενθέσεις. Ανάμεσα στα άγκιστρα ορίζουμε τον κώδικα που θα εκτελεστεί από την anonymous function. Υπάρχει ένα ακόμα σετ παρενθέσεων μετά από το τελευταίο άγκιστρο. Αυτές είναι το πιο σημαντικό σημείο ορισμού του anonymous function γιατί ορίζουν την εκτέλεση του για αυτό και ονομάζονται execution parentheses.

Όπως ήδη έχουμε αναφέρει, μπορούμε να περάσουμε και παραμέτρους στην anonymous function. Για να γίνει αυτό πρέπει να περάσουμε τις μεταβλητές στις execution παρενθέσεις, ενώ στις παρενθέσεις δίπλα από την λέξη func ορίζουμε πόσες και ποια είδη παραμέτρων δέχεται το anonymous function.

main.go


package main

import "fmt"

func main() {

    welcomeMessage:="Welcome to Kassapoglou.github.io/main site"

    func(str string){
        fmt.Println(str)
    }(welcomeMessage)
}

Output


Welcome to Kassapoglou.github.io/main site

Στο παράδειγμα που μόλις εκτελέσαμε, στην anonymous function func(str string) ορίζουμε ότι θα δέχεται μια παράμετρο είδος string. Ενώ μέσα στις execution παρενθέσεις ορίζουμε την τιμή που περνάμε στην anonymous function μέσω της μεταβλητής welcomeMessage.

Τα παραδείγματα που έχουμε δει μέχρι τώρα, ορίζουν μια anonymous function και αμέσως μετά την εκτελούν. Δεν είναι όμως απαραίτητο να ακολουθήσουμε αυτή τη διαδικασία. Μπορούμε να αναθέσουμε το αποτέλεσμα μιας anonymous function σε μια μεταβλητή έτσι ώστε να την χρησιμοποιήσουμε αργότερα στον κώδικα μας.

main.go

    
package main

import (
    "fmt"
    "reflect"
)

func main() {
    x:=func(){
        fmt.Println("This is an anonymous function")
    }
    fmt.Println("Retrieving type of x:")
    fmt.Println(reflect.TypeOf(x))
    fmt.Println("Retrieving value of x:")
    x()
}
        
    
    

Output

    
Retrieving type of x:
func()
Retrieving value of x:
This is an anonymous function
    
    

Αναθέτουμε το αποτέλεσμα της anonymous function σε μια μεταβλητή x. Η μεταβλητή x είναι είδος func( ) και αυτό άλλωστε επιβεβαιώνουμε καλώντας την fmt.Println(reflect.TypeOf(x)). Αφού λοιπόν η μεταβλητή x είναι είδος func( ) τότε για να καλέσουμε την function θα πρέπει να βάλουμε και τις παρενθέσεις όπως κάνουμε και σε κανονικές functions με όνομα.

Τώρα που έχουμε μια καλύτερη κατανόηση για το τι είναι anonymous functions μπορούμε να προχωρήσουμε σε μια πιο δυνατή ιδιότητα των anonymous functions που ονομάζεται closure. Closure είναι μια ειδική μορφή anonymous function που έχει την δυνατότητα να αναφέρεται σε μεταβλητές που δεν είναι ορισμένες τοπικά στο anonymous function. Αν δηλαδή έχουμε ορίσει στο επίπεδο της func main( ) μερικές μεταβλητές και ένα closure, τότε το closure έχει πρόσβαση σε αυτές τις μεταβλητές χωρίς μάλιστα να χρειάζεται να τις ορίσουμε σαν παραμέτρους στην anonymous function.

main.go

        
package main

import "fmt"

func main() {
    i:=0
    increment := func()int{
        i+=1
        return i
    }
    fmt.Println(increment())
    fmt.Println(increment())
    i+=10
    fmt.Println(increment())
}
            
        
        

Output

        
1
2
13
        
        

Στην αρχή ορίζουμε μια μεταβλητή i να έχει τιμή 0. Αναθέτουμε μια anonymous function σε μια καινούργια μεταβλητή με το όνομα increment. Η anonymous function αυξάνει την τιμή του i κατά μια μονάδα και την επιστρέφει πίσω στο πρόγραμμα. Αξίζει να παρατηρήσετε ότι δεν έχουμε ορίσει input παραμέτρους στην anonymous function. Έπειτα τυπώνουμε το αποτέλεσμα και παίρνουμε τους αριθμούς 1 και 2.

Το επόμενο βήμα είναι να αυξήσουμε την τιμή του i κατά 10 αλλά όχι χρησιμοποιώντας την anonymous function. Εδώ όμως είναι που βλέπουμε πως υπάρχει πρόβλημα. Όταν εκτελούμε πάλι την anonymous function θα επιθυμούσαμε το αποτέλεσμα να είναι 3 και όχι 13.

Βλέποντας τον κώδικα, πολύ εύκολα αναγνωρίζουμε το γεγονός ότι οποιοδήποτε μέρος του κώδικα μέσα στην main function έχει πρόσβαση στο i. Αυτό που πραγματικά θα θέλουμε να πετύχουμε είναι η μεταβλητή increment να είναι ο μόνος επιτρεπτός τρόπος να αλλάξει η τιμή του i. Στην ουσία θέλουμε να προστατέψουμε το i από οποιαδήποτε άλλη anonymous function ή κώδικα να μπορεί να αλλάξει την τιμή του.

Αλλάζουμε λοιπόν τον κώδικα του προηγούμενου παραδείγματος σε αυτό:

main.go


package main

import "fmt"

func increment() func() int{
    i := 0
    return func() int {
        i +=1
        return i
    }
}

func main() {
    increase :=increment()
    fmt.Println(increase())
    fmt.Println(increase())
    fmt.Println(increase())
}   

Output


1
2
3 

Ορίζουμε λοιπόν μια function με το όνομα increment( ) ή οποία επιστρέφει ένα anonymous function είδος int. Τώρα ορίζουμε μια μεταβλητή i μέσα στην increment( ) function σε αντίθεση με το προηγούμενο πρόγραμμα όπου είχαμε ορίσει την i σε επίπεδο main( ). Με αυτή την αλλαγή μόνο η function increment έχει πρόσβαση στην i μεταβλητή.

Η function increment( ) επιστρέφει λοιπόν μια anonymous function της οποία ο κώδικας αυξάνει την τιμή του i κατά 1 και μετά μας επιστρέφει την int τιμή της μεταβλητής. Για αυτό άλλωστε ορίσαμε ότι η function increment( ) θα μας επιστρέφει μια anonymous function με int data type.

Το πιο σημαντικό βήμα στον καινούργιο μας κώδικα είναι η γραμμή increase := increment( ). Εδώ, αυτό που γίνεται στην πραγματικότητα, είναι η ανάθεση του func( ) int σε μια μεταβλητή. Η increment( ) εκτελείται μόνο μια φορά και αυτή είναι εδώ για να αναθέσει την func( ) int στην μεταβλητή. Κατά την εκτέλεση του κώδικα μας, και καλείται καμία άλλη φορά.

Τώρα η increase είναι είδος func( ) int. Κάθε φορά που καλούμε την increase( ) στην πραγματικότητα εκτελείται ο κώδικας της anonymous function.