今川館

都内勤務の地味OLです

サロゲートペアの入った文字列を処理する場合の注意点

サロゲートペア

「𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。」

ほっけの ひらきを いざかやで ちゅうもんして、いちじかん たつが まだこない。
ぞくな きゃくが てんいんを ひきとめて なじる からだ。

年末の東京らしい風景を彷彿とさせるこの一文にはサロゲートペアの文字がふたつ含まれている。

ひとつは「𩸽」という文字
もうひとつは「𠈻」という文字である。

JavaScriptでのサロゲートペア文字列のメモ - Qiita
この記事に詳しく書かれているが、サロゲートペアは通常の文字列処理が通用しない厄介者である。

Goの文字列の扱い

Goでは文字をruneという型で処理する決まりらしい。
いつもの『プログラミング言語Go』の記述を引用すると、

Unicodeは世界のすべての書記体系のすべての文字、アクセントや他の発音区別記号、タブやキャリッジリターンなどの制御コード、多数の難解な文字を集めており、各文字にUnicodeコードポイント(Unicode code point)あるいはGo用語ではルーン(rune)と呼ばれる規格番号を割り当てています。

つまり、文字をruneとして取り出すと、番号で管理するそうだ。

実際、「ほっけ」と「ぞく」の文字をruneのリテラルで表すと、

package main

import "fmt"

func main() {
	var hokke, zok rune

	hokke, zok = '\U00029E3D', '\U0002023B'
	fmt.Printf("%c, %c", hokke, zok) // 𩸽, 𠈻
}

このように、16進数の番号を書く。

stringをrangeに渡すと[]runeに置き換えてくれる

Goでは文字列をrangeに渡すとちゃんとruneのスライスに置き換えてくれる。
このため、「一文字ずつ」ループを当たり前のようにできる。

exp := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。"

for _, x := range exp {
	fmt.Printf("%c", x)
}
fmt.Println()

これで変数expに書いた通りの文章がコンソールに出力される。

もしこれをbyteのスライスにかえてしまうと都合が悪い。

exp := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。" // 46文字

for _, x := range []byte(exp) {
	fmt.Printf("%c", x)
}

こっちを動かすと、画像のようにめちゃくちゃな出力になってしまう。

f:id:imagawa_yakata:20161216144357p:plain

utf8.DecodeRuneを使うとrange同様、安全に文字を取り出せる。

// import "unicode/utf8"

exp := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。"
var xs []byte

xs = []byte(exp)
for len(xs) > 0 {
	r, size := utf8.DecodeRune(xs)
	fmt.Printf("%c", r)
	xs = xs[size:]
}
fmt.Println()

lenで文字数を数えたいか、バイト数を数えたいか

冒頭の文は全部で46文字あるが、単純に文字列をlenで調べてしまうとバイト数を返す。
これは、Pythonでlen(unicode)は文字数、len(str)はバイト数を返す話と似ている。

正しく文字数を取得したい場合は[]runeにキャストしてlenに渡すか、utf8.RuneCountInStringを使う。

exp := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。" // 46文字

fmt.Println(exp)
fmt.Println(
	len(exp),                    // 138
	utf8.RuneCountInString(exp), // 46
	len([]rune(exp)))            // 46

サンプルコード

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	exp := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。" // 46文字

	fmt.Println(exp)
	fmt.Println(
		len(exp),                    // 138
		utf8.RuneCountInString(exp), // 46
		len([]rune(exp)))            // 46

	for _, x := range exp {
		fmt.Printf("%c", x)
	}
	fmt.Println() // ここで改行を挟まないと何故か1回めのループの「𩸽」の文字が化けて表示される。

	var xs []byte

	xs = []byte(exp)
	for len(xs) > 0 {
		r, size := utf8.DecodeRune(xs)
		fmt.Printf("%c", r)
		xs = xs[size:]
	}
	fmt.Println()

	// DecodeLastRuneだと逆から反復
	xs = []byte(exp)
	for len(xs) > 0 {
		r, size := utf8.DecodeLastRune(xs)
		fmt.Printf("%c", r)
		xs = xs[:len(xs)-size]
	}
}

go installを使え -- QCon London 2016

2016年版Goのベストプラクティス

日本語訳(POSTD)
6年間におけるGoのベストプラクティス | プログラミング | POSTD

原著
Go best practices, six years in

こんな記事を見つけて読んでいた。

内容は15項目にもわたるプラクティスの紹介なのだが、今回、特に興味を持ったのが以下だった。

1. $GOPATH/binを$PATHに追加しましょう。インストールしたバイナリが扱いやすくなります。
2. リポジトリfooが主にバイナリ用なら、ライブラリコードはサブディレクトリlib/に置き、fooパッケージと名付けましょう。
3. リポジトリが主にライブラリ用なら、バイナリはcmd/配下の個別のサブディレクトリに置きましょう。
15. go buildよりもgo installを使いましょう。

GOPATH/binにPATHを通し、go installでビルドする

まず、15番の「$GOPATH/binを$PATHに追加 & go buildよりもgo installを使いましょう」という話に興味を持った。

今まで「go build」を実行するとバイナリが生成されることは知っていたが、「go install」というコマンドの存在を知らなかった。
「go install」を実行すると$GOPATH/binの下にバイナリが配置されるので、PATHを通しておけばいつでも実行できて便利ですよ、という意図らしい。

リポジトリ構成はgo getに対応を心がける

以下、バイナリとライブラリを作る場合でリポジトリ構成を変えた方が良いらしい。

[引用] バイナリを作る場合

github.com/peterbourgon/foo/
    main.go      // package main
    main_test.go // package main
    lib/
        foo.go      // package foo
        foo_test.go // package foo

[引用] ライブラリを作る場合

github.com/peterbourgon/foo
    foo.go      // package foo
    foo_test.go // package foo
    cmd/
        foo/
            main.go      // package main
            main_test.go // package main

この構成案は既に過去の記事で調べていたが、出典はこのカンファレンスだったのかという感想。

go getに対応したリポジトリ構成に、go installを加えることでシンプルに開発環境を構築することができそうだ。

go installを使えば、bin/の下にバイナリが配置されるので、go buildで生成したバイナリを誤ってコミットしてしまうミスも減ると思った。

ラベル付き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. 間接演算子*を使うと、ポインタの本体をコピーする。

いやぁ、難しい・・