├── .gitignore ├── .gitmodules ├── README.md ├── config.toml ├── content ├── about.md └── post │ ├── c-go-cgo.md │ ├── constants.md │ ├── context.md │ ├── errors-are-values.md │ ├── generate.md │ ├── gif-decoder-exercise-in-go-interfaces.md │ ├── gif-decoder-exercise-in-go-interfaces │ ├── gif-decoder-exercise-in-go-interfaces_image00.jpg │ ├── gif-decoder-exercise-in-go-interfaces_image01.gif │ ├── gif-decoder-exercise-in-go-interfaces_image02.jpg │ └── gif-decoder-exercise-in-go-interfaces_image03.gif │ ├── go-concurrency-patterns-timing-out-and.md │ ├── go-image-package.md │ ├── go-image-package │ ├── go-image-package_image-package-01.png │ ├── go-image-package_image-package-02.png │ ├── go-image-package_image-package-03.png │ ├── go-image-package_image-package-04.png │ └── go-image-package_image-package-05.png │ ├── matchlang.md │ ├── normalization.md │ ├── package-names.md │ ├── pipelines.md │ ├── race-detector.md │ ├── slices.md │ ├── strings.md │ ├── toward-go2.md │ ├── toward-go2 │ ├── error.png │ ├── go1-preview.png │ ├── go1-release.png │ ├── mail.png │ ├── process.png │ ├── process2.png │ ├── process34.png │ ├── process5.png │ └── tweet.png │ └── versioning-proposal.md ├── static ├── apple-touch-icon.png ├── favicon.ico └── google790b4cd70b0c49d5.html ├── themes └── .gitmodules └── translation_mapping.txt /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | .DS_Store 3 | Thumbs.db 4 | *~ 5 | #* 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes/simpleblog"] 2 | path = themes/simpleblog 3 | url = https://github.com/ymotongpoo/simpleblog.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Go Blog 日本語訳プロジェクト 2 | 3 | [The Go Blog](http://blog.golang.org/) の日本語訳プロジェクトです。 4 | 5 | 本家と同様に訳文本文は Creative Commons Attribution 3.0 License で公開し、 6 | ソースコードは BSD License とします。 7 | 8 | ドキュメント本体はMarkdownを[Hugo](http://gohugo.io/)でビルドしていますので、修正は `master` ブランチへ 9 | Pull Requestをお願いします。 10 | https://github.com/ymotongpoo/goblog-ja 11 | 12 | またビルドとデプロイは[wercker](http://wercker.com/)を利用しています。 13 | 14 | 翻訳済み記事等に関しては @cia-rana さんがまとめてくれましたので、こちらを確認してください。 15 | https://gist.github.com/cia-rana/82797c12dcdf2e18a802d80b4ba22e89 -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | BaseURL = "https://www.ymotongpoo.com/works/goblog-ja/" 2 | LanguageCode = "ja-jp" 3 | Title = "The Go Blog 日本語訳" 4 | Copyright = "Except as noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a BSD license. The Go team (original) and Yoshi Yamaguchi (translation)" 5 | Paginate = 200 6 | PaginatePath = "/works/goblog-ja/page" 7 | 8 | [Params] 9 | description = "The Go Blogの日本語訳を公開しています。修正は https://github.com/ymotongpoo/goblog-ja/ まで。" 10 | twitter = "ymotongpoo" 11 | github = "ymotongpoo" 12 | ga = "UA-6420772-8" 13 | -------------------------------------------------------------------------------- /content/about.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-02-04T11:20:36+09:00" 3 | draft = false 4 | title = "about" 5 | +++ 6 | 7 | # The Go Blog 日本語訳 8 | 9 | [The Go Blog](https://blog.golang.org/) の日本語訳プロジェクトです。 10 | 11 | 本家と同様に訳文本文は Creative Commons Attribution 3.0 License で公開し、 12 | ソースコードは BSD License とします。 13 | 14 | ドキュメント本体はMarkdownを [Hugo](http://gohugo.io/) でビルドしていますので、修正は `master` ブランチへ 15 | Pull Requestをお願いします。 16 | https://github.com/ymotongpoo/goblog-ja 17 | 18 | ビルドは [wercker](https://app.wercker.com/#applications/54eea70ed9b146366338d508/) で継続的に実施しています。 19 | 20 | ## Disclaimer 21 | のんびり翻訳してるので気長にお待ちください。誤訳、typoなどは気軽にissueをあげてください。 22 | 23 | ## Contributers 24 | 25 | 翻訳の投稿やtypo以上のまとまった修正を行った人を載せています 26 | 27 | * ymotongpoo 28 | * cia-rana 29 | * hiroshi-manabe -------------------------------------------------------------------------------- /content/post/c-go-cgo.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2011-03-17T16:49:45+09:00" 3 | title = "C? Go? Cgo!" 4 | draft = false 5 | tags = ["cgo"] 6 | +++ 7 | 8 | # C? Go? Cgo! 9 | [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) By Andrew Gerrand 10 | 11 | ## はじめに 12 | 13 | cgoを使うことでGoパッケージを通してCコードを呼び出すことができます。ある特徴を持って書かれたGoのソースファイルが与えられると、cgoは単一のGoパッケージにまとめることが可能なGoとCのファイルを出力します。 14 | 15 | 例として、ここにCの関数 `random` と `srandom` をラップした2つの関数 - `Random` と `Seed` - を提供するGoパッケージがあります。 16 | 17 | ``` 18 | package rand 19 | 20 | /* 21 | #include 22 | */ 23 | import "C" 24 | 25 | func Random() int { 26 | return int(C.random()) 27 | } 28 | 29 | func Seed(i int) { 30 | C.srandom(C.uint(i)) 31 | } 32 | ``` 33 | 34 | ここで何が起きているか見ていきましょう、importの文から見ていきます。 35 | 36 | `rand` パッケージは `"C"` をインポートしていますが、Goのライブラリにはそのようなパッケージがないことに気づくでしょう。 `C` は"疑似パッケージ"で、Cの名前空間への参照としてcgoにより解釈される特別な名前です。 37 | 38 | `rand` パッケージは `C` パッケージに対する4つの参照を含んでいます: `C.random` と `C.srandom` の呼び出しと、 変換 `C.uint(i)` 、そして `import` 文です。 39 | 40 | `Random` 関数はC標準ライブラリの `random` 関数を呼び出し、その結果を返しています。C内で、`random` はcgoにおける `C.long` 型である `long` 型の値を返しています。それはこのパッケージ外部のGoコードで使われる前に、通常のGoの型変換によってGoの型に変換しなければなりません: 41 | 42 | ``` 43 | func Random() int { 44 | return int(C.random()) 45 | } 46 | ``` 47 | 48 | 型変換を明示的に示すための一時変数を用いた同様の関数は以下のとおりです: 49 | 50 | ``` 51 | func Random() int { 52 | var r C.long = C.random() 53 | return int(r) 54 | } 55 | ``` 56 | 57 | `Seed` 関数はある意味で逆のことをします。その関数は正規のGoの型 `int` を引数にとり、それをCの型 `unsigned int` に変換し、Cの関数 `srandom` に渡します。 58 | 59 | ``` 60 | func Seed(i int) { 61 | C.srandom(C.uint(i)) 62 | } 63 | ``` 64 | 65 | cgoは `unsigned int` を `C.uint` として認識していることに注意してください。数値型名の完全なリストは [cgo documentation](http://golang.org/cmd/cgo) をご覧ください。 66 | 67 | 私たちがこの例においてまだ考察していない詳細部分は、 `import` 文の上にあるコメントです。 68 | 69 | ``` 70 | /* 71 | #include 72 | */ 73 | import "C" 74 | ``` 75 | 76 | cgoはこのコメントを認識してます。 `#cgo` とそれに続く単一のスペースで始まる行は全て取り除かれます。それらはcgoのための指示子となります。パッケージのCの部分をコンパイルする際に残りの行はヘッダーとして用いられます。この場合それらの行はちょうど単一の `#include` 文となりますが、それらはほぼ全てのCコードとなりえます。パッケージのCの部分をビルドする際に、コンパイラやリンカのためのフラグを提供するために `#cgo` が使われます。 77 | 78 | 制限があります: `//export` 指示子を使用する場合、コメント中のCコードは宣言(`extern int f();`)のみ含むことができ、定義(`int f() { return 1; }`)を含むことができません。Cコードにアクセス可能なGoコードを作るために `//export` 指示子を使うことができます。 79 | 80 | `#cgo` と `//export` 指示子に関するドキュメントは[cgo documentation](http://golang.org/cmd/cgo/)にあります。 81 | 82 | ## Strings と things 83 | 84 | Goと違い、Cには明示的なstring型がありません。Cにおける文字列は、NULL終端された文字配列によって表されます。 85 | 86 | GoとCの間の文字列変換は、`C.String` と `C.GoString` 、そして `C.GoStringN` 関数を用いて行うことができます。それらの変換は文字列データのコピーを生成します。 87 | 88 | 以下の例はCの `stdio` ライブラリの `fputs` 関数を用いて標準出力に文字列を書き込む `Print` 関数の実装です: 89 | 90 | ``` 91 | package print 92 | 93 | // #include 94 | // #include 95 | import "C" 96 | import "unsafe" 97 | 98 | func Print(s strics := C.CString(s) 99 | cs := C.CString(s) 100 | C.fputs(cs, (*C.FILE)(C.stdout)) 101 | C.free(unsafe.Pointer(cs} 102 | } 103 | ``` 104 | 105 | Cコードによって生成されたメモリアロケーションは、Goのメモリマネージャーから認識されません。`C.CString` (または任意のCのメモリアロケーション)を用いてCの文字列を生成するときは、`C.free` を呼び出してメモリを解放することを忘れてはいけません。 106 | 107 | `C.CString` の呼び出しは文字配列の最初を示すポインタを返すので、関数から抜け出す前にそのポインタを [unsafe.Pointer](http://golang.org/pkg/unsafe/#Pointer) に変換し、`C.free` を用いてメモリアロケーションを解放します。cgoプログラムにおける共通のイディオムは、この書き換えた `Print` のように、アロケートしたあとすぐに [defer](http://golang.org/doc/articles/defer_panic_recover.html) によって解放することです(特に、あとに続くコードが一つの関数呼び出しよりも複雑な場合): 108 | 109 | ``` 110 | func Print(s string) { 111 | cs := C.CString(s) 112 | defer C.free(unsafe.Pointer(cs)) 113 | C.fputs(cs, (*C.FILE)(C.stdout)) 114 | } 115 | ``` 116 | 117 | ## cgoのパッケージをビルドする 118 | 119 | cgoのパッケージをビルドするために、いつもどおり [go build](http://golang.org/cmd/go/#Compile_packages_and_dependencies) または [go install](http://golang.org/cmd/go/#Compile_and_install_packages_and_dependencies) を使いましょう。go toolは特別な `"C"` インポートを認識し、それらのファイルのために自動的にcgoを使います。 120 | 121 | ## さらなるcgoのリソース 122 | 123 | [cgo command](http://golang.org/cmd/cgo/) のドキュメントにCの擬似パッケージやビルドプロセスについてより詳しく載っています。Goディレクトリツリー内の [cgo examples](http://golang.org/misc/cgo/) により高度な概念が示されています。 124 | 125 | 最後に、これが内部でどのように動いているか興味がございましたら、ランタイムパッケージの [cgocall.go](https://golang.org/src/runtime/cgocall.go) の先頭のコメントをご覧ください。 126 | 127 | *By Andrew Gerrand* 128 | 129 | ## あわせて読みたい 130 | * [HTTP/2 Server Push](https://blog.golang.org/h2push) 131 | * [Introducing HTTP Tracing](https://blog.golang.org/http-tracing) 132 | * [Generating code](https://blog.golang.org/generate) 133 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 134 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 135 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 136 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 137 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 138 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 139 | * [The Go image package](https://blog.golang.org/go-image-package) 140 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 141 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 142 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 143 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 144 | * [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) 145 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 146 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 147 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 148 | * [JSON and Go](https://blog.golang.org/json-and-go) 149 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 150 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 151 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 152 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 153 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) 154 | -------------------------------------------------------------------------------- /content/post/constants.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2014-08-25T14:49:44+09:00" 3 | draft = false 4 | title = "定数 (constants)" 5 | +++ 6 | 7 | # 定数 8 | [Constants](https://blog.golang.org/constants) by Rob Pike 9 | 10 | ## はじめに 11 | Goは、数値型を混合して操作することを許さない静的型付け言語です。 `flaot64` を `int` に 12 | 足せませんし、さらに言えば `int32` を `int` を足すこともできません。しかし、 `1e6*time.Second` や 13 | `math.Exp(1)` あるいは `1<<('\t'+2.0)` と書くことは許されています。Goでは、定数は変数と違って、 14 | 通常の数字と同様に振る舞います。この記事では、なぜそうなっているのか、そしてそれが何を意味するのかを 15 | 説明します。 16 | 17 | ## 背景: C言語 18 | 19 | Go言語を考え始めた初期の頃、C言語やその系譜の言語が数値型をまぜこぜに使うことを許していることで起きている多くの問題について話しました。 20 | 多くの奇妙なバグ、クラッシュ、移植可能性の問題は、サイズと「符号有無」が異なる整数を混ぜている式によって起きています。 21 | 経験の多いCプログラマにとっても、次の計算結果はわかるかもしれませんが、アプリオリには明らかではありません。 22 | 23 | ``` 24 | unsigned int u = 1e9; 25 | long signed int i = -1; 26 | ... i + u ... 27 | ``` 28 | 29 | 結果はどれくらいの大きさになるのでしょう。その値はなんでしょうか。符号は付いているのかいないのか。 30 | 31 | 見難いバグがここに潜んでいます。 32 | 33 | C言語には「the usual arithmetic conversions」と呼ばれる規則があり、年月を経てその規則が変わってきたことがこの規則の儚さを示しています。 34 | (長年にわたって多くのバグを生んできました) 35 | 36 | Goを設計するときに、数値型を混ぜてはいけないということをしっかりと決めることでこの地雷原を避ける事にしました。 37 | `i` と `u` を足したい場合には、結果をどうしたいかを明示しなければいけません。次のように変数があったとして、 38 | 39 | ``` 40 | var u uint 41 | var i int 42 | ``` 43 | 44 | `uint(i)+u` または `i+int(u)` のどちらかに書けます。これはどちらの書き方でも足し算の意味と型がはっきりと表現されていますが、 45 | C言語の場合とは異なって `i+u` と書くことはできません。 `int` が32ビット型の場合でさえ、 `int` と `int32` を混ぜることすらできません。 46 | 47 | この厳格さによって、よくありがちなバグや他の問題が取り除かれます。これはGoにおいて非常に重要な性質です。 48 | しかしこの厳格さにはコストが掛かります。意味を自明にするために、ときにこの厳格さがプログラマに不格好な 49 | 型変換のコードを追加することを求めます。 50 | 51 | では定数ではどうでしょうか。これまでに述べてきたことからすると、どうすれば規則を守ったまま `i = 0` または `u = 0` と書けるでしょうか。 52 | `0` の型は何でしょうか。単純な状況、たとえば `i = int(0)` ような場面で、型変換を書かなければいけないのは筋がよくありません。 53 | 54 | 私たちはすぐに数値の定数を扱う場合には、他のC言語の類似言語での動作とは異なる振る舞いをさせれば良いと気が付きました。 55 | 多くの考察と実験の後に、ほぼ常に正しいと信じられ、つねにプログラマを定数の型変換から解放し、それでいてコンパイラに 56 | 警告されることなく `math.Sqrt(2)` のように書ける設計を思いつきました。 57 | 58 | 手短に言えば、Goの定数は、とにかくほとんど場合、うまく動きます。ではそれがどのようになっているか見てみましょう。 59 | 60 | ## 用語解説 61 | 62 | まず、手短に定義します。Goでは `const` は `2` 、 `3.14159` 、 `"scrumptious"` といったスカラー値に対する名前を決める 63 | キーワードです。このような値は、名前が付いているかにかぎらず、Goでは _定数_ と呼ばれます。定数は定数からなる式からも 64 | 生成することが出来ます。たとえば `2+3` 、 `2+3i` 、 `math.Pi/2` あるいは `("go"+"pher")` といったものがそれです。 65 | 66 | 定数がない言語もありますし、 `const` という単語に対してより汎用的な定義がある言語やより汎用的な用途がある言語もあります。 67 | たとえばC言語はC++では、 `const` はより複雑な値のより複雑な性質を分類する型修飾子です。 68 | 69 | しかしGoでは、定数はとても単純で、変化しない値のことを指します。以降はGoでの場合のみを話します。 70 | 71 | ## 文字列定数 72 | 73 | 数値の定数には多くの種類があります。たとえば整数、浮動小数点数、ルーン(訳注:Unicodeのコードポイントを表す整数値)、符号あり、符号なし、虚数、複素数などがあります。 74 | まずはより単純な形式の定数である文字列定数から話を始めましょう。文字列定数は理解がしやすく、Goにおける定数の型の問題を調べるときにより小さな部分だけ考えればよくなります。 75 | 76 | 文字列定数はある文字列をダブルクォーテーションで囲ったものです。(Goにはraw文字列リテラルもあり、これはバッククォートで囲みます。ここの議論においては 77 | 両者ともに同様の性質があるため省きます。)ここに文字列定数があります。 78 | 79 | ``` 80 | "Hello, 世界" 81 | ``` 82 | 83 | (文字列の表現と解釈に関してより詳しい内容は[こちらのブログポスト](https://blog.golang.org/strings)を参照してください。) 84 | 85 | この文字列定数はどんな型でしょうか。見たままで答えれば `string` ですが、それは _間違い_ です。 86 | 87 | これは _型付けされていない文字列定数_ で、不変の文字列の値を持っているがまだ決まった型がないもの、といえるでしょう。 88 | そうです、これは文字列なのですが、Goでの `string` 型の値ではないのです。型付けされていない文字列定数は、名前を与えた時にも 89 | 同様のままでいます。 90 | 91 | ``` 92 | const hello = "Hello, 世界" 93 | ``` 94 | 95 | この宣言のあと、 `hello` もまた型付けされていない文字列定数となります。型付けされていない定数はただの値で、 96 | 異なる型の値と結合する厳格な規則に従わなければならなくなる決められた型をまだ持っていない値となります。 97 | 98 | この _型付けされていない_ 定数という考え方によって、Goで定数を自由度高く使うことができるのです。 99 | 100 | それでは _型付けされた_ 文字列定数とはなんなのでしょう。それは次のように型を与えられた定数のことです。 101 | 102 | ``` 103 | const typedHello string = "Hello, 世界" 104 | ``` 105 | 106 | `typedHello` の宣言は、等号の前で `string` 型が明示されていることに注目してください。これは `typedHello` が 107 | Goの `string` 型であることを意味していて、Goでの異なる型の変数に代入できないことを意味しています。 108 | これは次のコードは動きますが、 109 | 110 | ``` 111 | var s string 112 | s = typedHello 113 | fmt.Println(s) 114 | ``` 115 | 116 | このコードは動かないということです。 117 | 118 | ``` 119 | type MyString string 120 | var m MyString 121 | m = typedHello // 型エラー 122 | fmt.Println(m) 123 | ``` 124 | 125 | 変数 `m` は `MyString` 型で、異なる型には値は代入できません。 `MyString` 型の値のみ代入できます。 126 | たとえば次のコードを見てください。 127 | 128 | ``` 129 | const myStringHello MyString = "Hello, 世界" 130 | m = myStringHello // OK 131 | fmt.Println(m) 132 | ``` 133 | 134 | あるいは型変換をさせてもできるでしょう。 135 | 136 | ``` 137 | m = MyString(typedHello) 138 | fmt.Println(m) 139 | ``` 140 | 141 | _型付けされていない_ 文字列定数の話に戻ると、型がないことによって、型付けされた変数に代入しても 142 | 型エラーが発生させないというのは役に立つ性質です。つまり、このようなコードや 143 | 144 | ``` 145 | m = "Hello, 世界" 146 | ``` 147 | 148 | あるいはこのように書けるのです。 149 | 150 | ``` 151 | m = hello 152 | ``` 153 | 154 | なぜなら、型付けされた定数の `typedHello` や `myStringHello` とは違って、型付けされていない定数の `"Hello, 世界"` や 155 | `hello` には _型がないからです_ 。 `string` と互換性のあるどのような型の変数にもエラーなしで代入できます。 156 | 157 | これらの型付けされていない文字列定数はもちろん文字列であるため、文字列を使って良い所でのみどこでも使えますが、 158 | `string` という _型_ を持っていないのです。 159 | 160 | ## デフォルト型 161 | 162 | Goのプログラマであるあなたは、まちがいなく次のような宣言を数多く見てきたことでしょう。 163 | 164 | ``` 165 | str := "Hello, 世界" 166 | ``` 167 | 168 | ここまでの説明を読んで、「もし定数が型付けされていないのであれば、どうやって `str` はこの変数宣言で型を得るのだろう」と疑問に思ったことでしょう。 169 | その答えはというと、型付けされていない定数にはデフォルトの型、つまり型が与えられてなかった場合に必要なときに値に対して与える暗黙的な型を持っているということです。 170 | 型付けされていない文字列定数であれば、デフォルト型は明らかに `string` なので、 171 | 172 | ``` 173 | str := "Hello, 世界" 174 | ``` 175 | 176 | や 177 | 178 | ``` 179 | var str = "Hello, 世界" 180 | ``` 181 | 182 | は次の宣言とまったく同じ意味になります。 183 | 184 | ``` 185 | var str string = "Hello, 世界" 186 | ``` 187 | 188 | 型付けされていない定数について考える1つの方法としては、彼らは値の理想的な空間にいて、そこではGoの完全な型システムほど 189 | 制限がないと思えばいいでしょう。しかしその定数にたいして何かしたいと思ったら、それを変数に代入する必要があり、 190 | 変数に代入するときには(定数自身ではなく) _変数_ には型が必要で、その定数は変数に対して自分がどのような型であるべきか 191 | 伝えることができます。この例では `str` は `string` 型の値になります。なぜなら型付けされていない文字列定数は 192 | そのデフォルト型である `string` であると宣言するからです。 193 | 194 | このように変数宣言をした場合、その変数は型と初期値を含めて宣言されています。しかしながら、ときに定数を、その値の代入先が 195 | 不明瞭なときに使うことがあります。たとえば次の文を考えてみましょう。 196 | 197 | ``` 198 | fmt.Printf("%s", "Hello, 世界") 199 | ``` 200 | 201 | `fmt.Printf` のシグネチャは次のとおりです。 202 | 203 | ``` 204 | func Printf(format string, a ...interface{}) (n int, err error) 205 | ``` 206 | 207 | これは、この関数の(フォーマット文字列のあとの)引数がインターフェース値であることを意味しています。 `fmt.Printf` が 208 | 型付けされていない定数を使って呼び出された場合、インターフェース値が作られて引数に渡されます。そしてその引数に保存される 209 | 具象型はその定数のデフォルト型になります。このプロセスは先に型付けされていない文字列定数で初期値を宣言した状況に似ています。 210 | 211 | この結果は次の例で確認できます。ここでは `fmt.Printf` で値を表示するのにフォーマット文字 `%v` を使い、値の型を表示するのに `%T` を使っています。 212 | 213 | ``` 214 | fmt.Printf("%T: %v\n", "Hello, 世界", "Hello, 世界") 215 | fmt.Printf("%T: %v\n", hello, hello) 216 | ``` 217 | 218 | (訳注: 出力は次のとおりです。) 219 | 220 | ``` 221 | string: Hello, 世界 222 | string: Hello, 世界 223 | ``` 224 | 225 | もし定数に型があれば、それがインターフェースに伝わり、次の例のようになります。 226 | 227 | ``` 228 | fmt.Printf("%T: %v\n", myStringHello, myStringHello) 229 | ``` 230 | 231 | (訳注: 出力は次のとおりです。) 232 | 233 | ``` 234 | main.MyString: Hello, 世界 235 | ``` 236 | 237 | (インターフェース値がどのように扱われるかをより詳しく知りたい場合は、次の 238 | [ブログポスト](https://blog.golang.org/laws-of-reflection) の最初の節を参照してください。) 239 | 240 | まとめとして、型付けされた定数はGoの型付けされた値のルールに従います。一方で、型付けされていない定数は 241 | 同じやり方でGoの型を決めるのではなく、より自由に型を混ぜたり一致させたりします。しかしながら、 242 | あからさまなデフォルト型が、他の型情報が得られない場合、そしてその場合に限り適用されます。 243 | 244 | ## 構文から決定されるデフォルト型 245 | 型付けされていない定数のデフォルト型は構文により決定されます。文字列定数であれば、唯一取りうる 246 | 暗黙型は `string` です。 [数値定数](http://golang.org/ref/spec#Numeric_types) の場合は 247 | 暗黙型はより多くの種類を取り得ます。整数定数は `int` がデフォルト型ですし、浮動小数点数定数は 248 | `float64` 、 ルーン定数は `rune` ( `int32` のエイリアス)、虚数定数は `complex128` がデフォルト型です。 249 | ここで、この権威あるprint式でデフォルト型を実際に動かして確認しましょう。 250 | 251 | ``` 252 | fmt.Printf("%T %v\n", 0, 0) 253 | fmt.Printf("%T %v\n", 0.0, 0.0) 254 | fmt.Printf("%T %v\n", 'x', 'x') 255 | fmt.Printf("%T %v\n", 0i, 0i) 256 | ``` 257 | 258 | (演習: `'x'` の場合の結果を説明しなさい。) 259 | 260 | ## 真偽値 261 | 型付けされていない文字列定数について話したことはすべて型付けされていない真偽値定数についてもあてはまります。 262 | `true` と `false` はどのような真偽値の変数にも代入できる型付けされていない真偽値定数で、一度型が与えられると 263 | 混ぜることは出来ません。 264 | 265 | ``` 266 | type MyBool bool 267 | const True = true 268 | const TypedTrue bool = true 269 | var mb MyBool 270 | mb = true // OK 271 | mb = True // OK 272 | mb = TypedTrue // ダメ 273 | fmt.Println(mb) 274 | ``` 275 | 276 | 上の例を実行して何が起きるか見たあと、 "ダメ" とコメントされている行をコメントアウトして 277 | 再び実行してみましょう。ここにあるパターンはすべて文字列定数の場合の規則に従っています。 278 | 279 | ## 浮動小数点数 280 | 浮動小数点数定数は多くの場合真偽値定数と同様です。これまで何度か出している例は浮動小数点数の場合にも 281 | 適用できます。 282 | 283 | ``` 284 | type MyFloat64 float64 285 | const Zero = 0.0 286 | const TypedZero float64 = 0.0 287 | var mf MyFloat64 288 | mf = 0.0 // OK 289 | mf = Zero // OK 290 | mf = TypedZero // ダメ 291 | fmt.Println(mf) 292 | ``` 293 | 294 | ひとつこの例でうまくいかないことがあるとすればGoには _2つの_ 浮動小数点数があることです。 295 | `float32` と `float64` です。型付けされていない浮動小数点数定数は `float32` に問題なく代入できますが、 296 | そのデフォルト型は `float64` です。 297 | 298 | ``` 299 | var f32 float32 300 | f32 = 0.0 301 | f32 = Zero // OK: Zeroは型付けされていない 302 | f32 = TypedZero // ダメ: TypedZeroはfloat64でありfloat32ではない 303 | fmt.Println(f32) 304 | ``` 305 | 306 | 浮動小数点数値はオーバーフロー、つまり値の範囲という概念を紹介するのに良い題材です。 307 | 308 | 数値定数は任意精度の数値空間に存在しています。つまり、それらは単なる有限小数なのです。 309 | しかし、それらがある変数に代入された場合には、その値が代入先に合うようにならなければいけません。 310 | 定数で非常に大きな値を宣言することが出来ます。 311 | 312 | ``` 313 | const Huge = 1e1000 314 | ``` 315 | 316 | これは単なる数字で、それ以外の何者でもありません。しかし、それを代入できませんし、表示することさえできません。 317 | この式はコンパイルすら通りません。 318 | 319 | ``` 320 | fmt.Println(Huge) 321 | ``` 322 | 323 | エラーは「constant 1.00000e+1000 overflows float64 (定数 1.00000e+1000 は float64 の範囲からオーバーフローしています)」 324 | というもので、これは正しい内容です。しかし、 `Huge` が使える時もあります。それは、他の定数との式の中で使って、 325 | その式の結果が `float64` の範囲内に収まる場合です。次の式を見てください。 326 | 327 | ``` 328 | fmt.Println(Huge / 1e999) 329 | ``` 330 | 331 | これは `10` を出力します。予想通りです。 332 | 333 | 同様に、浮動小数点数定数には非常に高い精度を持たせることが出来るので、それが関わる数値計算はより正確になります。 334 | [math](https://golang.org/pkg/math) パッケージで定義されている定数は `float64` で扱える範囲よりも 335 | 多くの桁数を持っています。これは `math.Pi` の定義です。 336 | 337 | ``` 338 | Pi = 3.14159265358979323846264338327950288419716939937510582097494459 339 | ``` 340 | 341 | この値が変数に代入されたときに、精度がある程度失われます。代入することで元の高精度の値に最も近い 342 | `float64` (あるいは `float32` )の値ができます。次のスニペットでは 343 | 344 | ``` 345 | pi := math.Pi 346 | fmt.Println(pi) 347 | ``` 348 | 349 | `3.141592653589793` が出力されます。 350 | 351 | 桁数を多く持てることで、 `Pi/2` のような計算やより複雑な評価をする際に結果が代入されるまで、 352 | より高い精度を維持したままでいられます。これによって、定数が関わる計算を精度を失うことなく 353 | 書きやすくなっています。また、浮動小数点の無限、アンダーフロー、 `NaN` といったコーナーケースが 354 | 定数の式では発生しません。(ゼロ除算はコンパイル時エラーとなり、またすべてが数字の場合には 355 | 「not a number」というようなエラーは発生しえません。) 356 | 357 | ## 複素数 358 | 複素数定数は多くの点で浮動小数点数定数と同様に振る舞います。いつもの例を複素数でやってみます。 359 | 360 | ``` 361 | type MyComplex128 complex128 362 | const I = (0.0 + 1.0i) 363 | const TypedI complex128 = (0.0 + 1.0i) 364 | var mc MyComplex128 365 | mc = (0.0 + 1.0i) // OK 366 | mc = I // OK 367 | mc = TypedI // ダメ 368 | fmt.Println(mc) 369 | ``` 370 | 371 | 複素数のデフォルト型は `complex128` で、2つの `float64` の値で構成された精度が大きい型です。 372 | 373 | 曖昧さ回避のためにいうと、例では `(0.0+1.0i)` と完全表記をしていますが、この値は `0.0+1.0i` と短く出来ますし、 374 | さらに言えば `1.0i` あるいは `1i` と書けます。 375 | 376 | ちょっと遊んでみましょう。Goでは、数値定数はただの数字だと知っています。もし数字が虚数部がない複素数だった場合に、 377 | つまりそれは実数なのでしょうか。一つ用意してみます。 378 | 379 | ``` 380 | const Two = 2.0 + 0i 381 | ``` 382 | 383 | これは型付けされていない複素数です。たとえ虚数部がなくても、式の _構文_ がデフォルト型 `complex128` を持つように 384 | 定義します。それゆえ、変数宣言にこの定数を使うと、デフォルト型は `complet128` になります。このスニペットは 385 | `complex128: (2+0i)` と出力します。 386 | 387 | ``` 388 | s := Two 389 | fmt.Printf("%T: %v\n", s, s) 390 | ``` 391 | 392 | しかし数値としては、定数 `Two` は情報を失うことなくスカラーの浮動小数点数、つまり `float64` あるいは `float32` に 393 | 保存することが出来ます。したがって、初期化あるいは代入で何も問題なく `Two` を `flaot64` に代入できます。 394 | 395 | ``` 396 | var f float64 397 | var g float64 = Two 398 | f = Two 399 | fmt.Println(f, "and", g) 400 | ``` 401 | 402 | 出力は `2 and 2` です。たとえ `Two` が複素数定数だとしても、スカラーの浮動小数点数の変数に代入できます。 403 | このように定数が型を「渡り歩く」ことができる機能があることが役立つとわかるでしょう。 404 | 405 | ## 整数 406 | ついに整数までやってきました。整数には [サイズや符号の有無など](http://golang.org/ref/spec#Numeric_types) 407 | 変化する部分がたくさんあります。しかしこれまでと同じ規則に従います。最後にまた、今回は `int` だけを使って 408 | いつもの例を見てみましょう。 409 | 410 | ``` 411 | type MyInt int 412 | const Three = 3 413 | const TypedThree int = 3 414 | var mi MyInt 415 | mi = 3 // OK 416 | mi = Three // OK 417 | mi = TypedThree // ダメ 418 | fmt.Println(mi) 419 | ``` 420 | 421 | 同じ例は整数型のどの型でも成立します。 422 | 423 | ``` 424 | int int8 int16 int32 int64 425 | uint uint8 uint16 uint32 uint64 426 | uintptr 427 | ``` 428 | 429 | (上記の型に加えて、 `uint8` のエイリアスの `byte` と `int32` のエイリアスの `rune` も同様です) 430 | とても多くの型がありますが、これまで十分に見てきた定数の振る舞いがここでも当てはまります。 431 | 432 | 先に述べたように、整数はいくつかの形式で表されますし、それぞれの形式にはそれぞれのデフォルト型があります。 433 | `123` 、 `0xFF` 、 `-14` というような単純な定数は `int` で、 `'世'` や `'\r'` というような引用された文字は 434 | `rune` です。 435 | 436 | どの定数の形式も符号なし整数型をデフォルト型としては持っていません。しかしながら、型付けされていない定数の柔軟性によって 437 | 私たちが型を明確にする限り単純な定数で符号なし整数の変数を初期化できます。これは虚部がゼロの複素数で `float64` の変数を 438 | 初期化できることに似ています。次にいくつかの方法で `unit8` の変数を初期化する方法を並べます。 439 | これらはすべて等価ですが、結果が符号なしになるように明示的に型を与えなければなりません。 440 | 441 | ``` 442 | var u uint = 17 443 | var u = uint(17) 444 | u := uint(17) 445 | ``` 446 | 447 | 浮動小数点数型の節でふれた値域に関する問題と同様に、すべての整数値がすべての整数型に適用できるわけではありません。 448 | 2つの問題が発生しえます。値が大きすぎる、または符号なし整数に負の値が代入されるかの2つです。たとえば、 `int8` は 449 | -128 から 127 の値域があり、この範囲を外れた定数は `int8` 型の変数には決して代入できません。 450 | 451 | ``` 452 | var i8 int8 = 128 // エラー: 値が大きすぎます。 453 | ``` 454 | 455 | 同様に、 `byte` としても使われる `uint8` は、値域が0から255で、これより大きい整数もしくは負の整数は `uint8` 型の整数には代入できません。 456 | 457 | ``` 458 | var u8 uint8 = -1 // エラー: 負の値。 459 | ``` 460 | 461 | 型チェックによりこのような誤りを検出できます。 462 | 463 | ``` 464 | type Char byte 465 | var c Char = '世' // エラー: '世' の値は 0x4e16 で、大きすぎます。 466 | ``` 467 | 468 | もしコンパイラがあなたの定数の使い方に対して文句を言ってきたら、おそらく今挙げたような間違いの現実でのバグでしょう。 469 | 470 | ## 演習: 最大の符号なし整数 471 | 472 | ここで、ちょっとした有益な演習を行ってみましょう。 `uint` の範囲内で最大値の定数を表現できるでしょうか。 473 | もし `uint` ではなく `uint32` であれば、このように書くことが出来ます。 474 | 475 | ``` 476 | const MaxUint32 = 1<<32 - 1 477 | ``` 478 | 479 | しかし私たちがほしいのは `uint` であり `uint32` です。 `int` 型と`uint` 型は同様に、32ビットか64ビットかといったビット数の指定はありません。 480 | どのビット数が使えるかはアーキテクチャに依存するので、単純に値を書くことは出来ません。 481 | 482 | [2の補数](http://ja.wikipedia.org/wiki/2%E3%81%AE%E8%A3%9C%E6%95%B0) 、これはGoの整数が利用すると定められていますが、 483 | そのファンであれば `-1` はビットがすべて1となるので、 `-1` のビットパターンは内部的には符号なし整数の最大値と同じになると知っています。 484 | それゆえ、次のように考えてしまいますが、 485 | 486 | ``` 487 | const MaxUint uint = -1 // エラー: 負の値 488 | ``` 489 | 490 | これは不正となります。なぜなら-1は符号なしの変数では表現しえないからです。 491 | `-1` は符号なしの値の値域にはありません。 492 | 同様の理由で、型変換も意味をなしません。 493 | 494 | ``` 495 | const MaxUint uint = uint(-1) // エラー: 負の値 496 | ``` 497 | 498 | たとえ、実行時に-1の値が符号なし整数に変換出来たとしても、定数の [変換](http://golang.org/ref/spec#Conversions) のルールはコンパイル時にこの種の抜け穴を禁止します。 499 | つまり、次のコードは実行はできますが 500 | 501 | ``` 502 | var u uint 503 | var v = -1 504 | u = uint(v) 505 | ``` 506 | 507 | これは `v` が変数だからという理由だけです。もし `v` を定数にすると、たとえ型付けされていない定数だとしても、禁止区域に戻ってきてしまいます。 508 | 509 | ``` 510 | var u uint 511 | const v = -1 512 | u = uint(v) // エラー: 負の値 513 | ``` 514 | 515 | また先ほどと同様のアプローチに戻ってみましょう。ただし今度は `-1` の代わりに `^0` として、0のビットの否定(NOT)にしてみましょう。 516 | この例も同様の理由で失敗します。数値の空間では `^0` は無限数を表すため、固定サイズの整数に代入する際に情報を失ってしまいます。 517 | 518 | ``` 519 | const MaxUint uint = ^0 // エラー: 桁あふれ 520 | ``` 521 | 522 | ではどのように符号なし整数の最大値を定数で表せばよいのでしょうか。 523 | 524 | 鍵となるのは、 `uint` 型の変数内のビットに対する操作に制約をかけて、 `uint` で表現できない、たとえば負の数といった値を避ける事です。 525 | 最も単純な `uint` 型の値は、片付けされた定数の `uint(0)` です。もし `uint` が32ビットまたは64ビットだった場合、それに応じて 526 | `uint(0)` も32個または64個の0のビットを持つことになります。これらのビットをそれぞれ反転すれば、正しい数の1のビットを持つこととなり、 527 | これが `uint` 型の値の最大値となります。 528 | 529 | したがって定数 `0` のビットを反転するのではなく、型付けされた定数 `uint(0)` のビットを反転するのです。 530 | これで欲しかった定数が得られます。 531 | 532 | ``` 533 | const MaxUint = ^uint(0) 534 | fmt.Printf("%x\n", MaxUint) 535 | ``` 536 | 537 | 現在の実行環境での `uint` を表現するビット数がいくつでも([playground](http://blog.golang.org/playground) では32ビットです)、 538 | この定数は正しく `uint` 型が持てる最大値を表現しています。 539 | 540 | この結果に至るまでの分析を理解すれば、Goでの定数について重要なてを理解したといえるでしょう。 541 | 542 | ## 数字 543 | Goにおける型付けされていない定数の概念は整数、浮動小数点数、複素数、さらには文字といった数値定数は 544 | すべてある種統一された空間に存在するということを意味しています。それらの定数を変数、代入、演算といった 545 | 計算の世界に持ち込んだときに実際の型が関係してきます。しかし、数値定数の世界に留まる限り、 546 | 値を混ぜたり合致させたりすることができます。これらの定数はすべて数値 1 を持っています。 547 | 548 | ``` 549 | 1 550 | 1.000 551 | 1e3-99.0*10-9 552 | '\x01' 553 | '\u0001' 554 | 'b' - 'a' 555 | 1.0+3i-3.0i 556 | ``` 557 | 558 | したがって、これらは暗黙のデフォルト値を持っていますが、型付けされていない定数として書かれているので、 559 | これらは任意の整数型の変数に代入できます。 560 | 561 | ``` 562 | var f float32 = 1 563 | var i int = 1.000 564 | var u uint32 = 1e3 - 99.0*10.0 - 9 565 | var c float64 = '\x01' 566 | var p uintptr = '\u0001' 567 | var r complex64 = 'b' - 'a' 568 | var b byte = 1.0 + 3i - 3.0i 569 | 570 | fmt.Println(f, i, u, c, p, r, b) 571 | ``` 572 | 573 | この出力は次のようになります。 `1 1 1 1 1 (1+0i) 1.` 574 | 575 | こんなおかしなことさえもできます。 576 | 577 | ``` 578 | var f = 'a' * 1.5 579 | fmt.Println(f) 580 | ``` 581 | 582 | これは145.5を出力しますが、この例は先の話を証明する以外特に意味はありません。 583 | 584 | 585 | しかし、これらのルールの本当に大事な点は柔軟性です。この柔軟性の意味するところは、 586 | Goでは同じ式の中で浮動小数点数と整数の変数を混ぜること、さらには `int` と `int32` の 587 | 変数すら混ぜることは不正ですが、たとえば 588 | 589 | ``` 590 | sqrt2 := math.Sqrt(2) 591 | ``` 592 | 593 | や 594 | 595 | ``` 596 | const millisecond = time.Second/1e3 597 | ``` 598 | 599 | あるいは 600 | 601 | ``` 602 | bigBufferWithHeader := make([]byte, 512+1e6) 603 | ``` 604 | 605 | と書くことは許されていて、あなたの期待するとおりの結果となります。 606 | 607 | なぜならGoでは、数値定数はあなたの期待したとおり、つまり数字として動作するからです。 608 | 609 | By Rob Pike 610 | -------------------------------------------------------------------------------- /content/post/context.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2014-07-29T09:00:00+09:00" 3 | draft = false 4 | title = "Goの並行パターン:コンテキスト (Go Concurrency Pattern: Context)" 5 | tags = ["concurrency", "cancellation", "context", "gorilla", "tomb"] 6 | +++ 7 | 8 | # Goの並行パターン:コンテキスト 9 | [Go Concurrency Pattern: Context](https://blog.golang.org/context) by Sameer Ajmani 10 | 11 | ## はじめに 12 | Goで書かれたサーバでは、サーバに来たリクエストはそれぞれそれ自身のゴルーチンで処理されます。 13 | リクエストハンドラはしばしばデータベースやRPCサービスといったバックエンドにアクセスするために追加のゴルーチンを起動します。 14 | リクエストの処理を行っているゴルーチンは、通常エンドユーザのアイデンティティや認可トークン、リクエストの期限などリクエスト固有の値へのアクセス権が必要です。 15 | リクエストがキャンセルされたりタイムアウトした場合には、それらのゴルーチンが使っていたリソースをシステムが再度要求することができるように、そのリクエストの処理を行っているすべてのゴルーチンは素早く終了すべきです。 16 | 17 | Googleで私たちは、APIの境界をまたぐリクエスト固有の値やキャンセルのシグナル、期限などを、 18 | あるリクエストの処理に関与するすべてのゴルーチンに投げることを容易にする、 `context` パッケージというパッケージを開発しました。 19 | パッケージは [golang.org/x/net/context](http://godoc.org/golang.org/x/net/context) に公開されています。 [^1] 20 | この記事ではそのパッケージの使い方と実際に動作する例を紹介したいと思います。 21 | 22 | [^1]: 訳註: 原文では `code.google.com/p/go.net/context` を参照していますが、現状に合わせてURLを変更しました。 23 | 24 | ## コンテキスト(Context) 25 | 26 | `context` パッケージの核となっているのは `Context` 型です。 27 | 28 | ``` 29 | // ContextはAPIの境界を越えて期限とキャンセルシグナルとリクエスト固有の値を保持します。 30 | // メソッドは複数のゴルーチンから同時に呼び出されても安全です。 31 | type Context interface { 32 | // Doneはこのコンテキストがキャンセルされたりタイムアウトした場合にcloseされます。 33 | Done() <-chan struct{} 34 | 35 | // ErrはDoneチャンネルが閉じた後なぜこのコンテキストがキャンセルされたかを知らせます。 36 | Err() error 37 | 38 | // Deadlineは設定されている場合にはいつこのContextがキャンセルされるかを返します。 39 | Deadline() (deadline time.Time, ok bool) 40 | 41 | // Valueはkeyに紐付いた値を返し、設定がない場合はnilを返します。 42 | Value(key interface{}) interface{} 43 | } 44 | ``` 45 | 46 | (この説明は要約されたもので、 [godoc](http://godoc.org/golang.org/x/net/context) が正式なものです。) 47 | 48 | `Done` メソッドは、 `Context` の代わりに動作する関数に対するキャンセルシグナルとして振る舞うチャンネルを返します。チャンネルが閉じられたときに、関数は処理を中断して戻るべきです。 49 | `Err` メソッドはなぜその `Context` がキャンセルされたかを示すエラーを返します。 50 | [パイプラインとキャンセル](https://blog.golang.org/pipelines) の記事では `Done` チャンネルのイディオムについてより詳細に議論しています。 51 | 52 | `Context` には `Done` チャンネルが受信専用であるのと同様の理由で `Cancel` メソッドがあり *ません* 。 53 | キャンセルシグナルを受け取る関数は通常シグナルを送る関数ではありません。 54 | 特に、親の操作が子の操作を行うためのゴルーチンを起動したときに、それらの子の操作が親の操作をキャンセルできるべきではありません。 55 | 代わりに `WithCancel` 関数(あとで説明します)で新しい `Context` の値をキャンセルする方法を提供します。 56 | 57 | `Context` は複数のゴルーチンから同時に使われても安全です。コード内では1つの `Context` を任意の数のゴルーチンに渡し、その `Context` をキャンセルしてすべてのゴルーチンに伝えることができます。 58 | 59 | `Deadline` は関数が処理を始めるべきかどうかを決定することができるメソッドです。 60 | もし残り時間が少なければ、起動する価値はありません。コードでは期限をI/O操作のタイムアウトとして利用することもあるでしょう。 61 | 62 | `Value` によって `Context` がリクエスト固有のデータを運ぶことができます。 63 | そのようなデータは複数のゴルーチンによって同時に利用されても安全です。 64 | 65 | ## 派生したコンテキスト 66 | 67 | `context` パッケージでは既存のコンテキストから新しい `Context` の値を *派生する* 関数を提供しています。 68 | これらの値は木構造になっていて、 `Context` がキャンセルされたときに、そこから派生した `Context` もすべてキャンセルされます。 69 | 70 | 71 | `Background` はすべての `Context` 木構造の根になっていて、決してキャンセルされることはありません。 72 | 73 | ``` 74 | // Background は空の Context を返します。 75 | // そのコンテキストは決してキャンセルされることはなく、期限はなく、また値を持ちません。 76 | // Backgroundは通常、main、init、テストの中で使われ、到着するリクエストの 77 | // 最上位の Context として使われます。 78 | func Background() Context 79 | ``` 80 | 81 | `WithCancel` と `WithTimeout` は派生した `Context` を返します。 82 | この `Context` は親の `Context` よりも早くキャンセルされます。 83 | 到着するリクエストに紐付いた `Context` は通常リクエストハンドラが戻すとキャンセルされます。 84 | `WithCancel` は複数のレプリカを使うときにまた冗長なリクエストをキャンセルするのにも便利です。 85 | `WithTimeout` はバックエンドサーバーへのリクエストの期限を設定するのに便利です。 86 | 87 | ``` 88 | // WithCancel は parent のコピーを返し、 parent.Done が閉じられた、 89 | // またはキャンセルが呼ばれるとすぐに、その Done チャンネルが閉じられます。 90 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 91 | 92 | // CancelFunc は Context をキャンセルします。 93 | type CancelFunc func() 94 | 95 | // WithTimeout は parent のコピーを返し、 parent.Done が閉じられた、 96 | // または timeout が過ぎるとすぐに、その Done チャンネルが閉じられます。 97 | // Context の Deadline は 現在時刻+timeout か親の期限のどちらか早いほうに設定されます。 98 | // もしタイマーがまだ動いていた場合、キャンセル関数はそのリソースを解放します。 99 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 100 | ``` 101 | 102 | `WithValue` はリクエスト固有の値を `Context` に紐付ける方法を提供します。 103 | 104 | ``` 105 | // WithValue は親のコピーを返し、そのValueメソッドがkeyに対しvalを返すようにします。 106 | func WithValue(parent Context, key interface{}, val interface{}) Context 107 | ``` 108 | 109 | `context` パッケージの使い方を理解するには動く実例を通して見るのが最良でしょう。 110 | 111 | ## 例: Google ウェブ検索 112 | 一つの例は `/search?q=golang&timeout=1s` のようなURLを処理して「golang」という検索クエリを [Google Web Search API](https://developers.google.com/web-search/docs/) に投げて、 113 | その結果を表示するようなHTTPサーバーです。 `timeout` パラメータはサーバーに指定時間が経過したら 114 | リクエストをキャンセルするように伝えます。 115 | 116 | コードは3つのパッケージに分かれています。 117 | 118 | * [server](https://blog.golang.org/context/server/server.go) は `main` 関数と `/search` のハンドラを提供します。 119 | * [userip](https://blog.golang.org/context/userip/userip.go) はリクエストからユーザーのIPアドレスを抜き出し、 `Context` に紐付ける関数を提供します。 120 | 121 | * [google](https://blog.golang.org/context/google/google.go) はGoogleにクエリを送信する `Search` 関数を提供します。 122 | 123 | ## サーバーのプログラム 124 | [server](https://blog.golang.org/context/server/server.go) のプログラムは `/serach?q=golang` のようなリクエストを処理して `golang` という検索クエリによるGoogle検索の最初の結果いくつかを返します。サーバーでは `handleSearch` という関数を `/search` のエンドポイントとして登録しています。ハンドラは `ctx` という最初の `Context` を生成して、ハンドラが値を返すときにそれがキャンセルされるように設定します。 125 | もしリクエストに `timeout` というURLパラメーターが含まれていたら、 `Context` は期限が来たら自動的にキャンセルされます。 126 | 127 | ``` 128 | func handleSearch(w http.ResponseWriter, req *http.Request) { 129 | // ctx はこのハンドラの Context です。 cancel を呼ぶことで 130 | // ctx.Done チャンネルが閉じられます。これで、このハンドラからのリクエスト用の 131 | // キャンセルシグナルです。 132 | var ( 133 | ctx context.Context 134 | cancel context.CancelFunc 135 | ) 136 | timeout, err := time.ParseDuration(req.FormValue("timeout")) 137 | if err == nil { 138 | // リクエストにはタイムアウトがあるので、期限が来たら自動的にキャンセルされる 139 | // コンテキストを作成します。 140 | ctx, cancel = context.WithTimeout(context.Background(), timeout) 141 | } else { 142 | ctx, cancel = context.WithCancel(context.Background()) 143 | } 144 | defer cancel() // handleSearchが値を返したらすぐに ctx をキャンセルします。 145 | ``` 146 | 147 | このハンドラは `google.Search` を `ctx` と `query` を使って呼び出します。 148 | 149 | ``` 150 | // Google検索を実行して結果を表示します。 151 | start := time.Now() 152 | results, err := google.Search(ctx, query) 153 | elapsed := time.Since(start) 154 | ``` 155 | 156 | 検索に成功したら、ハンドラは結果を返します。 157 | 158 | ``` 159 | if err := resultsTemplate.Execute(w, struct { 160 | Results google.Results 161 | Timeout, Elapsed time.Duration 162 | }{ 163 | Results: results, 164 | Timeout: timeout, 165 | Elapsed: elapsed, 166 | }); err != nil { 167 | log.Print(err) 168 | return 169 | } 170 | ``` 171 | 172 | ## userip パケージ 173 | [userip](https://blog.golang.org/context/userip/userip.go) パッケージはリクエストからユーザーのIPアドレスを抜き出し、それを `Context` に紐付ける関数を提供します。 `Context` はキーと値の対応表を提供します。このとき、キーも値もともに `interface{}` 型です。 174 | キーの型は同値性をサポートしなければならず、値の型は複数のゴルーチンから同時に使われても安全でなければなりません。 175 | `userip` のようなパッケージはこの対応表の詳細を隠し、特定の `Context` の値にたいして強く型付けされたアクセスを提供します。 176 | 177 | キーの衝突を避けるために、 `userip` ではエクスポートされていない型である `key` を定義し、この型の値をコンテキストのキーとして使います。 178 | 179 | ``` 180 | // key型は他のパッケージで定義されているコンテキストのキーと衝突しないよう、エクスポートされません。 181 | type key int 182 | 183 | // userIPkey はユーザーのIPアドレスのためのコンテキストのキーです。値がゼロの場合は任意の値となります。 184 | // もしこのパッケージが他のコンテキストのキーであったなら、別の整数値になっていたことでしょう。 185 | const userIPKey key = 0 186 | ``` 187 | 188 | `FromRequest` は `userIP` の値を `http.Request` から抜き出します。 189 | 190 | ``` 191 | func FromRequest(req *http.Request) (net.IP, error) { 192 | ip, _, err := net.SplitHostPort(req.RemoteAddr) 193 | if err != nil { 194 | return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr) 195 | } 196 | ``` 197 | 198 | `NewContext` は与えられた `userIP` の値を持つ新しい `Context` を返します。 199 | 200 | ``` 201 | func NewContext(ctx context.Context, userIP net.IP) context.Context { 202 | return context.WithValue(ctx, userIPKey, userIP) 203 | } 204 | ``` 205 | 206 | `FromContext` は `Context` から `userIP` を抜き出します。 207 | 208 | ``` 209 | func FromContext(ctx context.Context) (net.IP, bool) { 210 | // ctx.Value が nil を返す場合、 ctx はキーになる値を持っていません。 211 | // このとき net.IP 型のアサーションは ok=false を返します。 212 | userIP, ok := ctx.Value(userIPKey).(net.IP) 213 | return userIP, ok 214 | } 215 | ``` 216 | 217 | ## google パッケージ 218 | [google.Search](https://blog.golang.org/context/google/google.go) 関数は [Google Web Search API](https://developers.google.com/web-search/docs/) に対してHTTPリクエストを送り、JSONエンコードされた結果をパースします。この関数は `Context` のパラメーター `ctx` を受け取り、もし `ctx.Done` が閉じられていたら、リクエストが実行中だったとしても、直ちに結果を返します。 219 | 220 | Google Web Search APIのリクエストには、クエリパラメータとして検索クエリとユーザーのIPアドレスが含まれています。 221 | 222 | ``` 223 | func Search(ctx context.Context, query string) (Results, error) { 224 | // Google Search API へのリクエストの準備 225 | req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil) 226 | if err != nil { 227 | return nil, err 228 | } 229 | q := req.URL.Query() 230 | q.Set("q", query) 231 | 232 | // ctx にユーザーのIPアドレスが有った場合、それをサーバーに転送します。 233 | // Google API はサーバーが起動したリクエストとエンドユーザーのリクエストを区別するために 234 | // ユーザーのIPアドレスを使います。 235 | if userIP, ok := userip.FromContext(ctx); ok { 236 | q.Set("userip", userIP.String()) 237 | } 238 | req.URL.RawQuery = q.Encode() 239 | ``` 240 | 241 | `Search` はHTTPリクエストを発行するためにヘルパー関数 `httpDo` を使い、リクエストまたはレスポンスが処理中に `ctx.Done` が閉じられるとHTTPリクエストをキャンセルします。 242 | `Search` はHTTPレスポンスを処理するために `httpDo` にクロージャーを渡します。 243 | 244 | ``` 245 | var results Results 246 | err = httpDo(ctx, req, func(resp *http.Response, err error) error { 247 | if err != nil { 248 | return err 249 | } 250 | defer resp.Body.Close() 251 | 252 | // JSON形式の検索結果をパースする。 253 | // https://developers.google.com/web-search/docs/#fonje 254 | var data struct { 255 | ResponseData struct { 256 | Results []struct { 257 | TitleNoFormatting string 258 | URL string 259 | } 260 | } 261 | } 262 | if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 263 | return err 264 | } 265 | for _, res := range data.ResponseData.Results { 266 | results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL}) 267 | } 268 | return nil 269 | }) 270 | // httpDo は先ほど渡したクロージャーが結果を返すのを待つので、ここで結果を読み込んでも安全です。 271 | return results, err 272 | ``` 273 | 274 | `httpDo` 関数はHTTPリクエストを実行し、そのレスポンスを新しいゴルーチンで処理します。 275 | `ctx.Done` が閉じられた場合にはゴルーチンが終了する前にリクエストをキャンセルします。 276 | 277 | ``` 278 | func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { 279 | // HTTPリクエストをゴルーチン内で実行し、レスポンスを f に渡す。 280 | tr := &http.Transport{} 281 | client := &http.Client{Transport: tr} 282 | c := make(chan error, 1) 283 | go func() { c <- f(client.Do(req)) }() 284 | select { 285 | case <-ctx.Done(): 286 | tr.CancelRequest(req) 287 | <-c // f が値を返すのを待つ 288 | return ctx.Err() 289 | case err := <-c: 290 | return err 291 | } 292 | } 293 | ``` 294 | 295 | ## Context用のコードを適用する 296 | 297 | 多くのサーバーフレームワークがリクエスト固有の値を保持するためにパッケージと型を提供しています。 298 | 既存のフレームワークを使ったコードと `Context` パラメータを期待するコードとの架け橋として 299 | `Context` インターフェースの新しい実装を定義することができます。 300 | 301 | たとえば、Gorillaの [github.com/gorilla/context](http://github.com/gorilla/context) パッケージは、HTTPリクエストからキーと値のペアへの対応表を提供することで、ハンドラがデータと受け取ったリクエストを紐付けることができるようになっています。 [gorilla.go](https://blog.golang.org/context/gorilla/gorilla.go) では、`Value` メソッドがGorillaパッケージ内の特定のHTTPリクエストに紐付いた値を返すような `Context` の実装を提供しています。 302 | 303 | 他のパッケージでは `Context` と似たキャンセルの仕組みをサポートしてきています。たとえば [Tomb](http://godoc.org/gopkg.in/tomb.v2) では、 `Dying` チャンネルを閉じることでキャンセルシグナルを送る `Kill` メソッドを提供しています。 304 | また `Tomb` は処理用のゴルーチンが終了するのを待つ、`sync.WaitGroup` に似たメソッドも提供しています。 305 | [tomb.go](https://blog.golang.org/context/tomb/tomb.go) では、親の `Context` がキャンセルされる、もしくは与えられた `Tomb` が殺された場合にキャンセルされる `Context` の実装を提供しています。 306 | 307 | ## 結論 308 | Googleでは、Goプログラマに受信と送信とのリクエスト間の経路での呼び出しにおいて、すべての関数で第1引数に `Context` パラメーターを渡すことを要求しています。 309 | これによって、多くの異なるチームが開発したGoのコードがお互いに上手く動くようになっています。 310 | `Context` によって期限とキャンセルをシンプルに制御できるようになり、またセキュリティ上の認証情報といった重要な値がGoのプログラム内を適切に通過することを確実にしています。 311 | 312 | `Context` 上に構築したいサーバーフレームワークは、そのパッケージと `Context` パラメーターがあると期待されるパッケージ間の橋渡しをするような `Context` の実装を提供すべきでしょう。そうするとクライアントライブラリは `Context` を呼び出し元のコードから受け取るでしょう。リクエスト固有のデータとキャンセルに関する共通のインターフェースを構築することで、 `Context` はパッケージ開発者がスケーラブルなサービスを作るとためにコードを共有することをより簡単にします。 313 | 314 | By Sameer Ajmani 315 | 316 | ## あわせて読みたい 317 | 318 | * [Go Concurrency Patterns: Pipelines and cancellation](http://blog.golang.org/pipelines) 319 | * [Goの並行パターン:パイプラインとキャンセル (Go Concurrency Pattern: Pipelines and cancellation)](../pipelines/) 320 | * [Introducing the Go Race Detector](http://blog.golang.org/race-detector) 321 | * [Advanced Go Concurrency Patterns](http://blog.golang.org/advanced-go-concurrency-patterns) 322 | * [Concurrency is not parallelism](http://blog.golang.org/concurrency-is-not-parallelism) 323 | * [Go videos from Google I/O 2012](http://blog.golang.org/go-videos-from-google-io-2012) 324 | * [Go Concurrency Patterns: Timing out, moving on](http://blog.golang.org/go-concurrency-patterns-timing-out-and) 325 | * [Goの並行パターン:タイムアウトと進行](../go-concurrency-patterns-timing-out-and/) 326 | * [Share Memory By Communicating](http://blog.golang.org/share-memory-by-communicating) 327 | -------------------------------------------------------------------------------- /content/post/errors-are-values.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-01-12T12:00:00+09:00" 3 | draft = false 4 | title = "エラーは値 (Errors are values)" 5 | +++ 6 | 7 | # エラーは値 8 | 9 | [Errors are values](http://blog.golang.org/errors-are-values) by Rob Pike 10 | 11 | Goプログラマ、特にまだGoに不慣れな開発者に共通する議論の話題といえば、エラー処理の方法でしょう。 12 | 議論が次のようなコードの連続になることを嘆く結論に至ることがしばしばあります。 13 | 14 | ``` 15 | if err != nil { 16 | return err 17 | } 18 | ``` 19 | 20 | 先日、確認できるすべてのオープンソースプロジェクトをスキャンしてみたところ、このスニペットは 21 | 先のような開発者が信じているほどではなく、せいぜい1ページに1つか2つ現れる程度であることがわかりました。 22 | それでもなお、いつも次のイディオムをタイプしなければいけないと強く信じているのであれば、 23 | それは何かが間違っていますし、明らかに問題の対象はGoそれ自信となってしまいます。 24 | 25 | ``` 26 | if err != nil 27 | ``` 28 | 29 | これは不幸なことですし、語弊があり、そして容易に訂正が可能なことです。 30 | おそらく、そのように信じてしまっているような状況になったのは、信じているプログラマがGoを使い始めて 31 | まだ日が浅く、「エラーをどう処理したらよいか」という問いに対して、このパターンを覚えて、 32 | そこで止まってしまっているのだと思います。他の言語ではtry-catch節や他の同様のエラー処理機構を 33 | 使っているのでしょう。それゆえ、そのプログラマは、私が古い言語ではtry-catch節を使っていたような場合でも、 34 | Goではただ `if err != nil` と打っていると考えているのでしょう。時間が経つにつれて、 35 | Goではこのようなスニペットが蔓延しだし、その結果不格好になってしまいました。 36 | 37 | この表現がしっくり来るかどうかはわかりませんが、こういったGoプログラマはエラーに関して根本的な点を 38 | 見失っています。 _エラーは値です。_ 39 | 40 | 値はプログラムが可能で、エラーは値なので、エラーはプログラム可能なのです。 41 | 42 | もちろん、エラー値がnilかどうかを検証する文はよくありますが、エラー値でできることは他にもたくさんあります。 43 | そしてそれらをあなたのプログラムに適用することで、プログラムが改善され、丸暗記で使っているおきまりの形のif文を 44 | 排除することができます。 45 | 46 | 次のスニペットは `bufio` パッケージの [Scanner](http://golang.org/pkg/bufio/#Scanner) 型の簡単な例です。 47 | Scanner型の [Scan](http://golang.org/pkg/bufio/#Scanner.Scan) メソッドは、Scannerの中にあるI/Oを処理し、 48 | その中ではもちろんエラーが発生するでしょう。しかし `Scan` メソッドはエラーを一切公開しません。 49 | 代わりにbool値を返し、別のメソッドがスキャンが終わったあとに、エラーが発生したかを報告します。 50 | クライアント側のコードは次のようになります。 51 | 52 | ``` 53 | scanner := bufio.NewScanner(input) 54 | for scanner.Scan() { 55 | token := scanner.Text() 56 | // tokenの処理 57 | } 58 | if err := scanner.Err(); err != nil { 59 | // エラーの処理 60 | } 61 | ``` 62 | 63 | たしかに、エラーがnilかどうかの確認はしていますが、その処理は一度しかしていません。 `Scan` メソッドは 64 | 代わりに次のように定義することもできたでしょう。 65 | 66 | ``` 67 | func (s *Scanner) Scan() (token []byte, error) 68 | ``` 69 | 70 | この場合はクライアント側のコードは次のようになるでしょう。(トークンの読み出し方に依存します) 71 | 72 | ``` 73 | scanner := bufio.NewScanner(input) 74 | for { 75 | token, err := scanner.Scan() 76 | if err != nil { 77 | return err // あるいはbreak 78 | } 79 | // tokenの処理 80 | } 81 | ``` 82 | さきほどと大きな違いはありませんが、唯一の重要な違いがあります。後者のコードでは、クライアントは 83 | 繰り返しの度にエラーの確認をしなければなりません。しかし実際の `Scanner` のAPIでは 84 | エラー処理は、トークンを繰り返し取得する肝心なAPIの要素からは抽象化され切り離されています。 85 | それゆえ、実際のAPIでは、クライアント側のコードはより自然な形になります。 86 | トークンを取り出す処理が完了するまでループして、エラーのことは考えずにすみます。 87 | エラー処理が一連のトークンの処理を分かりにくくすることはありません。 88 | 89 | 抽象化の下で、実際に何が起きているかですが、もちろん、 `Scan` がI/Oエラーに遭遇したら、 90 | すぐさまそれを記録し、 `false` を返します。クライアントが別のメソッドである `Err` を呼び出したら、 91 | そのエラーの値を返します。これは些細な事ではありますが、これは 92 | 93 | ``` 94 | if err != nil 95 | ``` 96 | 97 | をクライアントのコード内のあちこちに書いたり、トークンごとにエラーを確認することとは違います。 98 | これがエラー値とプログラミングをするということです。単純なプログラミングですが、 99 | それでもこれがプログラミングなのです。 100 | 101 | 設計のされかたに関わらず、どのようにエラーが公開されていても、それをプログラムが確認することは非常に重要です。 102 | これから議論することはどのようにエラーチェックを避けるかという話ではなく、いかにエラー処理を優雅に行うかという話です。 103 | 104 | これから話すエピソードは、東京で開催されたGoCon 2014 autumnに参加した際に話題になった、 105 | エラーの確認処理が繰り返し現れるコードについてです。熱心なGopherである [@jxck_](https://twitter.com/jxck_) が 106 | よく嘆かれているエラーの確認処理について話していました。 107 | 彼が書いたコードは、目的としてはこのようなものでした。 108 | 109 | ``` 110 | _, err = fd.Write(p0[a:b]) 111 | if err != nil { 112 | return err 113 | } 114 | _, err = fd.Write(p1[c:d]) 115 | if err != nil { 116 | return err 117 | } 118 | _, err = fd.Write(p2[e:f]) 119 | if err != nil { 120 | return err 121 | } 122 | // 以下続く 123 | ``` 124 | 125 | このコードは繰り返しが多いですね。実際のコードでは、さらに多く繰り返しがあり、ヘルパー関数を使ってリファクタリングするのは 126 | 容易なことではありませんでしたが、理想的な形にすると、エラー値に対する関数リテラルが助けになるでしょう。 127 | 128 | ``` 129 | var err error 130 | write := func(buf []byte) { 131 | if err != nil { 132 | return 133 | } 134 | _, err = w.Write(buf) 135 | } 136 | write(p0[a:b]) 137 | write(p1[c:d]) 138 | write(p2[e:f]) 139 | // 以下続く 140 | if err != nil { 141 | return err 142 | } 143 | ``` 144 | 145 | このパターンはそれなりにうまくいきますが、書き込みを行う関数それぞれに対しクロージャが必要になります。 146 | 別々のヘルパー関数を用意するのはよりぎこちない書き方になってしまいます。なぜなら、 `err` 変数を 147 | それらの関数の呼び出しごとに管理する必要があるからです。(試してみてください。) 148 | 149 | 先に触れた `Scan` メソッドでの考え方を借りて、このコードをより綺麗で、一般的で、再利用可能な形にできます。 150 | このテクニックを議論の中で @jxck_ に伝えたのですが、どのように適用するかまでは伝わらなかったようでした。 151 | 色々と意見を交わしつつ、いくらか言語の障壁に阻まれながら、最終的に彼にラップトップ借りる許可を得て、 152 | コードを書くことでその方法を実演しました。 153 | 154 | 私は次のような `errWriter` というオブジェクトを定義しました。 155 | 156 | ``` 157 | type errWriter struct { 158 | w io.Writer 159 | err error 160 | } 161 | ``` 162 | 163 | そして、 `write` という1つのメソッドを付与しました。これは標準的な `Write` というシグネチャで 164 | ある必要はなく、また違いを明確にするという理由もあって、小文字にしました。 `write` メソッドは 165 | `errWriter` の中にある `Writer` の `Write` メソッドを呼び、後に参照される最初のエラーを 166 | 記録します。 167 | 168 | ``` 169 | func (ew *errWriter) write(buf []byte) { 170 | if ew.err != nil { 171 | return 172 | } 173 | _, ew.err = ew.w.Write(buf) 174 | } 175 | ``` 176 | 177 | エラーが発生すると、ただちに `write` メソッドは何も処理しなくなり、最初のエラーが保存されただけの状態になります。 178 | 179 | この `errWrite` 型と `write` メソッドで、先のコードは次のようにリファクタリングされます。 180 | 181 | ``` 182 | ew := &errWriter{w: fd} 183 | ew.write(p0[a:b]) 184 | ew.write(p1[c:d]) 185 | ew.write(p2[e:f]) 186 | // 以下続く 187 | if ew.err != nil { 188 | return ew.err 189 | } 190 | ``` 191 | 192 | このコードは、クロージャを用いた場合と比べても、より簡潔になっています。また実際に書き込みを行っている一連の部分は 193 | ページ内で読みやすい形で行われています。もう取り散らかったコードはありません。エラー値(とインターフェース)を使ったプログラミングで 194 | コードがより素敵になりました。 195 | 196 | 同じパッケージ内の他のコードがこの考えに乗ること、あるいは直接 `errWriter` を使うことはありえます。 197 | 198 | また、 `errWriter` があれば、特にもっとわざとらしくない例で、より多くのことができるようになります。 199 | バイト数を積算していくこともできます。アトミックに一つのバッファに書き込みを行うこともできます。 200 | さらにもっとたくさんのことができます。 201 | 202 | 事実、このパターンは標準ライブラリによく出てきます。 `archive/zip` や `net/http` といったパッケージが使っています。 203 | より顕著な例としては `bufio` パッケージが実際に `errWriter` の考え方を実装しています。 204 | `bufio.Writer.Write` はエラーを返しますが、それは `io.Writer` インターフェースを考慮してのことです。 205 | `bufio.Writer` の `Write` メソッドは、ちょうど先の例の `errWriter.write` メソッドと同様の振る舞いになっています。 206 | `Flush` がエラーを出力するので、先の例はこのように書くことができます。 207 | 208 | ``` 209 | b := bufio.NewWriter(fd) 210 | b.Write(p0[a:b]) 211 | b.Write(p1[c:d]) 212 | b.Write(p2[e:f]) 213 | // 以下続く 214 | if b.Flush() != nil { 215 | return b.Flush() 216 | } 217 | ``` 218 | 219 | この手法には、少なくともいくつかのアプリケーションにおいては、一つ重大な欠点があります。 220 | エラーが発生するまで、処理がどの程度行われたかを知る術がないのです。それを知るためには、 221 | よりきめ細やかな手法が必要となります。もっとも、多くの場合では最後に全か無かの確認さえすれば十分です。 222 | 223 | エラー処理の繰り返しを避けるための手法の一つだけをみていました。心に留めておいてほしいことは、 224 | `errWriter` の利用、つまり `bufio.Writer` の手法がエラー処理を簡潔にする唯一の手法ではなく、 225 | この手法はすべての状況に適しているわけではないということです。しかしながら、ここで学んだ肝心な点は、 226 | エラーは値であり、Goプログラミング言語の全能力をもってすれば、それらを処理することは可能であるということです。 227 | 228 | エラー処理を簡潔にするように言語を使いましょう。 229 | 230 | しかし覚えておいてください。何をするにおいても、常にエラー処理を行いましょう! 231 | 232 | 最後に、 @jxck_ さんとのやりとりのすべてを見たい方は、彼が録画した動画とともに、[彼のブログ](http://jxck.hatenablog.com/entry/golang-error-handling-lesson-by-rob-pike) も読んでみましょう。 233 | 234 | By Rob Pike 235 | -------------------------------------------------------------------------------- /content/post/generate.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2014-12-22T14:49:44+09:00" 3 | draft = false 4 | title = "コードのジェネレート (Generating code)" 5 | +++ 6 | 7 | # コードのジェネレート 8 | [Generating code](https://blog.golang.org/generate) by Rob Pike 9 | 10 | 普遍的な計算の性質、チューリング完全、とは、コンピュータプログラムがコンピュータプログラムを書けるということです。 11 | これは、実際に評価されるべきほどには評価されていない、強力な考え方です。たとえば、これはコンパイラの定義の 12 | 大きな部分を占めています。また `go test` コマンドの動作にも関わってきます。 `go test` はテスト対象の 13 | パッケージをスキャンして、そのパッケージ用のテストハーネスを含んだGoプログラムを書き出して、 14 | それをコンパイルして実行します。現代のコンパイラは非常に速いので、このコストが高そうな一連の処理も 15 | 1秒以内に完了できます。 16 | 17 | プログラムがプログラムを書く例は他にもたくさんあります。たとえば [Yacc](http://golang.org/cmd/yacc/) は、 18 | 文法の記述を読み込んで、文法をパースするプログラムを書き出します。Protocol Bufferの「コンパイラ」は 19 | インターフェースの記述を読み込んで、構造の定義とメソッドとその他必要なコードを生成します。 20 | あらゆる種類の設定ツールも同様の動作をします。メタデータや環境を確認して、ローカルの状態に合わせた 21 | 足場を生成してくれます。 22 | 23 | それゆえプログラムを書くプログラムはソフトウェアエンジニアリングにおいて重要な要素ですが、 24 | ソースコードを生成するYaccのようなプログラムは、その出力がコンパイルされるように 25 | ビルドプロセスに組み込まれる必要があります。Makeのような外部のビルドツールが使われる場合、 26 | これは非常にたやすいことです。しかしGoでは、go toolがソースコードから必要なビルド情報を 27 | すべて取得するので、問題が有ります。go toolだけでYaccを走らせる簡単な機構がないのです。 28 | 29 | いままでは、ありませんでした。 30 | 31 | [最新のGoのリリース](http://blog.golang.org/go1.4)である、1.4ではそのようなツールを実行しやすくする 32 | 新しいコマンドが追加されました。 `go generate` というコマンド名で、Goのソースコード内の特別なコメントを 33 | スキャンすることで動作します。 `go generate` は `go build` の一部ではないことを理解するのが大切です。 34 | `go generate` は依存性の解析はまったく行わず、また明示的に `go build` の前に実行されなければなりません。 35 | `go generate` はGoパッケージの利用者ではなく、その作者が利用することを意図したものです。 36 | 37 | `go generate` コマンドは簡単に使えます。準備運動として、Yaccの文法を生成する方法を紹介しましょう。 38 | たとえば、ここに `gopher.y` というYaccの入力用ファイルがあったとします。このファイルでは 39 | あなたの新しい言語の文法が定義してあるとします。この文法を実装するGoのソースファイルを生成するには 40 | 通常であれば標準でついてくるGo版のYaccを次のように起動するでしょう。 41 | 42 | ``` 43 | go tool yacc -o gopher.go -p parser gopher.y 44 | ``` 45 | 46 | `-o` オプションは出力ファイル名を指定し、 `-p` オプションはパッケージ名を指定します。 47 | 48 | `go generate` にこの処理を行わせるには、同じディレクトリ内の通常の(自動生成でない) `.go` ファイルのいずれかに、 49 | ファイル内のどこかに次のようなコメントを追加します。 50 | 51 | ``` 52 | //go:generate go tool yacc -o gopher.go -p parser gopher.y 53 | ``` 54 | 55 | このテキストは先のコマンドに、 `go generate` が認識するための特別なコメントを先頭に付けただけのものです。 56 | コメントは行頭から開始しなくてはならず、また `//` と `go:generate` の間にスペースを入れてはいけません。 57 | そのマーカー以降は `go generate` が実行するコマンドを指定します。 58 | 59 | では実行してみましょう。ソースのディレクトリに移動し、 `go generate` を実行して、 `go build` 等を実行します。 60 | 61 | ``` 62 | $ cd $GOPATH/myrepo/gopher 63 | $ go generate 64 | $ go build 65 | $ go test 66 | ``` 67 | 68 | これだけです。エラーが無かった場合、 `go generate` は `yacc` を起動して `gopher.go` を生成します。 69 | そして生成されると、そのディレクトリには必要なGoのソースコードが揃うので、ビルドしてテストして 70 | いつもどおりの処理を行うことができます。 `gopher.y` が変更されるたびに、 `go generate` を実行して 71 | パーサーを再度生成しましょう。 72 | 73 | `go generate` の動作やオプション、環境変数の内容などを知りたい場合は、 74 | [デザインドキュメント](http://golang.org/s/go1.4-generate)を参照してください。 75 | 76 | go generateはMakeや他のビルド機構で出来なかったことはなにもできませんが、 `go` ツールに付随するもの、 77 | つまり余計なインストールが要らず、Goのエコシステムに上手く適用出来ます。 `go generate` は、 78 | それが生成するコードが対象のマシンで得られない場合にのみ、パッケージ作者が使うためのものであり、 79 | パッケージの利用者のためのものではないことを心に留めておいてください。また、もし生成されたコードが 80 | `go get` でインポートされることを意図しているのであれば、生成された(そしてテストされた!)ファイルは 81 | あとにソースコードレポジトリに追加されるべきです。 82 | 83 | `go generate` について少しわかったところで、なにか新しいことをしてみましょう。 84 | `go generate` が役に立つまったく別の例としては、 `golang.org/x/tools/` 内の `stringer` と呼ばれる、 85 | 新しいプログラムがあります。これは整数の定数に対して自動的に `String` メソッドを書いてくれます。 86 | このツールはリリースでは配布されていませんが、簡単にインストールすることが出来ます。 87 | 88 | ``` 89 | $ go get golang.org/x/tools/cmd/stringer 90 | ``` 91 | 92 | [`stringer`](http://godoc.org/golang.org/x/tools/cmd/stringer) のドキュメントからの例を持ってきました。 93 | あるコードに異なる錠剤の種類を定義するための整数の定数があったとします。 94 | 95 | ``` 96 | package painkiller 97 | 98 | type Pill int 99 | 100 | const ( 101 | Placebo Pill = iota 102 | Aspirin 103 | Ibuprofen 104 | Paracetamol 105 | Acetaminophen = Paracetamol 106 | ) 107 | ``` 108 | 109 | デバッグ用途にこれらの定数を綺麗に表示したい、つまりシグネチャ付きのメソッドが欲しくなります。 110 | 111 | ``` 112 | func (p Pill) String() string 113 | ``` 114 | 115 | 手でそのコードを書くのは簡単です。おそらく次のようになるでしょう。 116 | 117 | ``` 118 | func (p Pill) String() string { 119 | switch p { 120 | case Placebo: 121 | return "Placebo" 122 | case Aspirin: 123 | return "Aspirin" 124 | case Ibuprofen: 125 | return "Ibuprofen" 126 | case Paracetamol: // == Acetaminophen 127 | return "Paracetamol" 128 | } 129 | return fmt.Sprintf("Pill(%d)", p) 130 | } 131 | ``` 132 | 133 | もちろん他にもこの関数を書く方法があります。stringもスライスをPillを使ってインデックスを 134 | つけることもできますし、mapを使うこともできますし、他の技もあります。どのような方法でも、 135 | 錠剤のセットが変わったらそれに合わせてコードを変更する必要がありますし、それが必ず正しいように 136 | する必要があります。(パラセタノールは2つの名前があるので他の錠剤より用心しなければなりません。) 137 | 加えて、型や値、たとえば符号有りか符号無しか、密か疎か、ゼロ基準か否かなど、に応じてどの手法を 138 | 採用するかを考えなければなりません。 139 | 140 | `stringer` のプログラムはこれらの疑問に対してすべて面倒を見てくれます。 141 | `stringer` は単体で動くプログラムではありますが、 `go generate` から呼び出すことを 142 | 意図されています。使うためにはソース、おそらく型定義の近くに、生成用のコメントを加えてください。 143 | 144 | ``` 145 | //go:generate stringer -type=Pill 146 | ``` 147 | 148 | このルールでは `go generate` は `stringer` ツールを実行して `Pill` 型に `String` メソッドを 149 | 生成するように指定しています。出力結果は自動的に `pill_string.go` に書き込まれます。 150 | ( `-output` フラグを使って出力先を変更することも出来ます。) 151 | 152 | 実行してみましょう。 153 | 154 | ``` 155 | $ go generate 156 | $ cat pill_string.go 157 | // generated by stringer -type Pill pill.go; DO NOT EDIT 158 | 159 | package pill 160 | 161 | import "fmt" 162 | 163 | const _Pill_name = "PlaceboAspirinIbuprofenParacetamol" 164 | 165 | var _Pill_index = [...]uint8{0, 7, 14, 23, 34} 166 | 167 | func (i Pill) String() string { 168 | if i < 0 || i+1 >= Pill(len(_Pill_index)) { 169 | return fmt.Sprintf("Pill(%d)", i) 170 | } 171 | return _Pill_name[_Pill_index[i]:_Pill_index[i+1]] 172 | } 173 | $ 174 | ``` 175 | 176 | `Pill` の定義を変更するたびに、 `String` メソッドを更新するために、 177 | 次のコマンドを実行する必要があります。 178 | 179 | ``` 180 | $ go generate 181 | ``` 182 | 183 | もちろん、同じパッケージには同様の型が複数あるので、それらの `String` メソッドすべてを 184 | 1回のコマンドで更新できるのです。 185 | 186 | 生成されるコードが醜いのは疑問の余地がありません。でもいいんです。人間がそのコードを 187 | 編集する必要はないですから。機械が生成したコードはしばしば醜いものです。醜いコードも 188 | 最適化の結果なのです。すべての名前が1つの文字列に押し込められています。こうすることで 189 | メモリを節約しています。(すべての名前に対して、1つの文字列のヘッダだけですみます。 190 | たとえ名前が何億兆あったとしてもです。)そして、配列の `_Pill_index` を作り、単純で 191 | 効率的な手法で値から名前への対応を作ります。 `_Pill_index` は `uint8` の配列であり、 192 | スライスではないことに気をつけてください。 `uint8` の配列なのは、値の空間をつなぐのに 193 | 必要十分な最小の整数だからです。もっと多くの値がある、あるいは負の値がある場合には、 194 | 生成される `_Pill_index` の型は `uint16` や `int8` になるでしょう。ようするに、対応ができる 195 | ベストな型です。 196 | 197 | `stringer` が生成するメソッドで使われている手法は定数のセットの特性によって変化します。 198 | たとえば、定数が疎の場合は、マップが使われます。2の累乗を表す定数のセットを使った例です。 199 | 200 | ``` 201 | const _Power_name = "p0p1p2p3p4p5..." 202 | 203 | var _Power_map = map[Power]string{ 204 | 1: _Power_name[0:2], 205 | 2: _Power_name[2:4], 206 | 4: _Power_name[4:6], 207 | 8: _Power_name[6:8], 208 | 16: _Power_name[8:10], 209 | 32: _Power_name[10:12], 210 | ..., 211 | } 212 | 213 | func (i Power) String() string { 214 | if str, ok := _Power_map[i]; ok { 215 | return str 216 | } 217 | return fmt.Sprintf("Power(%d)", i) 218 | } 219 | ``` 220 | 221 | 手短に言えば、自動でメソッドを生成させたほうが、人間が書くよりもより効率的なものが生成できるということです。 222 | 223 | Goのコードベースにはすでに多くの `go generate` の事例があります。 `unicode` パッケージでのUnicode表の生成や、 224 | `encoding/gob` で配列を効率的にエンコードあるいはデコードするメソッドの作成、`time` パッケージでのタイムゾーンデータの 225 | 生成などがあります。 226 | 227 | ぜひ `go generate` を創造的に使ってください。いろいろ試せるように公開しているのです。 228 | 229 | `go generate` を自分で使うのではない場合にも、ぜひ `String` メソッドを書く際には新しい `stringer` ツールを使ってください。 230 | 機械に仕事をさせましょう。 231 | 232 | ## あわせて読みたい 233 | 234 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 235 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 236 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 237 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 238 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 239 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 240 | * [The Go image package](https://blog.golang.org/go-image-package) 241 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 242 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 243 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 244 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 245 | * [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) 246 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 247 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 248 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 249 | * [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) 250 | * [JSON and Go](https://blog.golang.org/json-and-go) 251 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 252 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 253 | * [Goの並行パターン:タイムアウトと進行](../go-concurrency-patterns-timing-out-and/) 254 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 255 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 256 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) 257 | -------------------------------------------------------------------------------- /content/post/gif-decoder-exercise-in-go-interfaces.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2011-05-25T00:07:38+09:00" 3 | title = "GIFデコーダ: Goインターフェースの練習(A GIF decoder: an exercise in Go interfaces)" 4 | draft = false 5 | tags = ["gif", "gopher", "image", "interface", "lagomorph", "lzw", "moustache", "rodent", "technical"] 6 | +++ 7 | 8 | # GIFデコーダ: Goインターフェースの練習 9 | [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) by Rob Pike 10 | 11 | ## はじめに 12 | 13 | 2011年5月10日にサンフランシスコで行われたGoogle I/Oのカンファレンスで、私たちはGo言語がGoogle App Engineで利用可能になったことを発表しました。Goは機械語に直接コンパイルするApp Engine上で利用可能となった最初の言語で、画像処理のようなCPUに負荷をかけるタスクにとってそれは良い選択でした。 14 | 15 | その流れで、私たちは以下のような画像を手軽により良くする [Moustachio](http://moustach-io.appspot.com/) と呼ばれるプログラムを実演しました: 16 | 17 | ![gif-decoder-exercise-in-go-interfaces_image00](./gif-decoder-exercise-in-go-interfaces_image00.jpg) 18 | 19 | 髭を加えて、その結果を共有しましょう: 20 | 21 | ![gif-decoder-exercise-in-go-interfaces_image02](./gif-decoder-exercise-in-go-interfaces_image02.jpg) 22 | 23 | アンチエイリアスが施された髭の描画を含む全てのグラフィック処理は、App Engine上で動作しているGoプログラムで完結します。(そのソースコードは [appengine-goプロジェクト](http://code.google.com/p/appengine-go/source/browse/example/moustachio/) で利用可能です。) 24 | 25 | Web上のほとんどの画像 - 少なくとも髭加工される可能性のある - がJPEGであるにも関わらず、ほかにも数えきれないほど広まっている画像形式があり、そしてアップロードされた数種類の画像形式を受け入れるのでそれは髭にとってもの分かりが良いように見えます。JPEGとPNGのデコーダはすでにGoの画像ライブラリの中にありましたが、昔からあるGIF形式のデコーダはなかったので、私たちは発表に間に合うようにGIFデコーダを書くとこにしました。そのデコーダは、問題解決のためにGoのインターフェースがどのようにしてその問題をより扱いやすくしているのかを示すいくつかのピースを含んでいます。このブログ記事の残りの部分ではその2、3個の例を述べています。 26 | 27 | ## GIFのフォーマット 28 | 29 | まず初めに、GIFの形式について簡単に見ていきましょう。GIFの画像ファイルは*パレット化*されており、つまりそれぞれのピクセル値はファイルに含まれているある決まったカラーマップにインデックス付けされています。ディスプレイの1ピクセルがたった8ビットで表されていた頃からGIF形式はあり、カラーマップは値の制限された組をスクリーンを明るくするために必要なRGB(赤、緑、青)の3値に変換するために使われていました。(これはJPEGとは対称的で、例えば、JPEGはエンコーダが直接カラー信号の分離を表現するためJPEGにはカラーマップはありません。) 30 | 31 | GIF画像は1ピクセル当たり1から8ビットの値をとることができ、包括的ですが、1ピクセルあたり8ビットが最も使われています。 32 | 33 | 少し単純化すると、GIFファイルはピクセル深度、画像の次元、カラーマップ(1枚の8ビット画像あたり256色のRGB値)をそれぞれ定義するヘッダーと、次いでピクセルデータを含んでいます。ピクセルデータは1次元のビットストリームとして格納され、写真には向きませんがコンピュータが生成するグラフィックスにとってはかなり効率的なLZWアルゴリズムを使って圧縮されます。そのとき圧縮データはある長さで区切られ、1バイトのカウント(0-255)とそれに続くバイト列という構成のブロックに分割されます: 34 | 35 | ![gif-decoder-exercise-in-go-interfaces_image03](./gif-decoder-exercise-in-go-interfaces_image03.gif) 36 | 37 | ## ピクセルデータのデブロッキング 38 | 39 | GIFのピクセルデータをGoでデコードするために、`compress/lzw` パッケージからLZWデコンプレッサを使うことができます。そのパッケージには、[ドキュメント](http://golang.org/pkg/compress/lzw/#NewReader)曰く、「rから読み出したデータを解凍することによって読み出し可能となる」オブジェクトを返すNewReader関数があります。 40 | 41 | ``` 42 | func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser 43 | ```` 44 | 45 | ここで、`order`はビットデータをパックする順番を定義し、`litWidth`はGIFファイルとピクセル深度(典型的には8)を対応させる際に使用するビット単位のワードサイズを意味しています。 46 | 47 | しかし、`NewReader` の最初の引数として入力ファイルを与えることはできません。それはデコンプレッサがバイトストリームを要求しているにも関わらずGIFデータはアンパックが必要なブロックストリームになっているからです。この問題を扱うために、それをデブロッキングするためのちょっとしたコードにより入力 `io.Reader` をラップすることができます。さらにそのコードを再び `Reader` として実装することもできます。つまり、デブロッキングするコードを `blockReader` と呼ばれる新しい型の `Read` メソッドの中で実装します。 48 | 49 | 以下は `blockReader` のデータ構造です。 50 | 51 | ``` 52 | type blockReader struct { 53 | r reader // Input source; implements io.Reader and io.ByteReader. 54 | slice []byte // Buffer of unread data. 55 | tmp [256]byte // Storage for slice. 56 | } 57 | ``` 58 | 59 | リーダー `r` は画像データのソースであり、そのソースは恐らくファイルかHTTP接続でしょう。`slice` と `tmp` フィールドはデブロッキングを管理するために使われます。以下は `Read` メソッドの全体像です。 60 | 61 | ``` 62 | 1 func (b *blockReader) Read(p []byte) (int, os.Error) { 63 | 2 if len(p) == 0 { 64 | 3 return 0, nil 65 | 4 } 66 | 5 if len(b.slice) == 0 { 67 | 6 blockLen, err := b.r.ReadByte() 68 | 7 if err != nil { 69 | 8 return 0, err 70 | 9 } 71 | 10 if blockLen == 0 { 72 | 11 return 0, os.EOF 73 | 12 } 74 | 13 b.slice = b.tmp[0:blockLen] 75 | 14 if _, err = io.ReadFull(b.r, b.slice); err != nil { 76 | 15 return 0, err 77 | 16 } 78 | 17 } 79 | 18 n := copy(p, b.slice) 80 | 19 b.slice = b.slice[n:] 81 | 20 return n, nil 82 | 21 } 83 | ``` 84 | 85 | 2-4行目はちょうどサニティーチェック(異常がないかの確認)に当たります。データを置くところがなければ0を返します。起こり得ないですが、念のためです。 86 | 87 | 5行目は `b.slice` の長さを確認することによって前回の呼び出しから左側にデータがあるかどうかを訊ねています。もしなければ、スライスの長さは0であり、`r` から次のブロックを読み出す必要があります。 88 | 89 | GIFブロックは1バイトのカウントで始まり、6行目で読み出しています。もしカウントが0であれば、GIFは、最後のブロックになったためにこれを定義します。したがって `EOF` を11行目で返します。 90 | 91 | 今、私たちは `blockLen` バイト読むべきだと分かっています。ですので、`b.tmp` の最初の `blockLen` バイトが `b.slice` を指すようにし、たくさんのバイトデータ読み出すためにヘルパー関数 `io.ReadFull` を使います。 92 | 93 | 18-19行目は `b.slice` から呼び出し側(訳注:レシーバー `b` のこと)のバッファに対しデータをコピーします。私たちは `Read` を実装していますが、`ReadFull` は実装していないので、要求されたバイト数よりも少ないバイト数を返すことを許可されています。それを実装するのは簡単です: `b.slice` から呼び出し側のバッファ(`p`)にデータをコピーし、コピー関数からの戻り値がコピーされたバイト数です。そのとき最初の `n` バイトを切り落とすために `b.slice` をリサイズし、次の呼び出しに備えています。 94 | 95 | スライス(`b.slice`)を配列(`b.tmp`)に結びつけて考えるのはGoプログラミングにおいては良いテクニックです。この場合、`blockReader` 型の `Read` メソッドは決して全てをアロケーションしないということを意味しています。カウント周り(スライス長に暗に示されています)を管理する必要がないことも意味しており、ビルトインの `copy` 関数がこれ以上コピーしないことを保証しています。(スライスについてより詳しく知りたい場合は、[the Go Blog のこの記事](http://blog.golang.org/2011/01/go-slices-usage-and-internals.html)をご覧ください。) 96 | 97 | `blockReader` 型が与えられると、画像データストリームを入力リーダーをラップすることによりアンパックできます。ファイルについてはこのようになります: 98 | 99 | ``` 100 | deblockingReader := &blockReader{r: imageFile} 101 | ``` 102 | 103 | このラッピングはブロックに区切られたGIF画像ストリームを `blockReader` の `Read` メソッドを呼び出すことにより利用可能な単純なバイトストリームに変換します。 104 | 105 | ## ピースを繋げる 106 | 107 | `blockReader` の実装とライブラリから利用可能なLZWコンプレッサにより、画像データストリームをデコードするために必要なピースが全て揃いました。驚くほど短く、素直にコードを紡ぎ合わせます。 108 | 109 | ``` 110 | lzwr := lzw.NewReader(&blockReader{r: d.r}, lzw.LSB, int(litWidth)) 111 | if _, err = io.ReadFull(lzwr, m.Pix); err != nil { 112 | break 113 | } 114 | ``` 115 | 116 | 以上です。 117 | 118 | 最初の行は `blockReader` を作り、デコンプレッサを作るためにそれを `lzw.NewReader` に通します。ここで、`d.r` は画像データを保持する `io.Reader` で、`lzw.LSB` はLZWデコンプレッサ内でのバイトオーダーを定義し、`litWidth` はピクセル深度です。 119 | 120 | 解析器が与えられると、次の行ではデータを解凍するための `io.ReadFull` を呼び出し、それを画像 `m.Pix` に格納しています。`ReadFull` から戻ると、画像データは解凍され表示可能な画像 `m` に格納されます。 121 | 122 | このコードはまず初めに動作します。本当です。 123 | 124 | `NewReader` 呼び出しをちょうど `blockReader` を `NewReader` 呼び出しの中で組み立てたように、`ReadFull` の引数リストに置くことで一時変数 `lzwr` を避けることができますが、1行のコードに詰め過ぎかもしれません。 125 | 126 | ## 結論 127 | 128 | データを再構築するためにこのように部品を組み立てることにより、Goのインターフェースはソフトウェアを組み立てやすくします。この例では、型安全なUnixパイプラインのように私たちはデブロッカーと `io.Reader` インターフェースを用いたデコンプレッサを一緒に繋ぎ合わせGIFデコーダを実装しました。さらに私たちは暗に示された `Reader` インターフェースの実装としてデブロッカーも書き、そのときはパイプライン処理に合うような外部宣言や雛形を必要としませんでした。ほとんどの言語においてとてもコンパクトに、それでもなお綺麗にそして安全にこのデコーダを実装するのは難しいですが、そのインターフェース機構はGoでほとんど自然にそれを作る約束事を減らすことに対しプラスとなります。 129 | 130 | それは他の写真(この場合GIF)に対しても価値があります: 131 | 132 | ![gif-decoder-exercise-in-go-interfaces_image01](./gif-decoder-exercise-in-go-interfaces_image01.gif) 133 | 134 | GIFの形式は [http://www.w3.org/Graphics/GIF/spec-gif89a.txt](http://www.w3.org/Graphics/GIF/spec-gif89a.txt) で定義されています。 135 | 136 | *By Rob Pike* 137 | 138 | ## あわせて読みたい 139 | 140 | * [HTTP/2 Server Push](https://blog.golang.org/h2push) 141 | * [Introducing HTTP Tracing](https://blog.golang.org/http-tracing) 142 | * [GopherCon 2015 Roundup](https://blog.golang.org/gophercon2015) 143 | * [Generating code](https://blog.golang.org/generate) 144 | * [The Go Gopher](https://blog.golang.org/gopher) 145 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 146 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 147 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 148 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 149 | * [The Go Programming Language turns two](https://blog.golang.org/go-programming-language-turns-two) 150 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 151 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 152 | * [The Go image package](https://blog.golang.org/go-image-package) 153 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 154 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 155 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 156 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 157 | * [Go at Google I/O 2011: videos](https://blog.golang.org/go-at-google-io-2011-videos) 158 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 159 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 160 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 161 | * [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) 162 | * [JSON and Go](https://blog.golang.org/json-and-go) 163 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 164 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 165 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 166 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 167 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) 168 | -------------------------------------------------------------------------------- /content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image00.jpg -------------------------------------------------------------------------------- /content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image01.gif -------------------------------------------------------------------------------- /content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image02.jpg -------------------------------------------------------------------------------- /content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/gif-decoder-exercise-in-go-interfaces/gif-decoder-exercise-in-go-interfaces_image03.gif -------------------------------------------------------------------------------- /content/post/go-concurrency-patterns-timing-out-and.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2010-09-23T15:02:14+09:00" 3 | draft = false 4 | title = "Goの並行パターン:タイムアウトと進行(Go Concurrency Patterns: Timing out, moving on)" 5 | tags = ["concurrency", "technical"] 6 | +++ 7 | 8 | # Goの並行パターン:タイムアウトと進行 9 | 10 | [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) by Andrew Gerrand 11 | 12 | 並行プログラミングにはイディオムがあります。良い例はタイムアウトです。 13 | Goのチャンネルではタイムアウトを直接はサポートしていませんが、その実装は容易です。 14 | たとえば、`ch` チャンネルから値を受信したいけれど、1秒以上は待ちたくないという状況を考えてみましょう。 15 | まずシグナル用のチャンネルを作り、そのチャンネルに送信する前に1秒待つゴルーチンを起動します。 16 | 17 | ``` 18 | timeout := make(chan bool, 1) 19 | go func() { 20 | time.Sleep(1 * time.Second) 21 | timeout <- true 22 | }() 23 | ``` 24 | 25 | その後、`select`構文を使って `ch` か `timeout` を待つようにします。 26 | もし1秒待っても `ch` から何も来なければ、 `timeout` のケースが選択され、`ch`からの読み込みは破棄されます。 27 | 28 | ``` 29 | select { 30 | case <-ch: 31 | // chから読み込む 32 | case <-timeout: 33 | // chからの読み込みはタイムアウト 34 | } 35 | ``` 36 | 37 | `timeout` チャンネルは1つの値をバッファし、タイムアウトのゴルーチンがそのチャンネルに値を送り、終了できるようになっています。 38 | このゴルーチンは、`ch`から値が受け取られたかを知りません。(あるいは気にしていません) 39 | つまりこのゴルーチンは`ch`からの読み込みがタイムアウトより前に起こったとしても、永遠には存在しえません。 40 | `timeout` チャンネルは最終的にガベージコレクタによって回収されます。 41 | 42 | (この例ではゴルーチンとチャンネルの機構をデモするために `time.Sleep` を使いました。 43 | 実際のプログラムでは `time.After` という、チャンネルを返し、決まった時間のあとにそのチャンネルに値を送る関数を使うべきでしょう。) 44 | 45 | このパターンの他の例を見てみましょう。この例では複数のレプリケーションされたデータベースから同時に読み込むプログラムを扱っています。 46 | このプログラムでは、値は1つだけ必要で最初に来た値だけを取得すべきです。 47 | 48 | `Query` 関数はデータベース接続のスライスと問い合わせの文字列を引数に取ります。 49 | この関数は各データベースに並列に問い合わせ、最初に受信した結果を返します。 50 | 51 | ``` 52 | func Query(conns []Conn, query string) Result { 53 | ch := make(chan Result, 1) 54 | for _, conn := range conns { 55 | go func(c Conn) { 56 | select { 57 | case ch <- c.DoQuery(query): 58 | default: 59 | } 60 | }(conn) 61 | } 62 | return <-ch 63 | } 64 | ``` 65 | 66 | この例では、クロージャーがノンブロッキングに送信します。これは、 `default` ケース付きの `select` 構文内の送信操作を使うことで実現しています。 67 | もし送信が出来なければ、直ちに `default` ケースが選択されます。 68 | 送信をノンブロッキングにすることで、ループ内で立ち上げられたゴルーチンが1つも無駄に生存しないことが保証されます。 69 | しかしながら、親の関数が値を受信する前に結果が来れば、チャンネルのバッファの準備ができていないため送信は失敗する可能性があります。 70 | 71 | この問題は[競合条件](https://en.wikipedia.org/wiki/Race_condition)として知られるものの教科書的な例ですが、修正は些細なものです。 72 | `ch` チャンネルを(バッファの長さをmakeの第2引数に加えることで)バッファして、最初の送信処理が値を送れるように保証すれば良いだけです。 73 | これによって送信処理は常に成功し、実行順に関係なく最初に到着した値が受信されるようになります。 74 | 75 | この2つの例はGoがゴルーチン間の複雑なやりとりを表現する際の簡潔さを表しています。 76 | 77 | By Andrew Gerrand 78 | 79 | ## あわせて読みたい 80 | * [Generating code](https://blog.golang.org/generate) 81 | * [コードのジェネレート](../generate/) 82 | * [Go Concurrency Patterns: Context](https://blog.golang.org/context) 83 | * [Goの並行パターン:コンテキスト](../context/) 84 | * [Go Concurrency Patterns: Pipelines and cancellation](https://blog.golang.org/pipelines) 85 | * [Goの並行パターン:パイプラインとキャンセル](../pipelines/) 86 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 87 | * [Advanced Go Concurrency Patterns](https://blog.golang.org/advanced-go-concurrency-patterns) 88 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 89 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 90 | * [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism) 91 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 92 | * [Go videos from Google I/O 2012](https://blog.golang.org/go-videos-from-google-io-2012) 93 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 94 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 95 | * [The Go image package](https://blog.golang.org/go-image-package) 96 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 97 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 98 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 99 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 100 | * [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) 101 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 102 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 103 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 104 | * [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) 105 | * [JSON and Go](https://blog.golang.org/json-and-go) 106 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 107 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 108 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 109 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) 110 | -------------------------------------------------------------------------------- /content/post/go-image-package.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2011-09-21T22:46:24+09:00" 3 | draft = false 4 | title = "Go image パッケージ(The Go image package)" 5 | tags = ["image", "libraries", "technical"] 6 | +++ 7 | 8 | # Go image パッケージ 9 | 10 | [The Go image package](https://blog.golang.org/go-image-package) By Nigel Tao 11 | 12 | ## はじめに 13 | 14 | [image](http://golang.org/pkg/image/) と [image/color](http://golang.org/pkg/image/color/) パッケージはいくつかの型を定義しています。`color.Color` と `color.Model` は色を、`image.Point` と `image.Rectangle` は基本的な2次元幾何学をそれぞれ記述しており、`image.Image` は色の長方形格子を表現するためにそれら2つの概念を1つにまとめます。[別記事](http://golang.org/doc/articles/image_draw.html) では [image/draw](http://golang.org/pkg/image/draw/) を用いた画像の構成について取り上げています。 15 | 16 | ## Color と Color Model 17 | 18 | [Color](http://golang.org/pkg/image/color/#Color) は色として考えられる任意の型をまとめた最小限のメソッドの組を定義したインターフェースです。つまり、赤、緑、青、アルファ値に変換できるものです。CMYK や YCbCr 色空間から変換するといったある種の変換は不可逆かもしれません。 19 | 20 | ``` 21 | type Color interface { 22 | // RGBA returns the alpha-premultiplied red, green, blue and alpha values 23 | // for the color. Each value ranges within [0, 0xFFFF], but is represented 24 | // by a uint32 so that multiplying by a blend factor up to 0xFFFF will not 25 | // overflow. 26 | RGBA() (r, g, b, a uint32) 27 | } 28 | ``` 29 | 30 | 戻り値には3つの重要な巧妙さがあります。1つ目は、赤、緑、青はプリマルチプライド・アルファであることです。25% 透明な完全飽和赤は 75% の r を返す RGBA によって表現されます。2つ目に、チャネルの有効範囲は 16 ビットです。100% の赤は 255 ではなく 65535 の r を返す RGBA によって表現されているので、CMYK や YCbCr からの変換はさほど情報量を失いません。3つ目に、最大値は 65535 ですが、戻り値の型は `uint32` となっていることです。これは2つの値の積がオーバーフローしないことを保証するためです。[Porter-Duff の](https://en.wikipedia.org/wiki/Alpha_compositing)古典代数学のスタイルでアルファマスクに従う2つの色を混ぜ合わせて第3の色を生成する乗算の際に起ります。 31 | 32 | ``` 33 | dstr, dstg, dstb, dsta := dst.RGBA() 34 | srcr, srcg, srcb, srca := src.RGBA() 35 | _, _, _, m := mask.RGBA() 36 | const M = 1<<16 - 1 37 | // The resultant red value is a blend of dstr and srcr, and ranges in [0, M]. 38 | // The calculation for green, blue and alpha is similar. 39 | dstr = (dstr*(M-m) + srcr*m) / M 40 | ``` 41 | 42 | もし非プリマルチプライド・アルファを使って処理を行おうとすると、コード片の最後の行はより複雑になるでしょう。それが `Color` がプリマルチプライド・アルファな値を使う理由です。 43 | 44 | image/color パッケージも `Color` インターフェースを実装するいくつかの具象型を定義しています。例えば、[`RGBA`](http://golang.org/pkg/image/color/#RGBA) は古典的な "1 チャネルあたり 8 ビット" の色を表現する構造になっています。 45 | 46 | ``` 47 | type RGBA struct { 48 | R, G, B, A uint8 49 | } 50 | ``` 51 | 52 | `RGBA` の `R` フィールドは 0 から 255 の範囲の値をとる 8 ビットのプリマルチプライド・アルファな色であることに注意してください。0 から 65535 の範囲の値をとる 16 ビットのプリマルチプライド・アルファな色を生成するために 0x101 を値に掛けることによって `RGBA` は `Color` インターフェースを満たします。同様に、PNG の画像形式で使われるように [`NRGBA`](http://golang.org/pkg/image/color/#NRGBA) 構造型は 8 ビットの非プリマルチプライド・アルファな色を表現します。`NRGBA` のフィールドを直接操作する際は、その値は非プリマルチプライド・アルファですが、`RGBA` メソッドを呼ぶ際は、その戻り値はプリマルチプライド・アルファです。 53 | 54 | [`Model`](http://golang.org/pkg/image/color/#Model) は単純なもので、おそらく非可逆的に `色` を別の `色` に変換することができます。例えば、`GrayModel` は 任意の `色` を不飽和化された [`Gray`](http://golang.org/pkg/image/color/#Gray) に変換できます。 `Palette` は制限されたパレットにより任意の `色` をある `色` に変換できます。 55 | 56 | ``` 57 | type Model interface { 58 | Convert(c Color) Color 59 | } 60 | 61 | type Palette []Color 62 | ``` 63 | 64 | ## Point と Rectangle 65 | 66 | [`Point`](http://golang.org/pkg/image/#Point) は右方向と下方向に増加する軸に従った整数格子上の (x, y) 座標です。それはピクセルでも格子で区切られた正方形でもありません。`Point` は固有の幅や高さ、色を持っていませんが、以下の図では色付きの小さな正方形を用いています。 67 | 68 | ``` 69 | type Point struct { 70 | X, Y int 71 | } 72 | ``` 73 | 74 | ![go-image-package_image-package-01](./go-image-package_image-package-01.png) 75 | 76 | ``` 77 | p := image.Point{2, 1} 78 | ``` 79 | 80 | [`Rectangle`](http://golang.org/pkg/image/#Rectangle) は左上と右下の `Point` によって定義される整数格子上にある軸に平行な長方形です。`Rectangle` も固有の色を持っていませんが、以下の図では色付きの細い線を用いて長方形の輪郭を描き、`最小` および `最大` の `Point` を呼びます。 81 | 82 | ``` 83 | type Rectangle struct { 84 | Min, Max Point 85 | } 86 | ``` 87 | 88 | 便利のため `image.Rect(x0, y0, x1, y1)` は `image.Rectangle{image.Point{x0, y0}, image.Point{x1, y1}}` と同値としますが、前者のほうが入力するのは簡単です。 89 | 90 | `Rectangle` は左上の点を排他し、右下の点を包含しています。`Point p` と `Rectangle r` に対し、`r.Min.X <= p.X && p.X < r.Max.X` を満たす場合のみ `p.In(r)` と表します、`Y` についても同様です。これはスライス `s[i0:i1]` がどうやって下限を含み上限を排他するのかということに似ています。(配列やスライスと違い、`Rectangle` はよく非ゼロの原点を含みます。) 91 | 92 | ![go-image-package_image-package-02](./go-image-package_image-package-02.png) 93 | 94 | ``` 95 | r := image.Rect(2, 1, 5, 5) 96 | // Dx and Dy return a rectangle's width and height. 97 | fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 false 98 | ``` 99 | 100 | `Point` を `Rectangle` に加えることでその `Rectangle` を変化させます。点と長方形は右下の象限内に制限されていません。 101 | 102 | ![go-image-package_image-package-03](./go-image-package_image-package-03.png) 103 | 104 | ``` 105 | r := image.Rect(2, 1, 5, 5).Add(image.Pt(-4, -2)) 106 | fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 true 107 | ``` 108 | 109 | 2つの長方形が交わると、もう1つの長方形が現れます、それは空かもしれません。 110 | 111 | ![go-image-package_image-package-04](./go-image-package_image-package-04.png) 112 | 113 | ``` 114 | r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5)) 115 | // Size returns a rectangle's width and height, as a Point. 116 | fmt.Printf("%#v\n", r.Size()) // prints image.Point{X:2, Y:1} 117 | ``` 118 | 119 | 点と長方形は値型によって関数に代入されたり戻されたりします。`Rectangle` を引数に取る関数は2つの `Point` か4つの `int` を引数にとる関数と同様に優れているでしょう。 120 | 121 | ## Image 122 | 123 | [Image](http://golang.org/pkg/image/#Image) は `Rectangle` 内の格子で区切られた正方形を `Model` から作られる `Color` に射影します。"(x, y) のピクセル" は点 (x, y), (x+1, y), (x+1, y+1), (x, y+1) によって定義される格子で区切られた正方形の色を参照します。 124 | 125 | ``` 126 | type Image interface { 127 | // ColorModel returns the Image's color model. 128 | ColorModel() color.Model 129 | // Bounds returns the domain for which At can return non-zero color. 130 | // The bounds do not necessarily contain the point (0, 0). 131 | Bounds() Rectangle 132 | // At returns the color of the pixel at (x, y). 133 | // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid. 134 | // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one. 135 | At(x, y int) color.Color 136 | } 137 | ``` 138 | 139 | よくある誤りは `Image` の範囲が (0, 0) から始まると思い込むことです。例えば、アニメーション GIF が画像列を含み、典型的に初めの画像以降のそれぞれの `Image` が変化した領域のピクセルデータだけ保持し、その領域は (0, 0) から始まる必要はありません。`Image` m のピクセル全体を走査するための正しい方法は以下のような感じです: 140 | 141 | ``` 142 | b := m.Bounds() 143 | for y := b.Min.Y; y < b.Max.Y; y++ { 144 | for x := b.Min.X; x < b.Max.X; x++ { 145 | doStuffWith(m.At(x, y)) 146 | } 147 | } 148 | ``` 149 | 150 | `Image` の実装はピクセルデータのインメモリスライスを元にする必要はありません。例えば、[`Uniform`](http://golang.org/pkg/image/#Uniform) は広大で一様な色の `Image` で、そのインメモリ表現は単なるその色です。 151 | 152 | ``` 153 | type Uniform struct { 154 | C color.Color 155 | } 156 | ``` 157 | 158 | それでもなお典型的に、プログラムはスライスベースの画像を求めています。[`RGBA`](http://golang.org/pkg/image/#RGBA) や [`Gray`](http://golang.org/pkg/image/#Gray) のような構造型(他のパッケージでは `image.RGBA` や `image.Gray` として参照する)はピクセルデータのスライスを保持し `Image` インターフェースを実装します。 159 | 160 | ``` 161 | type RGBA struct { 162 | // Pix holds the image's pixels, in R, G, B, A order. The pixel at 163 | // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. 164 | Pix []uint8 165 | // Stride is the Pix stride (in bytes) between vertically adjacent pixels. 166 | Stride int 167 | // Rect is the image's bounds. 168 | Rect Rectangle 169 | } 170 | ``` 171 | 172 | それらの型も一度に1ピクセルを修正する `Set(x, y int, c color.Color)` メソッドを提供します。 173 | 174 | ``` 175 | m := image.NewRGBA(image.Rect(0, 0, 640, 480)) 176 | m.Set(5, 5, color.RGBA{255, 0, 0, 255}) 177 | ``` 178 | 179 | たくさんのピクセルデータを読み書きしたい場合はより効率的にできますが、構造型の `Pix` フィールドに直接アクセスするのはより複雑になります。 180 | 181 | スライスベースの `Image` の実装も同じ配列を元にした `Image` を返す `SubImage` メソッドを提供しています。サブスライス `s[i0:i1]` の内容を修正すると元のスライス `s` の内容に影響が及ぶことと同様に、サブ画像のピクセルを修正すると元の画像のピクセルに影響が及びます。 182 | 183 | ![go-image-package_image-package-05](./go-image-package_image-package-05.png) 184 | 185 | ``` 186 | m0 := image.NewRGBA(image.Rect(0, 0, 8, 5)) 187 | m1 := m0.SubImage(image.Rect(1, 2, 5, 5)).(*image.RGBA) 188 | fmt.Println(m0.Bounds().Dx(), m1.Bounds().Dx()) // prints 8, 4 189 | fmt.Println(m0.Stride == m1.Stride) // prints true 190 | ``` 191 | 192 | 画像の `Pix` フィールドで動作する下層のコードを扱ううえで、`Pix` の範囲を超えることは画像の範囲外のピクセルに影響を与えるということを理解しておいてください。上記の例では、`m1.Pix` によって変換されたピクセルは青で共有されます。`At` や `Set` メソッドまたは [image/draw パッケージ](http://golang.org/pkg/image/draw/) のような上層のコードは画像の範囲に対する操作を制限します。 193 | 194 | ## 画像の形式 195 | 196 | 標準パッケージライブラリは GIF、JPEG、PNG など多くのありふれた画像形式をサポートしています。もとの画像ファイルのフォーマットが分かっている場合、[`io.Reader`](http://golang.org/pkg/io/#Reader) から直接デコードできます。 197 | 198 | ``` 199 | import ( 200 | "image/jpeg" 201 | "image/png" 202 | "io" 203 | ) 204 | 205 | // convertJPEGToPNG converts from JPEG to PNG. 206 | func convertJPEGToPNG(w io.Writer, r io.Reader) error { 207 | img, err := jpeg.Decode(r) 208 | if err != nil { 209 | return err 210 | } 211 | return png.Encode(w, img) 212 | } 213 | ``` 214 | 215 | 形式不明な画像データがある場合、[`image.Decode`](http://golang.org/pkg/image/#Decode) 関数は形式を検出することができます。認識される形式の集合は実行時に構築され、標準パッケージライブラリ内の形式に制限されていません。画像形式パッケージは典型的に init 関数内で自身の形式を登録し、main パッケージは形式の登録が副作用するようにそのようなパッケージ単体を "アンダースコアインポート" できます。 216 | 217 | ``` 218 | import ( 219 | "image" 220 | "image/png" 221 | "io" 222 | 223 | _ "code.google.com/p/vp8-go/webp" 224 | _ "image/jpeg" 225 | ) 226 | 227 | // convertToPNG converts from any recognized format to PNG. 228 | func convertToPNG(w io.Writer, r io.Reader) error { 229 | img, _, err := image.Decode(r) 230 | if err != nil { 231 | return err 232 | } 233 | return png.Encode(w, img) 234 | } 235 | ``` 236 | 237 | *By Nigel Tao* 238 | 239 | ## あわせて読む 240 | * [HTTP/2 Server Push](https://blog.golang.org/h2push) 241 | * [Introducing HTTP Tracing](https://blog.golang.org/http-tracing) 242 | * [Generating code](https://blog.golang.org/generate) 243 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 244 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 245 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 246 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 247 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 248 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 249 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 250 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 251 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 252 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 253 | * [Spotlight on external Go libraries](https://blog.golang.org/spotlight-on-external-go-libraries) 254 | * [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) 255 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 256 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 257 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 258 | * [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) 259 | * [JSON and Go](https://blog.golang.org/json-and-go) 260 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 261 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 262 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 263 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 264 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) -------------------------------------------------------------------------------- /content/post/go-image-package/go-image-package_image-package-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/go-image-package/go-image-package_image-package-01.png -------------------------------------------------------------------------------- /content/post/go-image-package/go-image-package_image-package-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/go-image-package/go-image-package_image-package-02.png -------------------------------------------------------------------------------- /content/post/go-image-package/go-image-package_image-package-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/go-image-package/go-image-package_image-package-03.png -------------------------------------------------------------------------------- /content/post/go-image-package/go-image-package_image-package-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/go-image-package/go-image-package_image-package-04.png -------------------------------------------------------------------------------- /content/post/go-image-package/go-image-package_image-package-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/go-image-package/go-image-package_image-package-05.png -------------------------------------------------------------------------------- /content/post/matchlang.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2016-02-09T21:55:32+09:00" 3 | draft = false 4 | title = "Goにおける言語とロケールのマッチング (Language and Locale Matching in Go)" 5 | tags = ["language", "locale", "tag", "BCP 47", "matching"] 6 | +++ 7 | 8 | # Goにおける言語とロケールのマッチング 9 | [Language and Locale Matching in Go](https://blog.golang.org/matchlang) By Marcel van Lohuizen 10 | 11 | ## はじめに 12 | ウェブサイトのような、ユーザーインターフェースで複数の言語をサポートするアプリケーションを考えてみましょう。 13 | ユーザーが望む言語のリストがある場合、アプリケーションはどの言語を表示すべきか決めなければなりません。 14 | このとき、アプリケーションがサポートする言語とユーザーが好む言語の間で最適な組み合わせを選ばなければなりません。 15 | この記事ではなぜこの決定が難しいか、そしてどうやってGoがそれを手助けできるかを説明します。 16 | 17 | ## 言語タグ 18 | 言語タグ、あるいはロケール識別子、は使用されている言語と方言をコンピュータが理解できる形でにした識別子です。 19 | もっともよく知られたものとしては IETF BCP 47 という標準で、Goのライブラリもこの標準に準じています。 20 | BCP 47 で定義されている言語タグと、それらが表す言語や方言をいくつか例を挙げてみましょう。 21 | 22 | | タグ | 説明 | 23 | |:------|:------------| 24 | |en |英語 | 25 | |en-US |アメリカ英語 | 26 | |cmn |標準中国語 | 27 | |zh |中国語(通常は標準語)| 28 | |nl |オランダ語 | 29 | |nl-BE |フラマン語 | 30 | |es-419 |ラテンアメリカスペイン語| 31 | |az, az-Latn |ともにラテン文字で書かれたアゼルバイジャン語| 32 | |az-Arab |アラビア文字で書かれたアゼルバイジャン語| 33 | 34 | 言語タグは一般的に言語コード(上記での“en”, “cmn”, “zh”, “nl”, “az”)が来た後に付加的な文字に関する副タグ(“-Arab”)や 35 | 地域に関する副タグ(“-US”, “-BE”, “-419”)、変数の副タグ(オックスフォード英語大辞典でのスペルのための “-oxendict”)、あるいは 36 | 拡張副タグ(電話帳順のための “-u-co-phonebk”)が続きます。 37 | もっとも一般的な形式は副タグが省略された形、たとえば “az-Latn-AZ” であれば “az” です。 38 | 39 | 言語タグがもっとも使われる場所は、システムがサポートしている言語の一覧からユーザーが好みの言語を選択するときでしょう。 40 | たとえば、(アフリカーンス語が選択できない場合に)アフリカーンス語を望んでいるユーザーに対しシステムはオランダ語を表示するという決定をする場合です。 41 | このような対応は言語間の包含性に関するデータを参照するというプロセスが関わってきます。 42 | この対応から得られたタグは、その後言語特有のリソース、例えば翻訳、並び順、タイトルなどの大文字小文字を自動で変えるアルゴリズムなどを 43 | 取得するために使われます。これらの処理はまた別の対応を必要とします。たとえば、ポルトガル語には決まった並び順がないため、 44 | 並び順を処理するパッケージはデフォルトのもの、すなわち「ルート」の言語の並び順にフォールバックすることになるでしょう。 45 | 46 | ## 言語の対応に関する厄介な性質 47 | 言語タグを扱うには注意が必要です。その理由は、自然言語の境界があいまいであること、言語タグの標準の発展の歴史によるもの、などが挙げられます。 48 | この節では言語タグを扱う際の厄介な側面をいくつかご紹介します。 49 | 50 | 51 | ### 異なる言語コードのタグが同じ言語を表している 52 | 53 | 歴史的かつ政治的な理由で、多くの言語コードは時とともに変遷していて、古い言語コードと新しい言語コードが共存していました。 54 | しかし、古いものも新しいものも同じ言語を参照しています。たとえば、標準中国語を表す公式な言語コードは “cmn” ですが、 “zh” がずっと 55 | 広く使われています。 “zh” は公式にはいわゆるマクロ言語として中国語全般を表すために予約されています。 56 | マクロ言語用の言語タグは、しばしばその言語群の中でもっとも良く話されている言語のコードとして使われます。 57 | 58 | ### 言語コードの対応だけでは不十分 59 | 60 | たとえばアゼルバイジャン語(“az”)は国によって異なる文字で書かれています。"az-Latn" はラテン文字、"az-Arab" はアラビア文字、 61 | “az-Cyrl” はキリル文字です。もし “az-Arab” を単純に “az” に置き換えてしまうと、結果としてラテン文字が表示されて、 62 | アラビア文字で書かれたアゼルバイジャン語しか理解できない人には意味のないものになってしまうでしょう。 63 | 64 | 異なる地域であることも異なる文字が使われる可能性を示唆します。たとえば “zh-TW” と “zh-SG” はそれぞれ繁体字中国語、 65 | 簡体字中国語であることを示しています。他の例としては “sr” (セルビア語)はデフォルトではキリル文字ですが、 66 | “sr-RU” (ロシアで書かれるセルビア語)はラテン文字なのです!似た例はキルギスや他の言語でも見られます。 67 | 68 | 副タグを無視すると、ユーザーにとって意味の分からないものが表示されるかもしれません。 69 | 70 | ### ユーザーが選択していない言語が最適な場合もある 71 | 72 | もっとも普及したノルウェー語(“nb”)は、デンマーク語と見分けが付きません。もしノルウェー語が選択できないのであれば、 73 | デンマーク語が次点として選ばれるべきでしょう。同様に、スイスのドイツ語(“gsw”)を選択したユーザーに 74 | ドイツ語(“de”)が表示されても問題無いでしょう。しかし、その逆はまったく当てはまりません。 75 | ウイグル語を選択したユーザーに対しては英語よりも中国語にフォールバックさせたほうが良いでしょう。 76 | ユーザーが選択した言語がサポートされていない場合に、必ずしも英語にフォールバックすることが最適ではないのです。 77 | 78 | ### 翻訳よりも言語の選択の方が重要 79 | 80 | あるユーザーがデンマーク語を第1の選択肢に、ドイツ語を第2の選択肢にしたとしましょう。 81 | もしアプリケーションがドイツ語を選択するならば、コンテンツをドイツ語に翻訳するだけではなく、照合処理も(デンマーク語ではなく)ドイツ語の 82 | ルールを使わなければいけません。そうしないと、たとえば動物のリストを並べるときに “Bär” (日:クマ)が “Äffin” (日:メスザル)よりも 83 | 前に来てしまいます。 84 | 85 | ユーザーが選択した言語の中からサポートする言語を選択するというのは、ハンドシェイクアルゴリズムに似ています。 86 | まずあなたがやり取りするためのプロトコル(言語)を決定し、そのあとはセッションが続く間はすべてのコミュニケーションを 87 | そのプロトコルで行うことに終始します。 88 | 89 | ### フォールバックに「親」の言語を使うのは簡単なことではない 90 | 91 | あなたのアプリケーションがアンゴラのポルトガル語(“pt-AO”)をサポートしているとしましょう。 92 | [golang.org/x/text](http://golang.org/x/text) 内のパッケージでは、この方言に対しての、照合や表示といった、特別なサポートはありません。 93 | そのような状況で採るべき正しい行動は、もっとも近い親方言を対応させることです。言語は階層的な関係にあり、特定の言語には、 94 | より一般的な親の方言があります。たとえば、“en-GB-oxendict” の親は “en-GB” であり、その親は “en” で、さらにその親は未定義の言語を表す 95 | “und” となります。これはルート言語としても知られています。照合においては、ポルトガル語には特定の並び順がないため、collateパッケージは 96 | ルート言語の並び順を選択するでしょう。displayパッケージでアンゴラのポルトガル語に最も近い親は、ヨーロッパポルトガル語(“pt-PT”)で、 97 | よりわかりやす “pt” ではありません。こちらはブラジルのポルトガル語を表します。 98 | 99 | 一般的に、親子関係は簡単ではありません。もう少し例を挙げましょう。 “es-CL” の親は “es-419” で、 “zh-TW” の親は “zh-Hant”、その親は “und” です。 100 | 祖語を選択しようと単純に副タグを取っただけの処理をすると、ユーザーが理解できない「方言」を選択してしまうかもしれません。 101 | 102 | ## Goでの言語のマッチング 103 | 104 | Goのパッケージ [golang.org/x/text/language](http://golang.org/x/text/language) は言語タグに関するBCP 47の標準を実装し、 105 | 共通ロケールデータレポジトリ(CLDR)にあるデータに基づいて、どの言語を使うべきかを判断するサポートを追加しています。 106 | 107 | ユーザーの選択した言語とアプリケーションがサポートしている言語との対応をするサンプルプログラムを示します。 108 | 109 | ``` 110 | package main 111 | 112 | import ( 113 | "fmt" 114 | 115 | "golang.org/x/text/language" 116 | "golang.org/x/text/language/display" 117 | ) 118 | 119 | var userPrefs = []language.Tag{ 120 | language.Make("gsw"), // Swiss German 121 | language.Make("fr"), // French 122 | } 123 | 124 | var serverLangs = []language.Tag{ 125 | language.AmericanEnglish, // en-US fallback 126 | language.German, // de 127 | } 128 | 129 | var matcher = language.NewMatcher(serverLangs) 130 | 131 | func main() { 132 | tag, index, confidence := matcher.Match(userPrefs...) 133 | 134 | fmt.Printf("best match: %s (%s) index=%d confidence=%v\n", 135 | display.English.Tags().Name(tag), 136 | display.Self.Name(tag), 137 | index, confidence) 138 | // best match: German (Deutsch) index=1 confidence=High 139 | } 140 | ``` 141 | 142 | ## 言語タグを作成する 143 | 144 | ユーザーが選択した言語コードの文字列から `language.Tag` を生成するもっとも単純な方法は `language.Make` を使うことです。 145 | これを使うと、不正な形式の入力からであっても意味のある情報を抽出することができます。たとえば “en-USD” はたとえ `USD` が不正な副タグ 146 | であっても、“en”の形に直してくれます。 147 | 148 | `language.Make` はエラーを返しません。エラーを返したとしてもデフォルト言語を使うのが普通なので、エラーを返さないというのは 149 | より便利な形と言えます。自分でエラーを扱うのであれば `Parse` を使いましょう。 150 | 151 | HTTPの `Accept-Language` ヘッダはしばしばユーザーが望む言語を渡す方法として使われます。 `ParseAcceptLanguage` 関数は 152 | そのヘッダをパースして言語タグのスライスへ変換し、望ましい順番に並べます。 153 | 154 | デフォルトでは、languageパッケージはタグを正規化しません。たとえば、言語タグが「圧倒的大多数」によく使われているのであれば、 155 | BCP 47が推奨する形に言語タグの文字列を消したりしません。同様に、CLDRの推奨する形も無視します。 156 | つまり “cmn” は “zh” に置換されませんし、“zh-Hant-HK” は “zh-HK” に簡約化されません。言語タグを正規化してしまうと、 157 | ユーザーの意図などの役に立つ情報まで捨ててしまいかねません。かわりに正規化は `Matcher` で扱われます。 158 | プログラマが望めば、正規化に関するオプションもすべて利用できます。 159 | 160 | ## ユーザーが望む言語をサポートしている言語に対応させる 161 | 162 | `Matcher` はユーザーが望む言語をサポートしている言語に対応させます。ユーザーは言語の対応に関するすべてのややこしい事柄に 163 | 関わりたくないのであれば、 `Matcher` が選んだ言語を利用すること強く推奨します。 164 | 165 | `Match` メソッドはユーザーが望む言語タグを(BCP 47の拡張からなる)ユーザー設定を経由してサポートされている言語タグへと渡します。 166 | それゆえ、`Match` から返されたタグを言語特有のリソースを取得するために使うことが重要です。たとえば “de-u-co-phonebk” は 167 | ドイツ語での電話帳の並び順を要求します。この拡張は対応時には無視されますが、collateパッケージがそれぞれの並び順の変数を選択するときに使われます。 168 | 169 | `Matcher` はアプリケーションがサポートする言語で初期化されます。これらの言語は通常翻訳先となる言語です。 170 | この言語のセットは通常は固定されていて、これによって `Matcher` がアプリケーション起動時に作成できます。 171 | `Matcher` は `Match` の初期化コストを下げパフォーマンスを改善するために最適化されています。 172 | 173 | languageパッケージはもっともよく使用される言語タグの事前定義済みセットを提供しています。これはアプリケーションがサポートする言語セットとして 174 | 使うことが出来ます。ユーザーは一般的に、サポートされる言語にピッタリと合致するタグを選ばなければいけないという心配をしなくてすみます。 175 | たとえば、アメリカ英語(“en-US”)は、デフォルトでアメリカ英語となる、より一般的な英語(“en”)と相互互換的に使うことが出来ます。 176 | `Matcher` にも同様のことが当てはまります。アプリケーション側では両方の言語に対応してもよく、特有のアメリカのスラングを “en-US” 用に追加することも出来ます。 177 | 178 | ## マッチングの例 179 | 180 | 次の `Matcher` と、サポートする言語のリストを考えてみましょう。 181 | 182 | ``` 183 | var supported = []language.Tag{ 184 | language.AmericanEnglish, // en-US: first language is fallback 185 | language.German, // de 186 | language.Dutch, // nl 187 | language.Portuguese // pt (defaults to Brazilian) 188 | language.EuropeanPortuguese, // pt-pT 189 | language.Romanian // ro 190 | language.Serbian, // sr (defaults to Cyrillic script) 191 | language.SerbianLatin, // sr-Latn 192 | language.SimplifiedChinese, // zh-Hans 193 | language.TraditionalChinese, // zh-Hant 194 | } 195 | var matcher = language.NewMatcher(supported) 196 | ``` 197 | 198 | 様々なユーザー設定とそれに対応するサポート言語のリストの対応を見てみましょう。 199 | 200 | ユーザー設定で "he"(ヘブライ語)を選択した場合、最適な対応は "en-US"(アメリカ英語)です。 201 | 良い対応がないため、matcherはフォールバック先の言語(サポート言語のリストの先頭)を使用します。 202 | 203 | ユーザー設定で "hr"(クロアチア語)を選択した場合、最適な対応は "sr-Latn"(ラテン文字で書かれたセルビア語)です。 204 | 理由は、同じ文字で書かれている場合、セルビア語とクロアチア語はお互い理解できる言語だからです。 205 | 206 | ユーザー設定で "ru, mo"(ロシア語、次点でモルダビア語)を選択した場合、最適な対応は "ro"(ルーマニア語)です。 207 | 理由はモルダビア語は正規化すると "ro-MD"(モルドバでのルーマニア語)に分類されるからです。 208 | 209 | ユーザー設定で "zh-TW"(台湾での標準中国語)を選択した場合、最適な対応は"zh-Hans"(簡体字で書かれた標準中国語)ではなく、 210 | "zh-Hant"(繁体字で書かれた標準中国語)です。 211 | 212 | ユーザー設定で "af, ar"(アフリカーンス語、次点でアラビア語)を選択した場合、最適な対応は "nl"(オランダ語)です。 213 | どちらの設定も直接はサポートされていませんが、オランダ語は、フォールバック言語の英語の設定された言語に対する近さよりも、 214 | アフリカーンス語にずっと近く対応しています。 215 | 216 | ユーザー設定で "pt-AO, id"(アンゴラのポルトガル語、次点でインドネシア語)を選択した場合、最適な対応は "pt-PT"(ヨーロッパのポルトガル語) 217 | であり、 "pt"(ブラジルのポルトガル語)ではありません。 218 | 219 | ユーザー設定で "gsw-u-co-phonebk"(スイスのドイツ語で、照合は電話帳の順を使用)を選択した場合、最適な対応は 220 | "de-u-co-phonebk(ドイツ語で、照合は電話帳の順を使用)となります。ドイツ語はサーバーの言語リスト内ではスイスのドイツ語に最適な対応であり、 221 | 電話帳順の照合というオプションは持ち越されます。 222 | 223 | ## 信頼スコア 224 | 225 | Goではルールベースの消去法を使った粗い信頼スコアを使っています。 226 | 言語の対応は Exact、High(Exactではないが明確な曖昧さはない)、Low(おおよそ対応しているかもしれないし、していないかもしれない)、Noに 227 | 分類されます。複数の言語が同等に対応した場合には、タイブレークのルールが順番に実行されます。複数の言語が同等に対応した場合には、 228 | 最初の言語が返されます。これらの信頼スコアは、たとえば比較的弱い対応を拒否するときに役に立ちます。 229 | ほかにも、たとえば言語タグから最適な地域や文字のスコアを付けるときにも使われます。 230 | 231 | 他のプログラミング言語での実装では、より細やかな、変数スケールのスコアリングをしています。 232 | 私たちは、Goでの実装は粗いスコアリングにすることで、より簡潔な実装で、よりメンテナンスしやすく、より速くなることがわかり、 233 | それにより、より多くのルールを扱えることとなりました。 234 | 235 | ## サポートされた言語を表示する 236 | 237 | [golang.org/x/text/language/display](http://golang.org/x/text/language/display) パッケージは言語タグを 238 | たくさんの言語で名前をつけることが出来ます。このパッケージには「自分自身」のタグ名を自分の言語で表示できるようにもなっています。 239 | 240 | たとえば 241 | 242 | ``` 243 | var supported = []language.Tag{ 244 | language.English, // en 245 | language.French, // fr 246 | language.Dutch, // nl 247 | language.Make("nl-BE"), // nl-BE 248 | language.SimplifiedChinese, // zh-Hans 249 | language.TraditionalChinese, // zh-Hant 250 | language.Russian, // ru 251 | } 252 | 253 | en := display.English.Tags() 254 | for _, t := range supported { 255 | fmt.Printf("%-20s (%s)\n", en.Name(t), display.Self.Name(t)) 256 | } 257 | ``` 258 | 259 | というコードは次のように表示します。 260 | 261 | ``` 262 | English (English) 263 | French (français) 264 | Dutch (Nederlands) 265 | Flemish (Vlaams) 266 | Simplified Chinese (简体中文) 267 | Traditional Chinese (繁體中文) 268 | Russian (русский) 269 | ``` 270 | 271 | 2番めの列で、大文字化に違いがあることに注目してください。個々の言語のルールを反映しています。 272 | 273 | ## 結論 274 | 275 | ぱっと見では、言語タグはきちんとした構造化データに見えますが、自然言語を表現するものなので、言語タグ間の関係を表す構造は実際には非常に複雑です。 276 | 特に英語話者のプログラマは、しばしば言語タグの文字列を操作した、独自のアドホックな言語の対応処理を書きたくなる衝動に駆られます。 277 | この文章で述べたように、その結果はひどいものになりえます。 278 | 279 | Goの [golang.org/x/text/language](http://golang.org/x/text/language) パッケージは、この複雑な問題を解決しつつも、 280 | シンプルで使いやすいAPIを提供しています。このパッケージでGoプログラミングを楽しんでください。 281 | 282 | By Marcel van Lohuizen -------------------------------------------------------------------------------- /content/post/normalization.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2013-11-26T10:49:22+09:00" 3 | draft = false 4 | title = "Goでの文字列の正規化 (Text normalization in Go)" 5 | tags = ["strings", "bytes", "runes", "characters"] 6 | +++ 7 | 8 | # Goでの文字列の正規化 9 | 10 | [Text normalization in Go](https://blog.golang.org/normalization) by By Marcel van Lohuizen 11 | 12 | ## はじめに 13 | 14 | 先の[記事](./strings/)では、Goでの文字列、バイト、文字について説明していました。 15 | 私は `go.text` レポジトリ(訳注:現在は `golang.org/x/text` パッケージ群、以下原文で `go.text` の部分は置き換える。)で多言語文字列処理向けの様々なパッケージの開発に関わってきました。 16 | これらのパッケージのいくつかは別のブログポストに譲って、この記事では [go.text/unicode/norm](http://godoc.org/code.google.com/p/go.text/unicode/norm) (訳注:現在は [golang.org/x/text/unicode/norm](http://godoc.org/golang.org/x/text/unicode/norm))に焦点を当てたいと思います。 17 | このパッケージは、先の[文字列に関する記事](./strings/)、そして本記事のタイトルとなっている、文字列の正規化を扱います。 18 | 正規化は生のバイト列よりも高水準での抽象化を扱います。 19 | 20 | 正規化についてのすべてを知りたければ、[Unicode標準の付録15](http://unicode.org/reports/tr15/)を読むのが良いでしょう。 21 | より読みやすい記事としては、対応する[Wikipediaのページ](http://en.wikipedia.org/wiki/Unicode_equivalence)(訳注:[日本語版](https://ja.wikipedia.org/wiki/Unicode%E6%AD%A3%E8%A6%8F%E5%8C%96))があります。 22 | ここでは、正規化がどのようにGoに関わっているかに焦点を当てます。 23 | 24 | ## 正規化とは何か 25 | 26 | 同じ文字列を表現するときにいくつかの方法があることがしばしばあります。たとえば、é(eのアキュート)は文字列内で単一のルーン("\u00e9")または 27 | 'e'のあとにアキュート・アクセントが続いたもの("e\u0301")として表現されます。Unicode標準によれば、この2つの表現はどちらも「正準等価」 28 | であり、同等に扱われるべきです。 29 | 30 | これら2つの表現の等価性を調べるために1バイトごとに比較していたのでは正しい結果は導けません。Unicodeでは2つの表現が正準に等価で、 31 | 同じ正規形に正規化される場合に、そのバイト表現が同じになるような正規形のセットを定義しています。 32 | 33 | またUnicodeでは同じ文字を表すけれども見た目が異なる表現を同等とみなす「互換等価」を定義しています。 34 | たとえば上付き文字の数字 '⁹' と通常の数字 '9' はこの形式では等価です。 35 | 36 | これら2つ等価な形式に対して、Unicodeでは合成と分解を定義しています。 37 | 合成は、結合して1つのルーンにできる複数のルーンをその1つのルーンにい置きv換えることです。 38 | 分解は、ルーンを要素に切り離すことを指します。すべてNFから始まる次の表は、Unicodeコンソーシアムが各形式を識別する際に使っているものです。v 39 | 40 | | |**合成** |**分解** | 41 | |:-----------|:---------|:-----------| 42 | |**正準等価** |NFC |NFD | 43 | |**互換等価** |NFKC |NFKD | 44 | 45 | ## Goの正規化に対するアプローチ 46 | 47 | 文字列に関する記事で言及したように、Goでは文字列内の文字が正規化されていることを保証していません。 48 | しかしながら、`golang.org/x/text` パッケージがそれを補填してくれます。 49 | たとえば [collate](http://godoc.org/golang.org/x/text/collate) パッケージは、Go言語特有の方法で文字列を順場に並べるパッケージで、 50 | これは正規化されていない文字列でも正しく動作します。 51 | `golang.org/x/text` 内のパッケージは必ずしも入力が正規化されている必要はありませんが、一般的に一貫した結果を得るためには 52 | 正規化が必要でしょう。 53 | 54 | 正規化のコストはタダではないですが速いです。特に、照合や検索の場合、または文字列がNFDかNFCのいずれかで、バイトを並び替えなくても分解するだけで 55 | NFDになる場合は顕著です。実際に、99.98%のウェブ上のHTMLページのコンテンツはNFC形式です。(マークアップは含めていません。 56 | 含めた場合はその割合はより大きくなります。)ほぼ間違いなくたいていのNFCは(メモリの確保を必要とする)並び替えの必要なく分解するだけでNFDになります。 57 | また、並び替えが必要な場合の検出も効率的なので、それが必要なまれな区画に対してだけ並び替えを行うことで時間を節約することが出来ます。 58 | 59 | より効率よくするために、照合(collate)のパッケージは通常は `norm` パッケージを直接は使わず、代わりに自身が持っている表の中に 60 | 正規化に関する情報をインタリーブするために `norm` パッケージを使います。 61 | 並び替えと正規化をインタリーブすることで、パフォーマンスに影響をあたえることなくその場で実行することが可能になります。 62 | オンザフライでの正規化のコストは事前に事前に文字列を正規化する必要がないことで補償され、編集時には正規化形式になっていることを保証します。 63 | 特に後者は厄介です。たとえば、2つのNFCで正規化された文字を合成してもNFCになるとは限らないからです。 64 | 65 | もちろん、よくあることですが、事前に文字列が正規化済みであることがわかっているのであれば、明白なオーバーヘッドは避けるべきでもあります。 66 | 67 | ## 何が困るのか 68 | 69 | これまで正規化をできれば避けたいという話をしてきましたが、そもそもなぜそれが懸念事項になるのか疑問の人もいるでしょう。 70 | その理由は、正規化が必要で、正規化が何か、そして正規化を正しくする方法を理解することが重要である場合があるからです。 71 | 72 | これらについて議論する前に、まず「文字」という概念を明確にしなければなりません。 73 | 74 | ## 文字とは何か 75 | 76 | 文字列に関する記事で説明したとおり、文字は複数のルーンに渡ることがあります。たとえば 'e' と '◌́'(アキュート "\u0301")は合成して 77 | 'é' という形式(NFDでは "e\u0301" )になります。この2つのルーンをまとめて1つの文字を表しています。 78 | 文字の定義は実装によって変わります。正規化においては文字を、他のルーンを変更したり後ろ向きに合成しないルーンと定義した開始ルーンから始まり、 79 | (通常はアクセントなどの)修飾などを行う後続ルーンによるルーン列として定義しています。後続ルーンの列は空になりえます。 80 | 正規化アルゴリズムは一度に1文字を処理します。 81 | 82 | 理論上は1つのUnicode文字を作る上でのルーン数には上限はありません。事実、文字に続く修飾の数、その繰り返しの数、修飾の重ねあわせには 83 | 上限がありません。 'e' に3つアキュートが付いたものを見たことがありますか。これです。'é́́' これは、標準上は完全に正しい4ルーンの文字です。 84 | 85 | 結果として、最下層においても、文字列は無制限のチャンクサイズの積み重ねの中で処理が行われる必要があります。 86 | 特にこれは、Go標準の `Reader` や `Writer` のインターフェースのように、文字列処理をストリームで取り組むときに扱いづらくなります。 87 | なぜなら、このモデルでは潜在的に無制限のサイズを保持するための中間バッファーも持つ必要もあるからです。 88 | また、率直に正規化を実装すると、処理が O(n²) になってしまいます。 89 | 90 | 実際に適用する場合には、このような大きな修飾のシーケンスを意味のある形で解釈することはありません。 91 | Unicodeでは Stream-Safe Text format(ストリーム安全な文字列形式)を定義しています。 92 | これは修飾(後続ルーン)の数の上限を最大で30に定めていて、この数は実用では十分な大きさです。 93 | それ以降の修飾はまとめられて、結合書記素結合子(Combining Grapheme Joiner, CGJ, U+034F)に置き換えられます。 94 | この決定によって、原文との一致性は少し下がりますが、より安全に処理できるようになります。 95 | 96 | ## 正規化形式で書く 97 | 98 | Goのコード内で正規化をする必要がない場合でもなお、外部とやり取りするときにはそうしたくなる場合があるでしょう。 99 | たとえば、NFCに正規化すると文字列を小さくでき、送信するコストを小さくすることが出来ます。 100 | ある言語、たとえば韓国語では、データを小さくすることは有用です。また、外部のAPIが特定の正規化形式を期待している場合もあります。 101 | あるいは、外部のシステムと同様に、ただ正規化してNFC形式にしたい場合もあるでしょう。 102 | 103 | 文字列をNFCとして書くには、[unicode/norm](http://godoc.org/code.google.com/p/go.text/unicode/norm)パッケージで 104 | `io.Writer`をラップして使うのが良いでしょう。 105 | 106 | ``` 107 | wc := norm.NFC.Writer(w) 108 | defer wc.Close() 109 | // 通常と同様にwriteする 110 | ``` 111 | 112 | 短い文字列を手早く変換したい場合は、この簡単な形式でも良いでしょう。 113 | 114 | ``` 115 | norm.NFC.Bytes(b) 116 | ``` 117 | 118 | `norm` パッケージは文字列の正規化のために他にも様々なメソッドを用意しています。 119 | 必要に応じて最適なものを選んでください。 120 | 121 | ## 類似した文字を見つける 122 | 123 | 'K'("\u004B")と 'K'(ケルビン記号 "\u212A")あるいは 'Ω'("\u03a9")と 'Ω'(オーム記号 "\u2126")の違いがわかりますか。 124 | 根本的には同じ文字の異形での細かな差異は、見逃しやすいものです。そのような異形を識別子やそのような類似した文字でユーザを惑わしすことが 125 | セキュリティの危険性を晒すような場所で用いることを禁止するのは良い考えです。 126 | 127 | NFKCやNFKDというような標準系は見た目が近い同一の形式を一つの値に対応させます。 128 | 2つのシンボルの見た目が似ていても、実際に異なるアルファベットの場合はこのような対応はしないことに注意してください。 129 | たとえば、ラテン文字の 'o'、ギリシャ文字の 'ο'、キリル文字の 'о' は依然として、これらの標準系で定義されたように異なる文字です。 130 | 131 | ## 文字列の変更を訂正する 132 | 133 | `norm` パッケージは文字列を修正する必要があるときにも助けになってくれます。 134 | "cafe" という単語を複数形の "cafes" に置換したい状況を考えてみましょう。 135 | コードスニペットは次のようになります。 136 | 137 | ``` 138 | s := "We went to eat at multiple cafe" 139 | cafe := "cafe" 140 | if p := strings.Index(s, cafe); p != -1 { 141 | p += len(cafe) 142 | s = s[:p] + "s" + s[p:] 143 | } 144 | fmt.Println(s) 145 | ``` 146 | 147 | このスニペットの出力は期待通り "We went to eat at multiple cafes" と表示されます。 148 | それでは、NFD形式で書かれたフランス語の綴りである "café" を含む文字列を考えてみましょう。 149 | 150 | ``` 151 | s := "We went to eat at multiple cafe\u0301" 152 | ``` 153 | 154 | 先程と同じスニペットを使うと、同じく複数形の "s" は 'e' の後に挿入されますが、アキュートの前に挿入されてしまいます。 155 | 結果は "We went to eat at multiple cafeś" となります。これは期待した結果ではありません。 156 | 157 | このコードが複数のルーンを使った文字の境界を反映せずに、文字の真ん中にルーンを挿入してしまうことが問題です。 158 | `norm` パッケージを用いて、先ほどのスニペットを次のように書き換えることが出来ます。 159 | 160 | ``` 161 | s := "We went to eat at multiple cafe\u0301" 162 | cafe := "cafe" 163 | if p := strings.Index(s, cafe); p != -1 { 164 | p += len(cafe) 165 | if bp := norm.FirstBoundary(s[p:]); bp > 0 { 166 | p += bp 167 | } 168 | s = s[:p] + "s" + s[p:] 169 | } 170 | fmt.Println(s) 171 | ``` 172 | 173 | この例は作為的なものですが、このコード片がやろうとしていることは明らかでしょう。 174 | 文字は複数のルーンから構成されうるという事実を意識しましょう。 175 | 一般的にこのような問題は文字の境界を認識している検索機能(`golang.org/x/text` パッケージとして計画されているようなもの)を使うことで 176 | 避けることが出来ます。 177 | 178 | ## イテレーション 179 | 180 | 他に `norm` パッケージより提供されている、文字列の境界を扱う上で便利な機能にはイテレータがあります。内容は [norm.Iter](http://godoc.org/golang.org/x/text/unicode/norm#Iter) で確認してください。 181 | これは選択した正規化形式での文字を1つずつイテレーションしていきます。 182 | 183 | ## 技を披露する 184 | 185 | 先にも述べたように、たいていの文字列はNFC形式で、このとき可能な限り土台の文字と修飾子は合成されて1つのルーンにされます。 186 | 文字を解析する場合には、しばしばルーンを最小限の要素に分解した後のほうが扱いやすいことがあります。 187 | このようなときNFD形式が便利です。たとえば、次のスニペットでは文字列を最小限の部品に分解し、 188 | アクセント記号をすべて取り除き、再度文字列をNFC形式に合成する `transform.Transformer` を作成します。 189 | 190 | ``` 191 | import ( 192 | "unicode" 193 | 194 | "golang.org/x/text/transform" 195 | "golang.org/x/text/unicode/norm" 196 | ) 197 | 198 | isMn := func(r rune) bool { 199 | return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks 200 | } 201 | t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) 202 | ``` 203 | 204 | ここで作成された `Transformer` は、次のように `io.Reader` 内のアクセントを取り除くために使うことが出来ます。 205 | 206 | ``` 207 | r = transform.NewReader(r, t) 208 | // 通常と同様にreadする 209 | ``` 210 | 211 | たとえば、このコードは、元の文字列がどのように正規化されていても、すべての "cafés" を "cafes" に変換します。 212 | 213 | ## 正規化の情報 214 | 215 | 先に述べたように、パッケージによっては正規化を事前に計算してテーブルに保存し、ランタイムでの正規化を必要最低限にします。 216 | `norm.Properties` 型はこれらのパッケージに必要なルーン毎の情報にアクセスできるようにしています。 217 | 特筆すべきは正規結合クラス(Canonical Combining Class)と分解情報にアクセスできる点です。 218 | この型の詳細を知りたい方は [ドキュメント](http://godoc.org/golang.org/x/text/unicode/norm/#Properties) を読んでください。 219 | 220 | ## パフォーマンス 221 | 222 | 正規化のパフォーマンスを理解してもらうために、 `strings.ToLower` のパフォーマンスと比較してみましょう。 223 | 次の表の1行目はすべて小文字でNFCになっており、すべての文字はそのまま返されます。 224 | 2行目のサンプルは大文字も混じりNFCでない文字も含まれていて、変換が必要になります。 225 | 226 | | 入力 |ToLower |NFC追加 |NFC変換 |NFCイテレーション | 227 | |:-------------------|:-------|:----------|:-------------|:---------------| 228 | |nörmalization |199 ns |137 ns |133 ns |251 ns (621 ns) | 229 | |No\u0308rmalization |427 ns |836 ns |845 ns |573 ns (948 ns) | 230 | 231 | イテレータの結果に関する列ではイテレータの初期化をすでにしている場合としていない場合の両方の計測結果を表示しています。 232 | 初期化をした場合は次回以降のイテレーションではバッファを再利用することが出来ます。 233 | 234 | ごらんの通り、文字列が正規化されているかどうかは非常に効率的に判断されています。 235 | 2行目の正規化のコストはバッファの初期化によるものが大きく、そのコストは大きな文字列を扱う際にはならされます。 236 | これらのバッファはあまり必要ないとわかってきたので、よく使われる小さい文字列の場合にもっと高速化できるように、 237 | いずれ実装を変更するかもしれません。 238 | 239 | ## 結論 240 | 241 | あなたがGoのプログラム内で文字列を扱っているのであれば、通常は文字列の正規化に `unicode/norm` パッケージを使う必要ありません。 242 | このパッケージは文字列を外のシステムに送るとき、あるいはより発展的な文字列処理をしたいときに、文字列が確実に正規化されているように 243 | したい場合には便利です。 244 | 245 | この記事では多言語文字列処理と同時に他の `golang.org/x/text` のパッケージについても簡単に触れまいた。 246 | そしてこの記事で理解したものの数よりも多くの疑問が湧いてきことでしょう。しかしながら、この話題に関する議論はまたの機会にしましょう。 247 | 248 | By Marcel van Lohuizen 249 | 250 | ## あわせて読みたい 251 | 252 | * [Strings, bytes, runes and characters in Go](https://blog.golang.org/strings) 253 | * [Goにおける文字列、バイト、ルーンと文字](../strings/) 254 | 255 | -------------------------------------------------------------------------------- /content/post/package-names.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-02-04T14:49:44+09:00" 3 | draft = false 4 | title = "パッケージ名 (Package names)" 5 | +++ 6 | 7 | # パッケージ名 8 | 9 | [Package names](http://blog.golang.org/package-names) By Sameer Ajmani 10 | 11 | ## 序文 12 | 13 | Goのコードはパッケージの形で整理されています。同一パッケージ内では、どのような識別子(名前)も 14 | 参照することが可能ですが、そのパッケージを利用する場合は、パッケージが外部に公開している型、関数、 15 | 定数、変数しか参照できません。パッケージの参照元は常に接頭辞としてパッケージ名を付ける必要があります。 16 | 例えば、 `foo.Bar` はインポートしている `foo` というパッケージ内の `Bar` という公開された名前を 17 | 参照しています。 18 | 19 | 良いパッケージ名はコードをより良いものにします。パッケージ名は、その中身の文脈を教えてくれ、参照元で 20 | 利用しているパッケージの目的と使用方法を理解しやすくしてくれます。またパッケージ名は、次第にそれが 21 | 大きくなるにつれて、メンテナーに何がそのパッケージに入るべきで、何が入るべきでないかを決定する手助けとなります。 22 | 良い名前を付けられたパッケージは必要なコードも探しやすくなります。 23 | 24 | Effective Goではパッケージ、型、関数、変数の命名に関する 25 | [ガイドライン](https://golang.org/doc/effective_go.html#names) を提供しています。 26 | この文章ではそちらに書かれている内容をさらに発展させて、標準パッケージ内で見られる 27 | 名前について調べてみようと思います。また悪いパッケージ名についても議論し、その修正方法についても 28 | 考えてみましょう。 29 | 30 | ## パッケージ名 31 | 32 | 良いパッケージ名は短くて明確です。小文字で、アンダースコア( `under_score` )がなく、 33 | 大文字小文字が混ざったもの( `mixedCaps` )でもありません。簡潔な名詞であることが多いです。 34 | たとえば次のようなものが挙げられます。 35 | 36 | * `time` (時間や時刻を計測したり表示する機能を提供している) 37 | * `list` (双方向リストを実装している) 38 | * `http` (HTTPクライアントとサーバの実装を提供している) 39 | 40 | 他の言語で典型的な命名規則はGoのプログラムではおそらく自然なものではないでしょう。 41 | ここに、他言語では良いスタイルとされているけれどGoでは不適切なパッケージ名の2つ例を挙げてみます。 42 | 43 | * `computeServiceClient` 44 | * `priority_queue` 45 | 46 | Goのパッケージではいくつかの型や関数を公開する方法をとっています。たとえば `compute` パッケージでは 47 | `Client` 型とサービスを使うためにメソッドを公開したり、複数のクライアント間での処理を分割するために 48 | 関数を公開することもできるでしょう。 49 | 50 | **短縮形は慎重に使うこと。** パッケージ名ではプログラマによく知られた短縮形であれば使ってもよいでしょう。 51 | 広く使われるパッケージ名はしばしば短縮形の名前になっています。 52 | 53 | * `strconv` (string conversion、文字列の変換) 54 | * `syscall` (system call、システムコール) 55 | * `fmt` (formmated I/O) 56 | 57 | 一方で、パッケージ名を短くしたせいでそのパッケージが曖昧であったり不明瞭になるようであれば、短縮をやめましょう。 58 | 59 | **ユーザから良い名前をとってしまわないようにしましょう。** クライアント側のコード内でよく使われる名前をパッケージ名に 60 | するのはやめましょう。たとえば、バッファ済みI/Oのパッケージは `bufio` であり、 `buf` ではありません。 61 | これは `buf` がバッファ用の変数名として都合が良いものだからです。 62 | 63 | ## パッケージ内の要素の命名 64 | 65 | クライアント側ではパッケージ名とそのパッケージ内の要素の名前を一緒に使うので、両者は対をなすものです。 66 | パッケージを設計する際には、クライアントで利用されるときのことを考えましょう。 67 | 68 | **どもった名前を避けましょう。** クライアント側ではパッケージの要素を参照する際にパッケージ名を接頭辞として使うので、 69 | そのような要素ではパッケージ名を繰り返さないようにしましょう。 `http` パッケージで提供されるHTTPサーバは `Server` であり、 70 | `HTTPServer` ではありません。クライアント側ではこの要素を `http.Server` という形で参照するので、曖昧さはありません。 71 | 72 | **関数名を簡潔にしましょう。** pkgパッケージの関数が `pkg.Pkg` 型(あるいは `*pkg.Pkg` 型)の値を返すときは、 73 | 関数名では混乱を避けるために型名を省略することがしばしばあります。 74 | 75 | ``` 76 | start := time.Now() // start は time.Time 型 77 | t, err := time.Parse(time.Kitchen, "6:06PM") // t は time.Time 型 78 | ``` 79 | 80 | `pkg` パッケージの `New` という関数は `pkg.Pkg` 型の値を返します。この関数はクライアント側でその型を使うときの 81 | 標準的なエントリーポイントです。 82 | 83 | ``` 84 | q := list.New() // q は *list.List 型 85 | ``` 86 | 87 | 関数が `pkg.T` 型の値を返し、 `T` が `pkg` 出ない場合、関数名にはクライアント側でわかりやすいように `T` が入るでしょう。 88 | よくある状況はパッケージ内に複数の New 系の関数がある場合です。 89 | 90 | ``` 91 | d, err := time.ParseDuration("10s") // d は time.Duration 型 92 | elapsed := time.Since(start) // elapsed は time.Duration 型 93 | ticker := time.NewTicker(d) // ticker は *time.Ticker 型 94 | timer := time.NewTimer(d) // timer は *time.Timer 型 95 | ``` 96 | 97 | クライアント側ではパッケージ名で区別できるため、異なるパッケージで同名の型を持つことが可能です。 98 | たとえば、標準ライブラリには `Reader` という名前の型がいくつかあります。 `jpeg.Reader` 、 99 | `bufio.Reader` 、 `csv.Reader` などがそれにあたります。パッケージ名 が `Reader` という名前と 100 | うまく合うようにすると、良い型名を作ることができます。 101 | 102 | もしパッケージの中身をよく表している接頭辞になるようなパッケージ名が思い浮かばない場合は、 103 | おそらくパッケージの抽象化が間違っているでしょう。クライアント側でそう使われるべきコードを書き、 104 | 書いたコードがみすぼらしい場合はパッケージを再構築しましょう。こういった姿勢でいれば、クライアント側で 105 | わかりやすいパッケージ名を定義することができ、メンテナンスもしやすいパッケージを作れます。 106 | 107 | ## パッケージのパス 108 | Goのパッケージには名前とパスがあります。パッケージ名はソースファイルの中のpackage宣言で確認できます。 109 | クライアントコードではパッケージ名を公開された名前として接頭辞に使います。クライアントコードでは 110 | パッケージをインポートするときにパッケージのパスを使います。慣例的に、パッケージのパスの最後の要素は 111 | パッケージ名になっています。 112 | 113 | ``` 114 | import ( 115 | "fmt" // fmt パッケージ 116 | "os/exec" // exec パッケージ 117 | "golang.org/x/net/context" // context パッケージ 118 | ) 119 | ``` 120 | 121 | ビルドツールはパッケージ名をディレクトリと対応させます。goツールは環境変数 [GOPATH](https://golang.org/doc/code.html#GOPATH) を使って、 "`github.com/user/hello`" 122 | というパスのファイルを `$GOPATH/src/github.com/user/hello` というディレクトリから探します。 123 | (もちろん、この動作はよく知られていることだとは思いますが、専門用語とパッケージの構造に関して明確にしておくことは大事です。) 124 | 125 | **ディレクトリ** 標準ライブラリでは、関連したプロトコルやアルゴリズムを使っているパッケージをまとめるために 126 | `crypto` 、 `cnotainer` 、 `encoding` 、 `image` というようにディレクトリを使っています。 127 | こういったディレクトリ内のパッケージ間は関連はありません。ディレクトリは単純にファイルを整理しているだけです。 128 | 循環参照をしていない限り、どんなパッケージでもインポートすることが可能です。 129 | 130 | 異なるパッケージで曖昧さなしに同名の型を使えるように、異なるディレクトリで同名のパッケージを作ることができます。 131 | たとえば [runtime/pprof](https://golang.org/pkg/runtime/pprof) はプロファイリングツール [pprof](https://code.google.com/p/gperftools) で 132 | 期待される書式のプロファイリングデータを生成し、一方で [net/http/pprof](https://golang.org/pkg/net/http/pprof) はこの書式でプロファイリングデータを 133 | 表示するHTTPエンドポイントを提供します。クライアントコードではそれらのパッケージをインポートする際にはパッケージのパスを利用するので、 134 | 混乱することはありません。もしクライアントのコードが今挙げた両方の `pprof` パッケージを利用する必要がある場合には、片方あるいは両方のパッケージを 135 | [リネーム](https://golang.org/ref/spec#Import_declarations) することができます。パッケージをリネームする際には、 136 | ローカルの名前はパッケージ名のためのガイドラインに従いましょう。(すべて小文字で `under_score` や `mixedCaps` がないもの) 137 | 138 | ## 悪いパッケージ名 139 | パッケージ名が悪いとコードが読みづらく、メンテナンスが難しくなります。いつくか悪いパッケージ名を認識し修正するためのガイドラインを挙げてみます。 140 | 141 | **意味のないパッケージ名を避ける** `util` 、 `common` 、 `misc` という名前では、クライアント側でそのパッケージが何のためのものなのかまったくわかりません。 142 | こういったパッケージ名では、クライアント側でどのようにそのパッケージを利用するかがわかりませんし、メンテナーがパッケージのメンテナンスに集中 143 | することが難しくなります。やがて、こういったパッケージは依存関係が大きくなり、ビルド時間が深刻にそして不必要に遅くなります。特に、大きな 144 | プログラムでこの傾向があります。またこのようなパッケージ名は一般的な名前なので、クライアント側でインポートされている他のパッケージと 145 | 衝突しやすく、区別するためにクライアント側に新しい名前を考えさせることを強制します。 146 | 147 | **一般的なパッケージに分割する** このようなパッケージを修正するために、共通の名前の要素を持った型と関数を探し 148 | そのような型や関数を独自のパッケージにまとめましょう。たとえば、次のようなコードがあったとします。 149 | 150 | ``` 151 | package util 152 | func NewStringSet(...string) map[string]bool {...} 153 | func SortStringSet(map[string]bool) []string {...} 154 | ``` 155 | 156 | クライアント側のコードは次のようになっているとします。 157 | 158 | ``` 159 | set := util.NewStringSet("c", "a", "b") 160 | fmt.Println(util.SortStringSet(set)) 161 | ``` 162 | 163 | これらの関数を `util` パッケージから取り出して、その内容に合う名前の新しいパッケージに移します。 164 | 165 | ``` 166 | package stringset 167 | func New(...string) map[string]bool {...} 168 | func Sort(map[string]bool) []string {...} 169 | ``` 170 | 171 | するとクライアント側のコードはこうなります。 172 | 173 | ``` 174 | set := stringset.New("c", "a", "b") 175 | fmt.Println(stringset.Sort(set)) 176 | ``` 177 | 178 | 一度このような変更をしてしまえば、新しいパッケージにどのような変更を加えればよいかわかりやすくなるでしょう。 179 | 180 | ``` 181 | package stringset 182 | type Set map[string]bool 183 | func New(...string) Set {...} 184 | func (s Set) Sort() []string {...} 185 | ``` 186 | 187 | このように変更することでクライアント側のコードはずっと簡潔になります。 188 | 189 | ``` 190 | set := stringset.New("c", "a", "b") 191 | fmt.Println(set.Sort()) 192 | ``` 193 | 194 | パッケージ名はパッケージの設計において最重要項目です。プロジェクト内の無意味なパッケージ名は排除しましょう。 195 | 196 | **全APIを単一パッケージにしない** 多くの善意なプログラマは、コードベースの中でエントリーポイントを見つけやすいように 197 | 公開するすべてのインターフェースを `api` 、 `types` 、 `interfaces` といった名前の単一のパッケージに 198 | まとめています。これは間違いです。そのようなパッケージは `util` や `common` というパッケージ名で 199 | 起きた問題と同じ問題に悩むことになります。つまり境界がどんどん大きくなり、ユーザにわかりにくく、 200 | 依存が強くなり、他のインポートしたパッケージと名前がぶつかるといった問題です。 201 | ディレクトリを使って実装から公開するパッケージを切り離す形で分割しましょう。 202 | 203 | **不必要なパッケージ名の衝突を避ける** 異なるディレクトリ内では同名のパッケージを置くことができるとはいっても、 204 | パッケージが一緒に使われることはよくあるので、パッケージには個別の名前をつけるべきです。そうすることで、 205 | 混乱を減らし、クライアント側のコードでパッケージ名のリネームをする必要が少なくなります。 206 | 同様の理由で、よく知られた `io` や `http` といった標準パッケージと同名のパッケージ名も避けましょう。 207 | 208 | ## 結論 209 | パッケージ名はGoのプログラムを書く上で良い命名の中心となるものです。時間をかけて良いパッケージ名を選び、 210 | コードをきれいに整頓しましょう。そうすることであなたのパッケージは使う側が理解しやすく使いやすいものとなり、 211 | またメンテナーが優雅に開発を続けられるプロジェクトとなることでしょう。 212 | 213 | ## あわせて読みたい 214 | 215 | * [Effective Go](https://golang.org/doc/effective_go.html) 216 | * [How to Write Go Code](https://golang.org/doc/code.html) 217 | * [Organizing Go Gode (2012年のブログポスト)](https://blog.golang.org/organizing-go-code) 218 | * [Organizing Go Code (2014年のGoogle I/Oでのプレゼン)](https://talks.golang.org/2014/organizeio.slide) 219 | 220 | By Sameer Ajmani 221 | -------------------------------------------------------------------------------- /content/post/pipelines.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2014-03-13T08:19:15+09:00" 3 | draft = false 4 | title = "Goの並行パターン:パイプラインとキャンセル (Go Concurrency Patterns: Pipelines and cancellation)" 5 | tags = ["concurrency", "pipeline", "cancellation"] 6 | +++ 7 | 8 | # Goの並行パターン:パイプラインとキャンセル 9 | [Go Concurrency Patterns: Pipelines and cancellation](https://blog.golang.org/pipelines) by Sameer Ajmani 10 | 11 | ## はじめに 12 | Goの並行性に関する基本要素によって、I/Oや複数のCPIを効率的に使うことができるストリーミングデータパイプラインを 13 | 簡単に構築することができます。この記事ではそのようなパイプラインの例を紹介し、操作が失敗したときに発生する 14 | 繊細な事柄にハイライトを当て、また失敗に綺麗に対応するテクニックを紹介します。 15 | 16 | ## パイプラインとはなにか 17 | Goにおいて、パイプラインの厳密な定義はありません。パイプラインは数ある並行プログラミングの種類の一つに過ぎません。 18 | 正式な定義ではないですが、パイプラインとはチャンネルによって接続された一連の _ステージ_ を挿します。 19 | そこでは、各ステージでは同じ関数を実行するゴルーチンのまとまりになっています。 20 | 各ステージではゴルーチンは次の役割を果たします。 21 | 22 | * _上流_ から _流入_ チャンネル経由で値を受け取る 23 | * そのデータに対してある関数を実行し、通常は新しい値を生成する 24 | * _下流_ へ _流出_ チャンネル経由で値を送信する 25 | 26 | 各ステージでは、任意の数の流入と流出のチャンネルを持っています。ただし最初と最後のステージは例外で、 27 | それぞれ流出と流入のチャンネルのみが存在します。最初のステージは時々 _ソース_ あるいは _プロデューサー_ と呼ばれ、 28 | 最後のステージは _シンク_ あるいは _コンシューマー_ と呼ばれます。 29 | 30 | パイプラインの考え方とそのテクニックを説明するために単純なパイプラインの例から始めてみましょう。 31 | あとでより現実的な例を紹介します。 32 | 33 | ## 数字を平方する 34 | 3つのステージからなるパイプラインを考えてみましょう。 35 | 36 | 最初のステージ `gen` は整数のリストからチャンネルに変換する関数です。このチャンネルがリスト内の整数を出すことになります。 37 | `gen` 関数は整数をチャンネルに送信するゴルーチンを起動し、すべての値が送信されたらチャンネルを閉じます。 38 | 39 | ``` 40 | func gen(nums ...int) <-chan int { 41 | out := make(chan int) 42 | go func() { 43 | for _, n := range nums { 44 | out <- n 45 | } 46 | close(out) 47 | }() 48 | return out 49 | } 50 | ``` 51 | 52 | 2番めのステージは `sq` で、チャンネルから整数を受信して、受信した整数それぞれの平方を出すチャンネルを返します。 53 | 流入のチャンネルが閉じて、すべての値を下流に送った後に、流出のチャンネルを閉じます。 54 | 55 | ``` 56 | func sq(in <-chan int) <-chan int { 57 | out := make(chan int) 58 | go func() { 59 | for n := range in { 60 | out <- n * n 61 | } 62 | close(out) 63 | }() 64 | return out 65 | } 66 | ``` 67 | 68 | `main` 関数はパイプラインを設定し、最後のステージを実行します。2番めのステージから値を受信し、チャンネルが閉じるまで、 69 | それぞれを出力します。 70 | 71 | ``` 72 | func main() { 73 | // パイプラインを設定する。 74 | c := gen(2, 3) 75 | out := sq(c) 76 | 77 | // 出力を消費する。 78 | fmt.Println(<-out) // 4 79 | fmt.Println(<-out) // 9 80 | } 81 | ``` 82 | 83 | `sq` では流入と流出のチャンネルでそれぞれ同じ型なので、 `sq` を何回でも繰り返すことができます。 84 | また `main` を他のステージと同様にrangeを使ったループに書き換えることもできます。 85 | 86 | ``` 87 | func main() { 88 | // パイプラインを設定して出力を消費する。 89 | for n := range sq(sq(gen(2, 3))) { 90 | fmt.Println(n) // 16 then 81 91 | } 92 | } 93 | ``` 94 | 95 | ## ファンアウト、ファンイン 96 | チャンネルが閉じるまで複数の関数が1つのチャンネルを読み込むことが可能です。 97 | これは _ファンアウト_ と呼ばれます。この構成はCPU使用率とI/Oを平行に使うために 98 | ワーカー群に仕事を分配する方法を提供しています。 99 | 100 | 1つの関数は入力チャンネルを多重化して1つのチャンネルに流し込むことで、すべての入力が閉じるまで 101 | 複数の入力から読み込み処理をすることができて、流し込む先のチャンネルはすべての入力が閉じると閉じられます。 102 | これを _ファンイン_ と呼びます。 103 | 104 | 先ほどのパイプラインを2つの `sq` のインスタンスを実行するように変更し、それぞれが同一の 105 | 入力チャンネルから読み込むようにできます。ここで新しい関数 `merge` を用意して結果をファンインします。 106 | 107 | ``` 108 | func main() { 109 | in := gen(2, 3) 110 | 111 | // sq の仕事を同一のチャンネル in から読み込む2つのゴルーチンに分配します。 112 | c1 := sq(in) 113 | c2 := sq(in) 114 | 115 | // c1 と c2 の結果をマージしたものを消費します。 116 | for n := range merge(c1, c2) { 117 | fmt.Println(n) // 4 then 9, or 9 then 4 118 | } 119 | } 120 | ``` 121 | 122 | `merge` 関数は、流入チャンネルそれぞれに対してゴルーチンを起動して、値を唯一の流出チャンネルに 123 | コピーすることで、チャンネルのリストを1つのチャンネルに変換します。すべての `output` ゴルーチンが 124 | 起動したら、 `merge` は更にもう一つゴルーチンを起動して、そのチャンネルへの送信がすべて終わったら 125 | 流出チャンネルを閉じます。 126 | 127 | 閉じたチャンネルに送信するとパニックになるので、closeを呼ぶ前にすべての値が送信されていることを 128 | 確実にすることが大事です。 [sync.WaitGroup](http://golang.org/pkg/sync/#WaitGroup) 型はこのような 129 | 同期を用意する簡単な方法を提供しています。 130 | 131 | ``` 132 | func merge(cs ...<-chan int) <-chan int { 133 | var wg sync.WaitGroup 134 | out := make(chan int) 135 | 136 | // cs 内の各入力チャンネルに対して output ゴルーチンを起動。 137 | // output は c が閉じるまで c から out に値をコピーして、その後 wg.Done を呼び出す。 138 | output := func(c <-chan int) { 139 | for n := range c { 140 | out <- n 141 | } 142 | wg.Done() 143 | } 144 | wg.Add(len(cs)) 145 | for _, c := range cs { 146 | go output(c) 147 | } 148 | 149 | // output ゴルーチンがすべて終了したら out を閉じるためのゴルーチンを起動する。 150 | // これは wg.Add が呼び出された後に起動しなければならない。 151 | go func() { 152 | wg.Wait() 153 | close(out) 154 | }() 155 | return out 156 | } 157 | ``` 158 | 159 | ## 早めに止める 160 | 私たちのパイプライン関数にはパターンがあります。 161 | 162 | * 送信が全て完了したら流出チャンネルを閉じるステージ 163 | * チャンネルが閉じるまで流入チャンネルから値を受信し続けるステージ 164 | 165 | このパターンは受信の各ステージを `range` のループで書くことができ、またすべての値が無事に下流に送信されたら 166 | 確実にすべてのゴルーチンが終了してくれます。 167 | 168 | しかし実際のパイプラインでは、ステージで流入するすべての値を受信するとは限りません。 169 | ときには設計がすべてを受信しないようにしていることがあります。受信するステージでは 170 | 処理のためにすべての値の中の一部だけが必要なことがあります。また、しばしば流入する値が 171 | 前のステージでの異常値を表している場合に終了することがあります。 172 | どちらの場合でも、受信するステージでは残りの値を待つべきではないですし、前段のステージで 173 | 後段のステージで必要にならない値を生成するのを止めたいものです。 174 | 175 | 私たち例のパイプラインでは、ステージが流入する値のすべてを消費できなかった場合、 176 | それらの値を送信しようとするゴルーチンはいつまでもブロックし続けます。 177 | 178 | ``` 179 | // 出力からの最初の値を消費する 180 | out := merge(c1, c2) 181 | fmt.Println(<-out) // 4 か 9 182 | return 183 | // outから2番めの値を受け取ってないため 184 | // 出力用のゴルーチンは2番めの値を送信しようとしてとどまってしまいます 185 | } 186 | ``` 187 | 188 | これがリソースリークです。ゴルーチンがメモリとランタイムの資源を消費して、ゴルーチンのスタック内の 189 | ヒープの参照はデータがガベージコレクトされないようにします。ゴルーチンはガベージコレクトされません。 190 | ゴルーチンは自分自身で終了しなければいけません。 191 | 192 | パイプラインの下流のステージがすべての流入する値を受信できなかった場合にも上流のステージが終了するように 193 | 変更する必要があります。修正方法の一つとして、流出のチャンネルにバッファを持たせる方法があります。 194 | バッファは決まった数の値を持つことができます。バッファ内に空きができ次第送信の操作が完了します。 195 | 196 | ``` 197 | c := make(chan int, 2) // バッファのサイズは 2 198 | c <- 1 // ただちに成功 199 | c <- 2 // ただちに成功 200 | c <- 3 // 他のゴルーチンが <-c として 1 を受信するまでブロック 201 | ``` 202 | 203 | チャンネルを作成するときに送信される値の数がわかっている場合、バッファの確保によってコードを短くできます。 204 | たとえば、 `gen` を整数のリストをバッファ付きのチャンネルにコピーするようにコードを書き換えて、 205 | 新しいゴルーチンを生成しないようにすることができます。 206 | 207 | ``` 208 | func gen(nums ...int) <-chan int { 209 | out := make(chan int, len(nums)) 210 | for _, n := range nums { 211 | out <- n 212 | } 213 | close(out) 214 | return out 215 | } 216 | ``` 217 | 218 | 先ほどのパイプラインの話に戻ると、 `merge` によって返される流出チャンネルにバッファを追加することを考えてみましょう。 219 | 220 | ``` 221 | func merge(cs ...<-chan int) <-chan int { 222 | var wg sync.WaitGroup 223 | out := make(chan int, 1) // 未読の入力に対して十分な領域 224 | // ... あとはさきほどと同じ ... 225 | ``` 226 | 227 | この変更でプログラム中でゴルーチンがブロックしてしまう件は修正されましたが、これは良くないコードです。 228 | ここでバッファサイズを1にしているのは、 `merge` が受信する値の数と下流のステージで消費される値の数を知っているから 229 | できることです。これは脆い設計です。 `gen` にさらに値を渡した場合、あるいは下流で読み取る値を減らした場合、 230 | 再びブロックするゴルーチンが発生してしまいます。 231 | 232 | かわりに、下流のステージがこれ以上入力を受け付けないことを送信元に伝える方法を提供する必要があります。 233 | 234 | ## 明示的なキャンセル 235 | `main` が `out` からの値をすべて受信せずに終了すると決めたとき、 `main` は上流のステージのゴルーチンに 236 | 送信しようとしている値を破棄するように伝えなければいけません。これは `done` というチャンネルに値を送ることで 237 | 実現しています。 `main` は潜在的にブロックする可能性がある2つの送信元があるので2つ値を送信します。 238 | 239 | ``` 240 | func main() { 241 | in := gen(2, 3) 242 | 243 | // in からともに値を読み取る2つのゴルーチンに sq の処理を分配します 244 | c1 := sq(in) 245 | c2 := sq(in) 246 | 247 | // 最初の値を出力から消費します 248 | done := make(chan struct{}, 2) 249 | out := merge(done, c1, c2) 250 | fmt.Println(<-out) // 4 or 9 251 | 252 | // まだ処理を続けている送信元に終了することを伝えます 253 | done <- struct{}{} 254 | done <- struct{}{} 255 | } 256 | ``` 257 | 258 | 送信するゴルーチンでは、送信の操作を `select` 文に置き換えて、 `out` への送信があった場合、もしくは `done` 259 | から値を受信した場合に処理が進むようにします。 `done` の型は空の構造体です。その理由は、値は関係ないからです。 260 | つまり、単純に `out` への送信を辞めるべきタイミングを示すイベントを受信するだけのものだからです。 261 | `output` ゴルーチンは上流のステージがブロックされないように流入チャンネルの `c` に対してループを続けます。 262 | (すぐ後で、このループが早めに終われるようにするかをお話します) 263 | 264 | ``` 265 | func merge(done <-chan struct{}, cs ...<-chan int) <-chan int { 266 | var wg sync.WaitGroup 267 | out := make(chan int) 268 | 269 | // output ゴルーチンを cs 内の各入力チャンネルに対して起動します。 270 | // output は c がチャンネルを閉じるまで、あるいは、doneから値を受け取るまで 271 | // 値をコピーし続け、その後 wg.Done を呼び出します。 272 | output := func(c <-chan int) { 273 | for n := range c { 274 | select { 275 | case out <- n: 276 | case <-done: 277 | } 278 | } 279 | wg.Done() 280 | } 281 | // ... あとはさきほどと同じ ... 282 | ``` 283 | 284 | このアプローチには問題が有ります。下流の _各_ レシーバーは潜在的にブロックしてしまう可能性のある上流の 285 | 送信元の数を事前に知る必要があり、それらの送信元が早く終了するためのシグナルを用意する必要があります。 286 | これらの数を数えているのは退屈でエラーの温床となります。 287 | 288 | 未知数で制限のない数のゴルーチンに対して下流に値を送信するのを止めさせる方法が必要です。 289 | Goではチャンネルを閉じることでこれを実現できます。なぜならば、閉じたチャンネルに対しての 290 | 受信操作は直ちに実行されチャンネルの要素の型のゼロ値が返されるからです。( [参照](http://golang.org/ref/spec#Receive_operator) ) 291 | 292 | これはつまり、 `main` ですべての送信元を単純に `done` チャンネルを閉じることでブロックから解放できる 293 | ということを意味しています。この閉じる操作によって送信元に効率的にシグナルを配信することができます。 294 | 私たちのパイプラインの _各_ 関数が `done` を引数として受け取るように拡張して、 `defer` 文によって 295 | `done` が閉じられるようにし、 `main` 内のすべての終了処理からパイプラインの各ステージに終了するようにシグナルを送信します。 296 | 297 | ``` 298 | func main() { 299 | // パイプライン全体で共有される done チャンネルを設定し、このパイプラインが 300 | // 終了するときにチャンネルを閉じます。これは起動したすべてのゴルーチンが 301 | // 終了するためのシグナルとしてのものです。 302 | done := make(chan struct{}) 303 | defer close(done) 304 | 305 | in := gen(done, 2, 3) 306 | 307 | // in から値を読み取る2つのゴルーチンに sq の処理を分散する。 308 | c1 := sq(done, in) 309 | c2 := sq(done, in) 310 | 311 | // output から最初の1つの値を消費する。 312 | out := merge(done, c1, c2) 313 | fmt.Println(<-out) // 4 or 9 314 | 315 | // deferされた呼び出しによって done は閉じられる 316 | } 317 | ``` 318 | 319 | これでパイプラインでの各ステージは `done` が閉じられるとすぐに終了できるようになりました。 320 | 上流の送信元である `sq` は `done` が閉じられれば送信をやめるとわかっているので、 321 | `merge` 内の `output` ルーチンは流入チャンネルの値をすべて出すことなく終了処理をすることが出来ます。 322 | `output` は `defer` 文によって、すべての終了処理経路で `wg.Done` が呼ばれることを保証しています。 323 | 324 | ``` 325 | func merge(done <-chan struct{}, cs ...<-chan int) <-chan int { 326 | var wg sync.WaitGroup 327 | out := make(chan int) 328 | 329 | // cs 内の各入力チャンネルに対して output ゴルーチンを起動する。 330 | // output は c 内の値を c もしくは done が閉じられるまで out にコピーする。 331 | // その後 wg.Done を呼ぶ。 332 | output := func(c <-chan int) { 333 | defer wg.Done() 334 | for n := range c { 335 | select { 336 | case out <- n: 337 | case <-done: 338 | return 339 | } 340 | } 341 | } 342 | // ... あとはさきほどと同じ ... 343 | ``` 344 | 345 | 同様に、 `sq` も `done` が閉じられるとすぐに終了処理を行うことができます。 `sq` は `defer` 文によって 346 | すべての終了処理経路の中で `out` チャンネルが閉じられることを保証しています。 347 | 348 | ``` 349 | func sq(done <-chan struct{}, in <-chan int) <-chan int { 350 | out := make(chan int) 351 | go func() { 352 | defer close(out) 353 | for n := range in { 354 | select { 355 | case out <- n * n: 356 | case <-done: 357 | return 358 | } 359 | } 360 | }() 361 | return out 362 | } 363 | ``` 364 | 365 | 次がパイプラインを構築する際のガイドラインです。 366 | 367 | * ステージはすべての送信の操作が終わったときに出力チャンネルを閉じます。 368 | * ステージは入力チャンネルが閉じるまで値を受信し続けるか、さもなくば送信側を解放します。 369 | 370 | パイプラインは値を取得する十分バッファがあることを確認できる場合、もしくは受信側がチャンネルを放棄した時に 371 | 明示的に送信側にシグナルを送れるようにすることで、送信者を解放します。 372 | 373 | ## ディレクトリツリーをダイジェストする 374 | 375 | より現実的なパイプラインを考えてみましょう。 376 | 377 | MD5はファイルのチェックサムとして便利なメッセージダイジェストアルゴリズムです。 378 | コマンドラインツールの `md5sum` はファイル一覧のダイジェスト値を表示します。 379 | 380 | ``` 381 | % md5sum *.go 382 | d47c2bbc28298ca9befdfbc5d3aa4e65 bounded.go 383 | ee869afd31f83cbb2d10ee81b2b831dc parallel.go 384 | b88175e65fdcbc01ac08aaf1fd9b5e96 serial.go 385 | ``` 386 | 387 | 私たちのサンプルプログラムは `md5sum` に似ていますが、ディレクトリを引数に取り、その配下にあるファイルをパス順に並べ、 388 | それぞれのダイジェスト値を表示します。 389 | 390 | ``` 391 | % go run serial.go . 392 | d47c2bbc28298ca9befdfbc5d3aa4e65 bounded.go 393 | ee869afd31f83cbb2d10ee81b2b831dc parallel.go 394 | b88175e65fdcbc01ac08aaf1fd9b5e96 serial.go 395 | ``` 396 | 397 | `main` 関数は、パス名とダイジェスト値のマップを返す `MD5All` というヘルパー関数を呼び出し、それをソートして結果を表示します。 398 | 399 | ``` 400 | func main() { 401 | // 指定されたディレクトリ配下のすべてのファイルのMD5チェックサムを計算し、 402 | // パス名順に結果を並べて表示する。 403 | m, err := MD5All(os.Args[1]) 404 | if err != nil { 405 | fmt.Println(err) 406 | return 407 | } 408 | var paths []string 409 | for path := range m { 410 | paths = append(paths, path) 411 | } 412 | sort.Strings(paths) 413 | for _, path := range paths { 414 | fmt.Printf("%x %s\n", m[path], path) 415 | } 416 | } 417 | ``` 418 | 419 | `MD5All` 関数が議論の対象です。 `serial.go` では、並行性をまったく使わずに、 420 | ディレクトリを再帰探索しながら単順に個々のファイルを読み込んで、チェックサムを計算しています。 421 | 422 | ``` 423 | // MD5All は root 配下のすべてのファイルを読み込み、各ファイルのファイルパスとMD5チェックサムの 424 | // マップを返します。ディレクトリの再帰探索の失敗、あるいは読み込みの失敗がが発生したら 425 | // MD5Allはエラーを返します。 426 | func MD5All(root string) (map[string][md5.Size]byte, error) { 427 | m := make(map[string][md5.Size]byte) 428 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 429 | if err != nil { 430 | return err 431 | } 432 | if !info.Mode().IsRegular() { 433 | return nil 434 | } 435 | data, err := ioutil.ReadFile(path) 436 | if err != nil { 437 | return err 438 | } 439 | m[path] = md5.Sum(data) 440 | return nil 441 | }) 442 | if err != nil { 443 | return nil, err 444 | } 445 | return m, nil 446 | } 447 | ``` 448 | 449 | ## 並列ダイジェスト 450 | 451 | [parallel.go](https://blog.golang.org/pipelines/parallel.go) では `MD5All` を2段階のパイプラインに分けています。 452 | 第1段階である `sumFiles` では、ディレクトリを探索し個々のファイルをそれぞれのゴルーチンでダイジェストし、 453 | `result` 型の値のチャンネルに結果を送ります。 454 | 455 | ``` 456 | type result struct { 457 | path string 458 | sum [md5.Size]byte 459 | err error 460 | } 461 | ``` 462 | 463 | `sumFiles` は2つのチャンネルを返します。1つは結果を表すもの、もう1つは `filepath.Walk` によって返されるエラーです。 464 | ディレクトリ探索の関数は個々のファイルを処理する新しいゴルーチンを立ち上げ、 `done` を確認します。 465 | `done` が閉じられたら、探索を直ちに終了します。 466 | 467 | ``` 468 | func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) { 469 | // For each regular file, start a goroutine that sums the file and sends 470 | // the result on c. Send the result of the walk on errc. 471 | c := make(chan result) 472 | errc := make(chan error, 1) 473 | go func() { 474 | var wg sync.WaitGroup 475 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 476 | if err != nil { 477 | return err 478 | } 479 | if !info.Mode().IsRegular() { 480 | return nil 481 | } 482 | wg.Add(1) 483 | go func() { 484 | data, err := ioutil.ReadFile(path) 485 | select { 486 | case c <- result{path, md5.Sum(data), err}: 487 | case <-done: 488 | } 489 | wg.Done() 490 | }() 491 | // Abort the walk if done is closed. 492 | select { 493 | case <-done: 494 | return errors.New("walk canceled") 495 | default: 496 | return nil 497 | } 498 | }) 499 | // Walk has returned, so all calls to wg.Add are done. Start a 500 | // goroutine to close c once all the sends are done. 501 | go func() { 502 | wg.Wait() 503 | close(c) 504 | }() 505 | // No select needed here, since errc is buffered. 506 | errc <- err 507 | }() 508 | return c, errc 509 | } 510 | ``` 511 | 512 | `MD5All` は `c` からダイジェスト値を受け取ります。 513 | `MD5All` はエラーがあれば先にエラーを返し、 `defer` で `done` を閉じます。 514 | 515 | ``` 516 | func MD5All(root string) (map[string][md5.Size]byte, error) { 517 | // MD5All closes the done channel when it returns; it may do so before 518 | // receiving all the values from c and errc. 519 | done := make(chan struct{}) 520 | defer close(done) 521 | 522 | c, errc := sumFiles(done, root) 523 | 524 | m := make(map[string][md5.Size]byte) 525 | for r := range c { 526 | if r.err != nil { 527 | return nil, r.err 528 | } 529 | m[r.path] = r.sum 530 | } 531 | if err := <-errc; err != nil { 532 | return nil, err 533 | } 534 | return m, nil 535 | } 536 | ``` 537 | 538 | ## 限定的並列処理 539 | 540 | [parallel.go](https://blog.golang.org/pipelines/parallel.go) での `MD5All` の実装では各ファイルに対し新しいゴルーチンを 541 | 起動させています。多くのファイルがあるディレクトリでは、この実装だとマシンに搭載されている以上のメモリをアロケートしかねません。 542 | 543 | 並列処理の中で読み込まれるファイル数を限定することで、このアロケーションを制限することができます。 544 | [bounded.go](https://blog.golang.org/pipelines/bounded.go) では、ファイルの読み込みに決められた数のゴルーチンを 545 | 作成することで実現しています。今回のパイプラインは3段階になっています。ディレクトリの再帰探索、ファイルの読み込みとダイジェスト値の計算、 546 | そしてダイジェスト値の回収です。 547 | 548 | 第1段階の `walkFiles` は、ファイルツリー内のファイルのパスを返します。 549 | 550 | ``` 551 | func walkFiles(done <-chan struct{}, root string) (<-chan string, <-chan error) { 552 | paths := make(chan string) 553 | errc := make(chan error, 1) 554 | go func() { 555 | // Walkが終了したらpathsチャンネルを閉じます。 556 | defer close(paths) 557 | // errcはバッファ済みなのでselectは必要ありません。 558 | errc <- filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 559 | if err != nil { 560 | return err 561 | } 562 | if !info.Mode().IsRegular() { 563 | return nil 564 | } 565 | select { 566 | case paths <- path: 567 | case <-done: 568 | return errors.New("walk canceled") 569 | } 570 | return nil 571 | }) 572 | }() 573 | return paths, errc 574 | } 575 | ``` 576 | 577 | 第2段階では一定数のダイジェスト値を計算するゴルーチンを起動します。このゴルーチンはパスにあるファイル名を受け取り、 578 | 結果をチャンネル `c` に送ります。 579 | 580 | ``` 581 | func digester(done <-chan struct{}, paths <-chan string, c chan<- result) { 582 | for path := range paths { 583 | data, err := ioutil.ReadFile(path) 584 | select { 585 | case c <- result{path, md5.Sum(data), err}: 586 | case <-done: 587 | return 588 | } 589 | } 590 | } 591 | ``` 592 | 593 | 先の例と違って、ダイジェスト値計算用ゴルーチンは出力チャンネルを閉じません。その理由は複数のゴルーチンが1つの共通のチャンネルに 594 | 値を送っているからです。代わりに、 `MD5All` 内ですべてのダイジェスト値計算用ゴルーチンが終了したらチャンネルを閉じるようにしています。 595 | 596 | ``` 597 | // ファイルの読み込みとダイジェスト値の計算をするゴルーチンを一定数起動 598 | c := make(chan result) 599 | var wg sync.WaitGroup 600 | const numDigesters = 20 601 | wg.Add(numDigesters) 602 | for i := 0; i < numDigesters; i++ { 603 | go func() { 604 | digester(done, paths, c) 605 | wg.Done() 606 | }() 607 | } 608 | go func() { 609 | wg.Wait() 610 | close(c) 611 | }() 612 | ``` 613 | 614 | 他の方法として、ダイジェスト計算用ゴルーチンそれぞれが出力用チャンネルを作ってそれを返すことも可能ですが、 615 | その場合は結果をファンインさせるゴルーチンが追加で必要になります。 616 | 617 | 第3段階では `c` から結果をすべて受け取り、そのあと `errc` 内のエラーを確認します。 618 | この確認は `c` の結果を受け取った後でなければいけません。なぜならこれより前だと、 `walkFiles` が下流に値を送るのをブロックしてしまうからです。 619 | 620 | ``` 621 | m := make(map[string][md5.Size]byte) 622 | for r := range c { 623 | if r.err != nil { 624 | return nil, r.err 625 | } 626 | m[r.path] = r.sum 627 | } 628 | // Walkが失敗したかを確認 629 | if err := <-errc; err != nil { 630 | return nil, err 631 | } 632 | return m, nil 633 | } 634 | ``` 635 | 636 | ## 結論 637 | 638 | この記事ではGoでストリーミングデータパイプラインを構築するテクニックを紹介しました。 639 | このようなパイプラインでエラーを扱う場合、各段階においてパイプラインが下流に値を送信するのをブロックしないように、 640 | そして下流の段階では入力データについて心配する必要がなくなるように、注意しなければいけません。 641 | 本記事の例では、 `"done"` シグナルをパイプラインによって起動されたすべてのゴルーチンに配信しする方法をお見せし、 642 | またパイプラインを正しく構築するガイドラインを定義しました。 643 | 644 | より深く理解したい人には次の記事をおすすめします。 645 | 646 | * [Go Concurrency Patterns](http://talks.golang.org/2012/concurrency.slide#1) ([動画](https://www.youtube.com/watch?v=f6kdp27TYZs)) ではGoにおける並行プログラミングの基礎とその適用方法をいくつか紹介しています。 647 | * [Advanced Go Concurrency Patterns](http://blog.golang.org/advanced-go-concurrency-patterns) ([動画](http://www.youtube.com/watch?v=QDDwwePbDtw)) ではより複雑なGoの機能、特にselectについて触れています。 648 | * Douglas McIlroy の論文 [Squinting at Power Series](http://swtch.com/~rsc/thread/squint.pdf) では、 649 | Goのような並行性がどのように複雑な計算を華麗にサポートするかを説明しています。 650 | 651 | By Sameer Ajmani 652 | 653 | ## あわせて読みたい 654 | * [Go Concurrency Patterns: Context](https://blog.golang.org/context) 655 | * [Goの並行パターン:コンテキスト (Go Concurrency Pattern: Context)](../context/) 656 | * [Introducing the Go Race Detector](https://blog.golang.org/race-detector) 657 | * [Advanced Go Concurrency Patterns](https://blog.golang.org/advanced-go-concurrency-patterns) 658 | * [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism) 659 | * [Go videos from Google I/O 2012](https://blog.golang.org/go-videos-from-google-io-2012) 660 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 661 | * [Goの並行パターン:タイムアウトと進行](../go-concurrency-patterns-timing-out-and/) 662 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) -------------------------------------------------------------------------------- /content/post/race-detector.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2013-06-26T20:38:05+09:00" 3 | draft = true 4 | title = "Goの競合検出機能の紹介(Introducing the Go Race Detector)" 5 | 6 | +++ 7 | 8 | # Goの競合検出機能の紹介 9 | 10 | [Introducing the Go Race Detector](https://blog.golang.org/race-detector) by By Dmitry Vyukov and Andrew Gerrand 11 | 12 | ## はじめに 13 | 14 | Race conditions are among the most insidious and elusive programming errors. They typically cause erratic and mysterious failures, often long after the code has been deployed to production. While Go's concurrency mechanisms make it easy to write clean concurrent code, they don't prevent race conditions. Care, diligence, and testing are required. And tools can help. 15 | 16 | We're happy to announce that Go 1.1 includes a race detector, a new tool for finding race conditions in Go code. It is currently available for Linux, OS X, and Windows systems with 64-bit x86 processors. 17 | 18 | The race detector is based on the C/C++ ThreadSanitizer runtime library, which has been used to detect many errors in Google's internal code base and in Chromium. The technology was integrated with Go in September 2012; since then it has detected 42 races in the standard library. It is now part of our continuous build process, where it continues to catch race conditions as they arise. 19 | 20 | ## How it works 21 | 22 | The race detector is integrated with the go tool chain. When the -race command-line flag is set, the compiler instruments all memory accesses with code that records when and how the memory was accessed, while the runtime library watches for unsynchronized accesses to shared variables. When such "racy" behavior is detected, a warning is printed. (See this article for the details of the algorithm.) 23 | 24 | Because of its design, the race detector can detect race conditions only when they are actually triggered by running code, which means it's important to run race-enabled binaries under realistic workloads. However, race-enabled binaries can use ten times the CPU and memory, so it is impractical to enable the race detector all the time. One way out of this dilemma is to run some tests with the race detector enabled. Load tests and integration tests are good candidates, since they tend to exercise concurrent parts of the code. Another approach using production workloads is to deploy a single race-enabled instance within a pool of running servers. 25 | 26 | ## Using the race detector 27 | 28 | The race detector is fully integrated with the Go tool chain. To build your code with the race detector enabled, just add the -race flag to the command line: 29 | 30 | ``` 31 | $ go test -race mypkg // test the package 32 | $ go run -race mysrc.go // compile and run the program 33 | $ go build -race mycmd // build the command 34 | $ go install -race mypkg // install the package 35 | ``` 36 | 37 | To try out the race detector for yourself, fetch and run this example program: 38 | 39 | ``` 40 | $ go get -race golang.org/x/blog/support/racy 41 | $ racy 42 | ``` 43 | 44 | ## Examples 45 | 46 | Here are two examples of real issues caught by the race detector. 47 | 48 | ### Example 1: Timer.Reset 49 | 50 | The first example is a simplified version of an actual bug found by the race detector. It uses a timer to print a message after a random duration between 0 and 1 second. It does so repeatedly for five seconds. It uses time.AfterFunc to create a Timer for the first message and then uses the Reset method to schedule the next message, re-using the Timer each time. 51 | 52 | ``` 53 | func main() { 54 | start := time.Now() 55 | var t *time.Timer 56 | t = time.AfterFunc(randomDuration(), func() { 57 | fmt.Println(time.Now().Sub(start)) 58 | t.Reset(randomDuration()) 59 | }) 60 | time.Sleep(5 * time.Second) 61 | } 62 | 63 | func randomDuration() time.Duration { 64 | return time.Duration(rand.Int63n(1e9)) 65 | } 66 | ``` 67 | 68 | 実行結果(毎回変化します) 69 | 70 | ``` 71 | 947.77941ms 72 | 1.029932961s 73 | 1.696078782s 74 | 1.931088833s 75 | 2.21820277s 76 | 2.76737009s 77 | 3.400339848s 78 | 3.732115996s 79 | 3.915233212s 80 | 4.395512661s 81 | 82 | Program exited. 83 | ``` 84 | 85 | This looks like reasonable code, but under certain circumstances it fails in a surprising way: 86 | 87 | ``` 88 | panic: runtime error: invalid memory address or nil pointer dereference 89 | [signal 0xb code=0x1 addr=0x8 pc=0x41e38a] 90 | 91 | goroutine 4 [running]: 92 | time.stopTimer(0x8, 0x12fe6b35d9472d96) 93 | src/pkg/runtime/ztime_linux_amd64.c:35 +0x25 94 | time.(*Timer).Reset(0x0, 0x4e5904f, 0x1) 95 | src/pkg/time/sleep.go:81 +0x42 96 | main.func·001() 97 | race.go:14 +0xe3 98 | created by time.goFunc 99 | src/pkg/time/sleep.go:122 +0x48 100 | ``` 101 | 102 | What's going on here? Running the program with the race detector enabled is more illuminating: 103 | 104 | ``` 105 | ================== 106 | WARNING: DATA RACE 107 | Read by goroutine 5: 108 | main.func·001() 109 | race.go:14 +0x169 110 | 111 | Previous write by goroutine 1: 112 | main.main() 113 | race.go:15 +0x174 114 | 115 | Goroutine 5 (running) created at: 116 | time.goFunc() 117 | src/pkg/time/sleep.go:122 +0x56 118 | timerproc() 119 | src/pkg/runtime/ztime_linux_amd64.c:181 +0x189 120 | ================== 121 | ``` 122 | 123 | The race detector shows the problem: an unsynchronized read and write of the variable t from different goroutines. If the initial timer duration is very small, the timer function may fire before the main goroutine has assigned a value to t and so the call to t.Reset is made with a nil t. 124 | 125 | To fix the race condition we change the code to read and write the variable t only from the main goroutine: 126 | 127 | ``` 128 | func main() { 129 | start := time.Now() 130 | reset := make(chan bool) 131 | var t *time.Timer 132 | t = time.AfterFunc(randomDuration(), func() { 133 | fmt.Println(time.Now().Sub(start)) 134 | reset <- true 135 | }) 136 | for time.Since(start) < 5*time.Second { 137 | <-reset 138 | t.Reset(randomDuration()) 139 | } 140 | } 141 | ``` 142 | 143 | Here the main goroutine is wholly responsible for setting and resetting the Timer t and a new reset channel communicates the need to reset the timer in a thread-safe way. 144 | 145 | A simpler but less efficient approach is to avoid reusing timers. 146 | 147 | ### Example 2: ioutil.Discard 148 | 149 | The second example is more subtle. 150 | 151 | The ioutil package's Discard object implements io.Writer, but discards all the data written to it. Think of it like /dev/null: a place to send data that you need to read but don't want to store. It is commonly used with io.Copy to drain a reader, like this: 152 | 153 | ``` 154 | io.Copy(ioutil.Discard, reader) 155 | ``` 156 | 157 | Back in July 2011 the Go team noticed that using Discard in this way was inefficient: the Copy function allocates an internal 32 kB buffer each time it is called, but when used with Discard the buffer is unnecessary since we're just throwing the read data away. We thought that this idiomatic use of Copy and Discard should not be so costly. 158 | 159 | The fix was simple. If the given Writer implements a ReadFrom method, a Copy call like this: 160 | 161 | ``` 162 | io.Copy(writer, reader) 163 | ``` 164 | 165 | is delegated to this potentially more efficient call: 166 | 167 | ``` 168 | writer.ReadFrom(reader) 169 | ``` 170 | 171 | We added a ReadFrom method to Discard's underlying type, which has an internal buffer that is shared between all its users. We knew this was theoretically a race condition, but since all writes to the buffer should be thrown away we didn't think it was important. 172 | 173 | When the race detector was implemented it immediately flagged this code as racy. Again, we considered that the code might be problematic, but decided that the race condition wasn't "real". To avoid the "false positive" in our build we implemented a non-racy version that is enabled only when the race detector is running. 174 | 175 | But a few months later Brad encountered a frustrating and strange bug. After a few days of debugging, he narrowed it down to a real race condition caused by ioutil.Discard. 176 | 177 | Here is the known-racy code in io/ioutil, where Discard is a devNull that shares a single buffer between all of its users. 178 | 179 | ``` 180 | var blackHole [4096]byte // shared buffer 181 | 182 | func (devNull) ReadFrom(r io.Reader) (n int64, err error) { 183 | readSize := 0 184 | for { 185 | readSize, err = r.Read(blackHole[:]) 186 | n += int64(readSize) 187 | if err != nil { 188 | if err == io.EOF { 189 | return n, nil 190 | } 191 | return 192 | } 193 | } 194 | } 195 | ``` 196 | 197 | Brad's program includes a trackDigestReader type, which wraps an io.Reader and records the hash digest of what it reads. 198 | 199 | ``` 200 | type trackDigestReader struct { 201 | r io.Reader 202 | h hash.Hash 203 | } 204 | 205 | func (t trackDigestReader) Read(p []byte) (n int, err error) { 206 | n, err = t.r.Read(p) 207 | t.h.Write(p[:n]) 208 | return 209 | } 210 | ``` 211 | 212 | For example, it could be used to compute the SHA-1 hash of a file while reading it: 213 | 214 | ``` 215 | tdr := trackDigestReader{r: file, h: sha1.New()} 216 | io.Copy(writer, tdr) 217 | fmt.Printf("File hash: %x", tdr.h.Sum(nil)) 218 | ``` 219 | 220 | In some cases there would be nowhere to write the data—but still a need to hash the file—and so Discard would be used: 221 | 222 | ``` 223 | io.Copy(ioutil.Discard, tdr) 224 | ``` 225 | 226 | But in this case the blackHole buffer isn't just a black hole; it is a legitimate place to store the data between reading it from the source io.Reader and writing it to the hash.Hash. With multiple goroutines hashing files simultaneously, each sharing the same blackHole buffer, the race condition manifested itself by corrupting the data between reading and hashing. No errors or panics occurred, but the hashes were wrong. Nasty! 227 | 228 | ``` 229 | func (t trackDigestReader) Read(p []byte) (n int, err error) { 230 | // the buffer p is blackHole 231 | n, err = t.r.Read(p) 232 | // p may be corrupted by another goroutine here, 233 | // between the Read above and the Write below 234 | t.h.Write(p[:n]) 235 | return 236 | } 237 | ``` 238 | 239 | The bug was finally fixed by giving a unique buffer to each use of ioutil.Discard, eliminating the race condition on the shared buffer. 240 | 241 | ## Conclusions 242 | 243 | The race detector is a powerful tool for checking the correctness of concurrent programs. It will not issue false positives, so take its warnings seriously. But it is only as good as your tests; you must make sure they thoroughly exercise the concurrent properties of your code so that the race detector can do its job. 244 | 245 | What are you waiting for? Run "go test -race" on your code today! 246 | 247 | By Dmitry Vyukov and Andrew Gerrand 248 | 249 | ## あわせて読みたい 250 | 251 | * [Generating code](https://blog.golang.org/generate) 252 | * [Go Concurrency Patterns: Context](https://blog.golang.org/context) 253 | * [Go Concurrency Patterns: Pipelines and cancellation](https://blog.golang.org/pipelines) 254 | * [Advanced Go Concurrency Patterns](https://blog.golang.org/advanced-go-concurrency-patterns) 255 | * [Go maps in action](https://blog.golang.org/go-maps-in-action) 256 | * [go fmt your code](https://blog.golang.org/go-fmt-your-code) 257 | * [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism) 258 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 259 | * [Go videos from Google I/O 2012](https://blog.golang.org/go-videos-from-google-io-2012) 260 | * [Debugging Go programs with the GNU Debugger](https://blog.golang.org/debugging-go-programs-with-gnu-debugger) 261 | * [The Go image/draw package](https://blog.golang.org/go-imagedraw-package) 262 | * [The Go image package](https://blog.golang.org/go-image-package) 263 | * [The Laws of Reflection](https://blog.golang.org/laws-of-reflection) 264 | * [Error handling and Go](https://blog.golang.org/error-handling-and-go) 265 | * ["First Class Functions in Go"](https://blog.golang.org/first-class-functions-in-go-and-new-go) 266 | * [Profiling Go Programs](https://blog.golang.org/profiling-go-programs) 267 | * [A GIF decoder: an exercise in Go interfaces](https://blog.golang.org/gif-decoder-exercise-in-go-interfaces) 268 | * [Introducing Gofix](https://blog.golang.org/introducing-gofix) 269 | * [Godoc: documenting Go code](https://blog.golang.org/godoc-documenting-go-code) 270 | * [Gobs of data](https://blog.golang.org/gobs-of-data) 271 | * [C? Go? Cgo!](https://blog.golang.org/c-go-cgo) 272 | * [JSON and Go](https://blog.golang.org/json-and-go) 273 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 274 | * [Go Concurrency Patterns: Timing out, moving on](https://blog.golang.org/go-concurrency-patterns-timing-out-and) 275 | * [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) 276 | * [Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating) 277 | * [JSON-RPC: a tale of interfaces](https://blog.golang.org/json-rpc-tale-of-interfaces) -------------------------------------------------------------------------------- /content/post/slices.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2013-09-26T09:42:44+09:00" 3 | draft = false 4 | title = "配列、スライス(と文字列):'append'の動作原理 (Arrays, slices (and strings): The mechanics of 'append')" 5 | tags = ["array", "slice", "string", "copy", "append"] 6 | +++ 7 | 8 | # 配列、スライス(と文字列):'append'の動作原理 9 | [Arrays, slices (and strings): The mechanics of 'append'](https://blog.golang.org/slices) By Rob Pike 10 | 11 | ## はじめに 12 | 13 | 手続き型プログラミング言語において共通した機能のひとつに配列という概念があります。配列は単純に見えて、言語に追加する際には 14 | 答えなければならない多くの設問にこたえなければいけません。たとえば次のようなものです。 15 | 16 | * 固定長なのか、可変長なのか 17 | * 長さは型に含めるのか 18 | * 多次元配列はどのように表現するか 19 | * 空の配列が何を意味するか 20 | 21 | これらの設問に対する回答が、配列が単に言語の一機能になるか、それとも言語設計の中心になるかをわけます。 22 | 23 | Goの開発の初期段階で、言語設計がしっくりくるまで、これらの設問に対する答えを決めるのに1年かかりました。  24 | 鍵となったのはスライスの導入でした。スライスによって、固定長の配列に柔軟性や拡張性をもったデータ構造を与えました。 25 | しかしながら、今日まで、Goを使い始めたばかりのプログラマはスライスの動作の理解にしばしばつまづいています。 26 | おそらくそれは他の言語での経験がスライスに対する考え方に影響しているからでしょう。 27 | 28 | この記事では、その混乱を解決しようと思います。そのために、ひとつひとつ部品を積み重ねて、組み込み関数の `append` がどのように動作するか、 29 | そしてなぜそのように動作するのかを説明します。 30 | 31 | ## 配列 32 | 33 | Goにおいて、配列は重要な要素ですが、工事における基礎のように、配列はより見えやすい構成要素の下に隠されています。 34 | より面白くて、強力で、輝くアイデアであるスライスの話をする前に、まずは簡単に配列の説明をしましょう。 35 | 36 | 配列はGoのプログラム内にはあまり見られません。なぜなら配列の大きさは型の一部で、それによって表現力が制限されるからです。 37 | 38 | 次の宣言では 39 | 40 | ``` 41 | var buffer [256]byte 42 | ``` 43 | 44 | は変数バッファを宣言しています。このバッファは256バイトを保持します。 `buffer` は型その大きさをを型情報に含んでいて 45 | `[256]byte` となっています。512バイトの配列は、その専用の型である `[512]byte` 型となります。 46 | 47 | 配列に紐付いたデータは、たった一つ、配列の要素だけです。構文の観点でいえば、さきほどの `buffer` はメモリ上では次のようになっています。 48 | 49 | ``` 50 | buffer: byte byte byte ... 256 times ... byte byte byte 51 | ``` 52 | 53 | つまり、変数は256バイトのデータを保持していて、ただそれだけです。私たちは各要素によく知られたインデックスの構文でアクセスできます。 54 | `buffer[0]` 、 `buffer[1]` から始まって `buffer[255]` までです。(0から255までインデックスの範囲で256の要素をカバーしています) 55 | この範囲の外側にある値のインデックスにアクセスしようとすると、プログラムがクラッシュします。 56 | 57 | `len` という名前の組み込み関数があり、これは配列、スライス、また他のいくつかのデータ型の要素数を返します。 58 | 配列においては、 `len` が何を返すかは明らかです。私たちの例では `len(buffer)` は固定値の `256` を返します。 59 | 60 | 配列には使われるべき場所があります。インスタンスの変換行列として良い表現になっています。しかし、Goで配列がもっともよく使われるのは、 61 | スライス内部で保存領域を確保する目的で使われるときです。 62 | 63 | ## スライス: スライスのヘッダー 64 | 65 | スライスでは面白い処理が行われていますが、スライスを上手に使うためには、それがどのような性質を持っていて、どんな振る舞いをするかを 66 | きちんと理解しなければいけません。 67 | 68 | スライスはスライスの変数自身とは別に保存された配列の境界を表現したデータ構造です。スライスは配列の一部を表現しています。 69 | 70 | 前の説の `buffer` という配列の変数を再利用して、100番目の要素から150番目の要素(正確には含まれる要素は100から149)を持つスライスを、 71 | 配列から切り取る(スライスする)ことで作ることもできるでしょう。 72 | 73 | ``` 74 | var slice []byte = buffer[100:150] 75 | ``` 76 | 77 | 上のスニペットでは、明示的になるように変数宣言を冗長な形で書きました。変数 `slice` は `[]byte` 型で、「バイトのスライス」と呼びます。 78 | `slice` は `buffer` という名前の配列から、100(含む)から150(除く)の要素を切り取ることで初期化されました。 79 | よりイディオムに近い構文では、初期化表現で追加される型情報を省略します。 80 | 81 | ``` 82 | var slice = buffer[100:150] 83 | ``` 84 | 85 | 関数内では、短い記法を使うこともできます。 86 | 87 | ``` 88 | slice := buffer[100:150] 89 | ``` 90 | 91 | この `slice` 変数とは一体なんでしょう。すべてを説明するわけではありませんが、とりあえずスライスを長さとポインターという2つの要素を持つ 92 | 小さなデータ構造とみなしてみましょう。するとスライスが作られるときには、その背後で次のようなことが起きていると考えられます。 93 | 94 | ``` 95 | type sliceHeader struct { 96 | Length int 97 | ZerothElement *byte 98 | } 99 | 100 | slice := sliceHeader{ 101 | Length: 50, 102 | ZerothElement: &buffer[100], 103 | } 104 | ``` 105 | 106 | もちろん、これは単なる説明にすぎません。このスニペットが `sliceHeader` 構造体はプログラマに見えないことを説明していて、 107 | 要素のポインターの型が要素の型に依存するものとは言え、このスニペットは一般的な仕組みを説明しています。 108 | 109 | これまで、配列を切り取る操作をしてきましたが、スライスを切り取ることもできます。次のとおりです。 110 | 111 | ``` 112 | slice2 := slice[5:10] 113 | ``` 114 | 115 | 配列から切り取ったときと同様に、この操作では新しいスライスを作成します。この場合は元のスライスの要素5から9(含む)のスライスで、 116 | これはつまり元の配列の要素105から109を意味します。スライスの変数 `slice2` の中の配列が基にしている `sliceHeader` 構造体は次のようになります。 117 | 118 | ``` 119 | slice2 := sliceHeader{ 120 | Length: 5, 121 | ZerothElement: &buffer[105], 122 | } 123 | ``` 124 | 125 | このヘッダーが依然として `buffer` 変数の中にあるものと同じ配列を参照していることに注意して下さい。 126 | 127 | 再切り取り(再スライス)、つまりスライスを切り取って、その結果を同じスライスに保存することもできます。 128 | 129 | ``` 130 | slice = slice[5:10] 131 | ``` 132 | 133 | この操作のあとは、 `slice` 変数の `sliceHeader` 構造体は `slice2` 変数のものと同様になります。 134 | たとえばスライスと切り詰めるときに再切り取りをよく目にします。次の式では `slice` の最初と最後の要素を捨てます。 135 | 136 | ``` 137 | slice = slice[1:len(slice)-1] 138 | ``` 139 | 140 | (演習:この代入のあとに `sliceHeader` 構造体がどのようになるかを書いてみてください) 141 | 142 | 経験あるGoプログラマがしばしば「スライスヘッダー」について話すのを聞くでしょう。なぜなら、スライスヘッダーは本当にスライス変数内に 143 | 保存されているからです。たとえば、 `bytes.IndexRune` のようなスライスを引数に取る関数を呼び出すとき、そのヘッダーが 144 | 関数に渡される実態です。この関数呼び出しでは 145 | 146 | ``` 147 | slashPos := bytes.IndexRune(slice, '/') 148 | ``` 149 | 150 | `IndexRune` 関数に渡される `slice` 引数は、実際には「スライスヘッダー」なのです。 151 | 152 | スライスヘッダー内にはもう一つのデータ要素があり、それは以降で説明しますが、まずはプログラムでスライスを使うときに、 153 | このスライスヘッダーが存在することが何を意味するのかを理解しましょう。 154 | 155 | ## スライスを関数に渡す 156 | 157 | スライスがポインターを持っていたとしても、スライスそれ自身は値であることを理解することが重要です。 158 | 一皮めくれば、スライスはポインターと長さを持つ構造体の値です。構造体のポインターではありません。 159 | 160 | この事実が大事です。 161 | 162 | 先の例で `IndexRune` を呼んだとき、 `IndexRune` にはスライスヘッダーのコピーが渡されました。 163 | この振る舞いは重要な結果をもたらします。 164 | 165 | 次の簡単な関数を考えてみましょう。 166 | 167 | ``` 168 | func AddOneToEachElement(slice []byte) { 169 | for i := range slice { 170 | slice[i]++ 171 | } 172 | } 173 | ``` 174 | 175 | これは関数名が示す通り、スライスの各インデックスを(`for range` ループを使って)繰り返し進めていき、各要素を1増加させています。 176 | 177 | 試してみましょう。 178 | 179 | ``` 180 | func main() { 181 | slice := buffer[10:20] 182 | for i := 0; i < len(slice); i++ { 183 | slice[i] = byte(i) 184 | } 185 | fmt.Println("before", slice) 186 | AddOneToEachElement(slice) 187 | fmt.Println("after", slice) 188 | } 189 | ``` 190 | 191 | 実行結果は次の通り。 192 | 193 | ``` 194 | before [0 1 2 3 4 5 6 7 8 9] 195 | after [1 2 3 4 5 6 7 8 9 10] 196 | ``` 197 | 198 | スライスヘッダーは値で渡されたとしても、配列の要素に対するポインターを含んでいるため、元のスライスヘッダーと関数に渡された 199 | スライスヘッダーのコピーは同じ配列を表しています。それゆえ、関数がreturnするとき、変更された要素は元の `slice` 変数からでも 200 | 確認できるのです。 201 | 202 | 関数に渡された引数は本当にコピーです。次の例で確認できます。 203 | 204 | ``` 205 | func SubtractOneFromLength(slice []byte) []byte { 206 | slice = slice[0 : len(slice)-1] 207 | return slice 208 | } 209 | 210 | func main() { 211 | fmt.Println("Before: len(slice) =", len(slice)) 212 | newSlice := SubtractOneFromLength(slice) 213 | fmt.Println("After: len(slice) =", len(slice)) 214 | fmt.Println("After: len(newSlice) =", len(newSlice)) 215 | } 216 | ``` 217 | 218 | 実行結果: 219 | 220 | ``` 221 | Before: len(slice) = 50 222 | After: len(slice) = 50 223 | After: len(newSlice) = 49 224 | ``` 225 | 226 | スライスの引数の中身は関数によって修正可能だけど、そのヘッダーは修正不可能であることを確認しました。 227 | `slice` 変数に保持されている長さは関数の呼び出しでは変更されません。なぜなら関数にはスライスヘッダーのコピーが渡されていて、 228 | 元のスライスヘッダーが渡されているわけではないからです。したがって、もしヘッダーを変更する関数を書きたいのであれば、 229 | いま例示したように、それを結果として返さなければいけません。 `slice` 変数は変更されていませんが、返ってきた値は新しい長さになっていて、 230 | それが `newSlice` に保存されます。 231 | 232 | ## スライスへのポインター:メソッドレシーバー 233 | 234 | スライスヘッダーを変更する関数を書く場合、関数にスライスヘッダーのポインターを渡すというのも手です。 235 | 先ほどの例の違うバージョンを書いてみます。 236 | 237 | ``` 238 | func PtrSubtractOneFromLength(slicePtr *[]byte) { 239 | slice := *slicePtr 240 | *slicePtr = slice[0 : len(slice)-1] 241 | } 242 | 243 | func main() { 244 | fmt.Println("Before: len(slice) =", len(slice)) 245 | PtrSubtractOneFromLength(&slice) 246 | fmt.Println("After: len(slice) =", len(slice)) 247 | } 248 | ``` 249 | 250 | 実行結果: 251 | 252 | ``` 253 | Before: len(slice) = 50 254 | After: len(slice) = 49 255 | ``` 256 | 257 | この例では、特に間接的な代入をしているところ(一時変数を使っています)がぎこちなく見えますが、ポインターへのスライスではよくある例です。 258 | スライスを変更するメソッドにポインターレシーバーを使うイディオムがあります。 259 | 260 | スライスに最後のスラッシュ以降を捨てるメソッドを持たせたいとしましょう。そのメソッドは次のように書けます。 261 | 262 | ``` 263 | type path []byte 264 | 265 | func (p *path) TruncateAtFinalSlash() { 266 | i := bytes.LastIndex(*p, []byte("/")) 267 | if i >= 0 { 268 | *p = (*p)[0:i] 269 | } 270 | } 271 | 272 | func main() { 273 | pathName := path("/usr/bin/tso") // string型からpath型への変換 274 | pathName.TruncateAtFinalSlash() 275 | fmt.Printf("%s\n", pathName) 276 | } 277 | ``` 278 | 279 | このサンプルを実行してみると、呼び出し元のスライスを期待通り更新していることがわかると重います。 280 | 281 | 実行結果: 282 | 283 | ``` 284 | /usr/bin 285 | ``` 286 | 287 | (演習:レシーバーの型をポインターではなく値に描き変えて再度実行してみましょう。実行結果がなぜそうなるか説明して下さい。) 288 | 289 | 一方で、(非英語のパス名は無視していますが)パス内のASCII文字を大文字に変換するメソッドを書きたい場合、 290 | メソッドは値レシーバーでも構いません。なぜなら値レシーバーでも同じ配列を参照しているからです。 291 | 292 | ``` 293 | type path []byte 294 | 295 | func (p path) ToUpper() { 296 | for i, b := range p { 297 | if 'a' <= b && b <= 'z' { 298 | p[i] = b + 'A' - 'a' 299 | } 300 | } 301 | } 302 | 303 | func main() { 304 | pathName := path("/usr/bin/tso") 305 | pathName.ToUpper() 306 | fmt.Printf("%s\n", pathName) 307 | } 308 | ``` 309 | 310 | 実行結果: 311 | 312 | ``` 313 | /USR/BIN/TSO 314 | ``` 315 | 316 | ここで `ToUpper` メソッドはインデックスとスライスの要素を取得するために `for range` 構文の中で2つの変数を使っています。 317 | この形によって、`for` の中身で `p[i]` を何度も書くことを回避しています。 318 | 319 | (演習: `ToUpper` メソッドをポインターレシーバーに変更して、動作が変わるか確認しましょう) 320 | (応用演習: `ToUpper` メソッドでASCII文字だけではなくUnicode文字を扱えるようにしてみましょう) 321 | 322 | ## 容量 323 | 324 | 次の、intのスライスの引数を1要素だけ拡大する関数を見てみましょう。 325 | 326 | ``` 327 | func Extend(slice []int, element int) []int { 328 | n := len(slice) 329 | slice = slice[0 : n+1] 330 | slice[n] = element 331 | return slice 332 | } 333 | ``` 334 | 335 | (なぜ修正したスライスを返す必要があるのでしょうか)実行してみます。 336 | 337 | ``` 338 | func main() { 339 | var iBuffer [10]int 340 | slice := iBuffer[0:0] 341 | for i := 0; i < 20; i++ { 342 | slice = Extend(slice, i) 343 | fmt.Println(slice) 344 | } 345 | } 346 | ``` 347 | 348 | 実行結果: 349 | 350 | ``` 351 | [0] 352 | [0 1] 353 | [0 1 2] 354 | [0 1 2 3] 355 | [0 1 2 3 4] 356 | [0 1 2 3 4 5] 357 | [0 1 2 3 4 5 6] 358 | [0 1 2 3 4 5 6 7] 359 | [0 1 2 3 4 5 6 7 8] 360 | [0 1 2 3 4 5 6 7 8 9] 361 | panic: runtime error: slice bounds out of range 362 | 363 | goroutine 1 [running]: 364 | panic(0x13f420, 0x1040a018) 365 | /usr/local/go/src/runtime/panic.go:481 +0x700 366 | main.main() 367 | /tmp/sandbox346230702/main.go:27 +0x220 368 | ``` 369 | 370 | スライスの容量が20まで増えているのがおわかりに・・・増えていません。 371 | 372 | スライスヘッダーの3つ目の要素についてお話するときがやってきました。スライスの容量です。 373 | スライスヘッダーには、配列へのポインターと長さに加えて、スライスの容量も保持しています。 374 | 375 | ``` 376 | type sliceHeader struct { 377 | Length int 378 | Capacity int 379 | ZerothElement *byte 380 | } 381 | ``` 382 | 383 | `Capacity` フィールドは内部で持っている配列に実際どれくらいの空き容量があるかを記録しています。 384 | この値は `Length` が取りうる最大です。スライスをその容量を超えて拡大させることは配列の制限を超えることであり、 385 | パニックを引き起こす原因となります。 386 | 387 | 先の例で `slice` を作成したあと 388 | 389 | ``` 390 | slice := iBuffer[0:0] 391 | ``` 392 | 393 | ヘッダーは次のようになっています。 394 | 395 | ``` 396 | slice := sliceHeader{ 397 | Length: 0, 398 | Capacity: 10, 399 | ZerothElement: &iBuffer[0], 400 | } 401 | ``` 402 | 403 | `Capacity` フィールドは、内部で持っている配列の長さからスライスの最初の要素の配列におけるインデックス(この場合は0)を引いたものとなります。 404 | スライスの容量を知りたければ、組み込み関数の `cap` を使います。 405 | 406 | ``` 407 | if cap(slice) == len(slice) { 408 | fmt.Println("slice is full!") 409 | } 410 | ``` 411 | 412 | ## make 413 | 414 | スライスをその容量以上に大きくしたい時にはどうしたらいいのでしょうか。出来ません! 415 | スライスの定義では、容量が大きくできる最大値です。しかし、求めている結果と同じことが、新しい配列を確保して、データをそこにコピーして、 416 | スライスが新しい配列を指すように変更することで可能となります。 417 | 418 | まず新しい配列を確保するところからはじめましょう。より大きな配列を確保して結果を切り取るために組み込み関数の `new` を使うこともできますが、 419 | 代わりに組み込み関数の `make` を使うほうが簡潔にできます。 `make` は新しい配列を確保して、それを指すスライスヘッダーを作成するということを 420 | 一度に行います。 `make` 関数は3つの引数を取ります。スライスの型、初期値の長さと容量です。容量はスライスのデータを保持するために `make` が 421 | 確保する配列の長さを意味します。次の関数呼び出しで、長さ10で余裕が5(15-10)あるスライスを作成しています。 422 | 423 | ``` 424 | slice := make([]int, 10, 15) 425 | fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice)) 426 | ``` 427 | 428 | 実行結果: 429 | 430 | ``` 431 | len: 10, cap: 15 432 | ``` 433 | 434 | このスニペットではintのスライスの容量を倍にし、長さは同じままに保っています。 435 | 436 | ``` 437 | slice := make([]int, 10, 15) 438 | fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice)) 439 | newSlice := make([]int, len(slice), 2*cap(slice)) 440 | for i := range slice { 441 | newSlice[i] = slice[i] 442 | } 443 | slice = newSlice 444 | fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice)) 445 | ``` 446 | 447 | このコードを実行すると、スライスは、配列を再確保する必要ができるまでに、ずっと多くの余裕ができます。 448 | 449 | 実行結果: 450 | 451 | ``` 452 | len: 10, cap: 15 453 | len: 10, cap: 30 454 | ``` 455 | 456 | スライスを作成するとき、長さと容量が同じであることがしばしばあります。組み込み関数の `make` にはこのよくある状況に合わせた 457 | 短い書き方があります。長さの引数を容量のデフォルト値にすることができます。容量の引数を書かなければ、長さと容量を同じ値にできます。 458 | 459 | 次のコードを実行すると 460 | 461 | ``` 462 | gophers := make([]Gopher, 10) 463 | ``` 464 | 465 | `gophers` スライスは長さと容量が10に設定されます。 466 | 467 | ## copy 468 | 469 | 前の説でスライス `slice` の容量を倍にしたときに、古いデータを新しいスライスにコピーするためにループを書きました。 470 | この処理をより簡単にするために、Goには `copy` という組み込み関数があります。引数は2つのスライスで、右側の引数から左側の引数に 471 | データをコピーします。 `copy` を使って前の例を書き直すと次のようになります。 472 | 473 | ``` 474 | newSlice := make([]int, len(slice), 2*cap(slice)) 475 | copy(newSlice, slice) 476 | ``` 477 | 478 | 実行結果: 479 | 480 | ``` 481 | len: 10, cap: 15 482 | len: 10, cap: 30 483 | ``` 484 | 485 | `copy` 関数は頭が良いです。両方の引数の長さに注意を払いながら、コピー可能な分だけコピーをします。 486 | 言い換えれば、`copy` 関数がコピーする要素の数は、2つの引数の長さの短い方です。 487 | 要素の大小を自分で管理しなくて済みます。また、常に確認する必要はないですが、 `copy` はコピーした要素の数を整数値で返します。 488 | 489 | `copy` 関数はコピー元とコピー先に重なるところがある場合にもきちんと動作します。つまり、1つのスライス内で要素を動かしたい場合です。 490 | 次の例では `copy` を使ってどのようにスライスの真ん中に値を挿入するかをお見せします。 491 | 492 | ``` 493 | // Insertは値をスライス中の指定したインデックスに挿入します。 494 | // 指定するインデックスはスライスの範囲内でなければいけません。 495 | // スライスには新しい要素が入る余地がなければいけません。 496 | func Insert(slice []int, index, value int) []int { 497 | // スライスを1要素だけ大きくする 498 | slice = slice[0 : len(slice)+1] 499 | // 指定したインデックスよりも大きいインデックスをずらして1要素分隙間をあける 500 | copy(slice[index+1:], slice[index:]) 501 | // 新しい値を保存する 502 | slice[index] = value 503 | // 結果を返す 504 | return slice 505 | } 506 | ``` 507 | 508 | この関数で注意することがいくつかあります。もちろん、まずはじめに、スライスの長さが変わったので、更新されたスライスを返さなければいけません。 509 | つぎに、この例では便利な略記法を使っています。この式は 510 | 511 | ``` 512 | slice[i:] 513 | ``` 514 | 515 | つぎの式とまったく同じ意味です。 516 | 517 | ``` 518 | slice[i:len(slice)] 519 | ``` 520 | 521 | また、まだこの使い方は見せていませんが、スライス記法で最初の要素を省略することも可能です。デフォルトでは0になります。 522 | したがって 523 | 524 | ``` 525 | slice[:] 526 | ``` 527 | 528 | はそのスライス自身を表すことにほかなりません。この書き方は配列をスライスにするときに便利です。 529 | この書き方は「ある配列の要素をすべて持つスライス」を宣言するのに最も短く書ける方法です。 530 | 531 | ``` 532 | array[:] 533 | ``` 534 | 535 | 話がそれてしまいました。 `Insert` 関数を実行してみましょう。 536 | 537 | ``` 538 | slice := make([]int, 10, 20) // capacity > length であることに注目。要素を追加するための余地です。 539 | for i := range slice { 540 | slice[i] = i 541 | } 542 | fmt.Println(slice) 543 | slice = Insert(slice, 5, 99) 544 | fmt.Println(slice) 545 | ``` 546 | 547 | 実行結果: 548 | 549 | ``` 550 | [0 1 2 3 4 5 6 7 8 9] 551 | [0 1 2 3 4 99 5 6 7 8 9] 552 | ``` 553 | 554 | ## `Append`: 例 555 | 556 | 2,3前の節で、スライスを1要素分だけ拡張する関数 `Extend` を書きました。しかしながら、あの実装はバグがありました。 557 | スライスの容量が小さすぎる場合、関数がクラッシュしてしまうからです。(`Insert` の例も同様の問題があります。) 558 | いまは修正するための部品がそろったので、intのスライスに対して堅牢な実装の `Extend` を書いてみましょう。 559 | 560 | ``` 561 | func Extend(slice []int, element int) []int { 562 | n := len(slice) 563 | if n == cap(slice) { 564 | // スライスがいっぱいなとき。拡大しなければいけない。 565 | // サイズが0でも拡大できるように2倍+1のサイズにする。 566 | newSlice := make([]int, len(slice), 2*len(slice)+1) 567 | copy(newSlice, slice) 568 | slice = newSlice 569 | } 570 | slice = slice[0 : n+1] 571 | slice[n] = element 572 | return slice 573 | } 574 | ``` 575 | 576 | この場合、スライスを返すという部分が特に重要です。理由は、結果のスライスを再確保したとき、それはまったく異なる配列を参照しているからです。 577 | スライスがいっぱいになったらどうなるか、短いスニペットで確かめてみましょう。 578 | 579 | ``` 580 | slice := make([]int, 0, 5) 581 | for i := 0; i < 10; i++ { 582 | slice = Extend(slice, i) 583 | fmt.Printf("len=%d cap=%d slice=%v\n", len(slice), cap(slice), slice) 584 | fmt.Println("address of 0th element:", &slice[0]) 585 | } 586 | ``` 587 | 588 | 実行結果: 589 | 590 | ``` 591 | len=1 cap=5 slice=[0] 592 | address of 0th element: 0x10430200 593 | len=2 cap=5 slice=[0 1] 594 | address of 0th element: 0x10430200 595 | len=3 cap=5 slice=[0 1 2] 596 | address of 0th element: 0x10430200 597 | len=4 cap=5 slice=[0 1 2 3] 598 | address of 0th element: 0x10430200 599 | len=5 cap=5 slice=[0 1 2 3 4] 600 | address of 0th element: 0x10430200 601 | len=6 cap=11 slice=[0 1 2 3 4 5] 602 | address of 0th element: 0x10436120 603 | len=7 cap=11 slice=[0 1 2 3 4 5 6] 604 | address of 0th element: 0x10436120 605 | len=8 cap=11 slice=[0 1 2 3 4 5 6 7] 606 | address of 0th element: 0x10436120 607 | len=9 cap=11 slice=[0 1 2 3 4 5 6 7 8] 608 | address of 0th element: 0x10436120 609 | len=10 cap=11 slice=[0 1 2 3 4 5 6 7 8 9] 610 | address of 0th element: 0x10436120 611 | ``` 612 | 613 | 最初の大きさ5の配列がいっぱいになったときに再確保していることに注目して下さい。 614 | 新しい配列が確保されたときに、容量の0番目の要素のアドレスが変わっています。 615 | 616 | 堅牢な `Extend` 関数を参考にして、複数の要素でスライスを拡張できるずっと便利な関数を書くことが出来ます。 617 | この実装では、Goで関数が呼びだされてた時に、関数の引数をスライスに変換できる機能を使います。 618 | つまりGoでの関数の可変数引数の機能を使います。 619 | 620 | いまから書く関数を `Append` としましょう。最初のバージョンでは、可変数引数の部分を明確にできるよう、 621 | 単純に `Extend` を繰り返し呼び出します。 `Append` のシグネチャーは次のようになります。 622 | 623 | ``` 624 | func Append(slice []int, items ...int) []int 625 | ``` 626 | 627 | これは `Append` は1つの引数 `slice` を取り、それ以降に0以上のintの変数が続くことを意味しています。 628 | これらの引数は、見て分かる通り、 `Append` がintを扱う限り、intのスライスです。 629 | 630 | ``` 631 | // Appendはスライスにitemsを追加します。 632 | // 最初のバージョン:ただExtendを繰り返し呼び出す。 633 | func Append(slice []int, items ...int) []int { 634 | for _, item := range items { 635 | slice = Extend(slice, item) 636 | } 637 | return slice 638 | } 639 | ``` 640 | 641 | `for range` ループが引数 `items` の要素に対して繰り返し `Extend` を呼び出していてます。 642 | `items` の暗黙的な型は `[]int` です。またブランク識別子 `_` を使って、ループのインデックスを捨てていることも注目してください。 643 | 今回の実装ではインデックスは必要ありません。 644 | 645 | 試してみます。 646 | 647 | ``` 648 | slice := []int{0, 1, 2, 3, 4} 649 | fmt.Println(slice) 650 | slice = Append(slice, 5, 6, 7, 8) 651 | fmt.Println(slice) 652 | ``` 653 | 654 | 実行結果: 655 | 656 | ``` 657 | [0 1 2 3 4] 658 | [0 1 2 3 4 5 6 7 8] 659 | ``` 660 | 661 | この例で新しいテクニックが消化されています。スライスをリテラルを書いて初期化しています。書き方は、スライスの型につづいて、 662 | 各要素をブレースで囲むというものです。 663 | 664 | ``` 665 | slice := []int{0, 1, 2, 3, 4} 666 | ``` 667 | 668 | `Append` 関数は他の理由でも興味深いです。要素を追加するだけではなく、呼び出し時に `...` 記法を使って 669 | 「展開」させることで、2つめのスライスをまるごと追加することが出来ます。 670 | 671 | ``` 672 | slice1 := []int{0, 1, 2, 3, 4} 673 | slice2 := []int{55, 66, 77} 674 | fmt.Println(slice1) 675 | slice1 = Append(slice1, slice2...) // The '...' is essential! 676 | fmt.Println(slice1) 677 | ``` 678 | 679 | 実行結果: 680 | 681 | ``` 682 | [0 1 2 3 4] 683 | [0 1 2 3 4 55 66 77] 684 | ``` 685 | 686 | もちろん、確保を1度だけしか行わず、`Extend`の中でスライスを作るようにすることで `Append` をより効率的にすることができます。 687 | 688 | ``` 689 | // Appendはスライスに要素を追加します。 690 | // 効率的なバージョン。 691 | func Append(slice []int, elements ...int) []int { 692 | n := len(slice) 693 | total := len(slice) + len(elements) 694 | if total > cap(slice) { 695 | // 再確保。新しいサイズは1.5倍なので、さらに拡大することができます。 696 | // Reallocate. Grow to 1.5 times the new size, so we can still grow. 697 | newSize := total*3/2 + 1 698 | newSlice := make([]int, total, newSize) 699 | copy(newSlice, slice) 700 | slice = newSlice 701 | } 702 | slice = slice[:total] 703 | copy(slice[n:], elements) 704 | return slice 705 | } 706 | ``` 707 | 708 | ここで、`copy` を2度使っているその使い方に注目して下さい。1回めはスライスのデータを新しく確保したメモリに移動するため、 709 | 2回めは追加する要素を古いデータのあとにコピーするために使っています。 710 | 711 | 試してみましょう。動作は前の例と同様です。 712 | 713 | ``` 714 | slice1 := []int{0, 1, 2, 3, 4} 715 | slice2 := []int{55, 66, 77} 716 | fmt.Println(slice1) 717 | slice1 = Append(slice1, slice2...) // The '...' is essential! 718 | fmt.Println(slice1) 719 | ``` 720 | 721 | 実行結果: 722 | 723 | ``` 724 | [0 1 2 3 4] 725 | [0 1 2 3 4 55 66 77] 726 | ``` 727 | 728 | ## `append`: 組み込み関数 729 | 730 | ようやく組み込み関数の `append` を設計する動機までたどり着きました。この関数は先の `Append` の例と同じ処理を行い、 731 | 同じくらい効率的で、それでいてあらゆるスライス型で動作します。 732 | 733 | Goの弱点として、ジェネリクス型の操作はランタイムに行わなければいけないというものがあります。 734 | いつか変わるかもしれませんが、いまは、スライスの操作が簡単になるように、汎用の `append` 関数を提供しています。 735 | 機能は先のintのスライスのバージョンと同じですが、あらゆるスライス型に対応している点が異なります。 736 | 737 | 覚えておいてほしいのは、スライスヘッダーは `append` を呼び出すたびに更新されるので、 738 | 呼び出したあとは返されたスライスを保存しなければいけません。事実、`append` を呼んだあとに結果を保存しないと 739 | コンパイラがエラーを吐きます。 740 | 741 | 次のコードは `append` を使った言い回しをprint文とともに並べたものです。 742 | そのまま実行したり、編集したりしながら、機能を確認してみてください。 743 | 744 | ``` 745 | // 2つのスライスを作成します。 746 | slice := []int{1, 2, 3} 747 | slice2 := []int{55, 66, 77} 748 | fmt.Println("Start slice: ", slice) 749 | fmt.Println("Start slice2:", slice2) 750 | 751 | // スライスにアイテムを追加します。 752 | slice = append(slice, 4) 753 | fmt.Println("Add one item:", slice) 754 | 755 | // スライスを他のスライスに追加します。 756 | slice = append(slice, slice2...) 757 | fmt.Println("Add one slice:", slice) 758 | 759 | // (intの)スライスのコピーを作ります。 760 | slice3 := append([]int(nil), slice...) 761 | fmt.Println("Copy a slice:", slice3) 762 | 763 | // スライスを自分自身の後ろにコピーします。 764 | fmt.Println("Before append to self:", slice) 765 | slice = append(slice, slice...) 766 | fmt.Println("After append to self:", slice) 767 | ``` 768 | 769 | 実行結果: 770 | 771 | ``` 772 | Start slice: [1 2 3] 773 | Start slice2: [55 66 77] 774 | Add one item: [1 2 3 4] 775 | Add one slice: [1 2 3 4 55 66 77] 776 | Copy a slice: [1 2 3 4 55 66 77] 777 | Before append to self: [1 2 3 4 55 66 77] 778 | After append to self: [1 2 3 4 55 66 77 1 2 3 4 55 66 77] 779 | ``` 780 | 781 | スライスの設計によって簡潔な記述できちんと動作することを理解できるので、時間を取って最後の例の詳細な動作を考えてみましょう。 782 | 783 | `append` や `copy` などのスライスの操作の例は、コミュニティが作成した ["Slice Tricks" というWikiページ](https://golang.org/wiki/SliceTricks) にあります。 784 | 785 | ## Nil 786 | 787 | 余談として、これまで得た知識を考えると、 `nil` スライスがどのように表現されるかわかります。 788 | 自然に考えるとスライスヘッダーのゼロ値は 789 | 790 | ``` 791 | sliceHeader{ 792 | Length: 0, 793 | Capacity: 0, 794 | ZerothElement: nil, 795 | } 796 | ``` 797 | 798 | あるいは 799 | 800 | ``` 801 | sliceHeader{} 802 | ``` 803 | 804 | と表現されます。重要な点は、要素のポインターも `nil` になっている点です。次の配列から作られたスライスは 805 | 806 | ``` 807 | array[0:0] 808 | ``` 809 | 810 | 長さ0(かつおそらく容量0)ですが、そのポインターは `nil` ではないので、これは `nil` スライスではありません。 811 | 812 | 明確にすると、空のスライスは(容量が0ではないと想定すると)拡大することができますが、 `nil` スライスは値を入れる配列がなく、 813 | 拡大して1つの要素も保持することさえできません。 814 | 815 | つまり、`nil` スライスは、たとえポインターが何も指していないとしても、機能的には長さ0のスライスと同値です。 816 | 長さ0のスライスは、長さが0で確保することで要素を追加することが出来ます。 817 | 例として、先の例でスライスを `nil` スライスに追加しているものを確認して下さい。 818 | 819 | ## 文字列 (string) 820 | 821 | この節では、スライスという観点から、Goにおける文字列の扱いについて簡単に説明します。 822 | 823 | 文字列は、実際に非常に単純です。文字列は読み込み専用のバイトのスライスにいくつか言語からの構文サポートがついたものです。  824 | 825 | 読み込み専用なので、(拡大させることができないことから)容量を考える必要がありません。かわりにたいていの目的においては、 826 | 単なる読み込み専用のバイトのスライスとして扱うことが出来ます。 827 | 828 | まずはじめに、個々のバイトにインデックスでアクセスすることが出来ます。 829 | 830 | ``` 831 | slash := "/usr/ken"[0] // '/' のバイト値を返します。 832 | ``` 833 | 834 | 部分文字列を取得するために文字列を切り取ることが出来ます。 835 | 836 | ``` 837 | usr := "/usr/ken"[0:4] // "/usr" という文字列を返します。 838 | ``` 839 | 840 | 文字列を切り取るときに、その裏で何が起きているかは明らかでしょう。 841 | 842 | 単なるバイトのスライスから、単純にキャストすることで文字列を作ることも出来ます。 843 | 844 | ``` 845 | str := string(slice) 846 | ``` 847 | 848 | 同様に逆も可能です。 849 | 850 | ``` 851 | slice := []byte(usr) 852 | ``` 853 | 854 | 文字列の内部にある配列は見えないようになっていて、文字列(string)を通してしかその配列の中身にはアクセスできません。 855 | つまり、文字列とバイトスライスの間で変換を行うと、かならず配列のコピーが発生するということです。 856 | もちろん、Goがコピー処理を行うので、自分で行う必要はありません。文字列からバイトスライスに変換した際には、 857 | 変換後のバイトスライス内の配列を操作しても、変換元の文字列には影響しません。 858 | 859 | 文字列をスライスのような設計にしたことによる重要な帰結として、部分文字列を非常に効率よく精製できるようになったことがあります。 860 | 部分文字列を作るときに必要なことは、2つのフィールドを持つ文字列のヘッダーを生成することです。 861 | 文字列は読み取り専用なので、元の文字列とそこから得られた部分文字列は同じ配列を安全に共有できます。 862 | 863 | 歴史的経緯:最初期の文字列の実装では常に再確保していましたが、Go言語にスライスがついかされたときに、スライスによって 864 | 効率的な文字列操作のためのモデルが提供されました。結果としていくつかのベンチマークで大幅な高速化が確認されました。 865 | 866 | もちろん、文字列に関してはもっと語ることがありますが、それは[別のブログポスト](./strings/)でより深く説明することにします。 867 | 868 | ## 結論 869 | 870 | スライスがどのように動作するかを理解するために、スライスがどのように実装されているかを理解することがその助けになります。 871 | スライスヘッダーという小さなデータ構造がスライス変数と紐付いていて、そのヘッダーが確保された配列とは別にデータの区切りを示しています。 872 | スライスの値を渡すと、ヘッダーはコピーされますが、内部の配列へのポインターはつねに共有されます。 873 | 874 | 一度動作原理を理解してしまえば、スライスは簡単に使えるだけでなく、特に `copy` や `append` といった組み込み関数のサポートで、 875 | 強力で表現力の高いものとなります。 876 | 877 | ## 関連記事 878 | 879 | Goのスライスに関する資料はたくさんあります。先にも触れた、["Slice Tricks"のWikiページ](https://golang.org/wiki/SliceTricks) には 880 | たくさんの例が載っています。[Go Slices](http://blog.golang.org/go-slices-usage-and-internals) のブログポストには、 881 | メモリのレイアウトが、綺麗な図とともに説明されています。Russ Coxの [Go Data Structures](http://research.swtch.com/godata) には 882 | スライスが、Goの他の内部データ構造とともに議論されています。 883 | 884 | 資料は他にもたくさんありますが、スライスを学ぶ最善の方法はそれを使うことです。 885 | 886 | By Rob Pike 887 | 888 | ## あわせて読みたい 889 | * [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) 890 | -------------------------------------------------------------------------------- /content/post/strings.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2013-10-23T09:18:55+09:00" 3 | draft = false 4 | title = "Goにおける文字列、バイト、ルーンと文字 (Strings, bytes, runes and characters)" 5 | tags = ["strings", "bytes", "runes", "characters"] 6 | +++ 7 | 8 | # Goにおける文字列、バイト、ルーンと文字 9 | [Strings, bytes, runes and characters in Go](https://blog.golang.org/strings) By Rob Pike 10 | 11 | ## はじめに 12 | 13 | [1つ前の記事](./slices/) では、その実装の背後にある機能を解説する例とともに、Goにおいてスライスがどのように動作するかを説明しました。 14 | その知識を前提として、この記事ではGoにおける文字列について話します。 15 | まず最初に、文字列はブログの記事にしては簡単すぎるように見えるかもしれませんが、上手に使うには文字列の動作を理解するだけでなく、 16 | バイト、文字、ルーンの違いについても理解し、UnicodeとUTF-8の違いについても理解し、文字列と文字列リテラルの違いについても理解し、 17 | その他多くの細かな違いについて理解する必要があります。 18 | 19 | この話題を議論するときの1つのアプローチとして、FAQである「Goの文字列のn番目のインデックスにアクセスした時に、 20 | なぜn番目の文字を取得できないのか」という質問の回答を考えてみましょう。この記事で説明していきますが、この質問には 21 | 現代社会でテキストがどのように動作しているかを多くの観点から考えるきっかけとなります。 22 | 23 | Goにかぎらず、これらの問題について考える最高の導入は、Joel Spolskyの有名なブログポスト、[The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)](http://www.joelonsoftware.com/articles/Unicode.html) です。 24 | 彼がその記事の中で挙げた点を、この記事でも繰り返し言及します。 25 | 26 | ## stringとは何か 27 | 28 | 基礎からはじめましょう。 29 | 30 | Goでは、文字列は実際には読み取り専用のバイトのスライスでした。バイトのスライスがなにかについて不確かな場合、あるいはそれがどう動作するか 31 | 不確かな場合は、[前のブログポスト](./slices/) を読んで下さい。この記事ではすでに前のブログポストを読んでいることを前提とします。 32 | 33 | stringは *任意の* バイトを保持できることをはっきりと述べておくことは重要です。 34 | stringはUnicode文字もUTF-8文字も、その他の事前定義の形式の文字を持つ必要はありません。 35 | stringの中身を考える限りにおいては、それはバイトのスライスについて考えることと同義です。 36 | 37 | (すぐあとで説明しますが)あるバイト値を定数で持つように `\xNN` という記法を使って文字列リテラルを定義しました。 38 | (もちろん、16進数でのバイト値の範囲は両端含んで `00` から `FF` です) 39 | 40 | ``` 41 | const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 42 | ``` 43 | 44 | ## 文字列を表示する 45 | 46 | サンプルの文字列内のいくつかのバイトは正しいASCII文字やUTF-8の値ではないので、直接表示するとおかしな出力になります。 47 | 単順に表示すると 48 | 49 | ``` 50 | fmt.Println(sample) 51 | ``` 52 | 53 | おかしな結果になります。(見た目は環境によって変わります。) 54 | 55 | ``` 56 | ��=� ⌘ 57 | ``` 58 | 59 | 文字列が本当はどのような値を保持しているかを見たければ、文字列を分解して、個々に調べる必要があります。 60 | それにはいくつかの方法があります。最も明示的な方法は、中身をループで回して、バイトを1つずつ取り出す方法です。 61 | 62 | ``` 63 | for i := 0; i < len(sample); i++ { 64 | fmt.Printf("%x ", sample[i]) 65 | } 66 | ``` 67 | 68 | 先に言ったように、文字列にインデックスでアクセスすると個々の文字ではなく個々のバイトにアクセスします。 69 | その話についてはあとで触れるとして、いまはバイトについてだけ考えましょう。バイトごとのループの出力はこのようになります。 70 | 71 | ``` 72 | bd b2 3d bc 20 e2 8c 98 73 | ``` 74 | 75 | 個々のバイトが、文字列を定義したエスケープ済み16進数と一致することに注目してください。 76 | 77 | 汚い文字列を表示できる形にするのにより短い書き方は `fmt.Printf` の `%x`(16進数)フォーマット書式です。 78 | この書式では文字列の一連のバイトを16進数の1バイトあたり2つ数字としてダンプします。 79 | 80 | ``` 81 | fmt.Printf("%x\n", sample) 82 | ``` 83 | 84 | これの出力を先の表示と比較してみましょう。 85 | 86 | ``` 87 | bdb23dbc20e28c98 88 | ``` 89 | 90 | コツとしては、書式内で `%` と `x` の間に空白を置く「空白」フラグを使うことです。上の書式文字列と比較してみましょう。 91 | 92 | ``` 93 | fmt.Printf("% x\n", sample) 94 | ``` 95 | 96 | 結果の出力にはバイトごとに間に空白が入り、より自然な形になったことに気がつくでしょう。 97 | 98 | ``` 99 | bd b2 3d bc 20 e2 8c 98 100 | ``` 101 | 102 | 他にも方法があります。 `%q` (引用)書式を使うと、文字列中でうまく表示ができないバイト列がある場合は、 103 | 出力がおかしくならないようにエスケープしてくれます。 104 | 105 | ``` 106 | fmt.Printf("%q\n", sample) 107 | ``` 108 | 109 | この方法は、文字列の大部分は読めるけれど、おかしな所を無くしたい時に便利です。先ほどの文字列では次のような出力になります。 110 | 111 | ``` 112 | "\xbd\xb2=\xbc ⌘" 113 | ``` 114 | 115 | この出力をよく見てみると、おかしな文字列の中にASCII文字の等号記号と半角スペースとよく知られたスウェーデン語の「Place of Interest(名所)」 116 | 記号があることがわかります。この記号はUnicode値ではU+2318で表され、UTF-8では16進数で28で表される半角スペースに続いて、 117 | e2 8c 98で表されます。 118 | 119 | 文字列の中におかしな値があることでよくわからなくなってしまったり混乱してしまうようであれば、`%q` 書式の中で「プラス」記号を使うと良いでしょう。 120 | このフラグはうまく表示できないバイト列をエスケープするだけでなく、UTF-8として解釈できる非ASCII文字のバイト列もエスケープします。 121 | このフラグを使うと、非ASCII文字のUTF-8として解釈できるUnicode値を文字列内に表示します。 122 | 123 | ``` 124 | fmt.Printf("%+q\n", sample) 125 | ``` 126 | 127 | この書式では、先ほどのスウェーデン語の記号のUnicode値は `\u` でエスケープされて表示されます。 128 | 129 | ``` 130 | "\xbd\xb2=\xbc \u2318" 131 | ``` 132 | 133 | これらのテクニックは文字列の中身をデバッグしたい時には知っておくと便利なものですし、これから先の議論においても便利なものになります。 134 | これらの方法は文字列の場合と同様にバイト列に対してもまったく同様に使えることも知っておくと良いでしょう。 135 | 136 | つぎに、これまでに挙げた表示のオプションを、実行できるプログラムの形ですべて挙げてみます。 137 | 138 | ``` 139 | package main 140 | 141 | import "fmt" 142 | 143 | func main() { 144 | const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 145 | 146 | fmt.Println("Println:") 147 | fmt.Println(sample) 148 | 149 | fmt.Println("Byte loop:") 150 | for i := 0; i < len(sample); i++ { 151 | fmt.Printf("%x ", sample[i]) 152 | } 153 | fmt.Printf("\n") 154 | 155 | fmt.Println("Printf with %x:") 156 | fmt.Printf("%x\n", sample) 157 | 158 | fmt.Println("Printf with % x:") 159 | fmt.Printf("% x\n", sample) 160 | 161 | fmt.Println("Printf with %q:") 162 | fmt.Printf("%q\n", sample) 163 | 164 | fmt.Println("Printf with %+q:") 165 | fmt.Printf("%+q\n", sample) 166 | } 167 | ``` 168 | 169 | (演習:上の例を変更して、文字列の代わりにバイトスライスを使ってみましょう。 ヒント:スライスを作るにはキャストを使います。) 170 | 171 | (演習:文字列内の個々のバイトに対して `%q` 書式を使ってみましょう。出力から何が分かるでしょうか。) 172 | 173 | ## UTF-8と文字列リテラル 174 | 175 | これまで見てきたように、文字列をインデックスでアクセスするとその場所にある文字ではなくバイトが返されます。stringはバイト列にすぎないのです。 176 | すなわち、string内に文字値を保存すると、そのバイト表現を保存します。何が起きているか、確認しながら見てみましょう。 177 | 178 | 次のサンプルは、1文字の文字列定数を3通りの方法で出力する簡単なプログラムです。それぞれ、単純な文字列、ASCII文字限定の引用文字列、 179 | 16進数での個々のバイト列です。混乱を避けるため、文字列定数がリテラル文字列のみを含むようにバッククォートで囲った「生文字列」を生成します。 180 | (先に見たようにダブルクォートで囲った通常の文字列ではエスケープシーケンスを含む可能性があります。) 181 | 182 | ``` 183 | func main() { 184 | const placeOfInterest = `⌘` 185 | 186 | fmt.Printf("plain string: ") 187 | fmt.Printf("%s", placeOfInterest) 188 | fmt.Printf("\n") 189 | 190 | fmt.Printf("quoted string: ") 191 | fmt.Printf("%+q", placeOfInterest) 192 | fmt.Printf("\n") 193 | 194 | fmt.Printf("hex bytes: ") 195 | for i := 0; i < len(placeOfInterest); i++ { 196 | fmt.Printf("%x ", placeOfInterest[i]) 197 | } 198 | fmt.Printf("\n") 199 | } 200 | ``` 201 | 202 | 出力は次のとおりです。 203 | 204 | ``` 205 | plain string: ⌘ 206 | quoted string: "\u2318" 207 | hex bytes: e2 8c 98 208 | ``` 209 | 210 | これでわかることは、Unicode文字値 U+2318 (「Place of Interest(名所)」記号 ⌘)は、バイト列 `e2 8c 98` で表現され、 211 | これらのバイト列は16進数値 `2318` のUTF-8エンコードであるとわかります。 212 | 213 | これは、UTF-8に詳しい人にとっては、明らかで瑣末なことかもしれませんが、文字列のUTF-8表現がどのように生成されるのかを説明するか 214 | を考えるみることは有意義なことです。単純な事実としては、UTF-8の文字列がソースコードが書かれた時に生成されている、ということです。 215 | 216 | GoのソースコードはUTF-8で書かれると定義されています。他のエンコードは許されていません。これは、ソースコード中に次の文字列を書いたときに、 217 | 218 | ``` 219 | `⌘` 220 | ``` 221 | 222 | そのプログラムを書くために使っているテキストエディターは記号 ⌘ のUTF-8エンコードをソーステキスト中に埋め込んでいる、 223 | ということを示唆しています。16進数バイトを表示するとき、それはエディターがファイルに埋め込んだデータをただダンプしているにすぎません。 224 | 225 | 短く言えば、GoのソースコードはUTF-8で、 *したがって、文字列リテラルのソースコードはUTF-8文字列なのです* 。 226 | 生文字列ではありえませんが、その文字列リテラルにエスケープシーケンスがなければ、生成された文字列は 227 | まさに引用符の間にあるソースの文字列だけを保持します。 228 | したがって、その定義とその生成過程から、生文字列は常にその正しいUTF-8表現を保持しています。 229 | 同様に、前の節で出てきたようなUTF-8の規則から外れたエスケープを持っていなければ、通常の文字列リテラルでも常に正しいUTF-8を保持しています。 230 | 231 | Goの文字列は常にUTF-8だと思っている人もいますが、そうではありません。文字列リテラルだけがUTF-8なのです。 232 | 前の節でお見せしたように、文字列値には任意のバイトを含むことが出来ます。この節で説明したように、文字列リテラルはバイトレベルでのエスケープが 233 | ない限り、常にUTF-8の文字列を保持しています。 234 | 235 | まとめると、文字列は任意のバイトを含むことが出来ますが、文字列リテラルから生成された場合は、そのバイト列は(ほぼ常に)UTF-8です。 236 | 237 | ## コードポイント、文字、ルーン 238 | 239 | ここまで、「バイト」と「文字」というそれぞれの言葉の使い分けに繊細な注意を払ってきました。 240 | その理由は、文字列がバイト列を保持できること、そして「文字」という概念はいささか定義が難しいことから来ています。 241 | Unicode 標準は、1つの値で表現される項目を指す場合「コードポイント」という用語を使います。 242 | コードポイント U+2318 、16進数値 2318 は記号 ⌘ を表します。 243 | (このコードポイントについてもっと知りたい場合は、その[Unicodeページ](http://unicode.org/cldr/utility/character.jsp?a=2318)を見てみましょう。) 244 | 245 | もっと平凡な例を出すと、Unicodeコードポイント U+0061 は、小文字のラテン文字 'A'、すなわち a です。 246 | 247 | しかし、小文字のグレーブアクセント付き文字の 'A'、つまり à はどう表現されるのでしょうか。これは文字で、コードポイント(U+00E0)もあります。 248 | しかし、ほかの表現もあります。たとえば、グレーブアクセントのコードポイント U+0300 を小文字 a のコードポイント U+0061 の「連結」を使って、 249 | 同じ文字 à を生成できます。一般的に、文字はいくつもの異なるコードポイントシーケンスで表現しうるため、異なるUTF-8のバイト列で表現できます。 250 | 251 | したがって、コンピュータにおける文字の概念はあいまい、あるいは少なくともややこしく、そのため注意して扱うのです。 252 | 安心して使えるように、ある文字が常に同じコードポイントで表現されるようにする正規化のテクニックがありますが、 253 | この記事の本題からは大きく外れてしまいます。後のブログエントリでGoのライブラリが正規化に対処しているかを説明しましょう。 254 | 255 | 「コードポイント」はいささか呼びにくいので、Goではその概念を表すより短い用語である「ルーン (rune)」を導入しました。 256 | この用語はライブラリやソースコードに出てきますが、「コードポイント」とまったく同義です。さらにGoにおいてはもう1つの意味があります。 257 | 258 | Go言語では `rune` は `int32` のエイリアスとして定義しています。したがって、プログラムではある整数値がコードポイントを表しているかどうかを 259 | 明確に区別することができます。さらに、Goでは、文字定数と思われているものは、ルーン定数になっています。 260 | 次の表現の型と値は、 261 | 262 | ``` 263 | '⌘' 264 | ``` 265 | 266 | 整数値 `0x2318` のルーンです。 267 | 268 | まとめると、次のような目立った特徴があります。 269 | 270 | * Goのソースコードは常にUTF-8 271 | * 文字列は任意のバイトを保持できる 272 | * 文字列リテラルは、バイトレベルのエスケープがない場合、常に正しいUTF-8シーケンスを保持する 273 | * これらのシーケンスは、ルーンと呼ばれるUnicodeコードポイントを表している 274 | * Goでは、文字列内の文字が正規化されている保証はない 275 | 276 | ## rangeループ 277 | 278 | GoのソースコードはUTF-8であるという、自明のような詳細に加えて、GoがUTF-8を本当に特別に扱っている唯一の点は、文字列を `for range` ループ 279 | するときです。 280 | 281 | 通常の `for` ループで何が起きるかはすでに見ています。対照的に `for range` ループでは、イテレーションごとに 282 | 1つのUTF-8にエンコードされたルーンをデコードします。ループが回るごとに、ループのインデックスはバイト換算したときの現在のルーンの開始位置となり、値はそのルーン値のコードポイントとなります。これまで紹介したものとはまた別の便利な `Printf` 書式 `%#U` を使った例をお見せします。 283 | この書式では、Unicode値のコードポイントとその表現を表示します。 284 | 285 | ``` 286 | const nihongo = "日本語" 287 | for index, runeValue := range nihongo { 288 | fmt.Printf("%#U starts at byte position %d\n", runeValue, index) 289 | } 290 | ``` 291 | 292 | これを出力すると、それぞれのコードポイントが複数のバイトから成っていることがわかります。 293 | 294 | ``` 295 | U+65E5 '日' starts at byte position 0 296 | U+672C '本' starts at byte position 3 297 | U+8A9E '語' starts at byte position 6 298 | ``` 299 | 300 | (演習:文字列内に不正なUTF-8バイト列を入れてみましょう。(方法はだって?)ループの繰り返しで何が起きるでしょうか。) 301 | 302 | ## ライブラリ 303 | 304 | Goの標準ライブラリではUTF-8文字列を解釈するための強力なサポートを提供しています。 305 | 目的に対して `for range` ループでは不十分な場合には、おそらく必要なものはライブラリ内のパッケージで提供されているでしょう。 306 | 307 | そのようなパッケージの中で最重要なものが [unicode/utf8](http://golang.org/pkg/unicode/utf8/) です。 308 | この中にUTF-8文字列を検証、分解、再構築するためのヘルパー関数があります。次の例は、先の `for range` ループの例と同じ処理をしますが、 309 | `unicode/utf-8` 内の `DecodeRuneInString` 関数を使っています。この関数の戻り値はルーンとUTF-8エンコードされたバイト幅です。 310 | 311 | ``` 312 | const nihongo = "日本語" 313 | for i, w := 0, 0; i < len(nihongo); i += w { 314 | runeValue, width := utf8.DecodeRuneInString(nihongo[i:]) 315 | fmt.Printf("%#U starts at byte position %d\n", runeValue, i) 316 | w = width 317 | } 318 | ``` 319 | 320 | 実行して先の例と同じ結果になることを確認してみましょう。 `for range` ループと `DecodeRuneInString` はまったく同じ繰り返し処理を 321 | するように定義されています。 322 | 323 | `unicode/utf-8` パッケージの[ドキュメント](http://golang.org/pkg/unicode/utf8/)を見て、他の関数も見てみましょう。 324 | 325 | ## 結論 326 | 327 | はじめに投げかけた質問に答えましょう。文字列はバイトからなり、それゆえインデックスでアクセスするとその場所の文字ではなくバイトを返します。 328 | 文字列は文字以外のものも保持します。事実、「文字」の定義は曖昧で、文字列は文字からできていると定義することで、その曖昧さを解決しようとする 329 | ことは間違いでしょう。 330 | 331 | Unicode、UTF-8、多言語文字列処理の世界に関してはまだまだ語ることがたくさんありますが、それは他の記事に任せましょう。 332 | いまは、あなたがGoの文字列がどのように振る舞うかを理解し、任意のバイトを含むかもしれないとしても、UTF-8はGoの文字列の設計における中心 333 | であることの理解がより深まったことを願っています。 334 | 335 | By Rob Pike 336 | -------------------------------------------------------------------------------- /content/post/toward-go2.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2017-07-13T15:35:46+09:00" 3 | title = "Go 2にむけて" 4 | draft = false 5 | tags = ["go2"] 6 | +++ 7 | 8 | # Go 2にむけて 9 | [Toward Go 2](https://blog.golang.org/toward-go2) by Russ Cox 10 | 11 | ## はじめに 12 | 13 | (この文章は本日行われた Gophercon 2017 での私の発表の書き起こしで、Goコミュニティ全体にGo 2のための議論や計画をする中での支援を求めるものです。 14 | 動画が公開されたらこちらにリンクする予定です。) 15 | 16 | Rob Pike、Robert Griesemer、そしてKen Thompsonが新しいプログラミング言語について数日議論を重ねた後、2007年9月25日にRobが「Go」という名前を提案しました。 17 | 18 | ![mail](./mail.png) 19 | 20 | 翌年、Ian Lance Taylorと私がチームに参加し、5人で2つのコンパイラと標準ライブラリを開発し、その成果が2009年11月10日の[オープンソースリリース](https://opensource.googleblog.com/2009/11/hey-ho-lets-go.html)となりました。 21 | 22 | ![tweet](./tweet.png) 23 | 24 | それからの2年は、できたばかりのGoのオープンソースコミュニティの支援のもとに、大小様々な変更を伴う実験をしつつGoを洗練させ、その結果が2011年10月5日に提案された[Go 1のリリース計画](https://blog.golang.org/preview-of-go-version-1)となりました。 25 | 26 | ![go1-preview](./go1-preview.png) 27 | 28 | さらにGoコミュニティからより多くの協力をうけ、Go 1のリリース計画を改定しつつ、それを元に実装を進め、最終的に2012年3月28日に[Go 1をリリース](https://blog.golang.org/go-version-1-is-released)しました。 29 | 30 | ![go1-release](./go1-release.png) 31 | 32 | およそ5年の歳月にわたる創造的で熱狂的な努力によって、"Go"という名前といくつかのアイデアのリストでしかなかったものが、本番環境で利用可能な安定した言語となりました。その最終成果が、Go 1のリリースです。 33 | またこの成果は、変化や仲間内での試行錯誤から安定性の明示的な変遷の結果でもあります。 34 | 35 | Go 1への歳月のなかで、私たちはGoを変更し、皆のGoプログラムをほぼ毎週壊していました。私たちはそういった変更がGoの本番での利用を妨げるものになっていたことは承知していました。 36 | 本番環境では言語の変更に追従するために毎週変更することなどはできませんから。[Go 1のリリース宣言のブログポスト](https://blog.golang.org/go-version-1-is-released)にも書いているように、 37 | Go 1のリリースの主要なモチベーションは、信頼できる製品やプロジェクトや出版物(ブログ、チュートリアル、カンファレンスでの発表、書籍)を作成する安定した基盤を提供し、それによってユーザーの製品がコンパイルし続けられ、何年も変更無しで実行し続けられるという信用を勝ち取ることでした。 38 | 39 | Go 1がリリースされた後、私たちはGoが設計された本来の目的である、本番環境での利用に時間を費やす必要があることを認識していました。それから言語の変更そのものからあえて離れ、私たちのプロジェクトでのGoの利用や実装の改善へと注力する対象を移しました。私たちはGoをさまざまな新しいシステムにポートし、Goがより効率的に実行するようにパフォーマンスに致命的なあらゆる箇所を再実装しました。また、[競合条件検出ツール(race detector)](https://blog.golang.org/race-detector)といった新しい重要なツールも追加しました。 40 | 41 | いま、私たちはGoを大規模の本番環境に耐える品質のシステムで使う経験を5年重ねてきました。私たちはどのような機能がそのような環境に適合し、またどのような機能がそうでないかの知見をためてきました。 42 | いまこそ、Goの進化と成長の次なる一歩をすすめ、Goの未来を計画するときです。本日、ここにGoコミュニティのみなさん、このGopherConにいる方も、その動画を見ている方も、このGoブログを後日読んでいる方も、そのすべての方々に、私たちとともにGo 2の計画や実装を進める支援をお願いいたします。 43 | 44 | ## 目標 45 | 46 | 私たちがいまGoの目標として掲げているものは2007年当時のものと変わりありません。私たちはプログラマが2つのスケーラビリティを管理するにあたって、より効率的になってほしいと考えています。まず本番環境の規模、特にクラウドソフトウェアなどの多くのサーバーと通信するような並行システムの管理。そして開発の規模、特にモダンなオープンソース開発で例示されるような疎結合で多くのエンジニアが協力しあうような巨大なコードベースの管理です。 47 | 48 | このようなスケーラビリティはどのような規模の会社でも関係してきます。たった5人のスタートアップ企業も他の企業から提供される巨大なクラウドベースのAPIサービスを利用するでしょうし、自社開発のものよりも多くのオープンソースのソフトウェアを使うことになるでしょう。 49 | スタートアップ企業が必要とする本番環境の規模と開発環境の規模は、Googleが必要とするそれと何も変わりません。 50 | 51 | Go 2に対する私たちの目標は、Goがスケールする際の致命的な点を修正することにあります。 52 | 53 | (これらの目標に関しては、Rob Pikeが2012年に書いた記事の"[Go at Google: Language Design in the Service of Software Engineering](https://talks.golang.org/2012/splash.article)"や、私の2015年のGopherConの発表の"[Go, Open Source, Community](https://blog.golang.org/open-source)"を参照してください。) 54 | 55 | ## 制約 56 | 57 | Goの目標は当初から何も変わっていませんが、Goが持つ制約は変わっています。最も重要な制約は、既存で利用されているGoの存在です。私たちは少なくとも[世界中に50万人のGo開発者がいる](https://research.swtch.com/gophercount)と見積もっています。つまり、少なくとも何百万ものGoのソースファイルが存在し、10億行のGoコードが存在するということです。 58 | これらのコードを書いているプログラマやこういったソースコードがGoの成功を表しています。しかし、これらはまたGo 2への制約でもあるのです。 59 | 60 | Go 2はこれらすべての開発者とともになければなりません。私たちはこうした開発者に、古い慣習を捨て、素晴らしいと感じられるときだけ新しい慣習を覚えて欲しいとお願いする必要があります。 61 | たとえば、Go 1以前では `error` 型で実装されていたメソッドは `String` という名前でしたが、Go 1では `Error` という名前に変更しました。これは `error` 型と自分自身で書式化できる他の型とを区別するために行われました。 62 | ある日、私は `error` 型を実装していて、何も考えずに `Error` メソッドでなく `String` メソッドを実装していました。これはもちろんコンパイルできませんでした。5年経った今でも完全には古い慣習を捨てきれていないのです。 63 | このようなわかりやすい名前にするための変更はGo 1では必要でしたが、Go 2ではよっぽどの理由がない限り混乱の元になるでしょう。 64 | 65 | またGo 2は既存のすべてのGo 1のソースコードとともになければなりません。私たちはGoのエコシステムを分断させてはなりません。Go 1で書かれたパッケージをインポートしているGo 2のパッケージ、あるいはその逆、といった混在したプログラムが複数年の移行期間の中で苦労することなく動作しなければなりません。 66 | 私たちはそれをどのように実現するかを見出さなければなりません。`go fix`のような自動化ツールがある部分それを担うでしょう。 67 | 68 | 混乱を最小限にするために、すべての変更は慎重な考察や計画そしてツールを必要とします。それは代わりに変更の大きさを制限するものでもあります。大きくても2つか3つであり、確実に5つ以上ではないでしょう。 69 | 70 | より多くの自然言語で使われる識別子をサポートしたり、2進数リテラルを追加するといった細かなメンテナンス的な変更は勘定に入れていません。細かな変更も重要ではありますが、それらは正しく動作させるのは比較的簡単です。いまここで触れているのは、大きな変更の可能性、たとえばエラー処理の追加サポートやイミュータブルや読み込み専用の値の導入、何らかのジェネリクスの追加、あるいはまだ提案されていないより重要な変更を指しています。 71 | これらの大きな変更のうち、2つか3つしか導入できません。慎重に選ぶ必要があります。 72 | 73 | ## プロセス 74 | 75 | ここで重要な疑問が湧いてくるでしょう。Goを開発するプロセスはどのようなものなのか。 76 | 77 | Goの初期の段階では、私たち5人しかいなかったので、隣り合ったガラスの仕切りがある共有ブース2つで働いていました。 78 | みなで1つのオフィスに集まって、問題を議論して、すぐに机に戻ってその解決策を実装に落とすといったことが容易に可能でした。 79 | RobとRobertのオフィスには小さなソファとホワイトボードがあり、誰かがそこに行ってホワイトボードに例を書き始める、ということがよくありました。例を書き終わるころには、他のみなが一区切りつけて、座って議論を始められるようになっている、というのがいつもの流れでした。 80 | こういった形式張らないなやり方は、今日の世界的なGoコミュニティではスケールしないことは明らかでしょう。 81 | 82 | Goがオープンソースとして公開された後の仕事の一部は、私たちの特に形式の決まっていなかったプロセスを、メーリングリスト、イシュートラッカー、50万人のGo開発者がいる世界により形式的な形に移行させることでした。しかし、私が知る限りこれまで明示的に私たちのプロセス全体を説明しませんでした。 83 | 意識的にそのことを考えていなかったのでしょう。しかし、振り返ってみると、次の図が、最初のプロトタイプを作ったころからの、私たちのGo開発における基本的な流れです。 84 | 85 | ![process](./process.png) 86 | 87 | ステップ1では、Goを使用することで、これによりGoでの体験を重ねます。 88 | 89 | ステップ2では、Goで解決が必要であろう問題を特定し、それを知らせ、他の人に説明し、書き下します。 90 | 91 | ステップ3では、その問題に対する解決策を提案し、議論を重ね、議論を元に解決策を改訂します。 92 | 93 | ステップ4では、その解決策を実装し、評価し、評価を元に改善していきます。 94 | 95 | 最後のステップ5では、その解決策をリリースし、言語自体やライブラリへの追加、もしくはみなが日々使うツールの形で提供します。 96 | 97 | ある変更に関して同じ人がすべてのステップを行う必要はありません。事実、通常は多くの人々があらゆるステップで協力し合い、一つの問題に対して多くの解決策が提案されます。また、どのステップでも、あるアイデアに関して先のステップに進まないほうがいいとわかったら、前のステップに戻ります。 98 | 99 | 私たちのこのプロセス全体の流れを話したことはないと思うのですが、一部に関しては話したことがあります。 100 | 2012年にGo 1をリリースして、いまこそGoを使いはじめるタイミングで、言語自体の変更をやめるときだ、と言ったのがステップ1についての説明でした。 101 | 2015年にGoの変更提案プロセスを導入したときには、ステップ3、4、5について説明しました。 102 | しかし、ステップ2に関しては詳細を話したことがなかったので、ここで説明しようと思います。 103 | 104 | (Go 1の開発と言語仕様の変更を止める話は、Rob PikeとAndrew GerrandのOSCON 2012の発表である"[The Path to Go 1](https://blog.golang.org/the-path-to-go-1)"を参照してください。提案プロセスに関しては、Andrew GerrandのGopherCon 2015での発表の"[How Go was Made](https://www.youtube.com/watch?v=0ht89TxZZnk)"や[提案プロセスに関するドキュメント](https://golang.org/s/proposal)を参照してください。) 105 | 106 | 107 | ## 問題を説明する 108 | 109 | ![process2](./process2.png) 110 | 111 | 問題を説明するときには2つの段階があります。第一段階(簡単なほう)は問題がなにかを正確に述べる部分です。開発者はこの点においては十分優れています。 112 | 結局、私たちが書くテストは解決される問題を記述したもので、その言語ではコンピューターでさえ理解できるほど正確な記述になっているからです。 113 | 第二段階(難しいほう)は、その問題の重要性を説明することです。そのことによって、その問題を解決することやその解決策を保守していくことになぜ時間を費やさなければならないのか、みんなが納得できるようになります。問題を正確に記述することと比較して、私たちは問題の重要性を説明する必要に迫られることはあまりなく、そのことにはそこまで優れていません。コンピューターは「なぜこのテストケースは重要なのですか。これが解く必要のある問題であることに間違いはないでしょうか。この問題を解決することは最重要事項ですか。」とは決して尋ねてきません。いずれそうなることはあるかもしれませんが、今日ではそうではありません。 114 | 115 | 2011年の古い例を見てみましょう。次の例は、Go 1を計画しているときに私が `os.Error` から `error.Value` への変更について書いたものです。 116 | 117 | ![error](./error.png) 118 | 119 | この報告では、正確な1行で書かれた問題の記述から始まります。問題点は、非常に低い階層のライブラリですべてが `os.Error` のために、`os`パッケージをインポートしていることです。 120 | その後下線で示したように5行続き、そこで問題の重要性を説明しています。`os`パッケージが利用しているライブラリではAPI内でエラーを表現することが出来ず、また他のパッケージはオペレーションシステムとまったく関係ないにも関わらず`os`パッケージに依存しています。 121 | 122 | この5行でこの問題が重要であると _あなた_ を説得できたでしょうか。それは私が省略した文脈をあなたがどれだけ汲めるかによります。 123 | 理解されるためには、読み手がなにを知る必要があるかを予測しなければなりません。この時の読み手、GoogleのGoチームにいた10人、にはこの50語の文章で十分でした。 124 | 同じ問題を昨秋のGothamGoでの聴衆、経験も様々で専門分野も異なる人々、に紹介するときには、より多くの文脈が必要で、その時は200語の説明と実コード例とダイアグラムを使いました。 125 | これが今日のGoコミュニティでの現状で、いかなる問題の重要性を説明するにも、同僚に話すときには省略してしまうような文脈を補足する必要があり、特に具体例によって説明されることが望まれます。 126 | 127 | 問題の重要性を相手に認識させることは本質的なステップです。問題があまり重要でなく見えたら、ほぼあらゆる解決方法がコストの高いものに見えてしまうでしょう。 128 | しかし重要な問題であれば、通常納得のいくコストの解決策が数多くあるものです。特定の解決策を採用することに合意できない場合に、しばしば解決しようとしている問題の重要性そのものの認識がずれているということがあります。これはすごく重要なことなので、後知恵かもしれませんが、この問題を明確に表しているここ最近あった2つの例を見てみましょう。 129 | 130 | ### 例: うるう秒 131 | 132 | 最初の例は時間についてです。 133 | 134 | あなたがあるイベントにかかる時間を計測したいとします。開始時間を記録し、イベントを実行して、終了時間を記録し、そして終了時間から開始時間を引きます。イベントが10ミリ秒かかれば、引き算の結果は10ミリ秒、もしくは計測誤差により多少の前後はあるかもしれませんが、同程度の結果となります。 135 | 136 | ``` 137 | start := time.Now() // 3:04:05.000 138 | event() 139 | end := time.Now() // 3:04:05.010 140 | 141 | elapsed := end.Sub(start) // 10 ms 142 | ``` 143 | 144 | この明白な手順は、[うるう秒](https://en.wikipedia.org/wiki/Leap_second)によって失敗します。 145 | 私たちの時計が地球の自転と同期がとれていない時に、うるう秒、公式には午後11時59分60秒、が深夜0時の直前に挿入されます。 146 | うるう年と違って、うるう秒は予測可能なパターンに従っていないので、プログラムやAPIの形にするのが難しくなっています。 147 | オペレーションシステムでは61の秒が存在する分を表現するかわりに、深夜0時になる直前に時計を巻き戻すことでうるう秒を実装しています。これによって11時59分には59秒が2回発生します。 148 | この時計のリセットによって時間が巻き戻ったように見えるので、さきほどの10ミリ秒のイベントは-990ミリ秒かかったように計測されてしまいます。 149 | 150 | ``` 151 | start := time.Now() // 11:59:59.995 152 | event() 153 | end := time.Now() // 11:59:59.005 (really 11:59:60.005) 154 | 155 | elapsed := end.Sub(start) // –990 ms 156 | ``` 157 | 158 | 時刻機構(以下 TOD、time-of-day clock)はこのような時計のリセットをまたぐイベントを計測するには不正確なので、オペレーションシステムではモノトニッククロック(monotonic clock)という2つめの時計を提供しています。 159 | この時計には絶対的な意味はなく、ただ秒を刻み、決してリセットされることはありません。 160 | 161 | 奇妙な時計のリセットの間以外は、モノトニッククロックはTODよりいいところは何もありません。そして、TODには時刻を表現するのに便利という利点があります。そこで単純化のためにGo 1の`time`パッケージではTODしか提供しませんでした。 162 | 163 | 2015年10月に時計のリセット、特に典型的にうるう秒をまたいだイベントをGoプログラムで計測が正確にできないということが記載された[バグレポート](https://golang.org/issue/12914)が上げられました。 164 | 提案された解決策は、問題の元のタイトルでもある「モノトニッククロックにアクセス可能な新しいAPIを追加する」というものでした。 165 | 私はこの問題は新しいAPIを正当化するには重要性が足りないと反対しました。この数カ月前、2015年中頃のうるう秒では、Akamai、Amazon、Googleは時計を1日かけてほんの僅かに遅らせて、追加の1秒を時計を巻き戻すことなくに吸収しました。 166 | この「[leap smear](https://developers.google.com/time/smear)」とよばれる手法が今後広く採用されれば、本番環境で問題となるうるう秒での時計のリセットはなくなるものと思われました。 167 | 一方で、Goに新しいAPIを追加することは新しい問題を追加する可能性があります。2種類の時計について説明し、ユーザーにどちらの時計をどのような場合に使うべきかを教育し、多くの既存のコードを変換するといったことを、めったに起こらず、そのうち自然と無くなりそうな問題のためにしなければならないのです。 168 | 169 | これに対して私たちは明確な解決策がない問題がある場合にいつも取る方法を行いました。ただ待ったのです。 170 | 待つことによって、その問題に対する経験を積み、理解を深める時間を確保し、また良い解決策を見つける時間も確保することができました。 171 | この件では、私たちは待つことによって、[Cloudflareの小さな障害](https://www.theregister.co.uk/2017/01/04/cloudflare_trips_over_leap_second/)という形で、問題の重要性を理解することが出来ました。 172 | 彼らのGoコードは、DNSリクエストが2016年末のうるう秒の間に-900ミリ秒かかったと計測しました。これによって同時多発でサーバ内でパニックが発生し、最大でDNSクエリの0.2%に障害が発生しました。 173 | 174 | CloudflareはGoがその仕様を意図された種類のクラウドシステムそのものであり、彼らの障害はGoがイベントの計測を正確にできないことによって引き起こされました。 175 | それから、これが重要な点なのですが、Cloudflareは、John Graham-Cummingが彼らの経験を"[How and why the leap second affected Cloudflare DNS](https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/)"というタイトルのブログポストで報告しました。 176 | Goを本番環境で利用した経験を具体的な詳細とともに共有することで、JohnとCloudflareは、私たちがうるう秒の時計のリセットの瞬間に発生する問題は、修正されないまま放置されるにはあまりに重大すぎると理解する手助けをしてくれました。 177 | この記事が公開されてから2ヶ月後、解決策を設計し実装して、[Go 1.9でリリース](https://beta.golang.org/doc/go1.9#monotonic-time)されました。(事実、[新規のAPIなし](https://golang.org/design/12914-monotonic)で実装しました。) 178 | 179 | ### 例: エイリアスの宣言 180 | 181 | 次の例はGoでのエイリアスの宣言のサポートについてです。 182 | 183 | 過去数年にわたって、Googleは大規模なコード変更に集中するチームを設立してきました。このチームはC++、Go、Java、Pythonなどの言語で書かれた[何百万ものソースファイルや何十億行ものコードのコードベース](http://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/pdf)にわたって適用されるAPIのマイグレーションやバグ修正を担うチームです。 184 | そのチームでの仕事から私が学んだことは、APIで使用する名前を別の名前にするときに、クライアントのコードを一度ではなく複数のステップで更新できることの重要性です。 185 | そうするためには、古い名前を利用してる場合は新しい名前に転送してくれるような宣言を書ける必要があります。 186 | C++にはこういった転送のために`#define`や`typedef`や`using`の宣言がありますが、Goには何もありません。 187 | もちろん、Goの目標のひとつは、巨大なコードベースでうまくスケールさせることで、Google社内でのGoのコードの量が増えるに連れ、何らかの転送の機構が必要であることと、他のプロジェクトや会社でも彼らのGoのコードベースが成長するに連れて同様の問題に面していることの両方が明確になりました。 188 | 189 | 2016年3月に、私はRobert GriesemerとRob Pikeと、Goでどのように段階的なコードベースの変更を行っていくかについて話しはじめ、エイリアス宣言という結論に至りました。これこそ必要とした転送の機構です。このとき、私はGoの進化の方向性をとてもよいものだと感じていました。 190 | 私たちはGoの最初期からエイリアスについて話してきました。事実、最初の言語仕様のドラフトには[エイリアス宣言の使用例](https://go.googlesource.com/go/+/18c5b488a3b2e218c0e0cf2a7d4820d9da93a554/doc/go_spec#1182)があります。しかしエイリアス、またのちに型エイリアスについて議論するたびに、私たちはそれの良い使い方をはっきりと挙げることはできなかったので、それを取り入れませんでした。 191 | いま、私たちは、洗練された概念だからという理由でではなく、Goの本来の目的であるスケーラブルなソフトウェア開発における重要な実用上の問題を解決するからという理由でエイリアスの追加を提案していました。 192 | 私はこの提案がGoの将来の変更のモデルとして機能することを願いました。 193 | 194 | 晩春に、RobertとRobが[提案](https://golang.org/design/16339-alias-decls)を書き、[RobertはGopherCon 2016のライトニングトーク](https://www.youtube.com/watch?v=t-w6MyI2qlU)でそれを発表しました。 195 | それからの数カ月間は良い進展がなく、とてもGoの将来の変更のモデルにはなるようなものではありませんでした。 196 | 数多く学んだ教訓の一つは、問題の重要性を正しく表現することの重要さでした。 197 | 198 | たった今、私はみなさんに問題を説明し、その際にはこの問題がどのように、なぜ発生したのかのいくらかの背景を共有しました。しかし、その問題がみなさんにいずれ影響するかを判断するのに役立つ具体的なコードは明示しませんでした。 199 | 去年の夏の提案とライトニングトークでは、C、L、L1、C1〜Cnといったパッケージを使った抽象的な例をだしましたが、開発者が実際に試せるような具体的な例は出しませんでした。 200 | 結果として、コミュニティからのフィードバックのほとんどは、エイリアスとはGoogleでの問題を解決するためのものであって、ほかの人には関係がない、という考えに基づくものになってしまいました。 201 | 202 | 私たちがGoogleでは、はじめうるう秒での時計のリセットをうまく扱えないことの深刻さを正確に把握できていなかったのと同様に、広くGoコミュニティ全体に段階的なコードマイグレーションの取り扱いと大規模な変更の間の修正の深刻さをうまく伝えられていませんでした。 203 | 204 | 秋に、私たちは再開しました。私は[発表](https://www.youtube.com/watch?v=h6Cw9iCDVcU)をし、そしてオープンソースのコードベースから引用した複数の具体例を使って[問題を説明する記事](https://talks.golang.org/2016/refactor.article)を書きました。それによってこの問題がGoogleだけでなく、みなに起こりえることを説明したのです。 205 | いまでは、より多くの人々がこの問題を理解し、その深刻性を理解することができています。そしてどのような解決方法が最適かについて[建設的な議論](https://golang.org/issue/18130)も行いました。 206 | その結果として[型エイリアス](https://golang.org/design/18130-type-alias)が[Go 1.9で導入され](https://beta.golang.org/doc/go1.9#language)、これによってGoがより大きな規模のコードベースにスケールする助けとなるでしょう。 207 | 208 | 209 | ## 体験レポート 210 | 211 | ここで得られた教訓は、問題の深刻性を異なる環境の人が理解できるように記述することは難しいけれど本質的なことであるということです。コミュニティとしてGoに対する大きな変更を議論するためには、解決したいいかなる問題もその深刻性を記述することに特別な注意を払う必要があります。 212 | もっとも明確な方法は、[Cloudflareのブログポスト](https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/)や[私のリファクタリングに関する記事](https://talks.golang.org/2016/refactor.article)のように、その問題がプログラムや本番環境に実際にどのような影響をおよぼすか見せることです。 213 | 214 | このような体験レポートは抽象的な問題を具体的なものに変えてくれ、その深刻性を理解する手助けとなります。 215 | またこれらはテストケースとしても役立ちます。いかなる解決策の提案も実際にそのレポートが記載している実世界の問題への影響を計測することで評価できます。 216 | 217 | たとえば、最近はジェネリクスについて調査しているのですが、私にはGoユーザーがジェネリクスを使って解決する必要がある詳細で具体的な問題がいまいちうまく想像できないのです。 218 | 結果として、ジェネリクスメソッド、つまりレシーバーとは区別されてパラメータ化されたメソッドをサポートするかといった設計に関する質問に答えることができません。 219 | もし実世界での利用例が多くあれば、深刻なものを調査することによってこのような問題に答えることができるでしょう。 220 | 221 | 他の例としては、いくつかの方法で`error`インターフェースを拡張する提案を見てきましたが、私は巨大なGoプログラムがどのようにエラーを解釈し扱おうとしているかを示している体験レポートを1つも見たことがなく、現状の`error`インターフェースがそういった動作をどのように妨げているかを示しているものはなおさら見たことがありません。 222 | こういったレポートがあれば、まず問題の詳細と深刻さをより良く理解する手助けとなります。これは問題を解決する前にまずやらなければならないことです。 223 | 224 | もっと例を挙げることもできるのですが、このあたりにしておきます。すべてのGoへの大きな変更は、Goが今日どのように人々に利用されているか、なぜ十分にうまくいっていないかを記載した1つ以上の体験レポートによって動機づけられるべきです。 225 | 疑う余地もなく大きな変更に対して、そのような体験レポートを見ることは多くなく、その中でも実例を伴ったものはほとんどありません。 226 | 227 | このような体験レポートはGo 2の提案プロセスの原料になります。そしてみなさんすべてにそういった体験レポートを書いていただく必要があります。それらが私たちがみなさんのGoでの体験を理解する手助けとなります。 228 | 50万人のGoユーザがいて、広範囲の様々な環境で働いています。 229 | ご自身のブログや、[Medium](https://www.medium.com/)、[GitHub Gist](https://gist.github.com/)(Markdownにするために`.md`拡張子をつけてください)や[Google Doc](https://docs.google.com/)、あるいはその他みなさんが好きな公開方法で構わないので、ぜひご自身の記事を書いてください。 230 | 記事を公開したら、あたらしいWikiのページに追加してください。 https://golang.org/wiki/ExperienceReports 231 | 232 | 233 | ## 解決策 234 | 235 | ![process34](./process34.png) 236 | 237 | もう解決する必要がある問題をどのように特定して説明するかはわかったので、すべての問題が言語仕様の変更で解決されるのが最適ではなく、またそれで良いことを簡単に触れたいと思います。 238 | 239 | 解決したい問題の一つに、コンピューターがしばしば基本的な算術演算の間に追加で結果を計算するが、Goがその結果には直接アクセスさせない、というものがあります。2013年にRobertが2値の結果(「カンマOK(comma-ok)」)表現のアイデアを基本的な算術に拡張するという提案を行いました。 240 | たとえば、`x`と`y`が`uint32`の値であったら、`lo, hi = x * y`は積の通常の下の32ビットだけでなく上の32ビットも返すというものです。 241 | この問題は特に深刻なようには見えなかったので、[解決策になり得るものを記録](https://golang.org/issue/6815)しましたが、実装はしませんでした。待ったのです。 242 | 243 | 最近になって、私たちはGo 1.9で[`math/bit`パッケージ](https://beta.golang.org/doc/go1.9#math-bits)を設計しました。これは様々なビット操作関数を持っています。 244 | 245 | ``` 246 | package bits // import "math/bits" 247 | 248 | func LeadingZeros32(x uint32) int 249 | func Len32(x uint32) int 250 | func OnesCount32(x uint32) int 251 | func Reverse32(x uint32) uint32 252 | func ReverseBytes32(x uint32) uint32 253 | func RotateLeft32(x uint32, k int) uint32 254 | func TrailingZeros32(x uint32) int 255 | ... 256 | ``` 257 | 258 | このパッケージでは各関数は良いGo実装となっていますが、コンパイラーは可能なときは特別なハードウェア処理に置き換えます。`math/bits`での経験から、いまやRobertと私は言語仕様を変えて追加の算術結果を取得できるようにすることは賢いことではないと認識し、かわりに適切な関数を`math/bits`のようなパッケージ内に定義するべきだと理解しました。 259 | ここでの最適解はライブラリの変更であり、言語仕様の変更ではありません。 260 | 261 | Go 1.0以降での他の解決したい問題として、ゴルーチンと共有メモリはGoプログラムに競合状態を入れ込みやすく、結果としてクラッシュを引き起こしたり、本番環境で他のおかしな状況を引き起こしたりしてしまう、という事実がありました。 262 | 言語仕様の変更に基づく解決策としては、データ競合を禁止するなんらかの方法を探して、競合状態のあるプログラムを書けないようにしたり、あるいは少なくともコンパイルができないようにする、といった方法がありえるでしょう。 263 | Goのような言語にそういった方法をどのように適用させるかはいまだにプログラミング言語の世界では解決していない課題です。 264 | かわりに私たちは新たなツールを配布元に追加して、簡単に使えるようにしました。そのツール、[競合状態検出ツール(race detector)](https://blog.golang.org/race-detector)は、Goを使う上でなくてはならない存在になりました。 265 | ここでの最適解はランタイムやツールの変更であり、言語仕様の変更ではありませんでした。 266 | 267 | もちろん、言語仕様の変更も今後ありうるでしょう、しかしすべての問題が言語仕様の変更で解決されるのが最適解になるわけではありません。 268 | 269 | ## Go 2のリリース 270 | ![process5](./process5.png) 271 | 272 | 最後に、私たちはどのようにGo 2をリリースするのでしょうか。 273 | 274 | 私が思うに、最善な計画としてGo 2の[後方互換性がある部分](https://golang.org/doc/go1compat)を、Go 1のリリース順序に則って徐々に機能毎にリリースしていくのが良いと考えています。 275 | これにはいくつか重要な性質があります。第一に、この方法はGo 1のリリースを[通常のスケジュール](https://golang.org/wiki/Go-Release-Cycle)を保っていて、状況に合わせてユーザが依存しているバグの修正や改善を行うことができます。第二に、Go 1とGo 2で開発リソースが分かれることを避けられます。 276 | 第三にGo 1とGo 2で分岐するのを避けることができ、マイグレーションが容易になります。第四に、私たちが一度に1つの変更に集中し、リリースができるようになり、品質を維持する助けになります。 277 | 第五に、私たちが後方互換を保つように機能を設計する励みになるということです。 278 | 279 | いかなる変更もGo 1のリリースに入り始める前に議論や計画をするための時間が必要ですが、私は1年後のGo 1.12くらいから小さな変更を入れ始めるのが妥当ではないかと見ています。 280 | またその間に、パッケージ管理のサポートをまず入れる時間を作ることができます。 281 | 282 | たとえばGo 1.20くらいで後方互換の仕事が終わったら、後方互換性のない変更をGo 2.0に入れることができるでしょう。 283 | 後方互換性のない変更がまったくないとわかった場合には、おそらくGo 1.20をGo 2.0と宣言するだけになるでしょう。 284 | いずれにせよ、Go 1系のリリース順序からGo 2系のリリース順序に移ったタイミングで、最後のGo 1系のリリースのサポート期間を伸ばすと思います。 285 | 286 | ここに書いたことはすべてちょっとした推測であり、先に書いた特定のリリース番号は大雑把に見積もっただけのものですが、はっきりと言えることは私たちはGo 1を捨てようというつもりはありません。 287 | そして事実、私たちは可能な限りGo 1を長生きさせたいと考えています。 288 | 289 | ## 支援が必要です 290 | 291 | **私たちはあなたがたの支援が必要です** 292 | 293 | 本日より、Go 2に向けての話し合いが始まります。それは、メーリングリストやイシュートラッカーのような公開された、オープンな場で行われるものです。それに伴うあらゆる場所で支援してください。 294 | 295 | いま、私たちがなによりも必要としているのは体験レポートです。 296 | あなたの環境ではGoがどう動作しているか、そして何よりも、どう動作していないかを是非教えてください。 297 | 実例や具体的な詳細、そして実体験を盛り込んだブログポストを書いてください。 298 | そしてそのリンクを私たちの[Wikiページ](https://golang.org/wiki/ExperienceReports)に追加してください。 299 | これこそが私たち、GoコミュニティがGoに変化をもたらすときの議論の始め方です。 300 | 301 | ご清聴ありがとうございました。 302 | 303 | By Russ Cox 304 | 305 | ## あわせて読みたい 306 | * [Go 2016 Survey Results](https://blog.golang.org/survey2016-results) 307 | * [Participate in the 2016 Go User Survey and Company Questionnaire](https://blog.golang.org/survey2016) 308 | * [Go, Open Source, Community](https://blog.golang.org/open-source) 309 | * [Four years of Go](https://blog.golang.org/4years) 310 | * [Get thee to a Go meetup](https://blog.golang.org/getthee-to-go-meetup) 311 | * [Go turns three](https://blog.golang.org/go-turns-three) 312 | * [Getting to know the Go community](https://blog.golang.org/getting-to-know-go-community) 313 | * [The Go Programming Language turns two](https://blog.golang.org/go-programming-language-turns-two) 314 | * [Spotlight on external Go libraries](https://blog.golang.org/spotlight-on-external-go-libraries) 315 | * [Third-party libraries: goprotobuf and beyond](https://blog.golang.org/third-party-libraries-goprotobuf-and) 316 | -------------------------------------------------------------------------------- /content/post/toward-go2/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/error.png -------------------------------------------------------------------------------- /content/post/toward-go2/go1-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/go1-preview.png -------------------------------------------------------------------------------- /content/post/toward-go2/go1-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/go1-release.png -------------------------------------------------------------------------------- /content/post/toward-go2/mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/mail.png -------------------------------------------------------------------------------- /content/post/toward-go2/process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/process.png -------------------------------------------------------------------------------- /content/post/toward-go2/process2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/process2.png -------------------------------------------------------------------------------- /content/post/toward-go2/process34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/process34.png -------------------------------------------------------------------------------- /content/post/toward-go2/process5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/process5.png -------------------------------------------------------------------------------- /content/post/toward-go2/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/content/post/toward-go2/tweet.png -------------------------------------------------------------------------------- /content/post/versioning-proposal.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2018-03-26T12:39:53+09:00" 3 | title = "Go におけるパッケージバージョニングに関するプロポーザル" 4 | draft = false 5 | tags = ["tools", "versioning"] 6 | +++ 7 | 8 | # Go におけるパッケージバージョニングに関するプロポーザル 9 | [A Proposal for Package Versioning in Go](https://blog.golang.org/versioning-proposal) by Russ Cox 10 | 11 | ## はじめに 12 | 13 | 8年前、Go チームは `goinstall` (のちの `go get`)や今日の Go 開発者にはお馴染みの分散化された URL のようなインポートパスを導入しました。`goinstall` をリリース後、最初に尋ねられた質問の一つにバージョン情報をどのようにして組み込むかというものがありました。私たちは分からなかったことを認めました。長いあいだ、私たちはパッケージのバージョニング問題はアドオンツールで解決するのが一番良いと考えており、人々にアドオンツールを作ることを勧めました。Go コミュニティは異なるアプローチでたくさんのツールを作りました。それにより私たちはその問題についてより深く理解することができ、2015年半ばまでにその問題には多くの解決方法があることが明らかとなりました。私たちは公式のツールをただ一つ採用する必要がありました。 14 | 15 | 2015年7月の GopherCon から同年秋までに行われたコミュニティでの議論の後、私たちはどのバージョンを使うか決めるために Rust の Cargo におけるタグ付きのセマンティックバージョニングやマニフェスト、ロックファイル、 [SAT ソルバー](https://research.swtch.com/version-sat) に代表されるパッケージのバージョニングに関するアプローチに従うという結論に至りました。このおおまかな計画に従い、`go` コマンドに統合するためのモデルとして役立てることを意図した Dep を作るために Sam Boyer はチームを先導しました。しかし、Cargo/Dep アプローチの論理包含についてより深く理解するにつれて、Go が細部(特に後方互換性に関する)を変更することで恩恵を受けることが明らかとなりました。 16 | 17 | ## 互換性への影響 18 | 19 | [Go 1](https://blog.golang.org/preview-of-go-version-1) の最も重要な新しい特徴は言語に関するものではありませんでした。Go 1 は後方互換性を重視していました。それまで約一か月毎に安定板のスナップショットをリリースしており、それぞれのリリースによる変更は互換性を極めて欠くものでした。Go 1 のリリース後すぐに、利益や採用において著しい加速を観測しました。[互換性に関する約束事](https://golang.org/doc/go1compat.html) により、開発者たちがプロダクションユースで Go を採用するとより一層快適になると考えています。またその約束事が、今日 Go の人気が高まっている主な理由だと考えています。2013年以降 [Go の FAQ](https://golang.org/doc/faq#get_version) は、パッケージの開発者に自らのユーザーが似たような互換性として期待していることを提供するよう促してきました。私たちはこれを _インポート互換性のルール(import compatibility rule)_ と呼んでいます。「古いパッケージと新しいパッケージが共に同じインポートパスを持つ場合、新しいパッケージは古いパッケージに対する後方互換性がなければならない。」 20 | 21 | それとは無関係に、[セマンティックバージョニング](http://semver.org/) は Go を含む多くの言語コミュニティにおいてソフトウェアのバージョンを記述するデファクトスタンダードとなりました。セマンティックバージョニングを用いることで、単一のメジャーバージョン内では後方のバージョンは前方のバージョンと後方互換性が保たれていることが期待されます。例えば、v1.2.3 は v1.2.1 や v1.1.5 と互換性がなければならないですが、v2.3.4 はそれらのいずれかと互換性がある必要はありません。 22 | 23 | 私たちが Go パッケージでセマンティックバージョニングを採用する場合、ほとんどの Go 開発者が期待する通り、インポート互換性のルールは、異なるメジャーバージョンは異なるインポートパスを使っていなければならないということを要求します。この結果、v2.0.0 で始まるバージョンは `my/thing/v2/sub/pkg` のようなインポートパス内にメジャーバージョンを含む、といった _セマンティックインポートバージョニング_ が可能となりました。 24 | 25 | 一年前、インポートパス内にバージョン数を含むかどうかは主に趣味の問題だと私は強く考えており、それを含むことが特別エレガントだったかは懐疑的でした。しかしその決定は論理的には趣味の問題ではないと分かりました。つまり、インポートの互換性とセマンティックバージョニングは共にセマンティックインポートバージョニングを必要としているのです。私はこれに気づいたとき、論理的な必要性に驚きました。 26 | 27 | [段階的なコードの修正](https://talks.golang.org/2016/refactor.article) や部分的なコードのアップグレードといったセマンティックインポートバージョニングとは独立した第2の論理的な考え方があることに気づき私はまた驚きました。大規模なプログラムおいて、特定の依存関係がある v1 から v2 に同時にアップデートすることを、プログラム内の全てのパッケージに期待するのは非現実的です。その代わりにプログラムの一部については v2 にアップグレードでき、その他の部分は v1 を使い続けることができるでしょう。しかしその一方で、ビルドされたバイナリは v1 と v2 の両方の依存関係を含んでいるでしょう。それらに同じインポートパスを与えると混乱につながり、私たちが _インポート一意性のルール(inport uniqueness rule)_ と呼んでいるもの、つまり異なるパッケージは異なるインポートパスを持っていなければならないというルールに違反します。部分的なコードのアップグレード、インポート一意性、_そして_ セマンティックバージョニングを行う唯一の方法はセマンティックインポートバージョニングなどを採用することです。 28 | 29 | セマンティックインポートバージョニングなしでセマンティックバージョニングを用いるシステムをビルドすることはもちろん可能ですが、部分的なコードのアップグレードかインポート一意性のどちらかを諦めなければなりません。Cargo はインポート一意性を諦めることにより部分的なコードのアップグレードを可能としています。したがって、所定のインポートパスは大規模なビルドの異なる部分で異なる意味合いを持つことができます。Dep は部分的なコードのアップグレードを諦めることでインポート一意性を保証しています。したがって、大規模なビルドに関する全てのパッケージは所定の依存関係によって定められたただ一つのバージョンを探し出さなければならず、大規模なプログラムほどビルドできない可能性が高まります。Cargo は大規模なソフトウェア開発に不可欠な部分的なコードのアップグレードを主張するのに適しています。Dep は同様にインポート一意性を主張するのに適しています。Go の現在のベンダリングサポートは使用するのが複雑であり、インポート一意性に違反する可能性があります。違反した結果生じた問題は開発者とツールの両方にとってとても理解しにくいものでした。部分的なコードのアップグレードとインポート一意性のどちらかを選択する際は、選択しなかったことによる影響を予測する必要があります。セマンティックインポートバージョニングにより選択を避け、代わりに両方を維持することができます。 30 | 31 | どのくらいインポート互換性がバージョン選択を簡素化するかに気づいたときも驚きました。これは、所定のビルドで用いるパッケージのバージョンを決める際の問題です。Cargo と Dep により、バージョン選択することと [ブール充足可能性を解決すること](https://research.swtch.com/version-sat) は等しく扱うことができ、それは有効なバージョン設定が存在するかどうかを判断するには高くつくかもしれないということ示唆しています。さらに、❝ベスト❞な設定を選択するために明確な基準がないと、有効な設定がたくさん見つかるかもしれません。代わりにインポート互換性に頼ることで Go は自明な線形時間のアルゴリズムを使用して、常に存在する唯一のベストな設定を探し出すことができます。私が _[最小バージョン選択(minimal version selection)](https://research.swtch.com/vgo-mvs)_ と呼んでいるこのアルゴリズムは、別々のロックファイルとマニフェストファイルの必要性をなくします。それらは開発者とツールの両方が直接編集した単一の小さな設定ファイルで置き換えられ、再現可能なビルドは引き続きサポートされます。 32 | 33 | Dep を使用した体験は互換性への影響を明らかにしています。Cargo とそれ以前のシステムに基づき、セマンティックバージョニングを採用する一環としてインポート互換性を諦めるために、私たちは Dep を設計しました。私はこれを意図的に決めたとは考えていません。私たちはそれらとは別のシステムに従っただけです。Dep を直接使用したことで、互換性のないインポートパスを許可することによってどのくらいの複雑さが生じるか正確に理解するのに役立ちました。セマンティックインポートバージョニングを導入することによってインポート互換性のルールを復活させることで、その複雑さを取り除き、よりシンプルなシステムにします。 34 | 35 | ## 進捗、プロトタイプ、プロポーザル 36 | 37 | Dep は2017年1月にリリースされました。そのベーシックモデル、つまり依存関係の要求を明示した設定ファイルに沿うタグ付けされたコードはほとんどの Go のバージョニングツールからの明確な一歩で、Dep 自身に集中することもまた明確な一歩でした。特にコード自体と依存関係の両方に関する Go のパッケージバージョニングについて Go 開発者が考えるのに慣れる助けとなるため、私は全面的にその採用を称えました。Dep が私たちを明らかに正しい方向へ導く中、私は細部に潜む複雑性の悪魔についてずっと心配していました。私は Dep が大規模なプログラム内で段階的にコードをアップグレードするためのサポートが不足していることについて特に心配していました。2017年は Sam Boyer やその他のパッケージ管理ワーキンググループを含む多くの人と話しましたが、誰一人として複雑性を減らす明確な方法が分かる人はいませんでした。(私はそれに加え多くのアプローチを見つけました。)年末に近づいてもそれは依然として SAT ソルバーのように見え、不十分なビルドが私たちができるベストなのかもしれません。 38 | 39 | 11月中旬、Dep がどのようにして段階的なコードのアップグレードをサポートできるか今一度考えていたとき、私はインポート互換性についての言い伝えがセマンティックインポートバージョニングを暗示していることに気づきました。大きな進歩のような気がしました。私はブログ記事 [semantic import versioning](https://research.swtch.com/vgo-import) に初稿を書き、Dep は慣習を採用することを提案するという結論に至りました。私は以前お話した人達にその草稿を送り、とても大きな反響を呼びました。賛否両論でした。私はアイデアをさらに広める前にセマンティックインポートバージョニングの論理包含をより深く理解する必要があることを認識し、行動に移しました。 40 | 41 | 12月中旬、私はインポート互換性とセマンティックインポートバージョニングを組み合わせることでバージョン選択を [最小バージョン選択](https://research.swtch.com/vgo-mvs) へと落とし込めることに気づきました。私はそれを理解したか確かめるために基礎的な実装を書き、それがとてもシンプルであったわけの背後に隠された理論を学び、そしてそれを説明する記事の草稿を書きました。それでも、Dep のような実際のツールではそのアプローチが実用的かまだ分かりませんでした。プロトタイプが必要であることは明らかでした。 42 | 43 | 1月に入り、私はセマンティックインポートバージョニングと最小バージョン選択を実装したシンプルな `go` コマンドのラッパーを作り始めました。簡単なテストはうまくいきました。月末に近づき、私のシンプルなラッパーは、多くのバージョニングされたパッケージを用いた実際のプログラムである Dep をビルドすることができました。ラッパーはまだコマンドラインインターフェースを持っておらず、いくつかの文字定数でハードコードされた状態で Dep をビルドしていましたが、そのアプローチは明らかに有効でした。 44 | 45 | 私は2月の最初の3週間を利用してラッパーを完全なバージョンの `go` コマンド `vgo` に書き直したり、[`vgo` について紹介するブログ記事一覧](https://research.swtch.com/vgo) の草稿を書いたり、Sam Boyer やパッケージ管理ワークグループ、Go チームと議論したりしました。そして2月の最後の週は Go コミュニティ全体に対して `vgo` やその背後に隠されたアイデアを共有しました。 46 | 47 | インポート互換性やセマンティックインポートバージョニング、最小バージョン選択のアイデアのコアに加え、`vgo` のプロトタイプには `goinstall` や `go get` との8年間の体験によって動機づけられた小さいながらも重要な数々の変更を導入しました。例えば、パッケージのコレクションを一つの単位とする [Go モジュール](https://research.swtch.com/vgo-module) という新しい概念、[検証可能ビルド・検証済みビルド](https://research.swtch.com/vgo-repro)、`$GOPATH` 外での動作や(ほとんどの)`vendor` ディレクトリの削除を可能とする [`go` コマンドを通したバージョンの認知](https://research.swtch.com/vgo-cmd) です。 48 | 49 | この全ての成果は先週私が [公式の Go の プロポーザル](https://golang.org/design/24301-versioned-go) として提出しました。完璧に実装されているように見えるかもしれませんが、まだプロトタイプの段階で、私たちと一緒にこれから完璧に仕上げていく必要があります。あなたは [golang.org/x/vgo](https://golang.org/x/vgo) から `vgo` のプロトタイプをダウンロードして試すことができます。また、`vgo` に慣れるために [Tour of Versioned Go](https://research.swtch.com/vgo-tour) を読むことができます。 50 | 51 | ## 今後の方針 52 | 53 | 私が先週提出したプロポーザルはまさに最初のプロポーザルです。Go 開発者は私たちが知らないとても賢いやり方で Go を使うので、Go チームや私が見つけられない問題があることを知っています。プロポーザルのフィードバックプロセスの目標は、現在のプロポーザルの問題を特定して対処するため、また将来リリースされる Go の最終的な実装ができるだけ多くの開発者にとってうまくいくようにするために私たち全員が協力することです。[プロポーザルに関する議論の issue](https://golang.org/issue/24301) で問題点を指摘してください。私はいただいたフィードバックにより [議論の概要](https://golang.org/issue/24301#issuecomment-371228742) や [FAQ](https://golang.org/issue/24301#issuecomment-371228664) を更新し続ける予定です。 54 | 55 | このプロポーザルが成功するためには、Go のエコシステム、特に今日のメジャーな Go プロジェクト全体がインポート互換性のルールやセマンティックインポートバージョニングを採用する必要があるでしょう。それがスムーズに行われるように、新しいバージョニングのプロポーザルをコードベースに組み込む方法や、体験のフィードバックを得る方法について質問があるプロジェクトと共にビデオ会議でユーザーフィードバックセッションを行う予定です。そのようなセッションに参加したい場合は、Steve Francia(spf@golang.org)までメールを下さい。 56 | 57 | 私たちは(最後に!)Go コミュニティに対し、パッケージのバージョニングを `go get` に組み込む方法に関する質問に対し公式の回答を一つだけ提供することを楽しみにしています。これまで私たちを助けてくださったみなさま、そしてこれから私たちを助けてくださるみなさまに感謝申し上げます。私たちはあなたたちの助けを借りて、Go の開発者が気に入るものをリリースできることを願っています。 58 | 59 | By Russ Cox 60 | 61 | ## あわせて読みたい 62 | 63 | * [The cover story](https://blog.golang.org/cover) 64 | * [The App Engine SDK and workspaces (GOPATH)](https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath) 65 | * [Organizing Go code](https://blog.golang.org/organizing-go-code) 66 | -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymotongpoo/goblog-ja/69e8426f7b1e791dae02ee4277a59d3b1504e401/static/favicon.ico -------------------------------------------------------------------------------- /static/google790b4cd70b0c49d5.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google790b4cd70b0c49d5.html -------------------------------------------------------------------------------- /themes/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hyde"] 2 | path = hyde 3 | url = https://github.com/spf13/hyde.git 4 | [submodule "herring-cove"] 5 | path = herring-cove 6 | url = https://github.com/spf13/herring-cove.git 7 | [submodule "lanyon"] 8 | path = lanyon 9 | url = https://github.com/tummychow/lanyon-hugo.git 10 | [submodule "hugo-incorporated"] 11 | path = hugo-incorporated 12 | url = https://github.com/nilproductions/hugo-incorporated.git 13 | [submodule "simple-a"] 14 | path = simple-a 15 | url = https://github.com/AlexFinn/simple-a.git 16 | [submodule "journal"] 17 | path = journal 18 | url = https://github.com/mpas/hugo-journal.git 19 | [submodule "liquorice"] 20 | path = liquorice 21 | url = https://github.com/eliasson/liquorice.git 22 | [submodule "hyde-x"] 23 | path = hyde-x 24 | url = https://github.com/zyro/hyde-x.git 25 | [submodule "purehugo"] 26 | path = purehugo 27 | url = https://github.com/dplesca/purehugo.git 28 | [submodule "tinyce"] 29 | path = tinyce 30 | url = https://github.com/roperzh/tinyce-hugo-theme.git 31 | [submodule "html5"] 32 | path = html5 33 | url = https://github.com/simonmika/hugo-theme-html5.git 34 | [submodule "persona"] 35 | path = persona 36 | url = https://github.com/aries1980/hugo-theme-persona.git 37 | [submodule "redlounge"] 38 | path = redlounge 39 | url = https://github.com/tmaiaroto/hugo-redlounge.git 40 | [submodule "hugo-uno"] 41 | path = hugo-uno 42 | url = https://github.com/SenjinDarashiva/hugo-uno.git 43 | [submodule "hugoscroll"] 44 | path = hugoscroll 45 | url = https://github.com/SenjinDarashiva/hugoscroll.git 46 | [submodule "stou-dk-theme"] 47 | path = stou-dk-theme 48 | url = https://github.com/stou/stou-dk-theme.git 49 | [submodule "landing-page-hugo"] 50 | path = landing-page-hugo 51 | url = https://github.com/crakjie/landing-page-hugo.git 52 | [submodule "hugo-base-theme"] 53 | path = hugo-base-theme 54 | url = https://github.com/crakjie/hugo-base-theme.git 55 | [submodule "poopshow"] 56 | path = poopshow 57 | url = https://github.com/esell/poopshow.git 58 | [submodule "aglaus"] 59 | path = aglaus 60 | url = https://github.com/dim0627/hugo_theme_aglaus 61 | [submodule "nofancy"] 62 | path = nofancy 63 | url = https://github.com/gizak/nofancy.git 64 | [submodule "twentyfourteen"] 65 | path = twentyfourteen 66 | url = https://github.com/jaden/twentyfourteen 67 | [submodule "tachyons"] 68 | path = tachyons 69 | url = https://github.com/marloncabrera/tachyons.git 70 | [submodule "vienna"] 71 | path = vienna 72 | url = https://github.com/keichi/vienna.git 73 | [submodule "beg"] 74 | path = beg 75 | url = https://github.com/dim0627/hugo_theme_beg.git 76 | -------------------------------------------------------------------------------- /translation_mapping.txt: -------------------------------------------------------------------------------- 1 | interface インターフェース 2 | channel チャンネル 3 | goroutine ゴルーチン 4 | struct 構造体 5 | array 配列 6 | slice スライス 7 | map マップ 8 | raw string raw文字列 9 | rune ルーン 10 | 11 | constant(s) 定数 12 | 13 | client code クライアント側のコード 14 | C C言語 15 | 16 | server サーバー 17 | hanlder ハンドラー 18 | user ユーザー 19 | 20 | upstream 上流 21 | downstream 下流 22 | --------------------------------------------------------------------------------