Goにおける列挙子と網羅性: コードジェネレータ作りました
はじめに
↑こちらの短い発表に大変刺激を受け、紹介されているパターンの実装を支援するコード生成ツールを作成しました…!
Goの列挙子
Goでは言語機能としてenumというものがなく、const
とiota
を使った定数が代わりに使用されることが多いです。
慣れてしまえばこのスタイルで問題なくコードをかけるのですが、業務でコードを書いていると↓のように思うことは多いはず。
- 網羅性チェックしたい
const ( patternA = iota patternB patternC ) switch c { case patternA: // ... case patternB: // ... // case patternC: // チェック漏れを検知することが難しい default: panic("unreachable code") // できればこれは書きたくない }
- 列挙子による条件分岐+その値に応じた型アサーションをもっとイケてる方法で書きたい(TypeScriptでいうdiscriminated unionのような)
// var v any switch c { case patternA: v.(typeA).DoWithA() // patternAとtypeAの結びつきが型レベルでは表現されていない case patternB: v.(typeB).DoWithB() }
チーム開発をやっていると、より安全な方法でコードを書きたくなるものですね。
はじめに紹介した発表では、これらを解決するための列挙型実装のパターンが紹介されています。
その他の困りどころや具体的な解決方法については動画を見ていただくのが一番なので、続いてツールの紹介をしていきます。
github.com/daichitakahashi/go-enum
このパッケージを利用して、列挙型を作ってみましょう。
列挙型とメンバーの定義
列挙型のインターフェースとそのメンバーとなる型は、自分で定義する必要があります。
次のようなコードを準備しましょう。
Fruits
型が列挙型のインターフェース、Apple
, Orange
, Grape
がそのメンバーとなる型です。
メンバーはenum.MemberOf[Fruits]
をベースにしたDefined typeです。enum.MemberOf
はこのgo-enumパッケージが公開する唯一の型となっています。
コード生成
上記のコードにはgo:generate
ディレクティブが含まれていますので、あとはgo generate
を実行するだけです。
すると、以下のようなコードが生成されます。
めでたく、列挙型が完成しました!
enum.MemberOf
この型は何の実装も持たない空の構造体となっており、コード生成の対象となる列挙型とそのメンバーをマークするためにあります。enumgen
コマンドはワーキングディレクトリのGoコードからマークされた型定義を収集し、コード生成の対象とします。
設定ファイルやコマンドライン引数ではなく型定義を使う方法が自分でも気に入っています[1]。
以下のように、メンバーとなる構造体に埋め込むことでもマーク可能です。
Visitor
型とAccept
メソッドの名前をカスタマイズする
enumgen
コマンドの--visitor
オプションと--accept
オプションを仕様することで、型名とメソッド名をカスタマイズすることが可能です。
まとめ
動画を見てください!それが一番大事です。
ちょっとしたツールですが github.com/daichitakahashi/go-enum もぜひ使ってみてください。
コード生成ツールは作るのも使うのも楽しいです。
-
型がimportされることでgo.modでのバージョン管理対象になるため、コード生成につかうコマンドもばっちり管理対象となるのも良いところ。ということは、例の
go:generate
ディレクティブで書いているバージョン指定(@latest
)は不要ですね…! ↩︎
Discussion