Skip to content

metagoを作った話 #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Aug 23, 2019
Merged

metagoを作った話 #40

merged 41 commits into from
Aug 23, 2019

Conversation

vvakame
Copy link
Owner

@vvakame vvakame commented Aug 23, 2019

リポジトリ

metagoはGo言語向けのメタプログラミングライブラリです。
考え方のベースとしてwireのシグニチャを定義し実装は機械的に生成する考え方と、VのReflection via codegenのホスト言語の構文でメタ構造を書く、というのを使っています。

本ライブラリはある程度動きますが、現時点ではトップレベルの定義を生成したり動的な名前のメソッドを生成したりすることはできません。
テストケースも圧倒的に不足しているため実用レベルに達しているかといわれると疑問があります。

モチベーション

Goは言語自体の持つ型の表現力が弱く、ボイラープレートなコードをたくさん書くことになりがちです。
そこで、何らかのデータを元にGoのコードを自動生成しよう!というアイディアに至るのに時間はかかりません。

一番最初に思いつくであろう、GoのコードをASTで組み立てる戦略は破滅的にめんどくさいです。
これは、生成したいと思っているGoのコードとASTを組み立てるコードにまったくもって相似ではないからです。

次のアイディアとして、Goのコードをテキストとして組み立てるものがあります。
jwgでは Printf を使ってソースコードを生成しています。
gqlgenでは text/template を使ってソースコードを生成しています。
これは、ASTを使って組み立てるのに比べるとだいぶ仕上がりが想像しやすいです。
一方でIDEからの支援が得にくく、出力後のコードがvalidなコードかというのは出力してみるまでわかりません。

新しいアプローチとして、本ライブラリ metago を考え、実装しました。
metagoでは生成するべきGoのコードをGoのコードで書きます。
鋳型になるGoコードのASTをこねこねして、ほしいGoコードに変換するイメージです。
これならば、IDEの支援を今までに比べると圧倒的に楽に実装することができます。

metago について

metagoでは、実際のソースコード中にマーカーを仕込んでいき最終的なソースコードを生成します。

あるオブジェクトのフィールド名と値を出力するテンプレートは次のようになります。

//+build metago

package main

import (
	"fmt"

	"github.com/vvakame/metago"
)

type Foo struct {
	ID   int64
	Name string
}

func main() {
	obj := &Foo{1, "vvakame"}
	mv := metago.ValueOf(obj)
	for _, mf := range mv.Fields() {
		fmt.Println(mf.Name(), mf.Value())
	}
}

これをmetagoで処理すると次のコードが得られます。
実際にプログラムとして動作させるのはこちらの生成されたコードです。

// Code generated by metago. DO NOT EDIT.

//+build !metago

package main

import (
	"fmt"
)

type Foo struct {
	ID   int64
	Name string
}

func main() {
	obj := &Foo{1, "vvakame"}

	{
		fmt.Println("ID", obj.ID)
	}
	{
		fmt.Println("Name", obj.Name)
	}
}

reflectパッケージに少し似ています。
どういったことができるかというのはtestbedディレクトリを見てみてください。

主なfeatureとして…

  • mv := metago.ValueOf(obj) によって metago.Value な値を取得
  • for _, mf := range mv.Fields() によって各フィールドに対する処理を展開・記述
  • mf.Name() によってフィールド名の取得
  • mf.Value() によってフィールドの値の取得
  • mf.StructTagGet("json") などでstructのタグの取得
  • mf.Value().(time.Time) といった型アサートとif文の組み合わせによるフィールドの型毎の処理の振り分け
    • type switchもサポート
  • インラインテンプレート(第一引数が mv metago.Value の関数)の利用

などに対応しています。

metago のインストールと実行

$ go get -u github.com/vvakame/metago/cmd/metago
$ metago -v .

vvakame added 30 commits July 30, 2019 18:33
@vvakame vvakame merged commit 94b232b into master Aug 23, 2019
@vvakame vvakame deleted the feat-metago-r2 branch August 23, 2019 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant