haya14busa

haya14busa’s memo

Go に暗黙の型変換機能を明示的に導入する

Go に暗黙の型変換はない

Go には Tour of Go でも習うように,暗黙の型変換といったものは存在せず,明示的に型変換をする必要があります.

Unlike in C, in Go assignment between items of different type requires an explicit conversion. – Type conversions https://tour.golang.org/basics/13

このデザインについては FAQ にも書いてあります. FAQ: Why does Go not provide implicit numeric conversions? https://golang.org/doc/faq#conversions

(厳密には interface への変換だけは勝手にやってくれるのでその意味では暗黙の型変換はあるといえる気もします)

このデザインは僕も好きです.The Zen of Python も言うように何事も “Explicit is better than Implicit” だと感じます. Go ではほぼ全ての機能が Explicit に表現され,Go の Readablity に繋がっています. はぁ…Go かわいいよ Go…

しかし,そうは言ってもint->int64などの安全な変換ふくめてひたすら手で型変換しなきゃいけないのはつらいこともあります. 競技プログラミングといった書捨てスクリプトで最初は int でやってたけど,int64 に変換しなきゃ…というときや, func max(x int64, ys ...int64) int64 っていうテンプレ関数を用意していても,型が int のままでは使えず毎回 int64() で囲ったり int() で戻したりする必要があります.

これは人間のやる仕事じゃない…ということでこの問題を解消するツール, go-typeconv を作りました.

go-typeconv 作った

haya14busa/go-typeconv: Bring implicit type conversion into Go in a explicit way

gotypeconv は Go のソースコードを受け取って,型まわりのエラーを見つけ,AST を自動で書き換えて修正します. gofmt と同様に,stdout に修正後のコードをプリントしたり,ファイルを直接書き換えたり,diffを表示することが出来ます.

最終的には明示的な型変換を使ったソースコードになるけど,その変換をある意味暗黙的にやってくれる.というのがコンセプトです.

インストール

1
$ go get -u github.com/haya14busa/go-typeconv/cmd/gotypeconv

使い方

./testdata/tour.input.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// https://tour.golang.org/basics/13
package main

import (
  "fmt"
  "math"
)

func main() {
  var x, y int = 3, 4
  var f float64 = math.Sqrt(x*x + y*y)
  var z uint = f
  fmt.Println(x, y, z)
}

上記のコードは以下で示すように型変換周りでエラーがでます.

1
2
3
$ go build testdata/tour.input.go
testdata/tour.input.go:11: cannot use x * x + y * y (type int) as type float64 in argument to math.Sqrt
testdata/tour.input.go:12: cannot use f (type float64) as type uint in assignment

gotypeconv を実行するとこの通り!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gotypeconv ./testdata/tour.input.go
// https://tour.golang.org/basics/13
package main

import (
        "fmt"
        "math"
)

func main() {
        var x, y int = 3, 4
        var f float64 = math.Sqrt(float64(x*x + y*y))
        var z uint = uint(f)
        fmt.Println(x, y, z)
}

func max(x int64, ys ...int64) int64 といったテンプレートあるけど int では使えない…というときもこの通り.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
  var (
      x int     = 1
      y int64   = 14
      z float64 = -1.4
  )

  var ans int = max(x, x+y, z)
  fmt.Println(ans)
}

func max(x int64, ys ...int64) int64 {
  for _, y := range ys {
      if y > x {
          x = y
      }
  }
  return x
}
1
2
3
4
5
6
7
8
9
10
11
12
$ gotypeconv -d testdata/max.input.go
diff testdata/max.input.go gotypeconv/testdata/max.input.go
--- /tmp/gotypeconv308367427    2017-02-06 08:53:30.460299789 +0900
+++ /tmp/gotypeconv267288262    2017-02-06 08:53:30.460299789 +0900
@@ -9,7 +9,7 @@
                z float64 = -1.4
        )

-       var ans int = max(x, x+y, z)
+       var ans int = int(max(int64(x), int64(x)+y, int64(z)))
        fmt.Println(ans)
 }

