今川館

都内勤務の地味OLです

ラベル付きbreak, continue, goto

Goのプログラムを見ていて、何年ぶりか分からないくらい久しぶりにラベル付きbreakを見かけた。

Goプログラミング言語仕様 - golang.jp

ラベル付きステートメント

ラベル付きステートメントは、goto、break、continueステートメントの宛先となります。

この通り、break, continueの他に、gotoにも使えるらしい。(つまりgoto文をGoは許している)

試してみた

ラベルは自分がいるスコープの外に定義されているものしか参照できない模様。
試しに以下のサンプルコードを書いて動かしてみた。

package main

import (
	"fmt"
	"time"
)

func main() {
L:
	for i := 1; true; i++ {

		for j := 1; true; j++ {

			fmt.Printf("i=%v> j=%v\n", i, j)

			if i == 2 && j == 2 {
				continue L
			}

			if i%2 != 0 && j == 3 {
				goto L2
			}
			if j == 3 {
				break L
			}

			time.Sleep(200 * time.Millisecond)
		}

	L2:
	}
}

自分でも挙動が読めない中、試行錯誤した結果、上記プログラムを動かすと以下の出力が得られる。

出力内容

i=1> j=1
i=1> j=2
i=1> j=3 // ここで goto L2 の条件にマッチしてjのループを抜ける
i=2> j=1
i=2> j=2 // ここで continue L の条件にマッチしてjのループを抜ける
i=3> j=1
i=3> j=2
i=3> j=3
i=4> j=1
i=4> j=2
i=4> j=3 // ここで break L の条件にマッチしてiのループを抜ける

当然これはやっておくでしょう

goto出てきたら当然コレはやっとくでしょう。

package main

import (
	"fmt"
	"strings"
	"time"
)

var ora = []string{
	"ハァ テレビもねぇ! ラジオもねぇ!",
	"クルマもそれほど走ってねぇ!",
	"ピアノもねぇ! バーもねぇ! おまわり毎日ぐーるぐる!",
}

const delay = time.Duration(750 * time.Millisecond)

func Sing(message string) {
	xs := strings.Split(message, " ")
	length := len(xs)
	for i, x := range xs {
		fmt.Print(x)
		if i+1 < length {
			fmt.Print(" ")
			time.Sleep(delay)
		}
	}
	time.Sleep(delay)
	fmt.Println()
}

func main() {

	for i := 0; true; i++ {

		Sing(ora[i])

		if i >= 2 {
			Sing("おらこんな村いやだぁ〜 おらこんな村いやだぁ〜")
			Sing("東京へ出るだぁ〜〜〜")
			goto Tokyo
		}
	}

Tokyo:

	for i := 0; true; i++ {
		Sing("東京へ出だな〜ら 銭コア貯めでぇ 東京でベコ飼うだぇ〜〜")
		break Tokyo
	}
}

最終的にここまで調整しました。

runtime.MemStatsでメモリ使用量を調べる&pprofモジュール

ポインタのことを理解するためにメモリの使用量を調べながら色々試そうと思ったが、どうやってやるのか分からないので調べた。

Understanding Go Lang Memory Usage · deferpanic eng blog

ここに詳しく書いてある。

  • 単にメモリの使用量を調べるだけならruntime.MemStatsで判る。
  • pprofモジュールを使うとtopコマンドみたいな詳細な出力を簡単に得られる。

runtime.MemStatsでメモリ使用量を調べる

サンプルコードとして「StringBuffer」というクラスを作って、Appendのメソッドチェーンでどんどん文字列を書き足していくプログラムを書いた。
そして処理前後でMemStatsから得られる情報を見比べて、使用量メモリが増えていることを確かめる。

package main

import (
	"bytes"
	"fmt"
	"runtime"
)

type StringBuffer struct {
	buf  bytes.Buffer
	size int
	err  error
}

func (s *StringBuffer) Append(text string) *StringBuffer {
	if s.err != nil {
		return s // Nothing to do.
	}
	size, err := s.buf.WriteString(text)
	s.size += size
	s.err = err
	return s
}

func (s *StringBuffer) String() string {
	return s.buf.String()
}

func NewBuffer() StringBuffer {
	return StringBuffer{}
}

