blue_field

技術的なメモとか読書記録とかいろいろ(の予定

Golangを触り始めた② 文法・型

前回に引き続き、Web+DB vol.82のGo特集が分かりやすいのでそれ見ながら。

ぜんぶ網羅して書いてないけど、メモ。Goのバージョンは1.3.3です。

変数

var message string = "Hello world"

変数宣言は、varで始まり、変数名、型の順。ちょっと違和感。

関数内部で変数宣言と初期化を行う場合は、以下のように:=を利用しても書ける。こっちの書き方を使うことが多そう。変数の型は、代入する値から推論される。

/* import文は省略 */

func main() {
    message := "Hello world"
    fmt.Println(message)
}

定数は、varじゃなくてconstで宣言。

if

条件部分に()は付けないが、処理部分は{}が必要。

func main() {
    a, b := 10, 1
    if a > b {
        fmt.Println("aの方がデカイ")
    }
}

for

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
}

whileloopは無いので、forで代替。

n := 0
for n < 10 {
    fmt.Println(n)
    n++
}

ループから抜けるのはbreak、次の繰り返しに行くのはcontinue

switch

func main() {
    for n := 0; n < 31; n++ {
        switch {
        case n%15 == 0:
            fmt.Println("FizzBuzz")
        case n%5 == 0:
            fmt.Println("Buzz")
        case n%3 == 0:
            fmt.Println("Fizz")
        default:
            fmt.Println(n)
        }
    }
}

関数

関数はfuncで宣言。引数がある場合は、変数と合わせて型も指定。戻り値がある場合は、引数の次に書き、型を指定。

func sum(i, j int) int {
    return i + j
}

func main() {
    n := sum(1, 2)
    fmt.Println(n) // 3
}

関数は複数の値を戻り値として返すことができる。その際、戻り値を格納する変数を必要数分用意してあげないといけない。用意する変数の数が合わないとコンパイルエラー。無視したい戻り値があるときは、_を利用して無視することを明示する。

func swap(i, j int) (int, int) {
    return j, i
}

func main() {
    x, y := 20, 30
    x, y = swap(x, y)
    fmt.Println(x, y) // 30, 20

    x, _ = swap(x, y)
    fmt.Println(x) // 20
}

関数でエラーを返す

上記のような複数の値を戻り値として返す特徴を利用し、Golangでは内部で発生したエラーを戻り値で表現する。

また、エラーはerrorsパッケージを使って自作できる。

// main.go

package main

import (
    "errors"
    "fmt"
    "log"
)

func div(i, j int) (int, error) {
    if j == 0 {
        return 0, errors.New("divided by zero")
    }
    return i / j, nil
}

func main() {
    n, err := div(10, 0)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(n)
}

上記を実行すると、こんな感じになる。

$ go run main.go
2014/10/26 15:29:31 divided by zero
exit status 1

配列

Golangの配列は固定長。 例えば、長さが3で要素の型がstringだったら、こう書く。

arr := [3]string{"a", "b", "c"}

// または
arr := [...]string{"a", "b", "c"}

配列型は長さも情報として持っているため、長さが異なると別の型と認識される。

func show(arr [4]string) {
    fmt.Println(arr)
}

func main() {
    var arr1 [4]string
    var arr2 [5]string

    fn(arr1)
    fn(arr2) // コンパイルエラー!
}

スライス

こっちが慣れ親しんでいる可変長配列。

var s []string

このように長さの情報を宣言に含めないで書くと、スライスになる。

append

スライス(の末尾)に値を追加をするにはappendを利用。

var s []int
s = append(s, 1, 2)
fmt.Println(s) // [1 2]

range

配列やスライスに格納された値を先頭から順に処理するときは、rangeを利用。

arr = [3]string{"a", "b", "c"}
for i, s := range arr {
    fmt.Println(i, s) // iが添字、sが値
} 

上記のような使い方はRubyeach_with_index的な感じかな。

値の切り出し

s := []int{1, 2, 3, 4, 5}

fmt.Println(s[2:4]) // [3 4]
fmt.Println(s[:3]) // [1 2 3]
fmt.Println(s[3:]) // [4 5]

引数として使うとき

以下のように書くと、任意の長さのスライスを引数として使える

func sum(nums ...int) (result int) {
    for _, n := range nums {
        result += n
    }
    return
}

func main() {
    fmt.Println(sum(1, 2, 3, 4)) // 10
}

マップ

Rubyでいうところのハッシュ的なやつ。以下のように宣言。

var month map[int]string = map[int]string{}

month[1] = "January"
month[2] = "February"

宣言と初期化を同時にやるならこう書く。

month := map[int]string {
    1: "January",
    2: "February",
}
fmt.Println(month) // map[1:January 2:February]

値へのアクセス

Rubyなどと方法は同じ。

jan := month[1]
fmt.Println(jan) // January

ただし、2つめの戻り値も実はある。 (このケースは変数を1つしか受け取らなくてもコンパイルエラー出ないのね)

2つめの戻り値は、指定したキーがこのマップに存在しているかを表すbool値。

_, ok = month[1]
if ok {
    // データが存在したとき、何らかの処理
}

マップからデータ消すときはdeleteを使う。

delete(month, 1)
fmt.Println(month) // map[2:February]

スライスと同様、rangeで繰り返し処理を実現できるが、取り出される順番は保証されないらしい。。

ポインタ

