オシャレでカッコイイGolangテク 15選

なんか最近異常にブログの下書きが溜まっていて、書きかけの記事が全部で100件を超えました。

その中でGoでこんな書き方できるよ〜みたいなのが結構いっぱいあって、一気にまとめて放出したくなったのでその話。

 

スワップ

多値を使うと一時変数を必要としないスワップが書けて「そうそうこれが書きたかったんだよね〜」って気分になれます。

a := 10
b := 20
a, b = b, a

これほんとカッコイイ。

 

インスタンスを作らないメソッド呼び出し

実はいちいち空インスタンスを作らなくてもメソッドは呼び出せるんですよ。

型名をレシーバにしてメソッドを呼び出すと、第一引数にインスタンスを、第二引数以降はメソッドの引数を渡すとメソッド呼び出しができます。

type Sample struct {}

func (s *Sample) Hello(name string) {
fmt.Println("Hello", name)
}

func main() {
(*Sample).Hello(nil, "World")
}

ちなみに、これを使うことで中身がnilの構造体でも呼び出してもpanicを起こさないので、メソッド内で「レシーバがnilのときはerrorを返す」みたいなことができます(まあそれやり出すと全部のメソッド呼び出しをこの書き方にする必要があるので、そんなことするならメソッドではなく関数にするべきですが)

 

struct cast

型に厳しいGoですが実は構造体もキャストできます。

type SampleA struct {
a, b int
c string
}
type SampleB struct {
a, b int
c string
}
a := SampleA{}
b := SampleB(a)

ただし、メンバの型と数と名前と順番が完全に一致していないと無理なので、使う機会は少ないかもしれないけど...... 

ちなみにスライスも内部的には構造体なので、当然キャスト可能。

type IntSlice []int

a := []int{}
b := IntSlice(a)

こっちは自然ですね。

 

式なしswitch