const LF = "\n"

var mem runtime.MemStats

func PrintMemory() {
	runtime.ReadMemStats(&mem)
	fmt.Println(mem.Alloc, mem.TotalAlloc, mem.HeapAlloc, mem.HeapSys)
}

func main() {
	PrintMemory() // 47504 47504 47504 884736

	buf := NewBuffer()

	for i := 0; i < 25000; i++ {
		buf.Append("Hello").Append(", ").Append("world.")
		buf.Append(LF).Append("I have a ").Append("pen.")
	}
	fmt.Println(buf.size, buf.err) // 675000 <nil>

	PrintMemory() // 2348200 2348200 2348200 3112960
}

出力内容

48336 48336 48336 884736
675000 <nil>
2349032 2349032 2349032 3112960

確かに増えている。

MemStatsで得られる情報は、ドキュメントのMemStatsTypeの項に列挙されているが数が多いので今回使ったものだけ引用。

runtime パッケージ - golang.jp

// 一般統計
// 更新時にロックはされないため、近似値です。
Alloc uint64 // アロケートされ、使用中のバイト数
TotalAlloc uint64 // アロケート済みのバイト数(解放された数も含む)

// 主要なヒープ割り当て統計
HeapAlloc uint64 // ロケートされ、使用中のバイト数
HeapSys uint64 // システムから取得したバイト数

だそうです。

書き込み用のファイルライクオブジェクトはbytes.Buffer

読み取り専用ファイルライクオブジェクトはstrings.NewReaderとbytes.NewReaderの両方が用意されているが、書き込み用はなぜかbytes.Bufferしか無かったのでこれを使った。

文字列を書き込む「WriteString」メソッド、byteを書き込む「WriteByte」、byteのスライスを書き込む「Write」などのメソッドがあるので、今回はWriteStringを使った。

pprofモジュールを使うとより視覚的なビューを利用できる

更に、pprofモジュールを使うとLinuxのtopコマンドみたいな詳細な出力を得られるらしい。

golangで書かれたプログラムのメモリ使用状況を見る - hakobe-blog ♨
Understanding Go Lang Memory Usage · deferpanic eng blog

ふたつの記事を参考に試してみた。

[foo/main.go]

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	wg.Add(1)
	wg.Wait()
}
$ cd foo
$ go build -o cmd
$ cmd  # 待機

ブラウザで所定のURLを問い合わせると詳細な情報が出力される。

f:id:imagawa_yakata:20161213140635p:plain

sync.WaitGroupとは?

これは、ひとつ以上のgoroutineを予約語goを使って動かして、すべてが終了するまで待ちたいときに使うものらしい。

sync - The Go Programming Language

type WaitGroup

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished.

Big Sky :: golang の sync パッケージの使い方

sync.WaitGroup は Wait() を呼び出すと Add() を呼び出した回数から Done() を呼び出した回数を引いて 0 になるまで待機する機能が簡単に実装出来ます。全ての goroutine の終了を待つ場合に使用します。

サンプルコードでは、Add(1)してDoneを一切呼ばないので、ずっと待つことになる。

ポインタが分からない③

やっと分かってきた感

まだポインタのことで悩んでいたのかと言われても仕方ないが、以下の記事を読んでいてやっと分かってきた感じがする。

C/C++は永久に不滅です! - Part4 誰もがつまずくポインタを完璧理解:ITpro

この中の「図3●int *p =&a;を実行したときのメモリーの状態」という図を見て、気づいたことが、

p := 10
q := &p
*q = 7

これがコンパイルエラーにならないんじゃないかということ。
実際ならなくて、3行めの文によってqの大元の変数pに7という別の数を代入したことになる。
間接参照演算子「*」を左辺に持ってきてはいけないと思いこんでいたことに気づく。

サンプルコード

package main

import "fmt"

type Point struct {
	x int
	y int
}

