LoginSignup
1
0

More than 5 years have passed since last update.

プログラミング言語Goを読みながらメモ(第五章)

Last updated at Posted at 2018-03-18

プログラミング言語 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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0