- 2014/3/15 (土) 14:00
- http://www.zusaar.com/event/4367004
- 多言語で基本的なプログラミング知識(Web 系?) がある。
- Go のインストール、開発環境(エディタとか)の準備済み。
- http://go-tour-jp.appspot.com/ を一通り(24, 48, 60, 69, 70 は飛ばしてよし)
- タスク管理ツール的なものを作りながら、 Go のひと通りの機能を触る。
- Go をひと通り触るのが目的なので、ツール自体の機能とかはこだわらない。
- 他の言語での経験がある方が対象なので、プログラミング言語の基本的な話は省略。
- 標準モジュールでできることのみ。
- 環境構築は終わっている前提。
- 1 ファイルで完結させる構成(main.go のみ)
- 時間がないので色々省略している部分は最後にリンクを。
- 時間があまったらテストの話。
ハンズオン形式で進め、途中で課題をいくつか実施。
main() がいわゆる main 文。 main() は main パッケージにのみ作成可能。
package main
import (
"fmt"
)
func main() {
fmt.Println("Go Kyoto!")
}
フォーマット
$ go fmt main.go
実行
$ go run main.go
Go Kyoto!
フォーマッタは、コミットフックやエディタで保存時にやるのを推奨。
電波がなくても、標準モジュールと自分が書いたモジュールのドキュメントはローカルで見られる。
$ godoc fmt
サーバも内蔵されている。
$ godoc -http=":4000" &
http://localhost:4000 で起動される。
fmt.Print() が stdout, log.Print() が stderr に出力。 init() は初期化処理が記述でき、 log.SetFlags() でフォーマットが変えられる。 フォーマットは http://golang.org/pkg/log/#pkg-constants 参照
import (
"fmt"
"log"
)
func init() { // 初期化処理
// 日付ではなく行番号になるのでおすすめ
log.SetFlags(log.Lshortfile)
}
func main() {
fmt.Println("Go Kyoto!")
fmt.Printf("%v", "Go Kyoto!\n")
log.Println("Go Kyoto!")
log.Printf("%v", "Go Kyoto!\n")
}
定義は変数名が先、型が後。
func main() {
var id int = 1
var detail string = "buy the milk"
var done bool = false
log.Println(id, detail, done)
}
func main() {
var (
id int = 1
detail string = "buy the milk"
done bool = false
)
log.Println(id, detail, done)
}
暗黙的型宣言
func main() {
id := 1
detail := "buy the milk"
done := false
log.Println(id, detail, done)
}
構造体。(オブジェクト的なもの) メンバは、大文字が Public、小文字が Private
type Task struct {
Id int
Detail string
Done bool
}
インスタンスの生成。 Task{} はインスタンスの実体を返し、 &Task{} はポインタを返す。
func main() {
// new 的なこと
task := &Task{
Id: 1,
Detail: "buy the milk",
Done: false,
}
log.Printf("%+v", task) // 見やすい
}
インスタンス生成時に、値を明示的に指定しなかったら暗黙的に各型に応じた初期値が使用される。これを ゼロ値 という。
func main() {
// bool のゼロ値は false なので Done は省略可
task := &Task{
Id: 1,
Detail: "buy the milk",
}
log.Printf("%+v", task) // 見やすい
}
Task を New する関数は NewTask という関数名にするのが通例。 (コンストラクタ相当)
func NewTask(id int, detail string) *Task {
task := &Task{
Id: id,
Detail: detail,
Done: false,
}
return task
}
func main() {
task := NewTask(1, "buy the milk")
log.Printf("%+v", task)
}
struct にはメソッドが定義できる。 レシーバが、実体のものとポインタのもので定義方法が違う。
String() は、表示などで文字列化するときに使われる Stringer というインタフェースのメソッド。(toString() とか相当)
func (task Task) String() string { // 引数なし,戻り値あり
return fmt.Sprintf("%d) %s (%t)", task.Id, task.Detail, task.Done)
}
func (task *Task) Finish() { // 引数,戻り値無し
task.Done = true
}
Task.Detail を引数で受け取った detail string でまるっと上書きする Edit メソッドを実装しなさい。
中括弧はいらない。 三項演算子はない。
func (task Task) String() string {
var status string
if task.Done {
status = "done"
} else {
status = "not yet"
}
return fmt.Sprintf("%d) %s (%s)", task.Id, task.Detail, status)
}
可変長な配列だと思えば良い。 追加には組み込み関数の append() を使用する。 (追加結果を返すことに注意)
func main() {
tasks := []*Task{
NewTask(1, "buy the milk"),
NewTask(2, "eat the pie"),
}
tasks = append(tasks, NewTask(3, "go to the bank"))
log.Printf("%s", tasks)
}
実際は、固定長配列のビューとなっている。 固定長な配列は別にある。 今回は make() を用いた初期化は省略。 詳細は http://blog.golang.org/go-maps-in-action を参照のこと。
繰り返しを表現する方法は for のみ。While などない。 range を用いてイテレーションが可能、インデックスと要素を返す。 (イテレータ相当)
for i, task := range tasks {
log.Printf("%d %s", i, task)
}
// 宣言して使わない変数は許されないので
// 使わないなら _ で宣言する
for _, task := range tasks {
log.Printf("%s", task)
}
Tasks を以下のように定義し、 NewTasks 関数と Add メソッドを実装しなさい。
type Tasks struct {
Owner string
Tasks []*Task // ここは空のスライスで初期化
}
動作例
func main() {
tasks := NewTasks("Jxck")
tasks.Add(NewTask(1, "buy the milk"))
tasks.Add(NewTask(2, "eat the pie"))
tasks.Add(NewTask(3, "go to the bank"))
log.Println(tasks.Owner)
for i, task := range tasks.Tasks {
log.Printf("%d %s", i, task)
}
}
タスク内容を net/http で公開するサーバを作成。 まず index ページとしてリンクのみを表示するハンドラを作成。 ハンドラを登録したサーバを port 3000 で起動。
func IndexHandler(w http.ResponseWriter, r *http.Request) {
// heredoc
html := `
<!DOCTYPE html>
<title>tasks</title>
<h1>here is your <a href="%s">tasks</a></h1>
`
fmt.Fprintf(w, html, "/tasks")
}
func main() {
http.HandleFunc("/", IndexHandler)
http.ListenAndServe(":3000", nil)
}
http.ResponseWriter は Writer というインタフェースを実装しており、書き込み可能なオブジェクト。 fmt.Fprintf は、第一引数に Writer を受け、第二引数をそこに書き込む。
今回は使用していないが、 *http.Request の中にはリクエストに関する情報が全て入っている。
TasksHandler を作成し、とりあえず main でやっていたことを移して、 http://localhost:3000/tasks でタスクをリスト表示する。
実装は、 fmt.Fprintf を用いて ResponseWriter にガンガン書き込む感じで良い。
動作例(html)
<!DOCTYPE html>
<title>tasks</title>
<h1>Jxck's tasks</h1>
<ul>
<li>1) buy the milk (not yet)</li>
<li>2) eat the pie (not yet)</li>
<li>3) go to the bank (not yet)</li>
</ul>
標準で text/template と html/template があり、インタフェースは基本的に同じだが、 html/template の方はエスケープ処理などを標準で行ってくれるため Web 用途ではこちらを使う。
func TasksHandler(w http.ResponseWriter, r *http.Request) {
tasks := NewTasks("Jxck")
tasks.Add(NewTask(1, "buy the milk"))
tasks.Add(NewTask(2, "eat the pie"))
// こういうのがあっても大丈夫
tasks.Add(NewTask(3, "<b>go to the bank</b>"))
html := `
<!DOCTYPE html>
<title>tasks</title>
<h1>{{.Owner}}'s tasks</h1>
<ul>
{{range .Tasks}}
<li>{{.}}</li>
{{end}}
</ul>
`
// 第二引数がエラーを返すイディオム
TasksList, err := template.New("tasklist").Parse(html)
if err != nil {
log.Fatal(err)
}
TasksList.Execute(w, tasks)
}
Go の関数は多値を返せる、 error が発生する場合は、引数の最後に返すのがイディオム。 実際はこれを処理したり、呼び出し元に返したりするが今回は log.Fatal() でプロセスを落としている。 (今回は扱わないが、 panic/recover というエラーの制御方法もある http://blog.golang.org/defer-panic-and-recover)
クロージャを用いて関数を返す関数にする。(Go では関数は First Class) tasks を受け取り http.Handler を返す関数にし、 main() から tasks を渡せるようにする。
func TasksHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
<title>tasks</title>
<h1>{{.Owner}}'s tasks</h1>
<ul>
{{range .Tasks}}
<li>{{.}}</li>
{{end}}
</ul>
`
TasksList, err := template.New("tasklist").Parse(html)
if err != nil {
log.Fatal(err)
}
return func(w http.ResponseWriter, r *http.Request) {
TasksList.Execute(w, tasks)
}
}
func main() {
// snip
http.HandleFunc("/tasks", TasksHandler(tasks))
// snip
}
Tasks を encoding/json を用いてシリアライズする。 基本的には Marshal() か MarshalIndent() に渡すだけ。 戻り値は []byte というバイトのスライスなので、文字列にする場合は変換が必要。
// b, err := json.Marshal(tasks) // インデントなし
b, err := json.MarshalIndent(tasks, "", " ") // プレフィックス、インデントあり
if err != nil {
log.Fatal(err)
}
log.Println(string(b)) // 文字列化
{
"Owner": "Jxck",
"Tasks": [
{
"id": 1,
"detail": "buy the milk",
"done": false
}
]
}
対応する key 名を変更したい場合はタグで指定する。
type Task struct {
Id int `json:"id"`
Detail string `json:"detail"`
Done bool `json:"done"`
}
type Tasks struct {
Owner string `json:"owner"`
Tasks []*Task `json:"tasks"`
}
http://localhost:3000/tasks.json でタスクを JSON で取得できる API を作成するため TasksJSONHandler を作成し、サーバに登録しなさい。
送信は fmt.Fprint() で良いでしょう。 Content-Type とか細かいことは気にしない!
当日時間があったので Slice の内部構造について解説し、 それを利用して Tasks.Del() を実装した。
func (tasks *Tasks) Del(id int) {
for i, task := range tasks.Tasks {
if task.Id == id {
copy(tasks.Tasks[i:], tasks.Tasks[i+1:])
tasks.Tasks[len(tasks.Tasks)-1] = nil
tasks.Tasks = tasks.Tasks[:len(tasks.Tasks)-1]
break
}
}
}
詳細は、この辺を参照してほしい。 https://code.google.com/p/go-wiki/wiki/SliceTricks
時間が余ったら、 tasks のモジュール分けとテストを書きます。
話せなかったこと
- interface
- new()
- channel, goroutine
- module 分割
- その他たくさん
- 言語仕様: http://golang.org/ref/spec
- Effective Go: http://golang.org/doc/effective_go.html
- パッケージドキュメント: http://golang.org/pkg
- 公式ブログ(細かい機能解説などがある): http://blog.golang.org/
事例集 からピックアップ
func (task *Task) Edit(detail string) {
ttask.Detail = detail
}
func NewTasks(owner string) *Tasks {
tasks := &Tasks{
Owner: owner,
}
return tasks
}
func (tasks *Tasks) Add(task *Task) {
tasks.Tasks = append(tasks.Tasks, task)
}
func TasksHandler(w http.ResponseWriter, r *http.Request) {
tasks := NewTasks("Jxck")
tasks.Add(NewTask(1, "buy the milk"))
tasks.Add(NewTask(2, "eat the pie"))
tasks.Add(NewTask(3, "go to the bank"))
fmt.Fprintf(w, "<!DOCTYPE html><title>tasks</title>")
fmt.Fprintf(w, "<h1>%s's tasks</h1>", tasks.Owner)
fmt.Fprintf(w, "<ul>")
for _, task := range tasks.Tasks {
fmt.Fprintf(w, "<li>%s</li>", task)
}
fmt.Fprintf(w, "</ul>")
}
func TasksJSONHandler(tasks *Tasks) func(w http.ResponseWriter, r *http.Request) {
b, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
log.Fatal(err)
}
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, string(b))
}
}
The MIT License (MIT) Copyright (c) 2013 Jxck