プログラミング言語 Go を読みながらメモ。

第一章 : https://qiita.com/Nabetani/items/077c6b4d3d1ce0a2c3fd
第二章 : https://qiita.com/Nabetani/items/d053304698dfa3601116
第三章 : https://qiita.com/Nabetani/items/2fd9c372fcd8383955a5
第四章 : https://qiita.com/Nabetani/items/59bfd00dc3323883a07f

で。

引数リスト

go
package main

import "fmt"

func first(x int, _ int) int  { return x }
func second(x int, y int) int { return y } // y が未使用だけどエラーにならない

func main() {
    fmt.Println(first(1, 2))
    fmt.Println(second(1, 2))
}

上記の second は、未使用のローカル変数でエラーになるかと思っていたんだけど、ならない模様。未使用のローカル変数はエラーだけど、未使用の関数引数は OK らしい。

名前付き引数がないのはちょっと残念というか意外。

非標準パッケージの利用

5.2 で、golang.org/x/net/html という非標準パッケージを利用しているが、利用する方法が書いていない。まったくよろしくない。

利用するためには

go get golang.org/x/net/html

を実行する必要がある。書いておいてほしい。

複数戻り値

多値呼び出しは、複数のパラメータを持つ関数呼び出しの唯一の引数として書くことが出来ます。

とあるので、多分駄目だろうと思いつつ試してみたのはこんなコード:

go
package main

import (
    "fmt"
)

func main() {
    fmt.Printf(fmtAndValue())           //=> okay
    fmt.Printf("%d %s", intAndString()) //=> multiple-value intAndString() in single-value context
}

func fmtAndValue() (format string, value int) {
    return "%d", 123
}
func intAndString() (int, string) {
    return 456, "hoge"
}

やはり、唯一であることが重要。

ruby の splat 展開みたいなのがあると便利そうなんだけど、ないのかな。

あと空リターン。なんかパスカルっぽい。でも

 空リターンは控えめに使うのが最善でしょう。

と、やや非推奨らしい。

エラー処理戦略

まあ確かに C++やRubyの例外は難しい部分もあるけど、みんながちゃんと使えば幸せだと思うんだよなぁ。
という私の思いとは関係なく、 Go のエラー処理戦略は第二のリターンバリュー。
握りつぶしやすすぎるのが気になるけど、そこも狙いのひとつなんだと思ったり。

log.Fatal シリーズが面白い。

go
package main

import (
    "fmt"
    "log"
)

func main() {
    log.Fatalln("hoge", "fuga", "piyo")
    fmt.Println("unreachable")
}

を実行すると

2018/03/17 20:43:16 hoge fuga piyo
exit status 1

と出力される。

関数値

関数値はマップのキーとして使ってはいけません。

「いけません」と言われると試したくなる。

go
package main

import "fmt"

func foo(int) int { return 1 }
func bar(int) int { return 2 }
func baz(int) int { return 3 }

func main() {
    a := make(map[func(int) int]int)
    fmt.Println(a) //=> invalid map key type func(int) int
    fmt.Println(foo == bar) //=> invalid operation: foo == bar (func can only be compared to nil)
}

エラーなので、いけませんというよりも「エラーになります」とかのほうが誤解がなくて良いと思った。

無名関数

go
package main

import (
    "fmt"
)

func squares() func() int {
    x := 0
    return func() int {
        x++
        return x * x
    }
}

func main() {
    f := squares()
    g := squares()
    fmt.Println("f#1", f()) //=> f#1 1
    fmt.Println("f#2", f()) //=> f#2 4
    fmt.Println("f#3", f()) //=> f#3 9
    fmt.Println("g#1", g()) //=> g#1 1
    fmt.Println("g#2", g()) //=> g#2 4
    fmt.Println("g#3", g()) //=> g#4 9
}

f に閉じ込められている x と、g に閉じ込められている x では値が異なることを確認。当たり前だけど。

ループ変数の補足

変数の寿命を捉えないと、クロージャを作るときに間違えやすい。

go
package main

import "fmt"

var names = []string{"foo", "bar", "baz"}

func main() {
    var funcs []func()
    for _, n := range names {
        funcs = append(funcs, func() {
            fmt.Println(n)
        })
    }
    for _, f := range funcs {
        f()
    }
}

は、

baz
baz
baz

