Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの更新が滞っており、情報が古くなっておりますのでご注意ください。

Archive

Tag: 実践Go言語

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


ウェブサーバ

Go言語のウェブサーバプログラムを作成して締めくくります。これは実際にはウェブ転送サーバの類です。Googleではhttp://chart.apis.google.comで、データをチャートやグラフに自動変換するサービスを提供していますが、このサービスはデータをクエリパラメータとしてURLに含めなくてはならないため対話的な使い方ができません。ここで紹介するプログラムは、文字を入力するとQRコード(テキストをエンコードした矩形マトリクス)を生成させるために先程のチャートサーバを呼び出す、ちょっとした画面を提供します。生成された画像を携帯電話のカメラで撮影するとURLなどに変換されるので、携帯電話の小さいキーを駆使してURLを入力する手間が省けます。

下はこのプログラム一式です。説明はそのあとに続きます。

package main

import (
    "flag"
    "http"
    "io"
    "log"
    "strings"
    "template"
)

var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
var fmap = template.FormatterMap{
    "html": template.HTMLFormatter,
    "url+html": UrlHtmlFormatter,
}
var templ = template.MustParse(templateStr, fmap)

func main() {
    flag.Parse()
    http.Handle("/", http.HandlerFunc(QR))
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

func QR(c *http.Conn, req *http.Request) {
    templ.Execute(req.FormValue("s"), c)
}

func UrlHtmlFormatter(w io.Writer, v interface{}, fmt string) {
    template.HTMLEscape(w, strings.Bytes(http.URLEscape(v.(string))))
}

const templateStr = `
<html>
<head>
<title>QR Link Generator</title>
</head>
<body>
{.section @}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={@|url+html}"
/>
<br>
{@|html}
<br>
<br>
{.end}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>
</body>
</html>
`

main関数までは簡単に説明します。まずサーバのHTTPポート番号のデフォルト値をflagに設定しています。変数templは目新しい部分で、ページを表示するためにサーバ上で実行されるHTMLテンプレートを構築しています。これについては後でもう少し説明します。

main関数で起動パラメータの解析(flag.Parse)後、以前説明した方法でサーバのルートパスとQR関数とを紐付けています。続いてサーバを起動するためにhttp.ListenAndServeが呼び出されます。サーバが動いている間は、ここでブロックされ続けます。

QR関数では、入力フォームデータが含まれているリクエストを受け取り、そのフォーム内のsと名付けられたデータを使ってテンプレート処理を実行します。

このテンプレートパッケージは、json-templateを参考に作られており、とても高機能です。このサーバプログラムでは、ほんの触り程度しか紹介しませんが、テンプレートパッケージの主用途はテンプレートテキストの一部分をtempl.Executeに渡されたデータから得られた要素(この場合はフォームの値)で書き換えることです。テンプレートテキスト(templateStr)内の波括弧{}で囲まれている部分でテンプレートの動作を指定します。{.section @}から{.end}までの部分はデータ項目@の値を伴って実行されます。この@は「カレントのアイテム」を表しており、今回の値はフォーム値です。(文字列が空のときは、テンプレートの該当部分は出力されません。)

{@|url+html}の箇所では、フォーマッタマップ(fmap)に"url+html"という名前で登録されているフォーマット用関数を使ってデータを処理するよう指定しています。実際に登録されているUrlHtmlFormatter関数では、ウェブページ上で文字列を安全に表示させるためのサニタイズ処理を行います。

テンプレート内のその他の文字列はただのHTMLで、ページが読み込まれたときに表示されます。ここでは駆け足で説明したので、詳しくはテンプレートパッケージのドキュメントを参照ください。

数行のコードと、データと掛け合わせて出力するHTMLテキストだけで便利なウェブサーバが手に入りました。このようにGo言語はたった数行でも色々なことが行える高機能な言語です。

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


エラー情報

ライブラリルーチンでは、呼び出し元にエラー情報などを返す必要が度々発生します。以前説明したように、Go言語の複数戻り値を使うと通常の戻り値と一緒にエラーの詳細情報を返すことが簡単にできます。またエラーは慣例的にos.Errorというシンプルなインタフェースを実装しています。

type Error interface {
    String() string
}

ライブラリの作成者はこのインタフェースと併せて、より高機能なモデルを自由に実装して構いません。こうすることでエラーを知らせるだけでなく、何らかの状況も提供することができます。この例として、os.Openは下のos.PathErrorを返しています。

// PathErrorは、エラーとそれを引き起こした操作及び
// ファイルパスが記録されます。
type PathError struct {
    Op string    // "open", "unlink", など
    Path string  // 関連ファイル
    Error Error  // システムコールからの戻り値
}

func (e *PathError) String() string {
    return e.Op + " " + e.Path + ": " + e.Error.String()
}

PathErrorStringメソッドは次のような文字列を生成します。

open /etc/passwx: no such file or directory

このようにエラー情報内に問題の起きたファイル名、操作、引き金となったオペレーティングシステムのエラーを含んでいると、エラーとなった呼び出し箇所から離れた箇所でエラー情報を出力したときにも役立ちます。「そのようなファイルやディレクトリはありません」と単に返されるより、多くの情報を与えてくれます。

呼び出し元で、エラーの正確な詳細情報が必要なときは、型スイッチまたは型アサーションを使い特定のエラーを探して詳細情報を得ることができます。たとえばPathErrorsの場合、内部のErrorフィールドを調べることでリカバリ可能か判断できることがあります。

for try := 0; try < 2; try++ {
    file, err = os.Open(filename, os.O_RDONLY, 0)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Error == os.ENOSPC {
        deleteTempFiles()  // 空き容量を回復
        continue
    }
    return
}

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


並列性

通信による共有

並列プログラミングは大きなトピックではありますが、ここではGo言語の仕様の中で特徴的な部分だけを取り上げます。

一般的な環境における並列プログラミングは、共有変数にアクセスするためには適切な実装が厳密に求められるため扱いにくいものでした。Go言語では、共有変数をチャネル上で引き回し、実行中の異なるスレッドからは実際に同時アクセスさせないという今までとは異なる手法を推奨しています。あるタイミングで値にアクセスできるゴルーチンはひとつだけなので、設計上データ競合は起きえません。

共有メモリを使った通信は行わず、代わりに通信を使うことでメモリを共有するようにしてください。

この手法は、過剰となる側面もあります。参照カウンタであれば、たとえば整数型の変数とミューテックスを併用するほうが最適かもしれません。しかし、高水準な手法として、アクセス制御のためにチャネルを使うことは、明解で正確なプログラムを記述することをより容易くします。

このモデルについて考える手立てとして、1台のCPU上で動いている典型的なシングルスレッドプログラムがあると仮定します。これには同期プリミティブは不要です。同様に同期が不要であるインスタンスを新しく起動して、これらスレッド間を通信させます。この通信が同期していれば、それ以外の同期はとりあえず必要ありません。このモデルと一致する例としては、Unixのパイプラインが良く知られています。Go言語におけるこの並列性へのアプローチは、Hoare氏のCommunicating Sequential Processes (CSP)が基になっており、Unixパイプにおけるタイプセーフの汎用化にもこれが見受けられます。

ゴルーチン

ゴルーチンと呼ぶようにした理由は、スレッド、コルーチン、プロセスといった既存の用語では正確に伝わらないためです。ゴルーチンが持つモデルは単純で、その役割は同一アドレス空間内で他のゴルーチン同士を並列実行することです。またゴルーチンは軽量で、スタック空間を割り当てるより若干コストがかかる程度です。開始時のスタックサイズは節約のため小さく取り、必要に応じてヒープ領域を割り当て(自由に)拡張します。

ゴルーチンはOSの複数スレッド上へ多重化されるので、ひとつが例えばI/O待ちなどでブロックされていても、他のゴルーチンの実行は継続します。ゴルーチンの仕組みによってスレッドを作成・管理する複雑さの大部分が軽減されます。

関数またはメソッドの呼び出しを新規ゴルーチンとして実行するには、呼び出しの直前にキーワードgoと記述してください。呼び出しが完了した時点で、すでにゴルーチンは作成されています。(この効能はUnixシェルにてバックグラウンドでコマンドを実行するために使用する「&」表記とほぼ同じです。)

go list.Sort()  // list.Sortを並列実行する(処理の完了は待たない)

関数リテラルを使うと手軽にゴルーチンが実行できます。

func Announce(message string, delay int64) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()  // 括弧に注意。関数を呼び出すために必要。
}

