web-dev-qa-db-ja.com

Goのファーストクラス関数

私は、ファーストクラスの機能をサポートするJavaScriptから来ました。たとえば、次のことができます。

  • 関数をパラメーターとして別の関数に渡す
  • 関数から関数を返します。

誰かがGoでこれを行う方法の例を教えてもらえますか?

51
Drew LeSueur

Go言語と関数型プログラミング が役立つ場合があります。このブログ投稿から:

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(私ではありません)

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

出力:

222111
222111
27
user2532519

仕様の関連セクション: Function types

ここでの他のすべての答えは、最初に新しいタイプを宣言します。これは適切で(練習)、コードを読みやすくしますが、これは要件ではないことを知っています。

次の例に示すように、新しい値を宣言せずに関数値を操作できます。

タイプ_float64_の2つのパラメーターと、タイプ_float64_の1つの戻り値を持つ関数タイプの変数の宣言は次のようになります。

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

加算関数を返す関数を書きましょう。この加算関数は、_float64_型の2つのパラメーターを受け取り、呼び出されたときにこれらの2つの数値の合計を返す必要があります。

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

最初の2つが_float64_型であり、3番目が関数値である3つのパラメーターを持つ関数を作成しましょう。関数は、タイプ_float64_の2つの入力パラメーターを取り、_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を作成するときに Short変数宣言 を使用できることに注意してください。

_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()
_
6
icza

Varを使用したり、型を宣言したりできますが、必要はありません。これは非常に簡単に行えます:

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

Webアプリでミドルウェアを連鎖するための再帰的な関数定義を備えた単なる頭の体操。

まず、ツールボックス:

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 をルーターとして、またミドルウェアの処理に使用しています。

0