vim-gofmt

haya14busa/vim-gofmt: Formats Go source code asynchronously with multiple Go formatters.

Vim で実行したい場合は (Experimentalですが) vim-gofmt というGoの formatter を複数,非同期で実行するプラグインを作成したのでこれを使ってみてください.

設定例:

1
2
3
4
5
let g:gofmt_formatters = [
\   { 'cmd': 'gofmt', 'args': ['-s', '-w'] },
\   { 'cmd': 'goimports', 'args': ['-w'] },
\   { 'cmd': 'gotypeconv', 'args': ['-w'] },
\ ]

実行: (filetype が Go のカレントバッファで) :Fmt

正直ワンバイナリでやってほしいけど,gofmt -sgoimports が同時に実行できないので, いずれ複数フォーマッタ対応のVim プラグイン 欲しいなと思ってたので作りました. どちらも,特に gofmt はかなり高速なので複数実行してもあまり気になりません.

なお非同期にgofmt実行するといっても,バッファが書き換えられていたら上書きしたりするような挙動ではないので安心してください. (不具合あったら直します)

なお

1
:command! GoTypeConv call gofmt#fmt([{ 'cmd': 'gotypeconv', 'args': ['-w'] }])

とか書いておくと:GoTypeConvでgotypeconv だけ実行出来たりしますが,API は変わるかも知れないのでご注意ください.

また以下のようにすると保存時に自動で実行できます.が,APIは(ry

1
2
3
4
augroup vim-gofmt
  autocmd!
  autocmd BufWritePre *.go :Fmt
augroup END

機械で出来ることは機械にやらせたい

途中で IDE ちゃんになっているようですが,Go の場合 IDE ちゃんじゃなくても大丈夫!

The grammar is compact and regular, allowing for easy analysis by automatic tools such as integrated development environments. – The Go Programming Language Specification - The Go Programming Language

Go は spec にまで書かれているように,Goのためのツールを書くのはとても簡単です.

修正系ツールだけでも

  • gofmt -s はフォーマットだけでなくシンプルにかけるところを自動で修正してくれ
  • goimports は自動で import 文を追加したり削除したりしてくれ
  • gotypeconv は型変換エラーを自動で修正してくれます

また proposal: gofmt: Add option to ignore parse error if no bad node in AST · Issue #18939 · golang/go のプロポーザルにあるように,実は末尾コンマがないといったエラーも自動で修正できます. (gofmtに入るかはわからないけど個人的には入って欲しいなー)

なお https://github.com/rhysd/gofmtrlx を入れると末尾カンマは修正してくれますし,実は go-typeconv も勝手に修正してくれます.

gosimple という simple にかけるところを教えてくれる linter もあります. (自動修正機能は今の所ないけどオプションあっても良い気がするなー)

linter がたくさんあることは Go の CI で lint と カバレッジ回して非人間的なレビューは自動化しよう in 2016年 - haya14busa で紹介しました.

go/* を使えばパッケージをソースコードをパースして AST を取得したり, それを Format された形式で print したり,型情報を取得したり… などと言ったことが簡単にできます.

golang.org/x/tools/go/loader を使えば,簡単に型情報を使える形でプログラムをロードできますし, golang.org/x/tools/go/ssa なんてものもあります.

go/* パッケージ だけでなく,golang.org/x/tools/go 下のパッケージをみても面白いです.

AST ベースのツールは他言語でもたくさんあると思うので比較的わかりやすいですが, 型情報まで使えるパッケージを提供している言語はそんなになくて難しいですが,godoc の他にも公式チュートリアルが参考になります.https://golang.org/s/types-tutorial

日本語情報でも motemen さんの GoのためのGotenntenn さんの各種記事 goパッケージで簡単に静的解析して世界を広げよう #golang - Qiita が詳しいです.

ぜひ,みなさんもGoのためのGo ツール作ってみると面白いかと思います. いろんなことができるので,なかなか楽しいです.

はぁ…Go かわいいよ Go…(ため息)

Comments