Goコード生成

Go generateを使用した自動コード生成に関するGoの公式ブログのRob Pikeの記事の翻訳。 この記事は少し古くなっています(Go 1.4がリリースされる前に書かれていましたが、Go 1.4ではGo Generateが登場しました)。



計算可能性理論の特性の1つであるチューリング完全性は、プログラムが別のプログラムを作成できることです。 これは十分に一般的ではありますが、価値があるほど高く評価されていない強力なアイデアです。 これは、たとえばコンパイラが何をするかを決定する重要な部分です。 また、 go testコマンドも同じ原理で動作します。テストが必要なパッケージをスキャンし、テストに必要なキットが追加された新しいGoプログラムを作成してから、コンパイルして実行します。 現代のコンピュータは非常に高速であるため、このような一見高価な一連のアクションが一瞬で完了します。



プログラムがプログラムを作成する際には、他にも多くの例があります。 たとえば、 Yaccは、文法の説明を読み取り、この文法を解析するプログラムを生成します。 「コンパイラ」プロトコルバッファーは、インターフェイスの説明を読み取り、構造、メソッド、およびその他のコードの定義を提供します。 さまざまな構成ユーティリティも同様に機能し、環境からメタデータを抽出し、カスタム起動コマンドを作成します。



したがって、プログラムを記述するプログラムはソフトウェア開発の重要な要素ですが、ソースコードを作成するYaccのようなプログラムは、その出力をコンパイラに渡すことができるようにビルドプロセスに統合する必要があります。 Makeなどの外部ビルドシステムを使用する場合、これは通常簡単です。 しかし、Goユーティリティでは、ビルドに関するすべての必要な情報がソースコードから取得されるため、これは問題です。 goツールでYaccを起動するメカニズムがないだけです。



ある意味では、ここまでです。



Goの最新リリース1.4には、同様のユーティリティを実行できる新しいコマンドgo generateが含まれています。 go generateと呼ばれ、起動時にコードをスキャンして、実行するコマンドを示す特別なコメントを探します。 go generateは go buildの一部でないことを理解することが重要です。 依存関係を分析しないため、ビルドの前に実行する必要があります。 Goパッケージの作成者向けであり、ユーザー向けではありません。



go generateコマンドは非常に使いやすいです。 ウォームアップするために、これを使用してYacc文法を生成する方法を次に示します。 新しい言語の文法を定義するgopher.yと呼ばれる入力yaccファイルがあるとします。 この文法を解析するGoコードを生成するには、通常、次のような標準のGoバージョンのyaccを実行します。

go tool yacc -o gopher.go -p parser gopher.y
      
      





ここの-oオプションは、結果のファイルの名前を示し、-pはパッケージ名を示します。



このプロセスを転送して生成を行うには、このディレクトリ内の通常の(自動生成ではない).goファイルにこのコメントを追加する必要があります。

//go:generate go tool yacc -o gopher.go -p parser gopher.y







このテキストは同じコマンドですが、先頭にコメントが追加され、生成されて認識されます。 コメントは行の先頭から開始し、//とgo:generateの間にスペースを入れないでください。 このトークンの後、残りはどのgo generateコマンドを実行する必要があるかを示します。



今すぐ実行します。 ソースディレクトリに移動し、go generateを実行してから、ビルドなどを実行します。

 $ cd $GOPATH/myrepo/gopher $ go generate $ go build $ go test
      
      





そして、それだけです。 エラーがない場合、go generateはyaccを呼び出し、gopher.goを作成します。この時点で、ディレクトリには必要なgoファイルがすべて含まれ、それらを収集、テスト、および通常どおり操作できます。 gopher.yが変更されるたびに、生成を再開してパーサーを再作成します。



go generateが内部的に機能する方法(パラメーター、環境変数などを含む)の詳細に興味がある場合は、 設計記述文書を参照してください。



Go generateは、Makeまたは別のビルドメカニズムを使用して実行できなかった処理は行いませんが、goコマンドですぐに使用できます(追加のものをインストールする必要はありません)。Goエコシステムに適合します。 最も重要なことは、呼び出されるプログラムがユーザーのマシンで利用できない可能性があるという理由だけの場合、これはユーザーではなくパッケージの作成者向けであることを忘れないでください。 また、パッケージをgo getで使用することになっている場合は、生成されたファイルをバージョン管理システムに追加して、ユーザーが使用できるようにすることを忘れないでください。



さて、これを新しいものに使用する方法を見てみましょう。 go generateが根本的に異なる例として役立つものとして、golang.org / x / toolsリポジトリに新しいストリンガープログラムがあります。 数値定数セットのString()文字列メソッドを自動的に生成します。 標準のGoキットの一部ではありませんが、簡単にインストールできます。

 $ go get golang.org/x/tools/cmd/stringer
      
      





