A Go programozási nyelv

Függvénytípusok a Go-ban

Névtelen függvények

Go támogatja a névtelen függvényeket. Leginkább a JavaScript névtelen függvényeire hasonlít, nem a Python lambda-kifejezéseire.

Névtelen függvények deklarációja nagyon egyszerű Go-ban:

func() { fmt.Println("hello") }

Önmagában kifejezésként nem sok haszna van, ezért elmenthetjük egy változóba, hozzáadhatjuk egy adatstruktúrához, vagy átadhatjuk egy másik függvénynek paraméterként.

fn := func() { fmt.Println("hello") }

Most van egy fn változónk, ami egy függvény, a típusa func(). Az összes többi függvényhez hasonlóan az fn() kifejezéssel hívható meg. Lehetőségünk van értékül adni másik func() típusú változónak is. Mivel a Go támogatja a klózokat, a függvényen belül elérjük a függvénnyel egy blokkban deklarált változókat is:

x := 5 fn := func() { fmt.Println("x is", x) } fn() x++ fn()

Az eredmény a következő:

x is 5
x is 6

Eddig hasonló a JavaScripthez, azzal a különbséggel, hogy ez statikusan típusozott.

Függvény-gyűjtemények

Természetesen lehetőségünk van függvények használatára minden olyan helyen ahol a szokásos típusokat használhatjuk. Például elkészíthetjük függvények slice-ját, véletlenszerűen választunk belőle egyet, majd végrehajtjuk.

Definiálunk egy binFunc típust, ami kétoperandusú műveletet jelöl. Két egész számot kap paraméterül, és egy számmal tér vissza.

