fmt.Formatterを実装して%vや%+vをカスタマイズしたり、%3🍺みたいな書式をつくってみよう #golang


## fmtパッケージのインタフェース

`fmt`パッケージにはいくつかインタフェースがあります。
例えば、ここではフォーマットに関わる以下の3つについて説明していきましょう。

* [Stringer](https://golang.org/pkg/fmt/#Stringer)
* [GoStringer](https://golang.org/pkg/fmt/#GoStringer)
* [Formatter](https://golang.org/pkg/fmt/#Formatter)

## Stringerインタフェース

`fmt.Stringer`インタフェースは有名でしょう。
定義は以下のようになっています。

```
type Stringer interface {
        String() string
}
```

`%s`や`%v`のフォーマットで文字列を作成する際に、このインタフェースを実装していると、`String`メソッドの実行結果が用いられます。

[Go Playgroundで見る](https://play.golang.org/p/hS6tAneZSH)

```
package main

import (
	"fmt"
)

type MyString string

func (s MyString) String() string {
	return "hi, MyString"
}

func main() {
	s := MyString("hello")
	fmt.Println(s)
	fmt.Printf("%s\n", s)
	fmt.Printf("%q\n", s)
	fmt.Printf("%v\n", s)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%#v\n", s)
}
```

*実行結果*

```
hi, MyString
hi, MyString
"hi, MyString"
hi, MyString
hi, MyString
"hello"
```


## GoStringerインタフェース

`Stringer`インタフェースの例の実行結果を見ると、`%#v`だけ表示結果がカスタマイズできていません。
`%#v`をカスタマイズするには、`fmt.GoStringer`インタフェースを実装する必要があります。
`fmt.GoStringer`インタフェースは以下のように定義されています。

```
type GoStringer interface {
        GoString() string
}
```

さて、以下のサンプルを動かしてみましょう。
うまく`%#v`の場合もカスタマイズできていることがわかると思います。

[Go Playgroundで見る](https://play.golang.org/p/4PWfxxxS6m)

```
package main

import (
	"fmt"
)

type MyString string

func (s MyString) String() string {
	return "hi, MyString"
}

func (s MyString) GoString() string {
	return "Yeah! hello %#v!"
}

func main() {
	s := MyString("hello")
	fmt.Println(s)
	fmt.Printf("%s\n", s)
	fmt.Printf("%q\n", s)
	fmt.Printf("%v\n", s)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%#v\n", s)
}
```

*実行結果*

```
hi, MyString
hi, MyString
"hi, MyString"
hi, MyString
hi, MyString
Yeah! hello %#v!
```

## Formatterインタフェース

それでは、`%s`の`s`を自作したい場合やもっと複雑な処理がしたい場合はどうするべきでしょうか？
そういう場合は、`fmt.Formatter`インタフェースを実装してやります。
ちょうど良い例として、[pkg/errorsパッケージ](https://github.com/pkg/errors)が参考になるでしょう。
以下のコードを見るだけでも、`v`や`s`の挙動を変えていることが、なんとなくわかるかと思います。

[pkg/errorsパッケージから引用](https://github.com/pkg/errors/blob/master/errors.go#L121-L135)

```
func (f *fundamental) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			io.WriteString(s, f.msg)
			f.stack.Format(s, verb)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, f.msg)
	case 'q':
		fmt.Fprintf(s, "%q", f.msg)
	}
}
```

さて、`fmt.Formatter`インタフェースの定義を改めて見てみましょう。

```
type Formatter interface {
        Format(f State, c rune)
}
```

`Format`というメソッドを実装する必要が分かります。
さて、このメソッドの`f State`と`c rune`という引数はそれぞれ何を表すのでしょうか？
`pkg/errors`パッケージの例を見ると、第2引数の`c rune`は`%s`や`%v`の`s`や`v`に当たる文字だということが分かります。

それでは、`f State`は一体何者でしょうか？
まずは定義を見てましょう。

```
type State interface {
        // Write is the function to call to emit formatted output to be printed.
        Write(b []byte) (n int, err error)
        // Width returns the value of the width option and whether it has been set.
        Width() (wid int, ok bool)
        // Precision returns the value of the precision option and whether it has been set.
        Precision() (prec int, ok bool)

        // Flag reports whether the flag c, a character, has been set.
        Flag(c int) bool
}
```

`State`もインタフェースであることが分かります。
ここで注目してほしいのは、`Write`メソッドを提供していることです。
`State`インタフェースを実装していれば、`io.Writer`インタフェースも実装している事になります。
つまり、`State`インタフェースは`io.Writer`を引数に取る`fmt.Fprintf`などの関数に渡せるということになります。
察しが良い方は、お気づきかと思いますが、`Format`メソッドには戻り値が無いので、`String`メソッドや`GoString`メソッドのように、カスタマイズしたフォーマットの適用結果を戻り値として返すことができません。
代わりに、`State`に対して書き込みを行うことでフォーマットの適用結果を反映させています。

続いて、他の`State`インタフェースのメソッドについても見てましょう。
`Width`メソッドは`%2d`のように幅を指定した際に、その幅を取得するためのメソッドのようです。
幅が指定されていない場合は、第2戻り値が`false`になるようです。

`Precision`メソッドは`%.3f`などのように、精度を指定した場合にその値が取得できるメソッドです。
こちらも同様に指定されていない場合は、第2戻り値が`false`になります。

最後に`Flag`メソッドは、引数に渡したフラグ`+`や`#`が設定されている場合は`true`を返すメソッドです。
`Flag('+')`は、`%v`の場合は`false`で`%+v`の場合は`true`を返します。

それでは、これらを踏まえて以下のサンプルを動かしてみましょう。
`c rune`には、🍺などの絵文字も書けるので、`%🍺`も使えるようになります！
幅や精度もうまく指定できていることが分かります。

[Go Playgroundで見る](https://play.golang.org/p/Dk09dGkj_L)

```
package main

import (
	"fmt"
	"strings"
)

type MyString string

func (s MyString) Format(f fmt.State, c rune) {
	if c == '🍺' && f.Flag('+') {
		var beers string
		if w, ok := f.Width(); ok {
			beers = strings.Repeat("🍺", w)
		} else {
			beers = "no beer!"
		}

		var stars string
		if p, ok := f.Precision(); ok {
			stars = strings.Repeat("🌟", p)
		}

		fmt.Fprintf(f, "%s%s", beers, stars)
		return
	}
	fmt.Fprintf(f, "%c %v %v", c, f.Flag('+'), f.Flag('#'))
}

func main() {
	s := MyString("Hello, playground")
	fmt.Printf("%v\n", s)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%#v\n", s)
	fmt.Printf("%#+s\n", s)
	fmt.Printf("%+3.2🍺\n", s)
}
```

```
v false false
v true false
v false true
s true true
🍺🍺🍺🌟🌟
```

## まとめ

`fmt.Format`を使えば、複雑なフォーマットを自分で作れることを説明しました。
ぜひ、デバッグに役立つフォーマットを作ってみてください。