Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの主要コンテンツは、公式サイトの日本語訳です。右側のメニューから選んでください。

Archive

Archive for 1月, 2010

実践Go言語(Effective Go)の翻訳、4回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


セミコロン

Go言語の文法上、ステートメントの終了にはC言語同様にセミコロンが使われますが、C言語とは異なるのは、それらセミコロンはソース上に現れないことです。その代わりに、lexer(字句解析プログラム)がソースを調べて、ある単純なルールに基づき自動的にセミコロンを付け加えるので、打ち込むコードにはセミコロンがほとんど不要です。

ルールはこうです。改行直前のトークンが識別子(intfloat64といった単語を含む)、または文字列や数値定数といった基本的なリテラル、または下のトークンのどれかであるときに、lexerはトークンの後ろにセミコロンを付け加えます。

break continue fallthrough return ++ -- ) }

このルールは次のようにまとめられます。「改行文字の直前に、ステートメントの終わりと成りえるトークンがあるときにセミコロンが挿入される。」

また、右波括弧”}”の直前にあるセミコロンも省略可能です。次のようなステートメントではセミコロンはいりません。

    go func() { for { dst <- <-src } }()

Go言語の慣用記法に沿ったプログラムにおいては、セミコロンが記述されるのはforループ節のイニシャライザ、条件、繰り返しの各要素を分割するようなときだけです。ただし一行を複数のステートメントに分割するにはセミコロンが必要なので、このようなコードを記述するときはセミコロンを挿入してください。

