Подтвердить что ты не робот

Функции первого класса в Go

Я исхожу из JavaScript, который поддерживает функцию первого класса. Например, вы можете:

  • передать функцию в качестве параметра другой функции
  • возвращает функцию из функции.

Может кто-нибудь дать мне пример того, как я буду делать это в Go?

4b9b3361

Ответ 1

Go Language и функциональное программирование могут помочь. Из этого сообщения в блоге:

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());
}

Автор - владелец блога: Dethe Elza (не я)

Ответ 2

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)
}

выход:

222111
222111

Ответ 3

Связанный раздел из спецификации: Типы функций.

Все остальные ответы здесь сначала объявляют новый тип, который хорош (практика) и делает ваш код более легким для чтения, но знайте, что это не является обязательным требованием.

Вы можете работать со значениями функций без объявления для них нового типа, как показано в приведенном ниже примере.

Объявление переменной типа функции, которая имеет 2 параметра типа float64 и имеет одно возвращаемое значение типа float64, выглядит так:

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

Пусть напишет функцию, которая возвращает функцию сумматора. Эта функция сумматора должна принимать 2 параметра типа float64 и должна возвращать сумму этих двух чисел при вызове:

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

Пусть записывается функция, которая имеет 3 параметра, первые 2 имеют тип float64, а 3 - значение функции, функция, которая принимает 2 входных параметра типа float64 и выдает значение типа float64, И функция, которую мы пишем, вызовет значение функции, которое передается ему как параметр, и используя первые 2 float64 значения в качестве аргументов для значения функции и возвращает результат, возвращаемый переданным значением функции:

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

Посмотрите наши предыдущие примеры в действии:

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

Обратите внимание, что при создании adder можно использовать объявление коротких переменных:

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

Попробуйте эти примеры на Go Playground.

Использование существующей функции

Конечно, если у вас уже есть функция, объявленная в пакете с тем же типом функции, вы также можете использовать это.

Например, math.Mod() имеет тот же тип функции:

func Mod(x, y float64) float64

Итак, вы можете передать это значение нашей функции Execute():

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

Печать 2, потому что 12 mod 10 = 2. Обратите внимание, что имя существующей функции действует как значение функции.

Попробуйте на Go Playground.

Примечание:

Обратите внимание, что имена параметров не являются частью типа, тип 2 функций, имеющих одинаковый параметр и типы результатов, идентичен независимо от имен параметров. Но знайте, что в списке параметров или результатов имена должны либо присутствовать, либо вообще отсутствовать.

Так, например, вы также можете написать:

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

Или:

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

Ответ 4

Пока вы можете использовать var или declare a type, вам не нужно. Вы можете сделать это довольно просто:

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

Ответ 5

Просто аналитик с рекурсивным определением функции для привязки middlewares в веб-приложении.

Во-первых, панель инструментов:

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
)

Как вы видите, тип Chain - это функция, которая возвращает другую функцию того же типа Chain (Как первый класс это!).

Теперь несколько тестов, чтобы увидеть это в действии:

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

Опять же, это был просто мотив, чтобы показать, что мы можем использовать функции первого класса в Go по-разному. Лично я использую chi в настоящее время как маршрутизатор и для обработки посредников.