func main() {
	p := Point{0, 0}
	q := &p
	r := *q // rにはqの大元の変数p(の値)を保持しておく。rとpは別のメモリが割り当てられる。

	fmt.Printf("p: %p q: %p  r:%p\n", &p, q, &r) // p: 0xc82000a320 q: 0xc82000a320  r:0xc82000a330

	p.x = 8
	fmt.Println(p, q, r) // {3 10} &{3 10} {0 0}

	// ここで、qの大元の変数に新しいPointのオブジェクトを入れる。
	// -> pに代入しているのと同じで、なおかつ、rには影響しない。
	*q = Point{3, 3} // 左辺に間接参照演算子「*」を持ってこられる点に驚き。

	p.y += 7

	fmt.Println(p, q, r) // {3 10} &{3 10} {0 0}
}

わかったこと

  1. 変数には、(普通の)変数とポインタ変数の2種類ある。
  2. 変数に変数を代入した場合は別々のメモリが割り振られる。 -> r = p としたら、pとrは別物。但し値がコピーされる。
  3. ポインタ変数を作って変数のアドレスを代入すると、同じものを指す。 -> q = &p としたら、pとqは同じもの。
  4. 間接参照演算子「*」を使うと、ポインタ変数の大元の変数にアクセスできる。これは参照も代入も可能。

ポインタが分からない②

ポインタのことがやっぱり分からない。

どうしても納得いかないので、もうちょっと良く考えてみることにした。

以下のサンプルコードを動かして色々試してみると、どうも難しく考えるからいけないんだと思うようになった。

単なるルールとして、「ポインタを使うと同じものを指す」とだけ覚えておけば別に困らない気がしてきた。

package main

import (
	"fmt"
)

type Point struct {
	x int
	y int
}

func main() {
	var p = &Point{10, 20}
	var P = Point{3, 6}

	// これは、qがpそのものであることを直感的に示している。
	q := p

	// こちらも、(PとQは別物であることを前提に)、QがPそのものを指し示すよう指示しているように見える。
	Q := &P

	// これは、rとpが異なるものであることを直感的に示している。
	r := *p

	// ところが、これが、R !== P であることがわからない。
	R := P

	// つまり、この式でR === Pであるという前提を暗に抱いているといえる。
	// よって、「ポインタを代入しない限り、別物」から考え始めるのが良い。

	// sにはqのポインタを代入するので、s === q であることに疑いはない。
	s := &q

	// これは、p自体を操作しているようにしか見えない。
	(*p).x = 12

	S := *p; S.x = -100 // これをやってもpは一切影響を受けない。

	// これもまた、P自体を操作しているようにしか見えない。
	(&P).x = 45

	r.y = 17 // rにはポインタを取らなかったので、ここでpは影響を受けない。

	fmt.Println(p, q, r, *s) // &{12 20} &{12 20} {10 17} &{12 20}
	fmt.Println(P, Q, R)     // {45 6} &{45 6} {3 6}
}

どうもわたしが理解できていなかったことは、

  • &や*を付けると同じものや違うものを指すと誤解していた
  • そうではなくて、代入すると別々に分けられるタイミングが来るようだ
  • しかし、アドレス演算子を使ってポインタを代入すると、左辺と右辺が同一であることを示せる
  • (*p)や(&P)を使ってある変数そのものを操作できることから、やっぱり演算子自体にオブジェクトを分別する役割は無くて、代入に起因するとしか思えない。

ここまで考えるうちにつらくなってきて、要は「ポインタを使うと同じものを指す」とだけ覚えておけば良いんじゃないかと思うようになった。

元々、変数をポインタ型で宣言してしまうと、今度は「それ自身」を取り出す術がなくなるので、「*」という演算子が用意されている、と。

(まだよくわかってない・・)

ポインタがわからない①

わたしがプログラミングを初めて覚えたのはJavaだったけど、以来、JavaScriptPythonなど、ポインタを使わない言語しか経験が無い。
だから、Goの学習で一番苦戦しているのが「ポインタ」である。

まず、何がわからないのかもわからない。

暗中模索というか、完全に手探りで色々試してみるうちに、

  1. 星とか&とかからして何がなにやらという状態。「変数は箱、ポインタはメモリの番地」とか言われてもさっぱり理解不能(苦笑)
  2. ローカル変数の宣言に*を付けるべきか?
  3. 関数のレシーバーないし引数に*を付けるべきか?
  4. 関数の戻り値に*を付けるべきか?
  5. 変数の頭にも*を付けて利用できることを知って、頭がパァ?