Go言語における関数リテラルはクロージャです。このとき関数内から参照されている変数は関数が実行している間、生存しつづけます。

この例では関数が完了したことを通知する手立てがないため、あまり実践的ではありません。完了通知を行うにはチャネルを使います。

チャネル

マップと同じくチャネルは参照型であり、makeを使って割り当てられます。作成時にオプションの整数パラメータを指定すると、チャネルのバッファサイズとして使われます。この値のデフォルトはゼロであり、ゼロのときバッファリングは行われず同期チャネルとなります。

ci := make(chan int)            // 整数型のバッファなしチャネル
cj := make(chan int, 0)         // 整数型のバッファなしチャネル
cs := make(chan *os.File, 100)  // Fileへのポインタ型のバッファありチャネル

チャネルは、値の交換および同期という通信機能を兼ね備えており、2つの計算処理(ゴルーチン)が予期しない状態とならないことを保証します。

チャネルを利用した優れたイディオムはたくさんありますが、手始めにひとつ紹介します。前のセクションではソート処理をバックグラウンドで起動しましたが、これにチャネルを使って起動したゴルーチンのソート完了を待つよう変更できます。

c := make(chan int)  // チャネルの割り当て
// ゴルーチンとしてsortを起動。完了時にチャネルへ通知
go func() {
    list.Sort()
    c <- 1  // 通知を送信。値は何でも良い
}()
doSomethingForAWhile()
<-c   // sortの完了待ち。送られてきた値は破棄

