見習い魔法使いの日常

果たして魔法が使える日は訪れるのか!?

Go言語によるWebアプリケーション開発を読んでみたけど、Goの文法が主にわからない件

背景

長らく僕のPCのデータとして君臨していた書籍をついに開くときがきました。この書籍発売したのが2016年。当時Goにはまっていた友人に借りるも、結局開かぬまま返却。その後、Goの勉強をして改めて読みたいと思い図書館で借りるも中途半端に1章くらい読んでまた返却。ついに電子版を購入するも、長らくただの容量として僕のPCを占領していたのですが、ついに解禁されました。

いやー長かった。そして、よしやるぞ!と意気込んだの良かったけれどGoの文法がわからないという初歩的なところでつまずきました。かれこれ1年触ってなかったら、そりゃもう覚えてないよ。

と、いうことで、今回は文法的 わからなかったことを中心にまとめていきたいと思います。

1章 WebSocketを使ったチャットアプリケーション

sync.Once型について

HTMLなどをハードコードしないで、テンプレートにまとめちゃいましょう!ってときに、テンプレートのコンパイルは一回だけにしましょうという処理に使われたやつです。読んでるだけだとへぇーこうやるんだ程度で進んでたけど、まあよくわかってない。

ということで、syncパッケージについて調べてみよう。
godocを叩くと...。

$ go doc sync
package sync // import "sync"

Package sync provides basic synchronization primitives such as mutual
exclusion locks. Other than the Once and WaitGroup types, most are intended
for use by low-level library routines. Higher-level synchronization is
better done via channels and communication.

Values containing the types defined in this package should not be copied.

type Cond struct{ ... }
    func NewCond(l Locker) *Cond
type Locker interface{ ... }
type Map struct{ ... }
type Mutex struct{ ... }
type Once struct{ ... }
type Pool struct{ ... }
type RWMutex struct{ ... }
type WaitGroup struct{ ... }

同期処理のパッケージですね。Once型あったので、詳細をみます。

$ go doc sync.Once
type Once struct {
	// Has unexported fields.
}
    Once is an object that will perform exactly one action.


func (o *Once) Do(f func())

書籍の通り、Doメソッドを持ってるみたい。

$ go doc sync.Once.Do
func (o *Once) Do(f func())
    Do calls the function f if and only if Do is being called for the first time
    for this instance of Once. In other words, given

    var once Once

    if once.Do(f) is called multiple times, only the first call will invoke f,
    even if f has a different value in each invocation. A new instance of Once
    is required for each function to execute.

    Do is intended for initialization that must be run exactly once. Since f is
    niladic, it may be necessary to use a function literal to capture the
    arguments to a function to be invoked by Do:

    config.once.Do(func() { config.init(filename) })

    Because no call to Do returns until the one call to f returns, if f causes
    Do to be called, it will deadlock.

    If f panics, Do considers it to have returned; future calls of Do return
    without calling f.

sync.Once.DoはOnceインスタンスに対して、最初にDoが呼び出された場合だけ関数fを呼び出す。
なるほど!!加えて、書籍には遅延初期化うんぬんかいてるけど、呼び出しがない限り実関数fも呼び出されないわけね。

独自のハンドラについて

初めて読んだとき意味がわからなくて調べたから覚えてる。まず、ハンドラって何?独自ってどういうこと?って苦しみました。ここでいうハンドラは普通にhttp.Handleなんだよね。で、独自っていうのが http.Handleはstring型とHandler型を受け取るんだけど Handlerってのはインターフェースだからこの要件を満たして独自のものを作ろうってこと。最初は意味がわからなかった。

$ go doc http.Handle
package http // import "net/http"

func Handle(pattern string, handler Handler)
    Handle registers the handler for the given pattern in the DefaultServeMux.
    The documentation for ServeMux explains how patterns are matched.

$ go doc http.Handler
package http // import "net/http"

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
    A Handler responds to an HTTP request.

    ServeHTTP should write reply headers and data to the ResponseWriter and then
    return. Returning signals that the request is finished; it is not valid to
    use the ResponseWriter or read from the Request.Body after or concurrently
    with the completion of the ServeHTTP call.

    Depending on the HTTP client software, HTTP protocol version, and any
    intermediaries between the client and the Go server, it may not be possible
    to read from the Request.Body after writing to the ResponseWriter. Cautious
    handlers should read the Request.Body first, and then reply.

    Except for reading the body, handlers should not modify the provided
    Request.

    If ServeHTTP panics, the server (the caller of ServeHTTP) assumes that the
    effect of the panic was isolated to the active request. It recovers the
    panic, logs a stack trace to the server error log, and either closes the
    network connection or sends an HTTP/2 RST_STREAM, depending on the HTTP
    protocol. To abort a handler so the client sees an interrupted response but
    the server doesn't log an error, panic with the value ErrAbortHandler.


func FileServer(root FileSystem) Handler
func NotFoundHandler() Handler
func RedirectHandler(url string, code int) Handler
func StripPrefix(prefix string, h Handler) Handler
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler

書籍では、独自のハンドラとして上述のsync.Onceを用いたサブディレクトリのHTMLを返すハンドラを作りました。この内容しょっぱなですが、結構難しいのではなかろうか。

バッファ付きのチャネルです。

はは、チャネルってなんだっけ?まとめると長くなりそうなので、別の記事で書きます。

ヘルパー関数

ヘルパー関数っ なんぞやあぁ!と最初は思いましたがまあ他言語でいう コンストラクタ みたいなやつでしょう。書籍にも記述がありますが、Goはクラスがなく構造体で代用しているのでヘルパー関数を使ってクラスっぽくしてるって僕は解釈してます。

まとめ

ふぅ。ほんとは1章丸々書きたかったけど、長くなりすぎるのもいやなのでこの辺で切りました。書籍の続きはログの出力からなので、1章のアプリケーションのコードに関する感じた疑問は全部かけたかな。個人的にはHTTPハンドラがよくわからなくて苦戦し、わかってしまえばもうちょろいって印象です。この辺りはWe開発経験のある人だとつまらない内容なのでしょう。僕はその経験がなく概念がわかっていなかったので苦労しました。ただ、調べる過程で何度も文章を読み直したりしたので、さらっと読んだ時よりすごく理解が深まりました。

このプログを書いてる段階で3章まで読んでいるのですが、結構小さな疑問とかはほったらかしにしてるので、書籍を読み進めつつ、次は2章をじっくりと振り返っていきたいと思います。