Scribble at 2022-05-23 13:21:54 Last modified: 2022-05-24 18:15:20

Go の構文を勉強していて、たいていは納得がいくし合理的で扱いやすい(特に while, foreach をなくして for で統一してるとか)のだけれど、いまのところ二つだけ構文の詳しい解説を調べる必要を感じたところがあった。まず一つ目は、スライスの記法だ。

Go では配列の要素を減らしたり増やしたりできないようになっている。したがって、var ar int[5] などと定義して作成した配列 ar に対して要素の数を変更する pop, push, shift, unshift なんて関数はない。もちろん、これらと同じことをやるルーチンは書けるけれど、それを解説しているサイトの多くが「配列」と「スライス」を厳密に区別しないで表記しているし、ルーチンの戻り値として作成された新しいスライスだというポイントをぜんぜん説明していない。結局、Go という処理系の内部処理や言語としての仕様に興味がない、コーディング小僧が解説を書くとこうなってしまう。表面的なサル同然のパズル的な関心でしかプログラムを扱っていない証拠だ。

で、問題のスライスの記法へ話を移すと、まず5個の要素をもつ配列 ar を作成しておく。

ar := int32[5] { 5, 3, 9, 12, 6 }

この配列から、[ low : high ] という記法を使ってスライス sl を作成すると、

sl := ar[1:5]

のようになる(sl は {3, 9, 12, 6 })。ここで "low" は取り出す開始インデックスであり、"high" は終了インデックスだと解説される(そして、自身の要素は含まないという面倒臭い注釈がつく)。しかし、"high" を終了「インデックス」と呼ぶのは正しいのだろうか。なぜなら、5個の要素を含む配列に [5] などというインデックスは存在しないからだ(配列の要素は、インデックス 0 から始まるので、インデックス 5 は6番目の配列要素となり、存在しない)。実際には "high" で指定しているのは「5番目の要素の値」であって、ar[4] のことなのだ。したがって、スライスの中で或る1個の要素だけを取り出すときは、たとえば ar[3] なら sl[3:4] となる。"low" はキーとしての(つまり0から始まる)基数を指定するのに対して、"high" だと「先頭から~番目の要素」という序数を指定するという違いがあるのは、なぜだろうか。low - high という対の概念として説明されていながら、これでは意味が対の概念となっていないせいで、混乱を起こすだけだ。

その理由は、実は Go の公式サイトにある言語仕様のドキュメントにちゃんと書いてある。つまり、"high" は "length equal to high - low" というスライスの長さを指定するのだ。よって、ar[1:3] はインデックス1の要素から 3 - 1 個の長さのスライスを取り出すという意味になる。そういう、スライスの長さを指定するための値だからこそ、インデックスとして配列やスライスのキーになっていなくても問題がないのである。ただ、これだと index and length という概念だから、引数を low - high という対の概念で表記するのは、やはり誤解を招きやすい。

なんてことは、ちょっと公式のドキュメントを読めばわかることなのだが、気にしないで配列とスライスをごちゃごちゃに表記したり、スライスの指定方法を天下り式にコピペするだけというドキュメントが Go でも(もちろん他の言語でも言えることだが)多い。

二つ目は、先日(5月20日)にも紹介した Caleb Doxsey の Introducing Go: Build Reliable, Scalable Programs (O'Reilly Media) だと、スライスにつかう copy() というビルトイン関数だ。ここは説明が不正確に思える。copy() 関数は copy( dst, src ) という書式になっていて、dst に src の要素をコピーするものであり、dst と src のスライスの要素数が異なる場合は、Caleb の説明では "If the lengths of the two slices are not the same, the smaller of the two will be used"(二つのスライスの長さが同じでないときは、少ないほうの長さが使われる)となっている。でも、「使われる」という言葉の意味を正確に説明しないと誤解を生じるだろう。なぜなら、

src := []int32{ 3, 5, 6, 7, 8 }

dst := make( []int32, 3 )

copy( dst, src )

と書くと、src の要素は5つで dst の要素は3つだが、なにも要素が少ない方の dst が「使われて(優先されて)」src の要素が3つに縮約されたり、もしくは dst の要素が「使われて(優先されて)」 src の要素が置き換わるわけではないからだ。「少ない方が『使われる』」とは、src から dst へ値をコピーするときに、dst の要素が多ければ、少ない src の値が dst の一部としてコピーされると言っているにすぎない。逆に src から dst へ値をコピーするときに(この操作そのものは、dst と src の要素の多い少ないという違いで変わるわけがない)、src の要素が多ければ、少ない dst の値が全て src の値で上書きされるということでもある。Go のコードではないが図式的に表すと、

src{ 1, 1, 1, 1, 1} --copy-- dst { 0, 0, 0 } => dst { 1, 1, 1 }

src{ 1, 1, 1 } --copy-- dst { 0, 0, 0, 0, 0 } => dst { 1, 1, 1, 0, 0 }

という話をしているにすぎないのだ。

あと、この本は最初の印象として「よく書けている」と評価したし、実際にわかりやすい英語でコーディングへ取り組みやすいように書かれているとは思うが、かなり雑な説明も見受ける。delete() 関数なんて "We can also delete items from a map usging the built-in delete function: delete( x, 1 )" という1行だけで済ませているが、x は要素を削除する map のことだとわかるにしても、第二引数が要素のキーであるということくらい説明をちゃんと書いてほしい。(よって、mp := make( map[string]int ) として mp["philsci"] = 53 とすれば、この要素を削除するには delete( mp, "philsci" ) と記述する必要がある。)

  1. もっと新しいノート <<
  2. >> もっと古いノート

冒頭に戻る


※ 以下の SNS 共有ボタンは JavaScript を使っておらず、ボタンを押すまでは SNS サイトと全く通信しません。

Twitter Facebook