受信側では常に、受信可能なデータが来るまでブロックされます。送信側はチャネルがバッファリングしていないときは、受信側が値を受信するまでブロックされます。チャネルがバッファリングしているときは、送信側がブロックされるのは値がバッファへコピーされる間だけです。このためバッファがいっぱいのときは、受信側で値を取り出すまで待機します。

スループットを制限するようなケースで、バッファありチャネルをセマフォのように使うことができます。下の例では、入って来たリクエストはhandleに渡されます。その中でまず値をチャネルへ送信し、リクエストを処理したのちチャネルから値を受信しています。チャネルのバッファが持つ容量を利用してprocessの同時呼び出し数を制限しています。

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1    // アクティブキューの空き待ち
    process(r)  // 時間のかかる処理
    <-sem       // 完了。次のリクエストを処理可能にする
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // handleの終了待ちはしない
    }
}

次も同様のアイデアで、一定数のhandleゴルーチンを起動しておき、その中でリクエストチャネルから全て読み込んでいます。ここではゴルーチンの数でprocessの同時呼び出し数を制限しています。このServe関数自体も、終了指示を受信するためのチャネルを引数として受け取り、ゴルーチンを起動したあと、このチャネルを受信することでブロックしています。

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *clientRequests, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // 終了指示待ち
}

チャネル内のチャネル

Go言語の特徴のなかで重要なもののひとつは、チャネルがメモリを割り当てることができ、かつ他の値と同じように引き回すことができる「優れた値」であることです。この機能は一般的に並列、非多重化を安全に実装するために使われます。

前セクションの例におけるhandleはリクエストを処理するための理想的なハンドラではありましたが、そこで扱っていたRequest型は未定義でした。もしその型に応答用チャネルが含まれていれば、各クライアント側でそれぞれ処理結果を受信する経路を用意することができます。下はRequest型の定義の概略です。

