Go's Declaration Syntax(Goの宣言構文について)

はじめに

Goを新たに学びに来た人にはGoの宣言構文は、Cの伝統的な構文となんだってこんなに違うんだと思っているでしょう。

Goの宣言がなぜ今の形になってしまったかについて二通りのアプローチから比較し、説明させていただきます。

Newcomers to Go wonder why the declaration syntax is different from the tradition established in the C family. In this post we'll compare the two approaches and explain why Go's declarations look as they do.

Cの構文について

まず最初に、Cの構文について話させてください。

First, let's talk about C syntax.

Cは普通ではありませんが賢い宣言構文のアプローチをとっています。

C took an unusual and clever approach to declaration syntax.

特殊な構文で型を宣言する代わりに、以下の様な１文で型の宣言と式の定義を同時に行っています。

Instead of describing the types with special syntax, one writes an expression involving the item being declared, and states what type that expression will have. Thus

int x;

xをint側で宣言する：'x'という式がintの型を持っている。

一般的に基本型で新規変数を宣言する場合は

基本型が左側に記述、その式が右側に記述されます

declares x to be an int: the expression 'x' will have type int. In general, to figure out how to write the type of a new variable, write an expression involving that variable that evaluates to a basic type, then put the basic type on the left and the expression on the right.

以下の場合、

Thus, the declarations

int *p; int a[3];

'*p'はint型なのでpはintのポインタ型です。

a[3]はint型なのでaはintの配列型です。

(3の部分は配列のサイズを示しているということは無視しています。)

state that p is a pointer to int because '*p' has type int, and that a is an array of ints because a[3] (ignoring the particular index value, which is punned to be the size of the array) has type int.

関数についてはどうでしょう？もともとCの関数宣言は

引数の型を括弧の外に記述していました。

What about functions? Originally, C's function declarations wrote the types of the arguments outside the parens, like this:

int main(argc, argv) int argc; char *argv[]; { /* ... */ }

main(args,argv)はintを返す式なのでmainは関数です

今風の書き方なら

Again, we see that main is a function because the expression main(argc, argv) returns an int. In modern notation we'd write

int main(int argc, char *argv[]) { /* ... */ }

基本的な構造は同じですね。

but the basic structure is the same.

これはとても賢いアイデアの構文ですが、早々に混乱を招きます。

代表的な例として、関数ポインタの宣言があります。

見てみましょう

This is a clever syntactic idea that works well for simple types but can get confusing fast. The famous example is declaring a function pointer. Follow the rules and you get this:

int (*fp)(int a, int b);

もし(*fp)(a, b)という式がint型を返却する関数呼び出しとして記述したのならfpは関数ポインタです。

ここで、もしfpの引数にfp自身の関数ポインタを取りたい場合は？

Here, fp is a pointer to a function because if you write the expression (*fp)(a, b) you'll call a function that returns int. What if one of fp's arguments is itself a function?

int (*fp)(int (*ff)(int x, int y), int b)

可読性が下がってきました

That's starting to get hard to read.

もちろん、関数を記述する際に変数名を除外することもできます。

main関数で試すと

Of course, we can leave out the name of the parameters when we declare a function, so main can be declared

int main(int, char *[])

argvは元々以下の宣言でした

Recall that argv is declared like this,

char *argv[]

真ん中から名前が抜け落ちていますがそれ以外の構成は変わっていません。

so you drop the name from the middle of its declaration to construct its type.

何らかの型を宣言するのに、名前が真ん中に入るというのはわかりづらいのです。

It's not obvious, though, that you declare something of type char *[] by putting its name in the middle.

名前なし引数を用いた場合のfpの宣言を見てみましょう

And look what happens to fp's declaration if you don't name the parameters:

int (*fp)(int (*)(int, int), int)

どこに名前が入っていたかわかりづらいです。

Not only is it not obvious where to put the name inside

int (*)(int, int)

どこに関数ポインタの宣言があり、その関数ポインタが何型を返却するかわかりますか？

it's not exactly clear that it's a function pointer declaration at all. And what if the return type is a function pointer?

int (*(*fp)(int (*)(int, int), int))(int, int)

もうわけがわからないよ

It's hard even to see that this declaration is about fp.

Cの宣言構文の難しさについてもっと複雑な例も説明できます。

なぜなら、Cの宣言構文の一例を示したにすぎないのですから。

You can construct more elaborate examples but these should illustrate some of the difficulties that C's declaration syntax can introduce.

もう一つ示してみましょう。

There's one more point that needs to be made, though.

型、宣言構文が同じであるため、式、型宣言の区別が難しいのです。

Because type and declaration syntax are the same, it can be difficult to parse expressions with types in the middle.

Cの型キャストの実例としてあげると以下のように常に型をカッコで囲む必要があります。

This is why, for instance, C casts always parenthesize the type, as in

(int)M_PI

Goの構文について

C系以外の言語は独特な型の宣言構文を使っています。

Languages outside the C family usually use a distinct type syntax in declarations.

コロンで区切り先頭に名前を入れるようなやつです。

例として示すとこんなかんじです。(特定の言語を示しているわけではないです。)

Although it's a separate point, the name usually comes first, often followed by a colon. Thus our examples above become something like (in a fictional but illustrative language)

x: int p: pointer to int a: array[3] of int

これらの宣言は明確です。もし読んでみて冗長と思うようなら、

