なぜ goa の DSL はブランク識別子への代入が必要なのか

goa のトップレベDSL

goa の design は DSL をネストさせながら記述しますが、その一番上の階層に来るものをトップレベDSL と呼びます。標準で用意されている apidsl では以下の DSL がそれに当たります。

  • API()
  • Resource()
  • Type()
  • MediaType()

前提として、すべての DSL は Go の関数であり、それを用いて記述される design は Go のソースコードです。

トップレベDSL の記述

goa ではトップレベDSL を以下のように記述します。

var _ = API("cellar", func() {
    // Definitions.
})

なぜ以下のように書けないのでしょうか?

API("cellar", func() {
    // Definitions.
})

Go の言語仕様

まず Go の言語仕様を確認します。

Go の言語仕様ソースファイル構成 という仕様が明記されています。それによるとソースファイルは以下の 3 つのブロックから構成されます。

この「トップレベル宣言」に含まれるのは以下の宣言です。

  • 定数 const
  • type
  • 変数 var
  • 関数・メソッド func

Go のコードとして見た API() DSL

翻って API() DSL を Go のコードとして見てみましょう。

var _ = API("cellar", func() {
    // Definitions.
})

上記の DSL は大きく以下の 3 つに分割することができます。

  1. API() という関数の呼び出し (Calls)
  2. var による変数宣言 (Variable declarations)
  3. = による代入 (Assignments)

総合するとこのコードは「代入を伴った変数宣言」であり、前述した Go のトップレベル宣言として有効です。

小ネタ

本来 var による変数宣言では型の指定が必要ですが、ここでは代入を伴っているため型推論が働いています。もちろん以下のように明示的に型の指定を行うこともできます。

var _ *design.APIDefinition = API("cellar", func() {
    // Definitions.
})

ブランク識別子への代入がないとどうなるか

もう以下の書き方がなぜ駄目なのかわかりますね。

API("cellar", func() {
    // Definitions.
})

API() は関数呼び出しであり Go のトップレベル宣言として記述することはできないのです。

結論

Go の言語仕様的にトップレベルに書けるのは以下のいずれかの宣言であるため、代入を伴った変数宣言とする必要があるから。

  • 定数 const
  • type
  • 変数 var
  • 関数・メソッド func