type Request struct {
    args        []int
    f           func([]int) int
    resultChan  chan int
}

リクエストオブジェクト内の処理結果受信用チャネルと同様に、クライアント側で関数とその引数も準備します。

func sum(a []int) (s int) {
    for _, v := range a {
        s += v
    }
    return
}

request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// リクエスト送信
clientRequests <- request
// 応答待ち
fmt.Printf("answer: %d\n", <-request.resultChan)

サーバ側で変更するのは、ハンドラ関数だけです。

func handle(queue chan *Request) {
    for req := range queue {
        req.resultChan <- req.f(req.args)
    }
}

これを現実的なコードにするには、やるべきことが多々残されていることは明らかですが、このコードはあくまで帯域制限、平行処理、非ブロック型RPC機構を実装したフレームワークであり、相互排他は考慮していません。

並列化

これらの考え方のもう一つの応用は、複数のCPUコアすべてを使い計算を並列処理することです。計算処理を個々に分割できるのであれば、各計算の完了を伝えるチャネルを用意することで並列化可能です。

下のサンプルのように、アイテムを格納したVector上で実行される高コストの演算処理があり、またその各アイテムにて演算される値同士が、お互い関連性を持たないと仮定します。

type Vector []float64

// v[i], v[i+1] ... v[n-1]までを演算処理に適用
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
    for ; i < n; i++ {
        v[i] += u.Op(v[i])
    }
    c <- 1    // この部分が終わったことを伝達
}

データをCPUコア数分割し、ループでコア毎に処理をひとつ起動します。演算処理が完了する順番は不定ですが、それは問題ではありません。単に全ゴルーチンを起動後、チャネルから受信した完了通知数をカウントするだけです。

const NCPU = 4  // CPUコア数

func (v Vector) DoAll(u Vector) {
    c := make(chan int, NCPU)  // 任意だが、バッファリングしたほうが賢明
    for i := 0; i < NCPU; i++ {
        go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)
    }
    // チャネルを空に
    for i := 0; i < NCPU; i++ {
        <-c    // 処理の完了をひとつ待つ
    }
    // すべて完了
}

gc6g等)の現実装のデフォルトでは、このコードは各CPUコアへ並列化はされません。ユーザ処理に対してはコアをひとつしか使用しないようになっているためです。システムコールで任意の数のゴルーチンをブロックすることは可能ですが、デフォルトで実行されるユーザレベルコードは常にひとつだけです。これはもっと賢くあるべきで、また今後賢くしていく予定はありますが、そうなるまでCPUを並列処理させたいときはコードを同時実行したいゴルーチン数をランタイムに指定してください。この指定方法は2つあり、環境変数GOMAXPROCSに使用するコア数(デフォルト1)を設定してからジョブを実行するか、もしくはruntimeパッケージをインポートし、runtime.GOMAXPROCS(NCPU)を呼び出してください。また、スケジューリングとランタイムが改善されたときは、この設定は不要となる予定です。

溢れるバッファ

並列プログラミング向け機能を利用して、より実装が簡単な非並列処理を記述することもできます。下はRPCパッケージから取り出した例です。クライアント側のゴルーチンではループ内でどこかのソース(おそらくネットワーク)からデータを受信します。バッファの割り当て・開放を減らすために使わなくなったバッファはリストに格納しておきます。これを実現するためにバッファありチャネルを使い、このチャネルが空のときは新たにバッファを割り当てます。受信したメッセージをバッファに格納すると、そのバッファはserverChan経由でサーバへ送信されます。

var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)

func client() {
    for {
        b, ok := <-freeList  // バッファがあれば取得
        if !ok {              // なければ新たに割り当てる
            b = new(Buffer)
        }
        load(b)              // 次のメッセージをネットから読み込む
        serverChan <- b      // サーバへ送信
    }
}