やっとここまで進んできたという状態である。
自分の理解のためにも、ここまで理解できたことをメモしておくことにする。

アドレス演算子とポインタの間接参照

まず、&と*の呼び名からして識別困難だった。(記号なのでぐーぐらびりてーが低いし)

Goでこれらを使って自分なりに説明してみたのが以下。

var p Point // pはPoint型
var q *Point // qはPointのポインタ型 (普通の型とポインタ型は区別される)

p = Point{10, 20} // pにPointのオブジェクトを作って代入
q = &p // qに、アドレス演算子&を使ってpのポインタを作って代入
r := *q // 間接演算子*を使って、...代入

&のことを「アドレス演算子」、*のことを「間接演算子」と呼ぶらしい。

Goのドキュメントでは「間接演算子」という言葉は出てこなかったので、MSDNから言葉を拝借した。

間接演算子: *

単項間接演算子 (*) はポインターを逆参照します。つまり、ポインター値を左辺値に変換します

Goプログラミング言語仕様 - golang.jp

*T型(ポインタ)であるxをオペランドとするポインタの間接参照*xは、xによって指されるT型の値を表します。

代入したとき

ではこの後、qのフィールド(q.x, q.y)を書き換えるとp, rには影響するだろうか?

var p Point
var q *Point

p = Point{10, 20}
q = &p
r := *q

q.x, q.y = 7, 8
fmt.Println(p, q, r) // ここで何が出力されるか?

わたしは「{7 8} {7, 8} {7, 8}」が出力されると思ったが、実際は

{7 8} &{7 8} {10 20}

何と!? 間接参照すると本体を複製して代入するようだ。
それと、ポインタと普通の型は出力の表記も区別され、ポインタだと先頭に「&」がついている。

関数を通したとき

次に、関数を定義するときに引数や戻り値にポインタ型を指定するとどうなるのか確かめた。

point := func(p Point) Point { return p }
point2 := func(p *Point) *Point { return p }
point3 := func(p *Point) Point { return *p }

L := Point{1, 2}
M := point(L)
N := point2(&M)
O := point3(N)

M.x, M.y = -5, -6
fmt.Println(L, M, N, O) // ここで何が出力されるか?

これを実行すると何が表示されるだろうか?

まず、関数を定義すると引数はコピーされるという話は知っていたので、LとMは別物でMとNが同じものを指すだろうと思った。

FAQ - golang.jp

関数パラメータは値渡しか?

C言語系の言語と同様にGo言語ではすべて値渡しです。すなわち関数は常に値をパラメータに代入する代入ステートメントがあるかのように、渡されたもののコピーを受け取ります。たとえば関数にint型の値を渡すとint型のコピーが作成され、ポインタの値を渡すとポインタのコピーが作成されますが、ポインタが指し示すデータがコピーされるのではありません。

なので、わたしは「{1 2} {-5 -6} {-5 -6} {-5 -6}」が出力されると思っていたが、実際は

{1 2} {-5 -6} &{-5 -6} {1 2}

やはり間接演算子でポインタの本体の方を引っ張り出すと複製するルールは変わらないようだ。
それと、ポインタ型の変数は先頭に「&」をつけて出力される。

学んだこと

  1. Point型と*Point型は別物と区別され、後者は先頭に「&」をつけて表記される。
  2. 間接演算子*を使うと、ポインタの本体をコピーする。

いやぁ、難しい・・

Goのflagパッケージを使ってサブコマンドを作る

GoでPythonのargparseみたいなサブコマンドを処理したい場合はflag.NewFlagSetというやつを使えば良いらしい。

Goでのやり方はこのページでドン!なんだけど。忘れないようにメモ。

Golang: Implementing subcommands for command line applications · Software adventures and thoughts

FlagSetを別々に作ってParseする

サブコマンドを作る場合はflag.NewFlagSetを呼び出すとFlagSetが返ってくるのでそれぞれ引数、オプションの解析をすれば良い。

今回は

  • help: ヘルプを表示する. -vオプションでより詳しく表示する.
  • money: 入力したふたつの通貨の為替レートを表示する.

上記2つのサブコマンドを持つコマンドを作る。