type binFunc func(int, int) int func main() { // seed your random number generator. rand.Seed(time.Now().Unix()) // create a slice of functions fns := []binFunc{ func(x, y int) int { return x + y }, func(x, y int) int { return x - y }, func(x, y int) int { return x * y }, func(x, y int) int { return x / y }, func(x, y int) int { return x % y }, } // pick one of those functions at random fn := fns[rand.Intn(len(fns))] // and execute it x, y := 12, 5 fmt.Println(fn(x, y)) }

Függvények mint mezők

Hasonlóan használhatunk függvényt egy struktúra egy mezőjeként is. Ez lehetővé teszi, hogy plusz információt adjunk egy függvénynek, pl egy címkét, ami alapján futásidőben elérhetjük.

type op struct { name string fn func(int, int) int } func main() { // seed your random number generator rand.Seed(time.Now().Unix()) // create a slice of ops ops := []op{ {"add", func(x, y int) int { return x + y }}, {"sub", func(x, y int) int { return x - y }}, {"mul", func(x, y int) int { return x * y }}, {"div", func(x, y int) int { return x / y }}, {"mod", func(x, y int) int { return x % y }}, } // pick one of those ops at random o := ops[rand.Intn(len(ops))] x, y := 12, 5 fmt.Println(o.name, x, y) fmt.Println(o.fn(x, y)) }

De akár a függvényeket tárolhatjuk map-ekben is: map[string]binFunc.

Rekurzív függvénytípusok

Egy másik érdekes nézőpontja a függvénytípusoknak az, hogy megengedi a rekurzív függvénytípusok definiálását, amik olyan függvénytípusok amik saját magukon végeznek műveletet. Az ilyen függvénytípus vagy saját típusát kapja paraméterül, vagy a saját típusa a visszatérési értéke.

Függvénytípusok mint Interface megvalósítás

A go nyelvben a függvénytípusoknak lehetnek metódusaik is. Elsőre talán nehéz látni ennek az előnyét. Két fő következménye van annak, hogy a függvénytípusoknak lehetnek metódusaik: az egyik, hogy mivel bármely típus, aminek lehetnek metódusai megvalósíthat egy interface-t, ezért a függvénytípusok is lehetnek érvények interface megvalósítások. Másrészről mivel a metódusoknak Go-ban lehetnek pointerei, használhatjuk őket függvénypointereken azért, hogy megváltoztassuk a mutatott függvény a metódus belsejében.

Először is, a legnyilvánvalóbb megoldás hogy egy függvénytípus megvalósítson egy interface-t az, hogy az interface-nek csak egy metódusa legyen. Definiáljunk egy add függvényt ami legyen Error() string függvény is egyben. Go-ban minden típus, aminek van Error() string metódusa, érvényes error típus is, tehát a függvényünk használhatjuk függvényként és error-ként is.

type binFunc func(int, int) int func add(x, y int) int { return x + y } func (f binFunc) Error() string { return "binFunc error" } func main() { var err error err = binFunc(add) fmt.Println(err) }

Érdemes megjegyezni, hogy ebben az esetben végre kell hajtanunk egy típuskonverziót. Az add függvényt, aminek a típusa func(int, int) int át kell konvertálnunk binFunc típusúvá. Hogy lássuk miért van erre szükség, tegyük fel a következőt: van egy másik érvényes konverzió func(int, int) int-nak, ami implementálja az error-t.

type loudBinFunc func(int, int) int func (f loudBinFunc) Error() string { return "THIS ERROR IS A LOT LOUDER" }

Ha binFunc és loudBinFunc is definiált, futásidőben nem tudjuk, hogyan kovertáljuk az add függvényünket. Még ha csak egy érvényes implementációja van egy interface-nek egy adott típusra, futásidőben akkor sem fog automatikusan konvertálódni. Elsőre nagyon erős megkötésnek hathat, de könnyebben lehet ezzel hibamentes kódot írni.

A standard könyvtárban találunk példát arra, hogy egy függvénytípus megvalósít egy interface-t a net/http csomagban. A http.Handler típus egy a standar könyvtár által használt interface.

type Handler interface { ServeHTTP(ResponseWriter, *Request) }

Van egy http.HandlerFunc típus is, ami csak egy csomagoló a func(http.ResponseWriter, *http.Request) köré, ami megvalósítja a http.Handler-t. Ez lehetővé teszi, hogy akár egy függvényt akár egy struktúrát használjunk web handler-ként. A végeredmény, hogy megkapjuk mind a http.Handler és és http.HandlerFunc függvényeket.

Függvények csatornái

Mivel a csatornák a Go primitív típusai, ezért bármilyen más típusból készíthethünk csatornát. Azaz csinálhatunk csatornát függvényekből is. Mivel a függvények lehetnek névtelene, valamint a függvények klózok, kombinálhatjuk ezt a három tulajdonságot, hogy névtelen függvényklózok csatornáját elkészítsük. A definíciója ennek a csatorna típusnak: chan func(). Készítsünk egy függvényekből álló slice-ot, de most mindegyiket klózként.

x := 10 fns := []func(){ func() { x += 1 }, func() { x -= 1 }, func() { x *= 2 }, func() { x /= 2 }, func() { x *= x }, }

Válasszunk ki véletlenszerűen egy függvényt.

func pickFunc(fns ...func()) func() { return fns[rand.Intn(len(fns))] }

Ezután készítünk egy func() típusú csatornát.

c := make(chan func())

Definiálunk egy függvényt, ami paraméterül kap egy függvénycsatornát, egy számot ami megmondja, hogy hány függvénynek kell a csatornára írnia, valamint függvények egy halmazát.

func produce(c chan func(), n int, fns ...func()) { defer close(c) for i := 0; i < n; i++ { c <- pickFunc(fns...) } }

A függvény elején a defer close(c)-vel biztosítjuk, hogy a csatorna bezáródik amikor már nem jön több érték. Ezután egy for ciklusban véletlenszerűen kiválasztunk egy függvényt, majd ezt beletesszük a csatornába. A fogadó oldalon csak ki kell olvasnunk az értékeket a csatornából a range fv használatával, majd azonnal futtathatjuk az olvasott függvényt.

for fn := range c { fn() fmt.Println(x) time.Sleep(delay) }