サーバ側のループではクライアントからメッセージを受信し処理を行ったあと、バッファリストにバッファを返却します。

func server() {
    for {
        b := <-serverChan    // 作業待ち
        process(b)
        _ = freeList <- b    // 空きがあればバッファを再利用
    }
}

クライアントの非ブロック受信では、利用可能であればバッファリストからバッファを取得します。利用可能なバッファがないときは新しくバッファを割り当てます。サーバのfreeListへの非ブロック送信は、バッファリストがいっぱいでなければbfreeListに戻します。バッファが溢れたときはガーベジコレクタによって回収されます。(送信演算子をブランク識別子へ代入することで非ブロック送信となりますが、操作が成功したかどうかは無視されます。) この実装では、ある境界線で溢れてしまうバケツのような空きバッファリストを作成し、冗長なコードを記述する代わりにチャネルのバッファとガーベジコレクタ任せにしています。

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


埋込み

Go言語には型によるサブクラス化という典型的な概念はありませんが、構造体またはインタフェースに型を埋込み、実装を「借りる」仕組みがあります。

インタフェースの埋込みはとても単純です。下は以前説明したio.Readerio.Writerインタフェースの定義です。

type Reader interface {
    Read(p []byte) (n int, err os.Error)
}

type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

ioパッケージではこれと同じように、オブジェクトに対しメソッドの実装を規定するためのインタフェースが、他にもいくつかエクスポートされています。たとえば、ReadWrite両方を持つインタフェースio.ReadWriterがあります。これら2つのメソッドを明示的に記述することでio.ReadWriterを定義することもできますが、次のようにして2つのインタフェースを埋込んで新しいインタフェースを作成するほうがより簡単で、意図が伝わりやすくなります。

// ReadWriteは、基本的なメソッドReadとWriteをグルーピングしたインタフェース
type ReadWriter interface {
    Reader
    Writer
}

このようにすることで、Readerが行えること、およびWriterが行えることをReadWriterが兼ね備えていて、このインタフェースが埋込みインタフェース(メソッド群内に共通のメソッドを持っていてはならない)の結合により作られていることが見て取れます。ただしインタフェース内に埋込むことができるのはインタフェースだけです。

この基本的な考え方は構造体にもあてはまりますが、構造体の場合はより広範囲に渡る影響があります。bufioパッケージは2つの構造体型、bufio.Readerbufio.Writerを持ち、当然それぞれパッケージioの対応するインタフェースを実装しています。bufioではさらに埋込みを利用して、ひとつの構造体にReaderWriterを組み込んでバッファ付きの読み書きを実装しています。下の例で構造体内に型を列挙していますが、このときフィールド名は付けていません。

// ReadWriterは、ReaderとWriterのポインタを格納している
// これはio.ReadWriterの実装
type ReadWriter struct {
    *Reader
    *Writer
}

この構造体はこう書き換えることも可能です。

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

ただこうした場合は、下のようにして呼び出しを転送するメソッドを用意し、フィールド内の各メソッドを実装しioインタフェースを満たすようにしなければなりません。

func (rw *ReadWriter) Read(p []byte) (n int, err os.Error) {
    return rw.reader.Read(p)
}

しかし、直接構造体を埋込んでしまえば、この冗長な記述は要らなくなります。埋込まれた型が持っているメソッドは自動で持ち上げられるため、すなわちbufio.ReadWriterbufio.Readerbufio.Writerのメソッドを持つだけでなく、3つのインタフェース(io.Readerio.Writerio.ReadWriter)の全てを満たすようになります。

埋込みとサブクラス化では大きな違いがあります。型を埋込んでいるとき、埋込んだ型が持っているメソッドは埋込み先のメソッドともなりますが、実行しているメソッドのレシーバは埋込み先の型ではなく、あくまで元の型です。サンプルコードの bufio.ReadWriterReadメソッドが実行されることと、先程の転送メソッドが実行されることは結果としてまったく同じです。(レシーバはReadWriterフィールドのreaderで、ReadWriter自身ではありません。)

