フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

Type funcを使ってみて動作イメージを掴む

f:id:lirlia:20210321183729p:plain

net/httpのHandler周りのコードを読んでいたらよくわからなくなったので理解のため簡単なコードを書いておきます。

HTTPserver

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // HandlerFunc は与えられたパターン(/hello) をDefaultServeMuxに登録します
    http.HandleFunc("/hello", helloHandler)
    http.ListenAndServe(":8080", nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Hello")
}

これは/helloにアクセスが来るとHelloと出力するHTTPサーバです。よくサンプルなどでみる簡単なものですが、裏側がどうなっているのかをあまり気にしたことはありませんでした。

こちらの記事でまとめてくれていますが、

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Hello")
}

この定義した関数をHandleFuncに渡して登録してもらうと、起動するサーバにて/helloのリクエスト時にhelloHandler関数を実行してくれます。

    http.HandleFunc("/hello", helloHandler)

で、よく分からなかったのが関数名を渡しているところです。これをどうやって処理しているんだろう?ときになり、簡単なサンプルコードを書いてみました。

サンプル

package main

import "fmt"

type TestFunc func(int, int) int
type ValuePool struct {
    a int
    b int
}

func sum(a int, b int) int {
    return a + b
}

func diff(a int, b int) int {
    return a - b
}

func (t ValuePool) Run(f TestFunc) {
    fmt.Println(f(t.a, t.b))
}

func main() {
    a, b := 100, 100

    v := &ValuePool{a, b}
    v.Run(sum)
    v.Run(diff)

}

このコードは以下で動かせます。


これが何をやっているのかを簡単に紹介します。ここが実際の処理を読んでいる箇所なのですが、sumdiff関数をそれぞれよんでいます。関数自体は四則演算なので内容は割愛しますが関数名を変えるだけで結果を変えることができています。

   v.Run(sum)
    v.Run(diff)

これをどのように実現しているのかというとTestFuncが肝になります。この型はfunc(int, int) intを表しています(別名)。同様にsumやdiff関数も同じ引数と戻り値の構造になっていますね。

type TestFunc func(int, int) int

〜

func sum(a int, b int) int {
    return a + b
}

func diff(a int, b int) int {
    return a - b
}

つまりsumやdiff関数はTestFuncと同じである(型的に)と見なすことができるのです。そのためこんなこともできちゃいます。

var t TestFunc
t = sum
fmt.Println(t(a, b))

t = diff
fmt.Println(t(a, b))

さて、このように関数をキャストすることができるので次はこのメソッドを見ます。これはValuePool構造体のメソッドでTestFunc型を引数にとって、受け取った関数を実行します。

func (t ValuePool) Run(f TestFunc) {
    fmt.Println(f(t.a, t.b))
}

そのため先ほど紹介したsumやdiff関数をここで実行しているのです。このようにすることでHTTPのところであった以下の関数も、引数が(w http.ResponseWriter, r *http.Request)で戻り値の無いfunc(w http.ResponseWriter, r *http.Request)型にキャストされ処理が行われるということですね。

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Hello")
}

自分で書いてみるとなんとなくわかりますが、これを使ってコードをシンプルにしよう!ってなるとまだまだヒラメかなさそうですね。。