(投稿した直後、既に日本語訳がQiitaにあったことに気付いた。http://qiita.com/abe00makoto/items/2b03b189d746ae231756 まあいいか)
by Rob Pike
元記事: http://blog.golang.org/gos-declaration-syntax
はじめに
Go言語にはじめて触れたプログラマーがまっさきに首をひねるのは、Go言語の宣言構文がC系の言語で確立されているものとずいぶん異なっている点だろう。この記事では、両者のアプローチを比較して、Goの構文が現在のような見た目になった理由について説明したいと思う。
C言語の構文
ではC言語の構文から説明しよう。C言語では、いささか風変わりかつそれなりに賢い宣言構文を採用した。わざわざ特殊な構文をこしらえて型(type)を記述したりする代わりに、宣言されようとしている項目を表す式(expression)を書き、それからその式がどんな型であるかを記述するようにしたのだ。
int x;
上のように書くことで、x
という式にint
という型が与えられる。一般的に、新しい変数の型を記述する際には、まず基本的な型として評価される変数を表す式を記述し、そしてその基本的な型を左に、式を右に書く。
int *p;
int a[3];
従って、上の変数宣言文では、p
はint
へのポインタであることになる。*p
はintという型を持つからだ。次のa
はint
の配列であることになる。a[3]
はint
という型を持つからだ(ここで配列のサイズを表している特定のインデックス値は無視する)。
では関数はどうだろうか。初期のC言語の関数宣言は、以下のように引数の型をかっこの外に書くようになっていた。
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
この式main(argc, argv)
はint
を返す、従ってmain
は関数である。この記法は後に以下のように改められたが、基本的な点は同じだ。
int main(int argc, char *argv[]) { /* ... */ }
この記法は統語的(syntactic)なアイディアを採用していて、単純な型を記述している分にはなかなかよかったのだが、何かと混乱を招きがちだった。有名な例としては、関数ポインタの宣言があるだろう。この記法に従って書くと以下のようになる。
int (*fp)(int a, int b);
ここでfp
は関数へのポインタとなる。(\*fp)(a,b)
という式を書くと、int
を返す関数が呼び出されるからだ。ではfp
の引数のうちの1つが、それ自体関数だったらどういうことになるだろうか。
int (*fp)(int (*ff)(int x, int y), int b)
早くも読む気が失せてきた。
御存知のとおり、関数を宣言する場合にはパラメータの名前を除外できるので、main
は以下のように書ける。
int main(int, char *[])
argv
は以下のように宣言できることを思い出そう。
char *argv[]
従って、宣言部から名前を取り除いて型を構成することができる。ただしこれだけでは、宣言部に名前を再び追加しても、何らかのchar *[]
型が宣言されたことが明確にならない。
しかも、パラメータに名前を付けなかった場合、fp
の宣言は以下のようなありさまになってしまう。
int (*fp)(int (*)(int, int), int)
この宣言のどこに名前を置いたらいいのか、これだけでは判然としない。
int (*)(int, int)
もっと言えば、これは果たして関数ポインタの宣言であるのかどうかすら怪しい。この状態で、今度は戻り値も関数ポインタにしたらどんなことになるだろうか。
int (*(*fp)(int (*)(int, int), int))(int, int)
こうなってくると、これはfp
の宣言であると断定することすら難しくなってきた。
もっとひどい例を挙げてもよいのだが、ともあれ、C言語の宣言構文によってもたらされた混乱の一端についてはおわかりいただけたのではないだろうか。
さらにもうひとつ指摘しておかなくてはならないことがある。C言語では型と宣言構文が一体化しているために、宣言文中で、式を型で切り分けることが困難になっているのだ。こういう事情があったがために、たとえばC言語ではキャストを行なう時に以下のように型を常にかっこで囲まざるを得なかったのだ。
(int)M_PI
Goの構文
C言語系以外の多くの言語では、分離された型構文が宣言部で使用されている。ここでの議論に直接関係はないのだが、こうした言語では最初に名前を記述し、その後ろにセミコロンを置いている。C言語で使用した例をこれに従って擬似言語で表すと、たとえば以下のような感じになる。
x: int
p: pointer to int
a: array[3] of int
ここでの宣言内容は明快だ。たとえ記述内容が増えても、左から右に順に読んでいけばよい。実はGo言語はここからヒントを得ているのだが、さらに簡潔なものにしたかったので、思い切ってコロンを取り除き、キーワードの一部も取り払った。
x int
p *int
a [3]int
[3]int
と、a
を式の中でどのように使用するかについては直接の対応関係はない (この点については次の節で説明する)。分離構文を受け入れるという面倒と引き換えに、明快さを得ることができたのだ。
今度は関数について考えてみよう。先のmain
宣言をGo言語用に書き換えてみることにする(なお実際のGo言語ではmain
関数は引数を取らない)。
func main(argc int, argv []string) int
char
の配列がstring
に変わった点を除けば、一見したところC言語のときと大差ないように見える。しかし読みやすさは圧倒的に高く、素直に左から右に読むだけでよい。
このmain
関数はint
とstring
のslice
を引数に取り、int
を返す。
今度はパラメータ名を取り除いてみよう。それでも明快さは変わらない。パラメータ名が型よりも前にあるおかげで、混乱が生じないで済む。
func main(int, []string) int
この「左から右」記法の長所の一つは、表記が込み入ってきてもちゃんと読めるという点だ。今度は関数変数 (function variable) の例を見てみよう(C言語の関数ポインタと類似している)。
f func(func(int, int) int, int) int
さらに、戻り値を関数にすれば以下のようになる。
f func(func(int, int) int, int) func(int, int) int
ここまでひねっても問題なく左から右に読み下すことができる。名前を型よりも先に書くというルールのおかげで、何の名前が宣言されているかが常に明確になっている。
型と式を構文上分離したおかげで、Go言語ではクロージャがうんと書きやすくなった。
sum := func(a, b int) int { return a+b } (3, 4)
ポインタ
ポインタは、このルールを証明するための例外と言ってもよいだろう。Go言語は、たとえば配列やスライスにおいて、型構文では型の左に[]を置いているが、式構文では式の右に[]を置いている。
var a []int
x = a[1]
C言語に慣れているユーザーに配慮して、Go言語でもポインタを表すのに*
を使用しているが、ポインタの型については、上の[]のときのように型と式で置き場所を逆にすることまではさすがにできなかった。Go言語では、ポインタは以下のように記述することはできるが、
var p *int
x = *p
以下のように記述することはできない。
var p *int
x = p*
なぜなら、後置の*
は乗算の記号*
と区別できなくなってしまうからだ。今にして思えば、Pascal風にポインタを^
で表しておけばよかった。
var p ^int
x = p^
つくづく、そうしておくべきだったと悔やんでいる(そのときにはXORも別の記号に変えておかねばならないだろうが)。型であっても式であっても、*
を前置してしまうといろんな意味で事態がややこしくなるのだ。
たとえば、変換を以下のように書くことは可能だが、
[]int("hi")
型の前に*
が付いていると、曖昧さを避けるために以下のように型をかっこで囲まなければならなくなる。
(*int)(nil)
*
をポインタ記号として使うことをあきらめておけば、こんなかっこはそもそも要らなかったのだ。
そういったわけで、Go言語はC言語風のポインタ表記を採用したのだが、そのおかげで、型と式の文法上のあいまいさをなくすために未だにかっこが必要になってしまっている。
最後になったが、Go言語の型文法は、C言語なんかよりずっとわかりやすいと信じている。特に文が込み入ってきたときにその差は大きくなるはずだ。
メモ
Go言語の宣言は左から右に素直に読める。以前から指摘されまくっているのだが、C言語の宣言は渦巻状に読まなければならないのだ。David Andersonの "Clockwise/Spiral Rule"を参照されるがよい。
By Rob Pike