また、埋込みを使うことで少し扱いやすくもなります。下の例では、通常の名前付きフィールドと並んで、埋込みフィールドを記述しています。

type Job struct {
    Command string
    *log.Logger
}

これでJob型は、log.Logger型が持つLogLogfといったメソッドを持つようになりました。もちろん、Loggerにフィールド名を与えることはできますが、それは必須ではありません。これで、Jobを使ってログが記録できるようになりました。

job.Log("starting now...")

このLoggerは普通の構造体フィールドであるため、いままで通りの方法で初期化が行えます。

func NewJob(command string, logger *log.Logger) *Job {
    return &Job{command, logger}
}

埋込まれているフィールドを直接参照しなければならないときは、フィールドの型名(パッケージ名は不要)をフィールド名として用います。つまりJob型である変数job*log.Loggerにアクセスしたいときはjob.Loggerと書きます。これはLoggerのメソッドに手を加えたいときに役立ちます。

func (job *Job) Logf(format string, args ...) {
    job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))
}

型を埋込むことで名前の競合が発生する恐れがありますが、その解決ルールは単純です。最初に、フィールドまたはメソッドXは、その他の、より深い入れ子内にあるXを隠蔽します。たとえばlog.LoggerCommandと名づけられたフィールドまたはメソッドが含まれていても、JobCommandフィールドがそれより優先されます。

2番目として、同一の入れ子階層に同じ名前が現れたとき、通常ではエラーとなります。たとえばJob構造体に、Loggerと名づけられた別のフィールドまたはメソッドが含まれているときlog.Loggerの埋め込みは不正です。ただし、このとき重複した名前が、型定義より外側のプログラムから一切アクセスされなければ大丈夫です。この決まりによって、埋め込まれた型へ外部から変更を加えてもある程度保護されます。つまり、フィールドの追加で他の型が持つフィールドとかち合ってしまっても、どちらのフィールドも一切使用されることがなければ何ら問題ありません。

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


インタフェースとそれ以外の型

インタフェース

Go言語のインタフェースは、オブジェクトの振舞いを規定する手立てです。このセクションではインタフェースにより実現できることすべてを説明します。今まですでに2、3の簡単な例を見てきました。たとえばカスタム出力はStringメソッドを実装することで作られ、一方FprintfWriteメソッドによって出力先をどこへでも変更できます。Go言語のコードでは、通常インタフェースは1~2個のメソッドしか持たず、またWriteメソッドを持つio.Writerのようにたいていメソッド名と関連した名前が付けられます。

型には複数のインタフェースを実装することができます。たとえばあるコレクション型がLen()Less(i, j int) boolSwap(i, j int)メソッドを持つsort.Interfaceを実装していればsortパッケージのルーチンを使ってソートが可能になり、その上さらに独自の出力メソッドを実装することもできます。下の例のSequence型は、少々不自然ですがこれらをすべて実装しています。

type Sequence []int