Goに興味を持つきっかけになるかもしれません。

Goはコロンやいくつかのキーワードを削除した表現になります。

These declarations are clear, if verbose - you just read them left to right. Go takes its cue from here, but in the interests of brevity it drops the colon and removes some of the keywords:

x int p *int a [3]int

[3]intという記述は、式と一致していません。

(次のポインタの章で言及します)

There is no direct correspondence between the look of [3]int and how to use a in an expression. (We'll come back to pointers in the next section.)

構文を分けるコストを犠牲にして明瞭さを獲得しています。

You gain clarity at the cost of a separate syntax.

次に関数について考えます。Goのmain関数に引数はありませんが、C言語のmainの宣言をGoで書いてみましょう。

Now consider functions. Let's transcribe the declaration for main, even though the main function in Go takes no arguments:

func main(argc int, argv *[]byte) int

ぱっと見はCとだいぶ違うでしょう、しかし左から右へ通して読み込んでみてください。

Superficially that's not much different from C, but it reads well from left to right:

main関数は一つのint型,byteのスライスポインタ型の引数を持ちint型を返却します。

function main takes an int and a pointer to a slice of bytes and returns an int.

引数名を抜いてみても混乱なく理解できます。

Drop the parameter names and it's just as clear - they're always first so there's no confusion.

func main(int, *[]byte) int

左に名前、右に宣言という構文は複雑にならずにうまく表現しています。

One value of this left-to-right style is how well it works as the types become more complex.

関数引数(Cでいう関数ポインタのこと)の宣言を見てみましょう

Here's a declaration of a function variable (analogous to a function pointer in C):

f func(func(int,int) int, int) int

もし関数を返却するなら、以下のようになります。

Or if f returns a function:

f func(func(int,int) int, int) func(int, int) int

読みやすいですね。そして名前がどこに入るかも明確です。そう最初に入れてやるのです。

It still reads clearly, from left to right, and it's always obvious which name is being declared - the name comes first.

そして型と式構文の間の違いは、以下のように

Goにおけるクロージャの記述性を容易にしてくれます。

The distinction between type and expression syntax makes it easy to write and invoke closures in Go:

sum := func(a, b int) int { return a+b } (3, 4)

ポインタについて

ポインタは例外的なルールになります。

注意点

配列とスライスで具体例をあげます。

Goの構文は以下に示すように、変数宣言構文は型の左にブラケット([])を置き、式構文は型の右にブラケットを置きます。

Pointers are the exception that proves the rule. Notice that in arrays and slices, for instance, Go's type syntax puts the brackets on the left of the type but the expression syntax puts them on the right of the expression:

var a []int //変数宣言構文(type syntax) x = a[1] //式構文(expression syntax)

Goのポインタはよく知られているCの構文に従いアスタリスク(*)を使います。

ただし、他と同じようにポインタ型の表現は左右が反転します。

例を参照して下さい

For familiarity, Go's pointers use the * notation from C, but we could not bring ourselves to make a similar reversal for pointer types. Thus pointers work like this

var p *int x = *p

下の記述じゃないことに気を付けてください。

We couldn't say

var p *int x = p*

なぜなら乗算の表現と被ってしまうからです。

以下の記述例のようにパスカルの(^)もサポートするか考えました。

because that postfix * would conflate with multiplication. We could have used the Pascal ^, for example:

var p ^int x = p^

^ をポインタに使い xor 演算子には別の記号を割り当てるべきだったかもしれないです。

ポインタを表すのにアスタリスクを使うといろいろとややこしくなるからです。

and perhaps we should have (and chosen another operator for xor), because the prefix asterisk on both types and expressions complicates things in a number of ways.

一例として、普通の型変換は以下のように書けます。

For instance, although one can write

[]int("hi")

しかし、アスタリスクで始まるときだけ以下のようにカッコが必要となります。

as a conversion, one must parenthesize the type if it starts with a *:

(*int)(nil)

アスタリスクをポインタ構文として用いることはカッコ囲み必須にならざるを得ませんでした。

Had we been willing to give up * as pointer syntax, those parentheses would be unnecessary.

したがって、Goのポインタ構文はよく知られているC形式に結び付けられます。しかし、そのことは、完全に文法の型および式を明確にするためにカッコを使用することを避ける事ができないことを意味します。

So Go's pointer syntax is tied to the familiar C form, but those ties mean that we cannot break completely from using parentheses to disambiguate types and expressions in the grammar.

しかしながら全体として、複雑な宣言の場合は特にGoの型宣言はCより可読性は向上しているはずです。

Overall, though, we believe Go's type syntax is easier to understand than C's, especially when things get complicated.

注意点

Goの宣言は左から右へ読んでいきます。

Cの場合はスパイラル状に読むことが指摘されています。

詳細はDavid Andersonの"Clockwise/Spiral Rule"を参照ください。

Go's declarations read left to right. It's been pointed out that C's read in a spiral! See The "Clockwise/Spiral Rule" by David Anderson.

By Rob Pike

2014/2/24 翻訳 適当訳ですいません。

2014/2/25 修正 usaiさん指摘を修正しました。ご指摘ありがとうございました。

2014/5/20 修正 shunichiさん。ご指摘ありがとうございました。

2014/5/29 修正 shunichiさん。何度も指摘していただきありがとうございます。修正かけました。