Go-ban a függvények első osztályú értékek. Ez azt jelenti, hogy a függvényekkel ugyanazokat a dolgokat teheted, mint bármely más értékkel: értékül adhatsz függvényeket változóknak, átadhatod őket argumentumként más függvényeknek, vagy akár függvényeket is visszaadhatsz más függvényekből.
Az alábbi példában két függvényt hozunk létre, engGreeting és espGreeting néven, majd értékül adjuk őket a greeting változónak:
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!
A függvényértékek lehetőséget adnak arra, hogy a függvényeket ne csak adatokkal, hanem viselkedéssel is paraméterezzük.
A következő példában viselkedést adunk át a dialog függvénynek a greetingFunc paraméteren keresztül:
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.
Egy inicializálatlan, függvénytípusú változó értéke nil.
Ezért ha meghívsz egy nil függvényértéket, az panic-ot okoz.
var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function
A függvényértékeket össze lehet hasonlítani a nil-lel.
Ez hasznos lehet, hogy elkerüljük a felesleges panic-okat a programban.
var dutchGreeting func(string) string
if dutchGreeting != nil {
dutchGreeting("Alice") // safe to call dutchGreeting
}
A függvényértékek használata a Go függvénytípusainak köszönhetően lehetséges.
Egy függvénytípus azoknak a függvényeknek a halmazát jelöli, amelyeknél a paramétertípusok sorrendje és az eredménytípusok sorrendje megegyezik.
A függvénytípusokra felhasználó által definiált típusokat is deklarálhatsz.
Például az előző példák dialog függvénye a következőképpen frissíthető:
type greetingFunc func(string) string
func dialog(name string, f greetingFunc) {
fmt.Println(f(name))
fmt.Println("I'm a dialog bot.")
}
Egy másik hatékony eszköz, amely az első osztályú függvények támogatásának köszönhetően érhető el, a névtelen függvények.
A névtelen függvényeket ott definiáljuk, ahol használjuk, a func kulcsszó után nem következik név.
Az ilyen függvények hozzáférnek a befoglaló függvény változóihoz.
Például:
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())
}
A fib meghívása deklarálja az n1 és n2 változókat, és visszaad egy névtelen függvényt, amely minden meghíváskor megváltoztatja ezeknek a változóknak az értékét.
A névtelen függvény N-edik meghívása a 0-tól induló Fibonacci-sorozat N-edik elemét adja vissza. A belső névtelen függvény hozzáfér a fib befoglaló függvény lokális változóihoz (n1 és n2).
Ez remek módja annak, hogy a függvényértékek megőrizzék az állapotukat a hívások között.
Azt mondjuk, hogy a névtelen függvény az n1 és n2 változók lezárása. A lezárások széles körben elterjedtek a programozásban, és előfordulhat, hogy más nyelvek is támogatják őket.
Bob pénzügyi tanácsadó, és segít az embereknek kezelni a kiadásaikat. Bob ügyfelei kiadási rekordokat küldenek neki, hogy elemezze azokat. Bobnak vannak rekordjai a korábbi időszakokról, így látni tudja a kiadások változásait. Bob nem szereti a naptárakat, és a dátumok helyett Bob-korszakot használ. A Bob-korszak azon napok száma, amelyek Bob ügyfelének tevékenységének megkezdése óta elteltek.
Ebben a feladatban egy olyan programot építesz, amely segít Bobnak kezelni és elemezni az ügyfelei kiadásait.
Bob Record és DaysPeriod típusokkal dolgozik.
A Record egy kiadási rekordot reprezentál, amely tartalmazza a napot, amelyen a kiadás történt, az elköltött összeget és a kiadás kategóriáját.
// Record represents an expense record.
type Record struct {
Day int
Amount float64
Category string
}
A DaysPeriod egy napokból álló tartományt reprezentál, és tartalmazza az összes napot a From naptól egészen a To napig.
A tartomány mindkét végpontja benne van.
// 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 minden nap rengeteg rekorddal foglalkozik, de attól függően, hogy éppen milyen elemzést készít, nem mindegyikük érdekes. Segítsünk Bobnak elvégezni a rekordok néhány alapvető szűrését.
Valósítsd meg az általános Filter függvényt, amely egy függvény által megadott feltétel szerint szűri a rekordokat.
Ez a szűrőfüggvény rekordok egy gyűjteményét és egy predikátumfüggvényt fogad, és csak azokat a rekordokat adja vissza a gyűjteményből, amelyek kielégítik a predikátumot.
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"}
// ]
Bobnak gyakran kell szűrnie azokat a rekordokat, amelyek egy adott időszakon belül vannak.
Valósítsd meg a ByDaysPeriod függvényt, amely segít Bobnak ilyen szűrőket létrehozni.
Ez a függvény egy DaysPeriod értéket fogad, és visszaad egy függvényt, amely egy rekordot vesz át, és megmondja, hogy a rekord benne van-e abban az időszakban, amelyet az argumentumként megadott DaysPeriod határoz meg.
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"},
// ]
Azon kívül, hogy időszak szerint szűri a rekordokat, Bobnak a kategóriájuk szerint is szűrnie kell a rekordokat.
Valósítsd meg a ByCategory függvényt, amely segít Bobnak ilyen szűrőket létrehozni.
Ez a függvény egy kategóriát fogad, és visszaad egy függvényt, amely egy rekordot vesz át, és megmondja, hogy a rekord kategóriája megegyezik-e az argumentumként megadott kategóriával.
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 a kiadások teljes összegét is szeretné tudni egy adott időszakban.
Valósítsd meg a TotalByPeriod függvényt, amely visszaadja a kiadások összegét az adott napokból álló időszakban.
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
A legösszetettebb, ügyfeleinek készített jelentésekhez Bobnak egyszerre kell a rekordokat kategória és időszak szerint szűrnie. Ez azt jelenti, hogy Bob szeretné tudni az adott időszakban egy kategóriába tartozó rekordok teljes kiadását.
Valósítsd meg a CategoryExpenses függvényt, amely visszaadja egy kategória kiadásainak teljes összegét egy adott napokból álló időszakban.
A függvénynek meg kell különböztetnie azt az esetet, amikor az adott kategória egyáltalán nem szerepel a kiadási rekordokban, attól az esettől, amikor a megadott időszakban nincsenek kiadások ebben a kategóriában.
Ha a kategória egyetlen rekordnak sem a kategóriája (függetlenül az időszaktól), a függvénynek hibát kell visszaadnia.
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
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Go nyelvet 34 fogalom166 feladat segítségével, valódi emberi mentorálással, mindez ingyen.