Στη Go, οι συναρτήσεις είναι τιμές πρώτης τάξης. Αυτό σημαίνει ότι μπορείς να κάνεις με τις συναρτήσεις τα ίδια πράγματα που μπορείς να κάνεις με όλες τις άλλες τιμές: να αναθέσεις συναρτήσεις σε μεταβλητές, να τις περάσεις ως ορίσματα σε άλλες συναρτήσεις ή ακόμη και να επιστρέψεις συναρτήσεις από άλλες συναρτήσεις.
Παρακάτω δημιουργούμε δύο συναρτήσεις, τις engGreeting και espGreeting, και τις αναθέτουμε στη μεταβλητή greeting:
import "fmt"
func engGreeting(name string) string {
return fmt.Sprintf("Hello %s, nice to meet you!", name)
}
func espGreeting(name string) string {
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}
greeting := engGreeting // greeting is a variable of type func(string) string
fmt.Println(greeting("Alice")) // Hello Alice, nice to meet you!
greeting = espGreeting
fmt.Println(greeting("Alice")) // ¡Hola Alice, mucho gusto!
Οι τιμές συνάρτησης σου δίνουν τη δυνατότητα να παραμετροποιήσεις συναρτήσεις όχι μόνο με δεδομένα αλλά και με συμπεριφορά.
Στο παρακάτω παράδειγμα, περνάμε συμπεριφορά στη συνάρτηση dialog μέσω της παραμέτρου greetingFunc:
func dialog(name string, greetingFunc func(string) string) {
fmt.Println(greetingFunc(name))
fmt.Println("I'm a dialog bot.")
}
func espGreeting(name string) string {
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}
greeting := espGreeting
dialog("Alice", greeting)
// =>
// ¡Hola Alice, mucho gusto!
// I'm a dialog bot.
Η τιμή μιας μη αρχικοποιημένης μεταβλητής τύπου συνάρτησης είναι nil.
Επομένως, η κλήση μιας τιμής συνάρτησης nil προκαλεί panic.
var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function
Οι τιμές συνάρτησης μπορούν να συγκριθούν με το nil.
Αυτό μπορεί να φανεί χρήσιμο για να αποφύγεις περιττά panic στο πρόγραμμα.
var dutchGreeting func(string) string
if dutchGreeting != nil {
dutchGreeting("Alice") // safe to call dutchGreeting
}
Η χρήση τιμών συνάρτησης είναι εφικτή χάρη στους τύπους συνάρτησης στη Go.
Ένας τύπος συνάρτησης περιγράφει το σύνολο όλων των συναρτήσεων με την ίδια ακολουθία τύπων παραμέτρων και την ίδια ακολουθία τύπων αποτελέσματος.
Οι τύποι που ορίζει ο χρήστης μπορούν να δηλωθούν πάνω σε τύπους συνάρτησης.
Για παράδειγμα, η συνάρτηση dialog από τα προηγούμενα παραδείγματα μπορεί να ενημερωθεί ως εξής:
type greetingFunc func(string) string
func dialog(name string, f greetingFunc) {
fmt.Println(f(name))
fmt.Println("I'm a dialog bot.")
}
Ένα άλλο ισχυρό εργαλείο που έχεις στη διάθεσή σου χάρη στην υποστήριξη συναρτήσεων πρώτης τάξης είναι οι ανώνυμες συναρτήσεις.
Οι ανώνυμες συναρτήσεις ορίζονται στο σημείο όπου χρησιμοποιούνται, χωρίς κάποιο όνομα μετά τη λέξη-κλειδί func.
Τέτοιες συναρτήσεις έχουν πρόσβαση στις μεταβλητές της περικλείουσας συνάρτησης.
Για παράδειγμα:
func fib() func() int {
var n1, n2 int
return func() int {
if n1 == 0 && n2 == 0 {
n1 = 1
} else {
n1, n2 = n2, n1 + n2
}
return n2
}
}
next := fib()
for i := range N {
fmt.Printf("F%d\t= %4d\n", i, next())
}
Μια κλήση στη fib δηλώνει τις μεταβλητές n1 και n2 και επιστρέφει μια ανώνυμη συνάρτηση που, με τη σειρά της, αλλάζει τις τιμές αυτών των μεταβλητών κάθε φορά που καλείται η συνάρτηση.
Η Ν-οστή κλήση της ανώνυμης συνάρτησης επιστρέφει τον Ν-οστό αριθμό της ακολουθίας Fibonacci ξεκινώντας από το 0. Η εσωτερική ανώνυμη συνάρτηση έχει πρόσβαση στις τοπικές μεταβλητές (n1 και n2) της περικλείουσας συνάρτησης fib.
Αυτός είναι ένας πολύ καλός τρόπος για να διατηρούν οι τιμές συνάρτησης κατάσταση ανάμεσα στις κλήσεις.
Λέμε ότι η ανώνυμη συνάρτηση είναι ένα κλείσιμο των μεταβλητών n1 και n2. Τα κλεισίματα χρησιμοποιούνται ευρέως στον προγραμματισμό και μπορεί να δεις και άλλες γλώσσες να τα υποστηρίζουν.
Ο Bob είναι οικονομικός σύμβουλος και βοηθάει τους ανθρώπους να διαχειρίζονται τα έξοδά τους. Οι πελάτες του Bob τού στέλνουν καταγραφές εξόδων για να τις αναλύσει. Ο Bob έχει καταγραφές από τις προηγούμενες περιόδους, ώστε να βλέπει τις αλλαγές στις δαπάνες. Ο Bob δε συμπαθεί τα ημερολόγια και χρησιμοποιεί το Bob epoch αντί για ημερομηνίες. Το Bob epoch είναι ο αριθμός των ημερών που έχουν περάσει από τότε που ο πελάτης του Bob ξεκίνησε τη δραστηριότητά του.
Σε αυτή την άσκηση, θα φτιάξεις ένα πρόγραμμα που θα βοηθήσει τον Bob να διαχειρίζεται και να αναλύει τα έξοδα των πελατών του.
Ο Bob δουλεύει με Records και DaysPeriods.
Μια Record αντιπροσωπεύει μια καταγραφή εξόδου που περιέχει την ημέρα κατά την οποία έγινε το έξοδο, τα χρήματα που δαπανήθηκαν και την κατηγορία του εξόδου.
// Record represents an expense record.
type Record struct {
Day int
Amount float64
Category string
}
Ένα DaysPeriod αντιπροσωπεύει ένα εύρος ημερών και περιλαμβάνει όλες τις ημέρες από την ημέρα From μέχρι την ημέρα To.
Και τα δύο άκρα περιλαμβάνονται στο εύρος.
// DaysPeriod represents a period of days.
type DaysPeriod struct {
From int
To int
}
p := DaysPeriod{From: 1, To: 31}
// p represents all days from the day 1 to the day 31:
// - days 1, 20, 16 and 31 are examples of days that are included
// in the range of time specified by p
// - days 50 and 40 are examples of days that are not included
// in the range of time specified by p
Ο Bob έχει να κάνει με πολλές καταγραφές κάθε μέρα, αλλά δεν είναι όλες ενδιαφέρουσες, ανάλογα με την ανάλυση που κάνει. Ας βοηθήσουμε τον Bob να κάνει ένα βασικό φιλτράρισμα των καταγραφών.
Υλοποίησε τη γενική συνάρτηση Filter για να φιλτράρεις καταγραφές σύμφωνα με ένα κριτήριο που δίνεται από μια συνάρτηση.
Αυτή η συνάρτηση φίλτρου δέχεται μια συλλογή καταγραφών και μια συνάρτηση κατηγόρημα και επιστρέφει μόνο τις καταγραφές της συλλογής που ικανοποιούν το κατηγόρημα.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
}
// Day1Records only returns true for records that are from day 1
func Day1Records(r Record) bool {
return r.Day == 1
}
Filter(records, Day1Records)
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"}
// ]
Ο Bob χρειάζεται συχνά να φιλτράρει καταγραφές που βρίσκονται σε μια δεδομένη χρονική περίοδο.
Υλοποίησε τη συνάρτηση ByDaysPeriod που θα βοηθήσει τον Bob να δημιουργεί τέτοια φίλτρα.
Αυτή η συνάρτηση δέχεται ένα DaysPeriod και επιστρέφει μια συνάρτηση που παίρνει μια καταγραφή και λέει αν η καταγραφή βρίσκεται στη χρονική περίοδο που ορίζει το DaysPeriod που δόθηκε ως όρισμα.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 26, Amount: 300, Category: "university"},
{Day: 28, Amount: 1300, Category: "rent"},
}
period := DaysPeriod{From: 1, To: 15}
Filter(records, ByDaysPeriod(period))
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"},
// {Day: 11, Amount: 300, Category: "utility-bills"},
// {Day: 12, Amount: 28, Category: "groceries"},
// ]
Εκτός από το φιλτράρισμα καταγραφών ανά χρονική περίοδο, ο Bob χρειάζεται επίσης να φιλτράρει καταγραφές ανά κατηγορία τους.
Υλοποίησε τη συνάρτηση ByCategory που θα βοηθήσει τον Bob να δημιουργεί τέτοια φίλτρα.
Αυτή η συνάρτηση δέχεται μια κατηγορία και επιστρέφει μια συνάρτηση που παίρνει μια καταγραφή και λέει αν η κατηγορία αυτής της καταγραφής είναι ίδια με την κατηγορία που δόθηκε ως όρισμα.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 28, Amount: 1300, Category: "rent"},
}
Filter(records, ByCategory("groceries"))
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"},
// {Day: 12, Amount: 28, Category: "groceries"},
// ]
Ο Bob θέλει επίσης να ξέρει το συνολικό ποσό των εξόδων σε μια χρονική περίοδο.
Υλοποίησε τη συνάρτηση TotalByPeriod για να επιστρέφει το άθροισμα των εξόδων στην περίοδο ημερών.
records := []Record{
{Day: 15, Amount: 16, Category: "entertainment"},
{Day: 32, Amount: 20, Category: "groceries"},
{Day: 40, Amount: 30, Category: "entertainment"}
}
p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}
TotalByPeriod(records, p1)
// => 16
TotalByPeriod(records, p2)
// => 50
Για τις πιο σύνθετες αναφορές που κάνει ο Bob στους πελάτες του, ο Bob χρειάζεται να φιλτράρει καταγραφές ανά κατηγορία και ανά χρονική περίοδο ταυτόχρονα. Αυτό σημαίνει ότι ο Bob θέλει να ξέρει τα συνολικά έξοδα για καταγραφές μιας κατηγορίας σε μια δεδομένη χρονική περίοδο.
Υλοποίησε τη συνάρτηση CategoryExpenses που επιστρέφει το συνολικό ποσό των εξόδων σε μια κατηγορία μέσα σε μια δεδομένη περίοδο ημερών.
Η συνάρτηση πρέπει επίσης να ξεχωρίζει την περίπτωση όπου η δεδομένη κατηγορία δεν υπάρχει στις καταγραφές εξόδων και την περίπτωση όπου δεν υπάρχουν έξοδα της κατηγορίας στην περίοδο που δόθηκε.
Όταν η κατηγορία δεν είναι κατηγορία καμίας από τις καταγραφές (ανεξάρτητα από τη χρονική περίοδο), η συνάρτηση πρέπει να επιστρέφει ένα σφάλμα.
p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 26, Amount: 300, Category: "university"},
{Day: 28, Amount: 1300, Category: "rent"},
}
CategoryExpenses(records, p1, "entertainment")
// => 0, error(unknown category entertainment)
CategoryExpenses(records, p1, "rent")
// => 1300, nil
CategoryExpenses(records, p2, "rent")
// => 0, nil
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Go με 34 έννοιες166 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.