もうすっかり春ですね。
というわけで、最近GoをやっていきということでA Tour of Goをコツコツと解いています。
※ shimabox/study-go でやっています
このA Tour of Go
は演習問題がところどころにあり、自分のようにGoまったくわからんという人間でも、やれば少しずつGoと会話できるようになる素晴らしいコンテンツになっています(自分はまだ終わっていませんが)。
今回はこの演習問題の中でExercise: Stringersを解いたときの自分のつまづきについて書いてみたいと思います。
最初に解いた方法
最初に解いた方法が以下の通りです。
exercise-stringers.go
package main import "fmt" type IPAddr [4]byte func (ip IPAddr) String() string { // IPAddrは[4]byteなので愚直にこれでもいいはず return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) } func main() { hosts := map[string]IPAddr{ "loopback": {127, 0, 0, 1}, "googleDNS": {8, 8, 8, 8}, } for name, ip := range hosts { fmt.Printf("%v: %v\n", name, ip) } }
type IPAddr [4]byte
という型がある- 4つの
byte
が入っていると決まっている - であれば、愚直に
fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
でええやん
という発想で解きました。
実行した結果は以下のとおりです。
$ go run exercise-stringers.go loopback: 127.0.0.1 googleDNS: 8.8.8.8
なんか合ってそう。
Go Playground – The Go Programming Language
ちょっと考えてみる
だがしかし、"%d.%d.%d.%d", ip[0], ip[1], ip[2]
とハードコーティングされているところが気になります。
例えば、仮にtype IPAddr [3]byte
となったとき、あっちこっち修正しなくてはいけなくなるので、うまいことハードコーティングを避ける方法を考えてみたいと思います。
※ IPアドレスを表現したIPAddr [4]byte
なんだからそんなことは考えなくていいと言われればそれまでなのですが、それだと試合終了だし他に似たケースも出てくるかもしれないので話を続けます
次に解いた方法
そこで、次に解いた方法がこちらになります。
type IPAddr [4]byte func (ip IPAddr) String() string { list := []string{} for _, v := range ip { list = append(list, string(v)) } // . で結合して返す return fmt.Sprint(strings.Join(list, ".")) }
- スライス(
[]string{}
)を用意 IPAddr
をforでまわして値をスライスに追加- IPAddrの要素vは
byte
なのでstring()
でキャスト- 注: これがそもそも違います
- スライスに格納されている要素を
"."
で結合して返す
では、実行してみます。
$ go run exercise-stringers.go loopback: ... googleDNS:.
なんということでしょう。わけわからん結果になりました。
Go Playground – The Go Programming Language
string()で整数をキャストすると空文字が返る
ちょっと中身を出力してみます。
for _, v := range ip { fmt.Printf("%v, %v, %T\n", v, string(v), string(v)) list = append(list, string(v)) }
こんなん出ました。
go run exercise-stringers.go 127, , string 0, , string 0, , string 1, , string loopback: ... 8,, string 8,, string 8,, string 8,, string googleDNS:.
見事にstring(v)
が空です。
string()で数値はキャストできない
はい。お察しの通り、string()
で数値はキャストできません。
type string の初期化をやっているようなものだからなのかな。。
(なんか手癖で書いちゃったな)
s := string(1)
実際にこう書いてみると、
conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)stringintconv // Google翻訳 型付けされていないintから文字列への変換は、数字の文字列ではなく、1つのルーンの文字列を生成します(fmt.Sprint(x)のことでしょうか)。
このように注釈がでるはずです。
なので、string()
でやっても引数がbyte
なので空文字(rune型?)になっていたということになります。
rune型とは
ここまで調べるとなると、この記事の範疇を超えてしまいそうなのでググってほしいのですが、かんたんにいうとコードポイントで表現された値のようです。
ちょっとこのへん読んでも理解が難しいのですが、単純に使い方が悪かったと今は思うことにします。
最終的に解いた方法
文字列へのキャストの方法が悪いことがわかったので、以下のようにして対応しました。
type IPAddr [4]byte func (ip IPAddr) String() string { list := []string{} for _, v := range ip { // fmt.Printf("%T", v) => uint8(byte) // vはuint8なのでstringに変換 // int(v)でintにしてからstrconv.Itoa()してappend list = append(list, strconv.Itoa(int(v))) } // . で結合して返す return fmt.Sprint(strings.Join(list, ".")) }
- 整数から文字列へのキャストはstrconv.Itoaを使う
Itoa(i int)
なのでint(v)
する- uint8からintへのキャストなので問題なし
fmt.Sprint(v)
でもいけます- こうすると、
type IPAddr [3]byte
でもいけるし、type IPAddr []byte
でもいけます- 他の箇所の修正は必要ですが、修正箇所は減るはず
- メモリ効率を考えると後者の
[]byte
はアレかもしれませんが、まぁそれはそれとして
実行した結果は以下のとおりです。
$ go run exercise-stringers.go loopback: 127.0.0.1 googleDNS: 8.8.8.8
GJ。
Go Playground – The Go Programming Language
まとめ
- 整数から文字列へのキャストは
strconv.Itoa(i int)
を使う string(i int)
だと空文字(rune型?)が返る- 無知は辛い
- Go難しいけど楽しい
おわりに
今までずーーーっとほぼPHP(Python, jsもたまに)を書いてきて、このままじゃアカンとGoの勉強を始めてみました。
型でいうとPHPもjs,TypeScriptも型はありますが(Pythonは型ヒントかしら)、やっぱりきちんとした?静的言語ということでGoは今までの感覚で書くことが出来ず歯がゆい感じがします。
でも、なんでしょう、プログラミングを覚えたてのような、あのピュアな感じを思い出しながらやれているので楽しいです。
とりあえず、今月中にはA Tour of Goを終わらせようと思っています。
参考
- ドキュメント – Go 言語
- builtin package – builtin – pkg.go.dev
- Go言語の文字列、バイト、ルーンと文字(翻訳) – Qiita
- Goのruneを理解するためのUnicode知識 – Qiita
- 【Go】rune型という見慣れない型 – Lapis Lazuli