// sort.Interfaceに必要な全メソッドを実装
func (s Sequence) Len() int {
    return len(s)
}
func (s Sequence) Less(i, j int) bool {
    return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

// 出力用メソッド - 出力前に要素を並び替え
func (s Sequence) String() string {
    sort.Sort(s)
    str := "["
    for i, elem := range s {
        if i > 0 {
            str += " "
        }
        str += fmt.Sprint(elem)
    }
    return str + "]"
}

変換

さきほどのSequenceStringメソッドを変更して、Sprintが本来持っているスライス出力機能を利用するようにしました。Sprintを呼び出す前にSequenceを純粋な[]intに変換することで、既存の処理を利用することが可能になります。

func (s Sequence) String() string {
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}

このとき変換が行われ、sがただのスライスとみなされるため、スライスに規定されている書式で文字列が返されます。ここで変換を行われなければ、SprintSequenceStringメソッドを見つけ出し、呼び出しを無限に繰り返してしまいます。この2つの型(Sequence[]int)は名前を除けば同一であるため、これらの型の間における変換は問題なく行われます。この変換では新しい値が作られることはなく、既存の値が一時的に新しい型を持つかのような働きをします。(これと異なる変換もあります。たとえば整数を浮動小数点へ変換したときは新しく値が作成されます。)

Go言語のプログラムでは、異なるメソッド群を使用するために式の型を変換することがよく行われます。例として、既存の型sort.IntArrayを使ってサンプルプログラム全体を下のように軽量化することが可能です。

type Sequence []int

// 出力用メソッド - 出力する前に要素を並び替え
func (s Sequence) String() string {
    sort.IntArray(s).Sort()
    return fmt.Sprint([]int(s))
}

これで、Sequenceに複数のインタフェース(ソートと出力)を実装する代わりに、データを複数の型(Sequencesort.IntArray[]int)に変換できることを利用して各機能を実現できるようになりました。このような使い方をすることは実際にはほとんどありませんが効果的な使い方です。

概説

ある型がインタフェースをひとつだけ実装していて、そのインタフェースのメソッド以外にエクスポートされたメソッドを持たないならば、型自体のエクスポートは不要です。インタフェースだけをエクスポートすることで、次の点を明白にすることができます。ひとつは、重要なのは実装ではなく振舞いであること。もうひとつは、振舞いは同じでも異なる機能を持った別個の実装であることです。また、共通メソッドを実装している各箇所で、その都度ドキュメントを記述する手間が省けるという利点もあります。

このような場合は、コンストラクタは実装している型ではなく、インタフェース値を返さなければなりません。一例として、ハッシュライブラリのcrc32.NewIEEE()adler32.New()では、双方ともhash.Hash32インタフェース型の値を返します。Go言語プログラムでこのハッシュアルゴリズムをAdler-32からCRC-32に変更するために必要なのは、コンストラクタの呼び出しを入れ替えるだけです。それ以外のコードは、ハッシュアルゴリズムの変更による影響を受けません。

同様のアプローチにより、crypto/blockパッケージのストリーム暗号アルゴリズムは、一連のブロック暗号から独立しています。これらは特定の実装を返すのではなく、bufioパッケージにならってCipherインタフェースをラップしたhash.Hashio.Readerio.Writerいずれかのインタフェース値を返します。

下はcrypto/block内のインタフェースです。

type Cipher interface {
    BlockSize() int
    Encrypt(src, dst []byte)
    Decrypt(src, dst []byte)
}

// NewECBDecrypterは、rからデータを読み込み、
// c内の電子コードブック(ECB)モードを使って復号化を行うReaderを返します。
func NewECBDecrypter(c Cipher, r io.Reader) io.Reader

// NewCBCDecrypterは、rからデータを読み込み、
// c内の暗号ブロックチェイニング(CBC)モードと初期化ベクタivを使って
// 復号化を行うReaderを返します。
func NewCBCDecrypter(c Cipher, iv []byte, r io.Reader) io.Reader

NewECBDecrypterおよびNewCBCReaderで扱うことができるのは、特定の暗号化アルゴリズムやデータソースではなく、どのCipherインタフェースの実装であっても、またどのio.Readerでも適用することが可能です。これらはio.Readerインタフェース値を返すため、ECB暗号化をCBC暗号化と入れ替えるときは部分的な変更だけですみます。コンストラクタを呼び出している箇所は変更が必要ですが、その周囲のコードではコンストラクタから返されるのはio.Readerだけとみなしていれば違いに気づくことさえないでしょう。

インタフェースとメソッド

ほぼすべての型に対してメソッドを付け加えることができるので、これはすなわち、どのインタフェースであっても、ほとんどの型に実装可能であると言えます。この実例のひとつが、Handlerインタフェースを定めているhttpパッケージにあります。Handlerを実装しているすべてのオブジェクトで、HTTPリクエストを処理することが可能です。

type Handler interface {
    ServeHTTP(*Conn, *Request)
}

ここでは簡略化のため、POSTは無視してHTTPリクエストが常にGETであると仮定します。 (この簡略化がハンドラの書き方に影響を及ぼすことはありません。) 下は、ページの訪問回数を単に数えるだけのハンドラの実装一式です。

// 単純なカウントサーバ
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
    ctr.n++
    fmt.Fprintf(c, "counter = %d\n", ctr.n)
}

