- 主催
GTUG Girls
- 日程
2013/09/12 19:00-21:45
- 上限参加人数
20名
- 講師
@ymotongpoo
- チューター
@Jxck_, @tenntenn
- イベントURL
https://groups.google.com/forum/#!topic/gtug-girls/fEllty6lvdM
- Go言語の紹介 (5分)
- Go言語の文法紹介 (45分)
- Webサーバを書いてみる (10分)
- テンプレートエンジンを使ってみる (10分)
- JSONパーサを使ってみる (10分)
- 2007年にRob Pike, Ken Thompson, Robert Griesemerが考案
- 2009年に一般公開
- 2012年にversion 1.0リリース
- 2013年3月にversion 1.1リリース
- 単一のマシンでも巨大なコードベースをビルド
- 依存関係をビルドファイル無しで解決
- 強い型付け・柔軟・型解決も素早い型システム
- GCがありマルチコアの並列性をサポートする
- LLな書きやすさ
独習するなら A Tour of Go をひと通りやれば習得可能。 ありがたいことに 日本語訳版 もあるので、英語が苦手な方でも 気軽に取り組めます。
またGoにはインタプリタはありませんが、標準パッケージがほとんどつかえて簡単にコードを試せる Go Playground というサイトも非常に便利です。 書いたコードに対して一意なショートカットURLも作成してくれるので、 誰かと簡単なモックアップを共有する際にも便利です。
これから紹介する文法は今回のチュートリアルを実施するために必要なものしか紹介していません。 さらに自分で何か作られる方は一度先に紹介したチュートリアルやドキュメントを実施してみてください。
定番のHello Worldを書いてみましょう。
package main // (1)
import "fmt" // (2)
func main() { // (3)
fmt.Println("Hello, 世界") // (4)
}
このコードだけでも学ぶことがたくさんあります。
main
関数を持つパッケージはmain
パッケージでなければいけませんimport
によってパッケージをインポートします- パッケージ名はダブルクォーテーションでくくります。
- 外部パッケージはレポジトリのホスト名+パス名だけ指定(例:
github.com/ymotongpoo/go-twitter/twitter
)
- 実行プログラムのエントリポイントは
main
関数です。 - パッケージ内の関数は (パッケージ名).(関数名) で指定します。
Goでは変数の宣言と代入は次のように行います。
var 変数名 型名
変数名 = 値
例えばこんな具合です。
var foo int
foo = 20 // = は代入の演算子
Goは強い型付けの言語なので、型が少しでも違っていると怒られます。暗黙の型キャストと言われるような、勝手な型変換はしてくれません。たとえば次のコードはエラーになります。
var foo int
foo = 3.1415 // 3.1415はfloat64の型なので怒られる
しかし毎度毎度変数の型宣言をすると疲れるので、初期化と型宣言を楽に行う方法があります。それが :=
という演算子です。この演算子を使うと、代入する値を元に変数の型を推測して、変数の型を決めたあとに代入を行ってくれます。
foo := 20 // fooはintと判断される
foo = 3.1415 // fooはint型の変数として宣言されているのでfloat64の値を代入しようとするとエラーになる
この :=
演算子のおかげでだいぶ楽に変数宣言ができますね。ちなみに :=
は変数を初期化するときにしか使えない演算子です。
Goでの関数の書き方は次のような形になります。
func 関数名(引数 引数の型, 引数 引数の型...) 戻り値の型 {
...
}
先ほどのHello Worldプログラムをちょっとだけ書き換えてみましょう。
package main
import "fmt"
func main() {
fmt.Println(SayHello("GTUG Girls"))
}
func SayHello(name string) string {
return "Hello, " + name
}
if文は他のプログラミング言語とほとんど同じ書き方です。
if 条件 {
trueだった場合
} else {
falseだった場合
}
複数の条件は論理演算子の &&
と ||
で結合します。
Goでは繰り返しの制御構文は for
しかありません。 まず他の言語で一般的な for
と同様の用途の場合は次のように書きます。
for 初期化式; 条件式; 再初期化式 {
...
}
他の言語での while
のような使い方は次のような形になります。
for 条件 {
...
}
または無限ループは以下のように書けます。
for {
...
}
関数の書き方と if
と for
を学んだので、例に漏れず FizzBuzz を書いてみましょう。 文字列の表示は fmt.Println()
で、余りの計算は %
で行えます。
ここに解答のサンプルを載せておきます。
http://play.golang.org/p/VfOKX9NoVT
LLなどではリストやマップがありますが、Goにも同様のものがあります。それがスライスとマップです。 まずスライスは他の言語でリストと呼ばれているものと似たものです。 先ほどの :=
演算子を使っての宣言だと、次のように行います。
foo := []型名{} // 要素0のスライスの場合
bar := []型名{要素1, 要素2, ...} // 要素込みでのスライスの初期化
スライスの各要素はすべて同じ型でないといけません。
foo := []int{1, 2, 3, 4, 5} // すべてintなのでOK
bar := []string{"spam", "egg", "ham", 3.1415} // 3.1415はstringではないのでエラーになる
既存のスライスに要素を追加する場合は組み込み関数の append を使います。 上の int
のスライス foo
に新しい要素を追加してみます。
foo = append(foo, 10) // []int{1, 2, 3, 4, 5, 10} となった
他にも組み込み関数の make
を使った初期化方法がありますので、後で確認してください。
なお for
文内では foreach
のような書き方として次のように range
キーワードを使うと便利です。
for i, v := range some_slice {
...
}
i
はインデックス、 v
はそのインデックスに対応する値です。
マップはPythonで言うところの辞書に当たります。
spam := make(map[キーの型名]値の型名)
たとえば、 int
をキーにして、 string
を値に持つマップを宣言してみましょう。
students := map[int]string{} // intをキーとしてstringを値とするマップを作成。
students[1] = "じーたぐがーる子"
students[2] = "ぐぐるごーふぁー夫"
fmt.Println(students[1]) // "じーたぐがーる子"と表示される
こちらも make
を使った初期化方法がありますので、見てみてください。 こちらも for
文内では foreach
のような書き方として次のように range
キーワードを使うと便利です。
for k, v := range some_map {
...
}
i
はキー、 v
はそのキーに対応する値です。
Goの標準パッケージに net/http
というHTTPサーバ&クライアント用パッケージがあります。 これを使ってHTTPサーバを書いてみましょう。
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, GTUG Girls")
}
これを実行して、 http://localhost:8080/ にアクセスしてみましょう。
$ go run hello.go
今日のハンズオンではプログラムを更新して実行する際にはこの go run
を使ってください。
先の例ではルートパスのみを受け付ける状態でしたので、複数のエンドポイントを用意してみます。
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/goodbye", goodbyeHandler)
http.ListenAndServe(":8080", nil)
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, GTUG Girls")
}
func goodbyeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Good Bye, GTUG Girls")
}
上のプログラムに対して好きなハンドラーを追加してみましょう。
当然ですが文字列としてHTMLを返してあげればキチンとページが表示されますね!
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/goodbye", goodbyeHandler)
http.ListenAndServe(":8080", nil)
}
var helloHTML = `<!doctype html>
<p style=">Hello, GTUG Girls` // (1)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, helloHTML)
}
var goodbyeHTML = `<!doctype html>
<p>Good Bye, GTUG Girls` // (1)
func goodbyeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, goodbyeHTML)
}
ここで新しいことを解説します。
- バッククォートはPythonでいうところのトリプルクォートや多言語でのヒアドキュメントと同じ位置づけです。
- ダブルクォートを含む文字列や複数行の文字列を扱いたいときに便利です。
先ほどのHTMLはメッセージ部分以外は共通しているのでテンプレートを使います。 Goには標準パッケージに text/template
と html/tempalte
という2つのパッケージがあります。 後者はテンプレートからHTMLを生成する際にデータとして入力されたHTMLはエスケープをしてくれるので安心です。
GoのテンプレートはPythonのJinja2に似たシステムを持っていて、テンプレートの継承なども可能です。 詳細は http://golang.org/pkg/html/template/ を参考にしてもらうとして、 今回は変数の埋め込みをしてみます。
const BaseHTML = `<!doctype html>
<p>{{ . }}` // (1)
var BaseTemplate = template.Must(template.New("base").Parse(BaseHTML)) // (2)
このコードの解説をします。
- 変数は
{{ }}
で囲みます。渡されたデータ自身はピリオド(.
)で表現されます。- 例えば
struct
を渡した場合には続けてフィールド名を書くことでアクセスできます。(eg.{{ .Name }}
)
- 例えば
- テンプレート用文字列は
Template
型の変数のParse()
関数で読み取って実体化します。- (2)の書き方がイディオムです。
これを先ほどのコードに適用すると次のようなコードとなります。
package main
import (
"html/template"
"net/http"
)
func main() {
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/goodbye", goodbyeHandler)
http.ListenAndServe(":8080", nil)
}
const BaseHTML = `<!doctype html>
<p>{{ . }}`
var BaseTemplate = template.Must(template.New("base").Parse(BaseHTML))
func helloHandler(w http.ResponseWriter, r *http.Request) {
BaseTemplate.Execute(w, "Hello, GTUG Girls")
}
func goodbyeHandler(w http.ResponseWriter, r *http.Request) {
BaseTemplate.Execute(w, "Good Bye, GTUG Girls")
}
JSONは encoding/json
を使って扱うことができます。 PythonなどではJSONをPythonの辞書型に変換しますが、Goではstructに変換します。
今回は OpenWeatherMap API を使ってみましょう。 試しに東京都の最新の天気を取得してみます。
$ curl "http://api.openweathermap.org/data/2.5/find?lat=35.6895&lon=139.6917&cnt=1"
次のようなJSONが返ってきます。
{
"message":"accurate",
"cod":"200",
"count":1,
"list":[
{
"id":1850144,
"name":"T\u014dky\u014d-to",
"coord":{
"lon":139.691711,
"lat":35.689499
},
"main":{
"temp":297.74,
"humidity":43,
"pressure":1013,
"temp_min":295.37,
"temp_max":300.37
},
"dt":1378815731,
"wind":{
"speed":2.95,
"deg":84.5
},
"sys":{
"country":"JP"
},
"rain":{
"1h":0.25
},
"clouds":{
"all":0
},
"weather":[
{
"id":521,
"main":"Rain",
"description":"shower rain",
"icon":"09n"
}
]
}
]
}
この中から、 weather
の main
と description
を取得して表示するとしましょう。
まずJSONを取得するためにAPIに対してHTTPリクエストを投げます。 HTTPリクエストはサーバと同様に net/http
を利用します。 典型的なリクエストは次の形で行います。
resp, err := http.Get(URL) // (1)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close() // (2)
ここでまた新しいことが出てきました。
http.GET(URL)
でURL
に対してGETリクエストを行うことができます。resp.Body
はきちんと閉じるようにdefer
でClose()
メソッドを呼んでおきましょう。defer
とは関数の最後にきちんと処理されるようにするためのキーワードです。
JSONのパースのためには先ほど説明したように専用のstructが必要となります。 ここで struct
について説明します。
Goにはクラスがありません。その代わり struct と interface というものがあります。 後者は今回は飛ばしますが大事なので後で調べてみてください。
struct
はフィールドと呼ばれる変数を束ねたものと考えてください。 例えば Human
型の struct
を宣言します。
type Human struct {
Name string
Age int
Height float64
Weight float64
}
struct の初期化と更新は次のように行います。
taro := Human{} // 初期化
taro.Name = "Yamada Taro"
taro.Age = 20
hanako := Human{ // まとめて初期化する場合
Name: "Yamada Hanako",
Age: 21,
Height: 158,
Weight: 50, // 最後のカンマは忘れがちなので注意
}
また struct はメソッドを持つことが出来ます。メソッドの書き方は関数とほとんど一緒で次のとおりです。
func (型変数名 struct名) メソッド名(引数 引数の型, ...) 戻り値の型 {
...
}
たとえば上の Human
型に Greet()
というメソッドを追加してみます。
func (h Human) Greet() string {
fmt.Printf("Hello, My name is %s. %d years old.", h.Name, h.Age)
}
さて、structの宣言方法と初期化の方法がわかったのでJSONをパースしてみます。 コツとしてはJSONで値に対応するものが通常のデータ型(intやstring等)でなかった場合、 新しく型を宣言して、その型のフィールドとして定義してしまうことです。
先ほどの返り値を見てみましょう。まず返ってきたJSON全体を表すstructを定義します。 weather
の中身はマップになっているので、ここで Weather
型というstructを用意します。
type APIResult struct {
L []List `json:"list"` // (1)
}
type List struct {
W []Weather `json:"weather"` // (2)
}
type Weather struct {
Main string `json:"main"` // (3)
Description string `json:"description"` // (3)
}
またまた新しいことが出てきました。
- まず
weather
を含む、返り値のJSONのトップレベルにある"list"
というキーを取得するため、json:"list"
というラベルを定義します。- リストを受け取る場合は、新しい型を定義して(この場合は
List
)、それのスライスにします
- リストを受け取る場合は、新しい型を定義して(この場合は
"list"
の要素一つ一つが新しいList
型に入るので、"weather
だけ取り出すべく、json:"weather"
というラベルを持つフィールドだけ用意します。- (2) で
"weather"
に対応する値のマップはWeather
型のフィールドW
に入れられることになります。 入れ物の中では"main"
と"description"
だけ受け取るようにWeather
型を定義します。
これを先ほどのアプリの中に /weather
というエンドポイントをつけて実装してみます。
package main
import (
"encoding/json"
"html/template"
"log"
"net/http"
)
const API = "http://api.openweathermap.org/data/2.5/find?lat=35.6895&lon=139.6917&cnt=1" // (1)
type APIResult struct {
L []List `json:"list"`
}
type List struct {
W []Weather `json:"weather"`
}
type Weather struct {
Main string `json:"main"`
Description string `json:"description"`
}
func (w Weather) String() string { // Weather型にメソッドを定義
return w.Main + " (" + w.Description + ")"
}
func main() {
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/goodbye", goodbyeHandler)
http.HandleFunc("/weather", weatherHandler)
http.ListenAndServe(":8080", nil)
}
const BaseHTML = `<!doctype html>
<p>{{ . }}`
var BaseTemplate = template.Must(template.New("base").Parse(BaseHTML))
func helloHandler(w http.ResponseWriter, r *http.Request) {
BaseTemplate.Execute(w, "Hello, GTUG Girls")
}
func goodbyeHandler(w http.ResponseWriter, r *http.Request) {
BaseTemplate.Execute(w, "Good Bye, GTUG Girls")
}
func weatherHandler(w http.ResponseWriter, r *http.Request) {
// GETリクエストでAPIを叩く
resp, err := http.Get(API)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
// 結果のJSONをAPIResult型の変数に入れる
var result APIResult
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&result) // 参照渡し (1)
if err != nil {
log.Println(err)
return
}
log.Println(result)
// 結果を "天気名 (細かい説明)" という形で表示する
BaseTemplate.Execute(w, result.L[0].W[0].String()) // メソッド呼び出し
}
ここで1つ新しいことが出てきたので見てみましょう。
- Goではスライスとマップ以外は基本的に「値渡し」になっています。
- 変数を関数に渡して、返り値の形でなく更新してもらいたい場合は参照渡しにします。
ここまで完成させたプログラムをいよいよコンパイルしてみましょう。 次のコマンドを実行して、 go run
で使った時と同様にアクセスできるか試してください。 また同様のPCの人同士(MBPの人同士、Win機同士)で作ったバイナリを交換して実行できるか試してください。
$ GOPATH=`pwd`; go build -o hello hello.go # Linux/BSDの人
$ ./hello
先のプログラムでAPIに渡す変数で cnt
の値を 10
に変更し、複数の結果を表示できるように改造してみましょう。
Note
ヒント: テンプレートでrangeとstructのフィールドにアクセスできる機能を使いましょう。 http://golang.org/pkg/text/template の "Variables" のセクションが参考になります。
Goで一番おもしろい機能としてgoroutineとchannelがあります。 これは非同期のプログラムをスレッドやプロセスを深く意識することなく簡単に書けます。
ここからGoの面白いところなので、ぜひgoroutineとchannelを試してみてください。 これは A Tour of Go のサンプルがとてもわかりやすいです。
Goは1.0のリリース後、急速にユースケースを増やしていて、これからがかなり期待される言語です。 興味を持った方はぜひその波に乗りましょう!
以下は気になった人向けの情報源です。
お疲れ様です。わかりやすくて良い資料ですね。
気になった点を。
map の初期化
Slice で
としたのなら、 Map も make 無しで説明したほうが良いような気がします。
HTML を表示してみる
あと、せっかく ` で囲むなら HTML は改行してもいいかもです。
HTTP リクエストをする
return log.Println(err) は log.Println() used as value エラーですね。
返すか log のどっちかでいいと思います。log なら Fatal の方がいいかもです。
以上です。
当日はよろしくお願いいたします。