stringerのドキュメントの例を次に示します。 さまざまな種類の薬物を定義する一連の数値定数を使用したコードがあるとします。

 package painkiller type Pill int const ( Placebo Pill = iota Aspirin Ibuprofen Paracetamol Acetaminophen = Paracetamol )
      
      





デバッグのために、これらの定数がその名前を適切にレンダリングできるようにしたい、言い換えると、次のシグネチャを持つメソッドが必要です:

 func (p Pill) String() string
      
      





たとえば、次のように、手で書くのは簡単です。

 func (p Pill) String() string { switch p { case Placebo: return "Placebo" case Aspirin: return "Aspirin" case Ibuprofen: return "Ibuprofen" case Paracetamol: // == Acetaminophen return "Paracetamol" } return fmt.Sprintf("Pill(%d)", p) }
      
      





もちろん、この関数を書く方法はいくつかあります。 ピル、マップ、またはその他の手法でインデックス付けされたラインスライスを使用できます。 何らかの方法で、薬のセットを変更するたびにそれをサポートする必要があり、コードが正しいことを確認する必要があります。 (たとえば、パラセタモールの2つの異なる名前は、このコードをそれよりも少し洗練されたものにします。) さらに、実装方法を選択するまさにその問題は、値のタイプに依存します:符号付きまたは符号なし、密で散在する、ゼロから開始するかどうかなどです。



ストリンガープログラムがこれを処理します。 手動で起動できますが、go generateで起動することを目的としています。 それを使用するには、ソースにコメントを追加します。ほとんどの場合、型定義を含むコードにコメントを追加します。

//go:generate stringer -type=Pill





このルールは、go generateがストリンガーコマンドを実行して、Pill型のStringメソッドを生成する必要があることを示します。 出力は、pill_string.goファイルに自動的に書き込まれます(出力は-outputフラグでオーバーライドできます)。



実行しましょう:

 $ go generate $ cat pill_string.go // generated by stringer -type Pill pill.go; DO NOT EDIT package pill import "fmt" const _Pill_name = "PlaceboAspirinIbuprofenParacetamol" var _Pill_index = [...]uint8{0, 7, 14, 23, 34} func (i Pill) String() string { if i < 0 || i+1 >= Pill(len(_Pill_index)) { return fmt.Sprintf("Pill(%d)", i) } return _Pill_name[_Pill_index[i]:_Pill_index[i+1]] } $
      
      





ピルまたは定数の定義を変更するたびに、実行しなければならないことはすべて

 $ go generate
      
      





Stringメソッドを更新します。 そしてもちろん、更新が必要な1つのパッケージに複数のタイプがある場合、go generateはそれらすべてを更新します。



生成されたコードがいことは言うまでもありません。 ただし、人々はこのコードを使用しないため、これで問題ありません。 自動生成されたコードは非常に多くの場合いです。 彼は可能な限り効果的になろうとします。 すべての名前は1行にまとめられ、メモリを節約します(無数の名前があっても、すべての名前に対して1行だけです)。 次に、配列_Pill_indexは、単純で非常に効率的な手法を使用して、型と名前を一致させます。 _Pill_indexは、uint8型の値の配列(スライスではなく、1つ少ないヘッダー)であり、必要な値を含むことができる最小の整数型であることに注意してください。 より多くの値または負の値がある場合、生成される_Pill_index配列のタイプは、uint16またはint8のいずれか適切な方に変更できます。



ストリンガーを使用して生成されるメソッドで使用されるアプローチは、定数セットのプロパティに応じて異なります。 たとえば、定数が放電される場合、彼はマップを使用できます。 以下は、2のべき乗を表す一連の定数に基づく簡単な例です。

 const _Power_name = "p0p1p2p3p4p5..." var _Power_map = map[Power]string{ 1: _Power_name[0:2], 2: _Power_name[2:4], 4: _Power_name[4:6], 8: _Power_name[6:8], 16: _Power_name[8:10], 32: _Power_name[10:12], ..., } func (i Power) String() string { if str, ok := _Power_map[i]; ok { return str } return fmt.Sprintf("Power(%d)", i) }
      
      





要約すると、メソッドの自動生成により、人が行うよりも問題をよりよく解決できます。



Goソースコードには、他にも多くのgoソースの例があります。 これには、UnicodeパッケージでのUnicodeテーブルの生成、encoding / gobでの配列のエンコードおよびデコードのための効率的なメソッドの作成、timeパッケージでのタイムゾーンデータセットの作成などが含まれます。



go generateを使用してください。 彼は実験を奨励するためにここにいます。



そうでなくても、ストリンガーを使用して、Stringメソッドを数値定数に追加します。 コンピューターに作業を任せてください。



All Articles