web-dev-qa-db-fra.com

Fonctions de première classe à Go

Je viens de JavaScript qui a un support de fonction de première classe. Par exemple, vous pouvez:

  • passer une fonction en tant que paramètre à une autre fonction
  • renvoyer une fonction d'une fonction.

Quelqu'un peut-il me donner un exemple de la façon dont je procéderais dans Go?

49
Drew LeSueur

Go Programmation fonctionnelle et linguistique pourrait vous aider. De cet article de blog:

package main
import fmt "fmt"
type Stringy func() string
func foo() string{
        return "Stringy function"
}
func takesAFunction(foo Stringy){
    fmt.Printf("takesAFunction: %v\n", foo())
}
func returnsAFunction()Stringy{
    return func()string{
        fmt.Printf("Inner stringy function\n");
        return "bar" // have to return a string to be stringy
    }
}
func main(){
    takesAFunction(foo);
    var f Stringy = returnsAFunction();
    f();
    var baz Stringy = func()string{
        return "anonymous stringy\n"
    };
    fmt.Printf(baz());
}

L'auteur est le propriétaire du blog: Dethe Elza (pas moi)

41
user180100
package main

import (
    "fmt"
)

type Lx func(int) int

func cmb(f, g Lx) Lx {
    return func(x int) int {
        return g(f(x))
    }
}

func inc(x int) int {
    return x + 1
}

func sum(x int) int {
    result := 0

    for i := 0; i < x; i++ {
        result += i
    }

    return result
}

func main() {
    n := 666

    fmt.Println(cmb(inc, sum)(n))
    fmt.Println(n * (n + 1) / 2)
}

sortie:

222111
222111
27
user2532519

La section correspondante de la spécification: Types de fonction .

Toutes les autres réponses ici déclarent d’abord un nouveau type, ce qui est une bonne pratique et facilite la lecture de votre code, mais sachez que ce n’est pas une obligation.

Vous pouvez utiliser des valeurs de fonction sans déclarer un nouveau type pour elles, comme illustré dans l'exemple ci-dessous.

Déclarer une variable de type fonction qui a 2 paramètres de type float64 et une valeur de retour de type float64 ressemble à ceci:

// Create a var of the mentioned function type:
var f func(float64, float64) float64

Écrivons une fonction qui retourne une fonction d'addition. Cette fonction additionneur doit prendre 2 paramètres de type float64 et doit renvoyer la somme de ces 2 nombres lorsqu'elle est appelée:

func CreateAdder() func(float64, float64) float64 {
    return func(x, y float64) float64 {
        return x + y
    }
}

Écrivons une fonction qui a 3 paramètres, les 2 premiers étant du type float64, et le 3ème étant une valeur de fonction, une fonction prenant 2 paramètres d'entrée de type float64 et produisant une valeur de type float64. Et la fonction que nous écrivons appellera la valeur de la fonction qui lui est transmise en tant que paramètre. Elle utilisera les 2 premières valeurs float64 comme arguments pour la valeur de la fonction et renverra le résultat renvoyé par la valeur de la fonction transmise:

func Execute(a, b float64, op func(float64, float64) float64) float64 {
    return op(a, b)
}

Voyons nos exemples précédents en action:

var adder func(float64, float64) float64 = CreateAdder()
result := Execute(1.5, 2.5, adder)
fmt.Println(result) // Prints 4

Notez bien sûr que vous pouvez utiliser la déclaration Variable courte lors de la création de adder:

adder := CreateAdder() // adder is of type: func(float64, float64) float64

Essayez ces exemples sur le Go Playground .

Utiliser une fonction existante

Bien sûr, si vous avez déjà une fonction déclarée dans un package avec le même type de fonction, vous pouvez également l'utiliser.

Par exemple, math.Mod() a le même type de fonction:

func Mod(x, y float64) float64

Vous pouvez donc transmettre cette valeur à notre fonction Execute():

fmt.Println(Execute(12, 10, math.Mod)) // Prints 2

Imprime 2 car 12 mod 10 = 2. Notez que le nom d'une fonction existante agit comme une valeur de fonction.

Essayez-le sur le Go Playground .

Remarque:

Notez que les noms de paramètre ne font pas partie du type, le type de 2 fonctions ayant le même paramètre et le même type de résultat est identique quel que soit le nom du paramètre. Mais sachez que dans une liste de paramètres ou de résultats, les noms doivent tous être présents ou tous absents.

Ainsi, par exemple, vous pouvez également écrire:

func CreateAdder() func(P float64, Q float64) float64 {
    return func(x, y float64) float64 {
        return x + y
    }
}

Ou:

var adder func(x1, x2 float64) float64 = CreateAdder()
6
icza

Bien que vous puissiez utiliser une variable ou déclarer un type, vous n'avez pas besoin de . Vous pouvez le faire tout simplement:

package main

import "fmt"

var count int

func increment(i int) int {
    return i + 1
}

func decrement(i int) int {
    return i - 1
}

func execute(f func(int) int) int {
    return f(count)
}

func main() {
    count = 2
    count = execute(increment)
    fmt.Println(count)
    count = execute(decrement)
    fmt.Println(count)
}

//The output is:
3
2
1
Carl

Juste un casse-tête avec une définition de fonction récursive pour chaîner les middlewares dans une application Web.

Tout d'abord, la boîte à outils:

func MakeChain() (Chain, http.Handler) {
    nop := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {})
    var list []Middleware
    var final http.Handler = nop
    var f Chain
    f = func(m Middleware) Chain {
        if m != nil {
            list = append(list, m)
        } else {
            for i := len(list) - 1; i >= 0; i-- {
                mid := list[i]

                if mid == nil {
                    continue
                }

                if next := mid(final); next != nil {
                    final = next
                } else {
                    final = nop
                }
            }

            if final == nil {
                final = nop
            }
            return nil
        }
        return f
    }
    return f, final
}

type (
    Middleware func(http.Handler) http.Handler
    Chain      func(Middleware) Chain
)

Comme vous le voyez, le type Chain est une fonction qui retourne une autre fonction du même type Chain (Comment est la première classe!!).

Maintenant quelques tests pour le voir en action:

func TestDummy(t *testing.T) {
    c, final := MakeChain()
    c(mw1(`OK!`))(mw2(t, `OK!`))(nil)
    log.Println(final)

    w1 := httptest.NewRecorder()
    r1, err := http.NewRequest("GET", "/api/v1", nil)
    if err != nil {
        t.Fatal(err)
    }
    final.ServeHTTP(w1, r1)
}

func mw2(t *testing.T, expectedState string) func(next http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            val := r.Context().Value(contextKey("state"))
            sval := fmt.Sprintf("%v", val)
            assert.Equal(t, sval, expectedState)
        })
    }
}

func mw1(initialState string) func(next http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := context.WithValue(r.Context(), contextKey("state"), initialState)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

type contextKey string

Encore une fois, c’était là un casse-tête pour montrer que nous pouvons utiliser des fonctions de première classe dans Go de différentes manières. Personnellement, j'utilise chi de nos jours en tant que routeur et pour le traitement des middlewares.

0
Kaveh Shahbazian