helpコマンドにはオプションを付けるので、以下のようにする。

help := flag.NewFlagSet("help", flag.ExitOnError)
verbosity := help.Int("verbosity", 0, "")
// または
// var verbosity int
// help.IntVar(&verbosity, "verbosity", 0, "")

money := flag.NewFlagSet("money", flag.ExitOnError)

必須引数はFlagSet.Args()で参照できる

moneyコマンドは通貨をふたつ入力するので、以下のようにする。

if len(money.Args()) < 2 {
	// e.g. $ <cmd> money USD
	fmt.Println("エラー: 通貨をふたつ入力してください")
	return
}

verbosityの数でメッセージの詳細度を上げたい

例えば「-v」よりも「-vv」、「-vvv」の方が詳細なメッセージを表示可能としたい。

最初、安易に以下のようにやってしまったが、

help := flag.NewFlagSet("help", flag.ExitOnError)
v1 := help.Bool("v", false, "")
v2 := help.Bool("vv", false, "")
v3 := help.Bool("vvv", false, "")

これだと、「-vvvv」などが解析エラーになってしまう。

なので、help.Args()をループで回して最大値を取るよう書き直した(以下)。

// 引数からメッセージ詳細度を算出する。算出に使わなかった引数は戻り値のスライスに返却する。
var regex = regexp.MustCompile("^-v+$")

func Verbosity(xs []string) (int, []string) {
	var v float64
	others := make([]string, 0, len(xs))
	for _, x := range xs {
		if regex.MatchString(x) {
			v = math.Max(v, float64(strings.Count(x, "v")))
		} else {
			others = append(others, x)
		}
	}
	return int(v), others
}

help := flag.NewFlagSet("help", flag.ExitOnError)
var verbosity int
help.IntVar(&verbosity, "verbosity", 0, "")

v, others := Verbosity(os.Args[2:])
help.Parse(others)
if verbosity < v {
	// e.g. <cmd> help -verbosity=0 -vvv
	help.Set("verbosity", strconv.Itoa(v))
}

サンプルコード

プログラム全体は以下のようになった。

package main

import (
	"flag"
	"fmt"
	"math"
	"os"
	"regexp"
	"strconv"
	"strings"
)

var regex = regexp.MustCompile("^-v+$")

// 引数からメッセージ詳細度を算出する。算出に使わなかった引数は戻り値のスライスに返却する。
func Verbosity(xs []string) (int, []string) {
	var v float64
	others := make([]string, 0, len(xs))
	for _, x := range xs {
		if regex.MatchString(x) {
			v = math.Max(v, float64(strings.Count(x, "v")))
		} else {
			others = append(others, x)
		}
	}
	return int(v), others
}

func main() {
	help := flag.NewFlagSet(	"help", flag.ExitOnError)
	var verbosity int
	help.IntVar(&verbosity, "verbosity", 0, "")

	money := flag.NewFlagSet("money", flag.ExitOnError)
	if len(os.Args) == 1 {
		fmt.Println("usage: argparse <command> [<args>]")
		fmt.Println("\thelp: ヘルプをプリントします")
		fmt.Println("\tmoney: 為替レートを調べます")
		return
	}

	switch os.Args[1] {
	case "help":
		v, others := Verbosity(os.Args[2:])
		help.Parse(others)
		if verbosity < v {
			// e.g. <cmd> help -verbosity=0 -vvv
			help.Set("verbosity", strconv.Itoa(v))
		}
	case "money":
		money.Parse(os.Args[2:])
	default:
		fmt.Printf("%q is not valid command.\n", os.Args[1])
		os.Exit(2)
	}

	if help.Parsed() {
		message := "Help me."
		if verbosity == 1 {
			message = "Help me!"
		}
		if verbosity == 2 {
			message = "Help me!!"
		}
		if verbosity >= 3 {
			message = "HELP ME!!"
		}
		fmt.Println(message)
	}

	if money.Parsed() {
		if len(money.Args()) < 2 {
			// e.g. $ <cmd> money USD
			fmt.Println("エラー: 通貨をふたつ入力してください")
			return
		}

		from, to := money.Arg(0), money.Arg(1)
		if from == "" || to == "" {
			// e.g. $ <cmd> money USD ''
			fmt.Println("エラー: 通貨をふたつ入力してください")
			return
		}
		fmt.Printf("%v/%v: 100円/$\n", from, to) // 適当
	}
}