(今回のテーマを気にとめつつ、FprintfがどのようにしてHTTP接続を出力するか注意してみてください。)
参考までに、こういったサーバをURLパスに割り当てる手順です。

import "http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

ところで、なぜCounterを構造体としているのでしょうか。必要なのは整数ひとつだけのはずです。(ただし値が増えたことを呼び出し側にも伝わるように、レシーバはポインタである必要があります。)

// 単純なカウントサーバ
type Counter int

func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
    *ctr++
    fmt.Fprintf(c, "counter = %d\n", *ctr)
}

自作プログラムが内部ステータスを持っていて、そこにページが訪問されたことを通知しなければならないとしたらどうすればよいでしょうか。このようなときは、次のようにチャネルとウェブページとを関連付けてください。

// 訪問がある度に通知を送信するチャネル
// (たぶんバッファリングされている必要がある)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(c *http.Conn, req *http.Request) {
    ch <- req
    fmt.Fprint(c, "notification sent")
}

最後に、パス/argsを訪問したときにサーバプログラムを起動した際に与えられた引数を出力するようにしてみましょう。引数の出力関数は下のように簡単に書けます。

func ArgServer() {
    for i, s := range os.Args {
        fmt.Println(s)
    }
}

これをHTTPサーバへ変更していきます。さきほどのArgServer関数を適当な型(値は何でも良い)のメソッドとすることもできますが、それより良い方法があります。メソッドはポインタとインタフェース以外すべての型に定義することができるため、関数に対してメソッドを書くことも可能です。httpパッケージには、下のコードが含まれています。

// HandlerFunc型は、通常の関数をHTTPハンドラとして
// 使用可能にするためのアダプタです。
// fが適切なシグネチャを持つ関数であれば、HandlerFunc(f)は
// fを呼び出すハンドラオブジェクトとなります。
type HandlerFunc func(*Conn, *Request)

// ServeHTTPはf(c, req)を呼び出す
func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
    f(c, req)
}

HandlerFuncServeHTTPメソッドを持つ型であるため、この型の値はHTTPリクエストを処理できます。メソッドの実装を見てください。このメソッドのレシーバは関数fであり、メソッド内でfを呼び出しています。これは少々風変わりではありますが、前出のチャネルをレシーバとしてそのチャネルへ送信するメソッドと大差ありません。

ArgServerをHTTPサーバにするため、まずは正しいシグネチャを持つように修正します。

// 引数サーバ
func ArgServer(c *http.Conn, req *http.Request) {
    for i, s := range os.Args {
        fmt.Fprintln(c, s)
    }
}

これでArgServerHandlerFuncと同じシグネチャを持つようになったので、以前SequenceIntArray.Sortを使用するためIntArrayに変換したときと同様に、ArgServerHandlerFunc内のメソッドを使用するためにHandlerFunc型に変換可能になりました。このセットアップを行うコードは次のように簡潔に書けます。

http.Handle("/args", http.HandlerFunc(ArgServer))

誰かが/argsページを訪問したときに呼び出されるハンドラは、値はArgServerで型はHandlerFuncとなりました。まず、HTTPサーバによってArgServerをレシーバとしてHandlerFunc型のServeHTTPメソッドが実行され、続いてHandlerFunc.ServeHTTP内のf(c, req)を通してArgServerが呼び出されます。そのあと引数の表示が行われます。

このセクションで、構造体、整数、チャネル、関数を使ってHTTPサーバを作成したのは、インタフェースがまさにメソッド群であり、(ほとんど)すべての型に対して定義できることを示すためです。