swichキーワードのあとに式を書かずに{を書くと、続くcaseはelse ifのような挙動をします。

func Less(a int) (result int) {
switch {
case a < 0:
result = -1
case a == 0:
result = 0
case 0 < a:
result = 1
}
return
}

さらっとnamed return valueを使っているけど、こういうのもちょっと覚えておくと便利だったりします。

 

unpack operator

sliceにsliceをappendするときに使うあれです。

func Sum(values ...int) (sum int) {
for _, v := range values {
sum += v
}
return
}

func main() {
slice := []int{1, 2, 3}
fmt.Println(Sum(1, 2, 3))
fmt.Println(Sum(slice...))
// Sum(1, slice...) ただし、これとか
// Sum(slice..., 1) これとかはできない
}

可変長引数にスライスを渡す場合は変数名のあとにピリオドを3つ付けることで良い感じに展開してくれます。

ただし、unpacked operatorを使うとそれ以外の引数を可変長引数に含めることはできない点に注意してください。

 

キャストiota

iotaはGoの言語仕様の中でもかなりテクいことができるので、いくつか紹介します。

キャストしたiotaはそれ以降の行でもキャストされます。

type Type int

const (
A = Type(iota) // A, B, Cの型は全てintではなくTypeになる
B
C
)

次のように書くこともできます。

const (
A Type = iota // これでもA, B, Cの型は全てintではなくTypeになる
B
C
)

 

式にiotaを含める

式中にiotaを書くこともできます。

os.FileModeのiotaみたいなのが書けるとカッコイイよね。

const (
// The single letters are the abbreviations
// used by the String method's formatting.
ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
ModeAppend // a: append-only
ModeExclusive // l: exclusive use
ModeTemporary // T: temporary file; Plan 9 only
ModeSymlink // L: symbolic link
ModeDevice // D: device file
ModeNamedPipe // p: named pipe (FIFO)
ModeSocket // S: Unix domain socket
ModeSetuid // u: setuid
ModeSetgid // g: setgid
ModeCharDevice // c: Unix character device, when ModeDevice is set
ModeSticky // t: sticky
ModeIrregular // ?: non-regular file; nothing else is known about this file

// Mask for the type bits. For regular files, none will be set.
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

ModePerm FileMode = 0777 // Unix permission bits
)

go/types.go at 13f179b9c8c3b9d9066e71d0a69cff8c0876098b · golang/go · GitHub

 

不規則なiota

ここまでくるともはやバッドプラクティスの域ですね。

const (
A = iota // A == 0
B // B == 1
C // C == 2
D = 17 // D == 17
E = iota // E == 4
F // F == 5
)

 

無名引数

このinterfaceを実装したいけど、この引数いらないんだよなぁみたいなときに知っておくとオシャレ。

func Hoge(_ int,s string) {
// 第一引数は使わないことを明示できる
}

ローカル変数が未使用だとそれだけでコンパイルエラーを吐くGoですが、引数はなぜか未使用でもコンパイルエラーを吐かないので、これを知らなくてもinterfaceの引数いらない問題は解決できるんですが、これを書いておけばこの引数は使わないことが明示できるのでDXが高まります。

 

ブール定数数値定数文字列定数以外にもconstが使いたい!

関数で包めばいいよ。

func GetColorList() map[string]uint32 {
return map[string]uint32{
"black": 0x000000,
"red": 0xff0000,
"green": 0x00ff00,
"blue": 0x0000ff,
"white": 0xffffff,
}
}

関数呼び出しのオーバーヘッドが気になる人もいるかもしれないですけど、go1.0以降では次の条件を満たす関数はインライン化できます。

  • 40行以下の関数(厳密には行ではなく式)
  • 関数呼び出しを含まない
  • ループを含まない
  • ラベルを含まない
  • クロージャを含まない
  • panic, recoverを使わない
  • switchを使わない
  • selectを使わない

この関数はこれらの条件を満たすので、関数呼び出しのオーバーヘッドは無視できます(サイズがデカいとアロケーション辛くない?とかプログラム全体でキャッシュ効かなくない?とか課題はあるけど、これで遅くなるようなアプリケーションはどうせもっと他に原因があるよ。推測するな計測せよってRob Pikeも言ってるし)

Goの最適化についてはGoのリポジトリWikiに載っているので、一度読んでおくと良いと思います。

CompilerOptimizations · golang/go Wiki · GitHub

 

無名再帰関数

varで宣言を先に書いてしまえば無名再帰関数が書けます(var使ってるから無名ちゃうやんけ!っていうツッコミはあると思うけど、どうせ無名関数を使いたいのはキャプチャがしたいだけで、本当に無名である必要がある場合ってほぼないと思うので)

var fib func(n int) int
fib = func(n int) int {
if n <= 2 {
return 1
}
return fib(n-1) + fib(n-2)
}

Goはクロージャが使えるのでZコンビネータを実装してあげても本物の無名再帰関数が書けますけど、本当にどうしても無名じゃないといけないとき以外は書くべきではないですよね(Zコンビネータを知らない人が読んだら完全に黒魔術になる)

 

GoStringer interface

fmtパッケージのPrint系関数の出力をいじれるStringer interfaceは有名ですけど、Stringer interfaceでは%#vのフォーマットをさわれません。%#vのフォーマットも触りたいならGoStringer interfaceを実装する必要があります。

type FmtHack string

func (s FmtHack) String() string {
return "Stringerをハック!"
}

func (s FmtHack) GoString() string {
return "GoStringerをハック!"
}

func main() {
fmt.Println(FmtHack(""))
fmt.Printf("%#v\n", FmtHack(""))
}

 

多値戻り値を引数にする

あまり一行でいくつも関数を呼ぶのは可読性落ちる気もするんですが、使わない変数をスコープ内にばらまくのもよくないと思うので、多値を返す関数は引数のように書くと変数を経由することなく一度に値を渡すことができます。

func SplitU32(u32 uint32) (uint16, uint16) {
upper := uint16(u32>>16) & 0xff
lower := uint16(u32) & 0xff
return upper, lower
}

func Sum(a uint16, b uint16) uint16 {
return a + b
}

func main() {
sum := Sum(SplitU32(0x00010001))
fmt.Println(sum)
}

 

ソート済みスライスへのinsert

sortパッケージのSearch系関数はスライスの中にkeyの要素が見つからなかった場合は挿入されるindexを返します。

slice := []int{10, 20, 30, 40, 50}
insert := 15
index := sort.SearchInts(slice, insert)
slice = append(slice[:index], append([]int{insert}, slice[index:]...)...)

ちなみに、appendの部分をこう書き換えると、コピーが1回で済み、memory garbageも発生しません。

slice = append(slice, 0)
copy(slice[index+1:], slice[index:])
slice[index] = insert

※ これらのinsertは簡潔に書けるってだけでappendが重いので要素数がNのとき \( O(NlogN) \)掛かります

※ でもランダムアクセスが多くて、ほぼinsertが発生しない場合は良いかもね

※ 逆にランダムアクセスが少なくて、ほぼinsertしか発生しない場合はcontainer/listとかを使おうね

※ insertもランダムアクセスも頻繁に発生するならTreapとかを使おうね

 

アロケーションをしないFilter

これは結構テクい。

あるsliceから特定の要素だけを抽出したいとき、Filterを書きたくなると思うんですが、副作用を許容すれば、アロケーションを走らせずに高速にFilterができます。

副作用でsrcが壊れるので注意してください。

func Filter(src []int, filterFunc func(int) bool) []int {
dst := src[:0]
for _, x := range src {
if filterFunc(x) {
dst = append(dst, x)
}
}
return dst
}

 

まとめ

なんかQiita記事みたいな仕上がりになってしまった。