動かすとこのような結果が出る。

$ go run foo.go help -verbosity=0 -vvv
# => HELP ME!!

$ go run foo.go help -vv
# => Help me!!

$ go run foo.go money USD JPY
# => USD/JPY: 100円/$

ANSIエスケープコードを使ってコンソールアニメーション

プログラミング言語Go』のスピナー

予約語「go」を使うとメインgoroutineと別のgoroutineで動かせる

プログラミング言語Go』の「ゴルーチンとチャネル」という章を読んでいて、メインルーチンの処理中にスピナーを回すサンプルコードが出てきたので自分でも試していた。

この、スピナーのコードが以下の通り、1文字出力してはキャリッジリターン「\r」でカーソル位置を戻して上書きを繰り返すものなのだが、これに予約語「go」を付けると別ルーチンで動いてくれる。

func spinner(delay time.Duration) {
	for {
		for _, r := range `-\|/` {
			fmt.Printf("\r%c", r)
			time.Sleep(delay)
		}
	}
}

func main() {
	go spinner(100 * time.Millisecond)
	// 中略: 変数accに処理時間を累積していく
	fmt.Printf("%v かかりました\n", acc)
}

ところが、スピナーが出力する文字列を以下のように変えてしまったら問題が起きた。

func spinner(message string, delay time.Duration) {
	for {
		for _, r := range `-\|/` {
			fmt.Printf("\r%c%v", r, message)
			time.Sleep(delay)
		}
	}
}

func main() {
	go spinner("コンピュータ思考中...", 140*time.Millisecond)
	// 中略: 変数accに処理時間を累積していく
	fmt.Printf("\r%v かかりました\n", acc)
}

スピナーで使った文字列が残ってしまう

これだと、処理が終わるまでは「-コンピュータ思考中...」というメッセージの先頭文字が切り替わり続けているが、終了時に

2.5s かかりました中...

このように、何ともマヌケな表示になってしまう。

これを、

2.5s かかりました

このように改めるにはどうした良いのかわからなかった。
キャリッジリターンを出力するとある程度文字を書き換えられるのだから、同じような特殊文字を出力すれば良いだろうとは思っていたのだが。

ANSIエスケープコード

調べてみると、stackoverflowの記事がヒットし、「ANSIエスケープコード」というものを出力すれば良いらしい。

How can I clear the terminal screen in Go? - Stack Overflow

You could do it with ANSI escape codes:

print("\033[H\033[2J")

そういえば.vimrcや.bashrcを書き換えて、プロンプトやlsで表示される文字色を変えたときに「\033[チョメチョメ」という表記を見かけたなぁと思った。

カーソル移動と文字消去

ANSIエスケープコード - コンソール制御 - 碧色工房

このページに非常に詳しく書いてあるのだが、カーソル移動や行や文字を消すコードがちゃんと設けられている。

ESC[nG
カーソルを現在の横位置に関係なく左端からnの場所に移動させる。 (左端を1とする。nには整数が入る、省略すると1)

ESC[nK
行消去、nを省略、もしくは0を指定した場合、カーソルより後ろを消去、 1を指定するとカーソルより前を消去、2を指定すると行全体を消去となる。

最初のESCは表示文字でないので便宜上このように表示しているが、(中略)
どちらも同じものを表現しているが、8進数を利用した\033で書かれる場合が多いようだ。

上記から、行を消し、その後カーソルを行頭に移動するには以下の出力をしてあげれば良い。

fmt.Printf("\033[2K\033[G")

文字に色をつける

更に、出力する文字の色を変えることも可能らしい。
そういえばAnsibleやgrepコマンドの出力結果には適宜色がついていたなぁと思い出す。

そこで、以下の通りプログラムを書き換えて、終了メッセージの先頭に「[ok] 」というプレフィクスを緑色で表示してみることにした。

fmt.Printf("\033[32m[ok]\033[39m %v かかりました\n", acc)

すると、このように、ちゃんと色がついた。

f:id:imagawa_yakata:20161211171023p:plain