を出力する。

python も同様だ。

python3.6
names = ["foo", "bar", "baz"]

funcs=[]
for n in names:
  funcs.append( lambda: print(n))
for f in funcs:
  f()

は、

baz
baz
baz

を出力する。

一方 ruby は each 内で変数が毎回作られるので

ruby
names = ["foo", "bar", "baz"]

funcs = []
names.each do |n|
  funcs.push(->(){ puts(n) })
end
for f in funcs do
  f[]
end

foo
bar
baz

を出力する。

Go の場合は、スコープの狭い変数を作ることで解決する。
こんな具合:

go
package main

import "fmt"

var names = []string{"foo", "bar", "baz"}

func main() {
    var funcs []func()
    for _, n := range names {
        n := n // 同名でも良い
        funcs = append(funcs, func() {
            fmt.Println(n)
        })
    }
    for _, f := range funcs {
        f()
    }
}

python の場合は、forループの内側に変数を作ってもうまくいかない。
以下のように

python3
names = ["foo", "bar", "baz"]

funcs=[]
for n in names:
  funcs.append(lambda n=n: print(n))
for f in funcs:
  f()

lambda のデフォルト引数を悪用して束縛するとうまくいく。

可変長引数

sum とか max を見ると、C++ のようなテンプレートが欲しくなるね。我慢我慢。

遅延関数呼び出し

ようやく出てきた defer

関数と引数の式は defer 文が実行されるときに評価されますが、実際の呼び出しは«中略»遅延されます。

すぐに評価されるってどういうこと?

go
package main

import "fmt"

func foo() string {
    fmt.Print("<foo>")
    return "FOO"
}

func main() {
    defer fmt.Println(foo())
    fmt.Print("bar")
}

は、

<foo>barFOO

を出力した。defer 文で fmt.Println は遅延されるけど、 foo() は遅延されない。そういうことか。

もうひとつ例を。

go
package main

import "fmt"

func foo(s string) func() {
    fmt.Print("foo")
    return func() {
        fmt.Print(s)
    }
}

func main() {
    defer foo("bar\n")()
    fmt.Print("baz")
}

これは

foobazbar

を出力する。

defer 文は、ブロックの最後ではなく、関数の最後に実行されるので

go
package main

import "fmt"

func main() {
    fmt.Print("begin ")
    for i := 0; i < 3; i++ {
        // do something
        defer fmt.Printf("%d ", i)
    }
    fmt.Print("end ")
}

begin end 2 1 0 

を出力する。
なるほど。長い関数を書くなというメッセージに見える。

まあ、無名関数を使って

go
package main

import "fmt"

func main() {
    fmt.Print("begin ")
    for i := 0; i < 3; i++ {
        func() {
            // do something
            defer fmt.Printf("%d ", i)
        }()
    }
    fmt.Print("end ")
}

としてもいいんだけど。

あと。
こんな例はどうだろう:

go
package main

import "fmt"

func main() {
    n := 10
    a := []byte{1, 2, 3}
    s := "hoge"
    defer fmt.Println(n, a, s)
    n += 100
    s = "fuga"
    a[0] = 100
}

これは

10 [100 2 3] hoge

を出力した。つまり、defer は変数ではなく値を束縛しているように見える。
クロージャとは動きが違うところが面白い。クロージャの場合は変数を束縛するので下記の通りになる:

go
package main

import "fmt"

func main() {
    n := 10
    a := []byte{1, 2, 3}
    s := "hoge"
    f := func() { fmt.Println(n, a, s) }
    n += 100
    s = "fuga"
    a[0] = 100
    f() //=>110 [100 2 3] fuga
}

defer とは動きが異なる。

panicrecover

まずは普通に。

go

defer の中に書くのが筋。
defer の外に書いても:

go
package main

import "fmt"

func main() {
    fmt.Println("panic?", recover()) //=> panic? <nil>
    panic("hoge")
    fmt.Println("panic?", recover()) // 呼ばれない
}

意味がない。

ともあれ。
panic を自分で呼ぶことはあるかもしれないとおもう。
でも、(ある種の冗談や練習以外の理由で) recover を自分で呼ぶことはあまりなさそうな感じ。

次はオブジェクト指向の第六章 https://qiita.com/Nabetani/items/1c100394a65af6506187

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.