ポインタ型の変数には、型の前に*を付ける。アドレスは変数の前に&をつけると分かる。Cと似ているらしい(触ったことない...)

func callByValue(i int) {
    i = 20
}

func callByRef(i *int) {
    *i = 20
}

func main() {
    i := 10
    callByValue(i)
    fmt.Println(i) // 10
    callByRef(&i)
    fmt.Println(i) // 20
}

defer

例えば、ファイル操作で利用するfile.Close()(ファイルを閉じる作業)は必ず実行されなければならない。だが、関数の途中でパニックが起こったりすると、この文まで到達できない可能性がある。

そういったケースに使うのがdefer。これを処理の前に書いておくと、main()を抜ける前にかならず実行されるようになる。

defer file.Close()

パニック

スライスの範囲外にアクセスしようとした時など、エラーを戻り値として返せない時、パニックという方法でエラーが発生する。

パニックは、recover()で取得し、その中でエラー処理をする。recover()deferの中に書く。

// main.go

func main() {
    defer func() {
        err := recover()
        if err != nil {
            log.Fatal(err)
        }
    }()

    a := []int{1, 2, 3}
    fmt.Println(a[10])
}
$ go run main.go
2014/10/26 16:33:39 runtime error: index out of range
exit status 1

panicは自分でも発生させられる。

// main.go

a := []int{1, 2, 3}
for i := 0; i < 10; i++ {
    if i > len(a) {
        panic(errors.New("index out of range"))
    }
    fmt.Println(a[i])
}
$ go run main.go
1
2
3
panic: runtime error: index out of range

goroutine 16 [running]:
runtime.panic(0xb7e60, 0x142b5c)
    /usr/local/Cellar/go/1.3.3/libexec/src/pkg/runtime/panic.c:279 +0xf5
(以下略)

Goで書かれたソースをほとんど読んだことないので、使いどころのイメージがまだあまりできない。。エラーは、できるだけ戻り値として表現し、無理なときにpanicにするって感じだろうか。

構造体

Rubyのクラス的なやつ。typeを用いて宣言。 構造体のあとにフィールドを記述する。フィールドは、名前が大文字から始まればパブリック、小文字からならパッケージ内のみから見える。

type Task struct {
    ID     int
    Detail string
    done   bool
}

func main() {
    task := Task{
        ID:     1,
        Detail: "Write a code",
        done:   true,
    }

    fmt.Println(task.ID) // 1
    fmt.Println(task.Detail) // "Write a code"
}

taskには構造体が代入され、各フィールドにはドットでアクセスできる。

ポインタ型として扱うことも可能。

new()

new()で構造体の初期化を行える。フィールドはすべてゼロ値となる。

func main() {
    var task *Task = new(Task)
    fmt.Println(task.done) // false
}

コンストラクタ

Newではじまる関数を定義し、その中で構造体を生成することで、コンストラクタの役割を果たす。

func NewTask(id int, detail string) *Task {
    task := &Task{
        ID:     id,
        Detail: detail,
        done:   false,
    }
    return task
}

func main() {
    task := NewTask(1, "Write a code")
    fmt.Printf("%+v", task) // &{ID:1 Detail:Write a code done:false}%
}

メソッド

型にはメソッド定義ができる。 以下は、レシーバをポインタとして受け取るケース。

func (task *Task) Finish() {
    task.done = true
}

func main() {
    task := NewTask(1, "Write a code")
    task.Finish()
    fmt.Printf("%+v", task) // &{ID:1 Detail:Write a code done:true}%
}

ポインタを使っているので、レシーバのdoneの値がtrueに変わっている。

インターフェース

インターフェースは、型がどのようなメソッドを実装すべきかを規定する。

type Finisher interface {
    Finish()
}

こんなかんじで、関数名にerを付けて書くのがお作法らしい。

型の埋め込み

Goには、継承という概念が無いみたい。代わりに、「他の型を埋め込む」という形で構造体やインターフェースを拡張できる。

以下、構造体の埋め込み例。

// main.go

package main

import "fmt"

type User struct {
    FirstName string
    LastName  string
}

func (u *User) FullName() string {
    fullname := fmt.Sprintf("%s %s", u.FirstName, u.LastName)
    return fullname
}

func NewUser(firstName, lastName string) *User {
    return &User{
        FirstName: firstName,
        LastName:  lastName,
    }
}

type Task struct {
    ID     int
    Detail string
    done   bool
    *User
}

func NewTask(id int, detail, firstName, lastName string) *Task {
    task := &Task{
        ID:     id,
        Detail: detail,
        done:   false,
        User:   NewUser(firstName, lastName),
    }
    return task
}

func main() {
    task := NewTask(1, "Write a code", "Yusuke", "Aono")
    fmt.Println(task.FirstName)
    fmt.Println(task.FullName())
    fmt.Println(task.User)
}
$ go run hello.go

Yusuke
Yusuke Aono
&{Yusuke Aono}

Taskの構造宣言時に、Userという型を埋め込む。これでTaskからUserのフィールドやメソッドにアクセスできるようになる。

おわり

お腹がすいたので、おわり。 Ruby書いてる時と異なり、留意しなきゃいけない点が多そうではある。ま、性質の違う言語なんだから当たり前か。。

エラー処理のあたりは難しそう(書くのめんどくさそう)な印象だが、もう少し書いてみないと判断できないですね。