注意事項:制御構造(ifforswitchselect)の左波括弧”{“を、その次の行に絶対に置いてはいけません。もし置いてしまうと、括弧の前にセミコロンが挿入され、それによって予期しない影響が起きる可能性があります。よって次のように記述してください。

if i < f() {
    g()
}

下のように書いてはいけません。

if i < f()  // wrong!
{           // wrong!
    g()
}

実践Go言語(Effective Go)の翻訳、3回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


名前

Go言語において、他の言語同様に名前は重要です。場合によっては、名前そのものが意味を持つことがあります。たとえば名前の頭文字が大文字かどうかで、パッケージの外からの可視性が決まります。Go言語プログラムにおける命名規則の解説に少し時間を割いてみましょう。

パッケージ名

パッケージがインポートされると、パッケージ名はそのパッケージへのアクセッサとなります。次に例を示します。

import "bytes"

このインポートによって、bytes.Bufferにアクセスすることができるようになります。

パッケージの内容を参照する際に、利用者すべてが同一の名前を使ってアクセスできるのであれば、それは有益なことです。それは即ち、パッケージ名が短く簡潔であり、すぐに連想できるような良い名前でなければならないということです。

慣例では、パッケージ名は小文字でひとつの単語です。アンダースコアや大文字が混ざって(mixedCaps)はいけません。

パッケージ使用者がその名前をタイプすることを考慮して、簡潔すぎるぐらいにしてください。名前が重複することを気にする必要はありません。パッケージ名は、単にインポート時にデフォルトとして使われる名前であり、ソースコード全体でユニークである必要はありません。万が一、パッケージのインポート時に重複が起きたときは、別なローカルな名前をつけることができます。いずれにせよ、インポートのファイル名によってどのパッケージが使われるかが決まるので、区別がつかなくなることはまずありません。

もう一つの慣例は、パッケージ名がそのソースディレクトリのベース名であるということです。たとえばsrc/pkg/container/vectorに置かれているパッケージは、"container/vector"としてインポートし、名前はvectorとなります。container_vectorcontainerVectorとはなりません。

パッケージを利用する側は、そのパッケージの内容へアクセスするためにパッケージ名を使用します。(import .表記を使うとアクセス時にパッケージ名が不要となりますが、これはテストや他の一般的でない状況に使うためのものなので、ここでは無視します。)パッケージ名を使うので、エクスポートされる名前は、同じ名称が繰り返されることを避けるためにパッケージ名を利用することがあります。たとえば、bufioパッケージ内の、バッファ付きリーダ型は、BufReaderではなくReaderと名づけられています。これはユーザ側からはbufio.Readerという明確かつ簡潔な名前で見えるためです。さらに、インポートされた実体は、常にそのパッケージ名を使って指定されるため、bufio.Readerio.Readerと名前がかち合うことはありません。おなじように、ring.Ring型の新しいインスタンスを作成する関数(Go言語におけるコンストラクタ)は通常、NewRingと名づけられますが、Ringがそのパッケージからエクスポートされている唯一の型で、かつパッケージ名がringであれば、単にNewとします。この関数はパッケージを利用する側からは、ring.Newとなります。このようにパッケージの構造を利用して良い名前を付けてください。

もう一つの短い例は、once.Doです。once.Do(setup)関数は、何をする関数か想像がつきますが、これをonce.DoOrWaitUntilDone(setup)と書き換えても、より分かりやすくはなりません。長い名前は、慣れたとしても読みやすくなることはありません。複雑もしくは微妙なニュアンスを持つものに名前をつけるときは、すべての情報を名前で表現しようとするより、通常は役立つドキュメントコメントを書いたほうがよいでしょう。

インタフェース名

慣例として、ひとつのメソッドだけを持つインタフェースには、そのメソッド名の後にサフィックス(-er)を加えた名前を付けます。ReaderWriterFormatterなどが該当します。こういった名前は他にもあり、インタフェース名とそこから得られる関数名が重要であるため、このようにして名前を作成します。

ReadWriteCloseFlushStringといったメソッドは、決められた役割とシグネチャを持ちます。混乱を避けるため、同じ役割およびシグネチャを持たないメソッドに対しこれらの名前を使わないでください。それとは反対に、型に実装しようとしているメソッドが、有名なメソッドと同じ役割を持つのであれば、それと同じ名前とシグネチャを使ってください。(注:文字列変換メソッドの名前は、ToStringではなくStringです)

MixedCaps

最後になりますが、Go言語の慣例では、複数の単語から成る名前をつけるときはアンダースコアを使わずに、MixedCapsまたはmixedCapsのように単語の先頭だけ大文字を用います。

実践Go言語(Effective Go)の翻訳、2回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


コメント

Go言語では、C形式の/* */ブロックコメント、およびC++形式の//行コメントが使用できます。基本は行コメントですが、ブロックコメントはパッケージのコメントとして使われる他、コードをブロック単位で無効化するのに役立ちます。

godocプログラム(ウェブサーバ機能もある)は、Go言語のソースファイルから、そのパッケージ内容に関したドキュメントを抽出します。トップレベルの宣言の直前に、空行を挟まずに記述されているコメントは、その宣言に対する説明として宣言とともに抽出されます。これらコメントのありようがgodocによって生成されるドキュメントの品質を左右します。

すべてのパッケージにはパッケージコメントが必要です。これはパッケージ文の前に置かれたブロックコメントです。パッケージが複数のファイルに分かれているときは、どれかひとつにパッケージコメントを記述しておけば、それが使われます。パッケージコメントはパッケージの説明、およびパッケージ全体の情報を提供するものであるべきです。パッケージコメントはgodocページの頭に出力されるので、そのあとに続いて出力されるドキュメントの詳細部に対する導入部分としての役割もあります。

/*
    The regexp package implements a simple library for
    regular expressions.

    The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

シンプルなパッケージであれば、パッケージコメントも簡潔で構いません。

// The path package implements utility routines for
// manipulating slash-separated filename paths.

コメントに、アスタリスクを並べるような特殊な書式は不要です。生成された出力は、等幅フォントで表示されるかどうか分からないので、gofmtのようにスペースによる配置に頼らないよう気をつけてください。最後になりますが、コメントは何ら解釈されることはないプレーンテキストであるため、HTMLや、_this_のようなアノテーションはそのままの形で出力されるので用いないほうがよいでしょう。

パッケージ内の、トップレベルの宣言の直前にあるコメントはすべて、その宣言に対するドキュメントコメントとして扱われます。また、プログラム内でエクスポート(大文字で記述)されている識別子にはすべて、ドキュメントコメントが必要です。

ドキュメントコメントは様々な自動フォーマットが適用されることを考慮すると英語の文章が望ましいです。また先頭の一文は、宣言した識別子で始まる文章で、かつ概要を記述したものでなければなりません。

// Compile parses a regular expression and returns, if successful, a Regexp
// object that can be used to match against text.
func Compile(str string) (regexp *Regexp, error os.Error) {

Go言語では宣言文をグループ化することができます。このときは一連の定数または変数に対して、ひとつのドキュメントコメントで記述することができます。このようなコメントは宣言全体に対して行われるので、たいていは形式的なものとなってしまいます。

// Error codes returned by failures to parse an expression.
var (
    ErrInternal      = os.NewError("internal error")
    ErrUnmatchedLpar = os.NewError("unmatched '('")
    ErrUnmatchedRpar = os.NewError("unmatched ')'")
    ...
)

プライベートな識別子であってもグループ化することで、項目どうしに関連性があることを表すことができます。下の例では、一連の変数がmutexによって保護されていることを示しています。

var (
    countLock   sync.Mutex
    inputCount  uint32
    outputCount uint32
    errorCount uint32
)

今回から実践Go言語(Effective Go)の翻訳をはじめます。
何回かに分けて公開しますので、おつきあいください。


はじめに

Goは新しい言語です。既存の言語からアイデアを取り入れてはいますが、他の言語にはない機能をもっているため、実際に記述されたGoのプログラムは、他の類似した言語とはだいぶ異なるものになります。C++またはJavaプログラムをGo言語へ直接変換しても、あまりうまくは行きません。JavaのプログラムはあくまでJavaで書かれており、Go言語で書かれてはいないからです。一方で、Go側の視点からこの問題を考えると、変換に成功したとしても、全く違うプログラムができてしまうことになります。言い換えると、Go言語を使いこなすには、Go言語の機能や文法を理解することが重要です。おなじく、Go言語のプログラミングにおける慣例を知っておくことも重要です。たとえば名前の付け方、書式化、プログラムの構築などです。慣例に従って記述されたプログラムは他のGo言語プログラマからも理解しやすいプログラムとなります。

この文書は、分かりやすく慣用的なGo言語のコードを書く際のヒントを与えます。また、この文章は言語仕様チュートリアルの補足であるため、それらを先に読んでおいてください。

サンプルプログラム

Go言語付属のパッケージソースは、コアライブラリを提供するだけではなく、Go言語のサンプルプログラムとしての役割もあります。問題への糸口や実装方法を探しているときに、このサンプルプログラムがその答えやアイデア、知識を提供してくれるでしょう。

ソースコードの書式

ソースコードの書式に関する問題点は多くの論議を呼んでいるのに関わらず、あまり重要視されておりません。人々は異なる書式に順応することができますが、それはしないに越したことはありませんし、全員が同じ書式を使うのであれば、このトピックに時間を割く必要もありません。問題点は、長文のドキュメントを読ませることなくこの理想に近づいていく方法です。

Go言語では、我々はちょっと変わったアプローチを取り、コンピュータにフォーマット問題の大部分を任せました。gofmtプログラムは、Go言語のプログラムを読み込むと、インデントと垂直位置を標準スタイルに変更し、必要であればコメントの内容を保ったまま再書式化した上でソースコードを出力します。見慣れない構文をどう扱えばよいか分からなければgofmtを実行してください。それが返した答えが合っていないようであれば、そのままにせずにプログラムを修正(もしくはバグ報告)してください。

例です。構造体のフィールドのコメントを一列に並べることに時間を費やす必要はありません。代わりにgofmtがそれを行ってくれます。はじめに、宣言を行います。

type T struct {
    name string // name of the object
    value int // its value
}

gofmtによってカラムが整列されます。

type T struct {
    name    string // name of the object
    value   int    // its value
}

ライブラリ内のすべてのコードは、gofmtで書式が揃えられています。

若干、フォーマットの詳細についての説明が残っていますので、簡潔に説明します。

インデント

インデントにはタブを使います。これはgofmtのデフォルトです。スペースは必要がない限り使わないでください。

行の長さ

Go言語には、行の長さ制限は有りません。パンチカードからはみ出す心配は無用です。行があまりにも長くなったときは、改行してタブでインデントしてください。

括弧

Go言語はあまり括弧を必要とはせず、制御機構(if, for, switch)の構文には括弧は不要となっています。また、演算子の優先順位は簡潔かつ明確です。たとえば、

x<<8 + y<<16

この式は、見たとおりの値を表します。

The Go Programming Language Specificationの翻訳、最終回です。
すべての訳はGo言語仕様[日本語訳]にまとめてあります。


システム考察

unsafeパッケージ

組み込みパッケージunsafeは、コンパイラに実装されており、低レベルのプログラミング向けの機能を提供します。これには型システムから逸脱した機能が含まれています。そのためunsafeを使っているパッケージは、型が安全であることを手作業でよく確認しておかなくてはなりません。このパッケージでは、以下のインタフェースを提供しています。

package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) int
func Offsetof(selector ArbitraryType) int
func Sizeof(variable ArbitraryType) int

func Reflect(val interface {}) (typ runtime.Type, addr uintptr)
func Typeof(val interface {}) reflect.Type
func Unreflect(typ runtime.Type, addr uintptr) interface{}

すべてのポインタ、およびuintptr型の値は、Pointerに変換することができます。またその逆も可能です。

Sizeof関数は、変数を表す式を受け取り、その変数のサイズをバイト数で返します。

Offsetof関数は、構造体のフィールド表すセレクタ(§セレクタ)を受け取り、構造体のアドレスからフィールドへの相対オフセットをバイト数で返します。下は構造体sのフィールドfを使った例です。

uintptr(unsafe.Pointer(&s)) + uintptr(unsafe.Offsetof(s.f)) == uintptr(unsafe.Pointer(&s.f))

コンピュータのアーキテクチャによっては、メモリのアドレスにアライメントが必要となることがあるため、変数のアドレスは、変数の型が持つアライメントも考慮します。Alignof関数は、変数を表す式を受け取り、変数(の型)のアライメントをバイト数で返します。下は変数xを使った例です。

uintptr(unsafe.Pointer(&x)) % uintptr(unsafe.Alignof(x)) == 0

AlignofOffsetofSizeofの呼び出しは、コンパイル時にint型の定数となります。

unsafe.Typeofunsafe.Reflectunsafe.Unreflect関数は、実行時にインタフェースに格納されている動的な型および値へのアクセスを許します。Typeofは、valの動的な型を runtime.Typeとして返します。Reflectは、valの動的な値のコピーを割り当て、型とそのコピーのアドレスを返します。UnreflectReflectの逆で、型とアドレスからインタフェースの値を作成します。これらの関数を利用しているreflectパッケージでは、インタフェースの値を安全、かつより便利に調べる方法を提供しています。

サイズとアライメントの保証

数値型(§数値型)では、以下に示すサイズが保証されています。

型                        サイズ(バイト数)

byte, uint8, int8         1
uint16, int16             2
uint32, int32, float32    4
uint64, int64, float64    8

以下に示す、アライメントの特性が最低限保証されています。

  1. 変数x(型は問わない):1 <= unsafe.Alignof(x) <= unsafe.Maxalign
  2. 変数xが数値型のとき:unsafe.Alignof(x)は、 unsafe.Sizeof(x)およびunsafe.Maxalign 以下であり、少なくとも1以上である。
  3. 変数xが構造体型のとき:unsafe.Alignof(x)は、xの各フィールドをfとしたときunsafe.Alignof(x.f)の中で一番大きい値と同じであり、少なくとも1以上である。
  4. 変数xが配列型のとき:unsafe.Alignof(x)は、unsafe.Alignof(x[0])と同じであり、少なくとも1以上である。

実装との差異 – TODO

  • 実装は、gotoステートメント、および(宣言以外の)宛先に対する制約を守っていません。
  • メソッド式は未実装です。
  • Gccgoではひとつのソースファイルに、ひとつのinit()関数しか許していません。