新世代の並列処理言語Google Goをひもとく

第4回 Goのswitch文で解くFizzBuzz問題と構造体のイントロ

赤坂 けい
チームWordProgress

2010/2/25

突然登場した新しいプログラミング言語「Go」。その独自性、魅力を余すところなく堪能してみよう(編集部)

goroutine以外の並列処理アプローチについての議論

 第3回「ハロー、goroutine!」から、Goの並列処理記述を特徴付けているgoroutineを取り上げている。goroutineは、チャネルなどを通じてメッセージをやり取りする軽量プロセスである。このアプローチは、Erlangの軽量プロセスと類似している。

 他方、HaskellClojureなどの関数型言語では、「ソフトウェアトランザクショナルメモリ(Software Transactional Memory:STM)」と呼ばれる並列処理のアプローチが前面に押し出されている感がある。

 STMもgoroutineも、これまで一般的であった「スレッド」を用いた並列処理モデルに対するオルタナティブを目指すものといえる。スレッド・プログラミングは、一般のプログラマにとって難易度が高いとされていることがその背景にある。

 最近、Goのメーリングリストにおいて、「Goは、goroutineに加えてSTMもサポートすべきではないか」という議論が行われている(軽量プロセスのアプローチと、STMのアプローチは排他的なものではなく、両者を実装している言語はいくつもある)。

 Goのメーリングリストということもあってか、いまのところ議論はgoroutineの優勢勝ちのようだ(例えば、STMよりもgoroutineの方がスケールする、STMの信頼性はいまだ低いなど)。

 しかし、今後の並列処理へのアプローチを考える上で興味深い内容が含まれているため、興味のある方はメーリングリストをチェックしてほしい(メーリングリスト内で「STM」を検索するのが手っ取り早い)。

 今回もGoの基本的な文法を扱いながら、goroutineについても見ていこう。

メーリングリストの議論では、IBM、インテル、サン・マイクロシステムズの手による3つのSTM実装(C++)を、HTM(ハードウェアベースのトランザクションメモリ)と比較し、STMはまだ実務で使えるレベルに達していないと結論付けた論文「view issue Software Transactional Memory: Why Is It Only a Research Toy?(2008年10月)」が引用されている。

STMに興味をお持ちの方は、こちらも一読をお勧めする。この論文に対するSTMの擁護コメント(例えば、Haskellなどで実装されている、型安全なトランザクションメモリの考察が欠けているなど)も興味深い。

switch文でFizzBuzz問題

 Goの基本的な制御構造で、まだ解説していなかったswitch文を確認しよう。Goの式は、Cのswitch文に比べると柔軟性が高く、使い勝手が良い。

 第一に、Goのswitch文では、条件節(case)のところに「式」を書くことができる。このことは、if文と同等の制御構造をswitch文で表現できることを意味する。

 試してみよう。

●if_switch.go
package main 

func main() {
    var i =3;
    if i < 5 {println("5以下")}
    else {println("それ以外")};
    //「if文」と同一の動作をする「switch文」
    switch {
        case i <5 : println("5以下") 
        default   : println("それ以外")
    }
}
●実行結果
5以下
5以下

 ここで、「if 〜 else 〜」文と「switch { case 〜 defalut 〜 }」文は、同一の動作をしている。もっとも、この例のように条件が1つしかない場合には、if文の方を使う方が明らかに自然だ。

 一方、複数の条件がある場合には、switch文を使う方が表現が簡潔になる。複数の条件がある例として、有名な「FizzBuzzゲーム」を記述してみよう。

 FizzBuzzゲームでは、1〜100までの数字を順に列挙していく。ただし、3の倍数のときはBuzz、5の倍数のときはFizz、3と5の倍数のときはFizzBuzzと表示する特別ルールがある。

 このことは、switch文を用いると以下のように書ける。

●FizzBuzz1.go
package main 
import ("fmt")

func main() {
    for i := 1; i <= 100; i++ {
        switch {
            case i % 15 == 0 : print ("FizzBuzz, ")
            case i % 5 == 0  : print ("Fizz, ")
            case i % 3 == 0  : print ("Buzz, ")
            default : fmt.Printf ("%d, ",i)
        }
    }
}
●実行結果
1, 2, Buzz, 4, Fizz, Buzz, 7, 8, Buzz, Fizz, 11, Buzz, 13, 14, FizzBuzz, 16, 17,
 Buzz, 19, Fizz, Buzz, 22, 23, Buzz, Fizz, 26, Buzz, 28, 29, FizzBuzz, 31, 32, B
uzz, 34, Fizz, Buzz, 37, 38, Buzz, Fizz, 41, Buzz, 43, 44, FizzBuzz, 46, 47, Buz
z, 49, Fizz, Buzz, 52, 53, Buzz, Fizz, 56, Buzz, 58, 59, FizzBuzz, 61, 62, Buzz,
 64, Fizz, Buzz, 67, 68, Buzz, Fizz, 71, Buzz, 73, 74, FizzBuzz, 76, 77, Buzz, 7
9, Fizz, Buzz, 82, 83, Buzz, Fizz, 86, Buzz, 88, 89, FizzBuzz, 91, 92, Buzz, 94,
 Fizz, Buzz, 97, 98, Buzz, Fizz,

 switch文の中の条件節は、ゲームのルールをそのまま列挙しているだけだ。switch文は、条件節(case)を上から順に評価し、条件にマッチした時点で、その節を実行し評価を打ち切る。

 これを利用して、ここではゲームのルールを特殊な例から順に表記することで、シンプルな記述が実現できている。

 それぞれの条件節は、「case 条件 : 実行内容」と表記する。また、いずれの条件にマッチしなかった場合の処理を、switch文の末尾に「default : 実行内容」の形式で記述できる。

 加えて、(if文で、実行内容部分の中括弧が省略不可であることとは異なり)、switch文の「case:」の後の実行内容部分では「{ }(中括弧)」を書かなくてもよいこと、実行内容の最後に終了条件(break)を記述する必要がないことも確認しておこう。

 続いて、値を返す関数内でのswitch文の使い方を見ておこう。

●FizzBuzz2.go
package main 
import ("fmt";"strconv")

//無名関数にFizzBuzzという変数名を付けている
var FizzBuzz = func (i int) (rtn string){
    switch {
        case i % 15 == 0 : rtn = "FizzBuzz"
        case i % 5 == 0  : rtn = "Fizz"
        case i % 3 == 0  : rtn = "Buzz"
        default : rtn = strconv.Itoa(i)
    }
    return
}
func main() {
    for i := 1; i <= 100; i++ {
        fmt.Printf("%s, ",FizzBuzz(i))
    }
}
●実行結果
1, 2, Buzz, 4, Fizz, Buzz, 7, 8, Buzz, Fizz, 11, Buzz, 13, 14, FizzBuzz, 16, 17,
 Buzz, 19, Fizz, Buzz, 22, 23, Buzz, Fizz, 26, Buzz, 28, 29, FizzBuzz, 31, 32, B
uzz, 34, Fizz, Buzz, 37, 38, Buzz, Fizz, 41, Buzz, 43, 44, FizzBuzz, 46, 47, Buz
z, 49, Fizz, Buzz, 52, 53, Buzz, Fizz, 56, Buzz, 58, 59, FizzBuzz, 61, 62, Buzz,
 64, Fizz, Buzz, 67, 68, Buzz, Fizz, 71, Buzz, 73, 74, FizzBuzz, 76, 77, Buzz, 7
9, Fizz, Buzz, 82, 83, Buzz, Fizz, 86, Buzz, 88, 89, FizzBuzz, 91, 92, Buzz, 94,
 Fizz, Buzz, 97, 98, Buzz, Fizz,

 冒頭で、int型の変数iを受け取り、string型の変数rtnを返す無名関数が定義され、それに変数名FizzBuzzという名前を付けている。同じく冒頭で、返り値の変数名rtnを定義し、returnには変数名を記述しなくてもよいという、特徴的な表記も用いている(冒頭で返り値を定義することによって、関数内の見通しがよくすることができると筆者は感じている)。

 もちろん、Cと同様に、関数冒頭で返り値の型のみを定義し、returnに返り値を記述することも可能である。すなわち、変数FizzBuzzは以下のようにも定義できる。

var FizzBuzz = func (i int) string{
    switch {
        case i % 15 == 0 : return "FizzBuzz"
        case i % 5 == 0  : return "Fizz"
        case i % 3 == 0  : return "Buzz"
    }
    return strconv.Itoa(i)
}

 最後に、switch文とgoroutineを組み合わせておこう。同じ処理を3度も記述するのは芸がないと思うので、ルールを一般化可能なコードにしておくことにする。

●FizzBuzz3.go
package main
import ("fmt")

var ch = make(chan string)

func FizzBuzzOne(init int) {
    const Fizz ,Buzz =5,3; //任意の2つの数値に置換可能
    Fin := Fizz*Buzz;
    
    for i := init * Fin +1; ;i++{
        switch {
            case i % Fin == 0 :
                ch <- "FizzBuzz.";
                return
            case i % Fizz == 0  : print ("Fizz,")
            case i % Buzz == 0  : print ("Buzz,")
            default : fmt.Printf ("%3d,",i)
        }
    }
}

func main() {
    for num := 0; num <= 10; num++ {
        go FizzBuzzOne(num);
        println(<- ch); // ここで「待ち」が入る
    }
}
●実行結果
  1,  2,Buzz,  4,Fizz,Buzz,  7,  8,Buzz,Fizz, 11,Buzz, 13, 14,FizzBuzz.
 16, 17,Buzz, 19,Fizz,Buzz, 22, 23,Buzz,Fizz, 26,Buzz, 28, 29,FizzBuzz.
 31, 32,Buzz, 34,Fizz,Buzz, 37, 38,Buzz,Fizz, 41,Buzz, 43, 44,FizzBuzz.
 46, 47,Buzz, 49,Fizz,Buzz, 52, 53,Buzz,Fizz, 56,Buzz, 58, 59,FizzBuzz.
 61, 62,Buzz, 64,Fizz,Buzz, 67, 68,Buzz,Fizz, 71,Buzz, 73, 74,FizzBuzz.
 76, 77,Buzz, 79,Fizz,Buzz, 82, 83,Buzz,Fizz, 86,Buzz, 88, 89,FizzBuzz.
 91, 92,Buzz, 94,Fizz,Buzz, 97, 98,Buzz,Fizz,101,Buzz,103,104,FizzBuzz.
106,107,Buzz,109,Fizz,Buzz,112,113,Buzz,Fizz,116,Buzz,118,119,FizzBuzz.
121,122,Buzz,124,Fizz,Buzz,127,128,Buzz,Fizz,131,Buzz,133,134,FizzBuzz.
136,137,Buzz,139,Fizz,Buzz,142,143,Buzz,Fizz,146,Buzz,148,149,FizzBuzz.
151,152,Buzz,154,Fizz,Buzz,157,158,Buzz,Fizz,161,Buzz,163,164,FizzBuzz.

 すでにお気付きの方も多いだろうが、FizzBuzz問題でgoroutineを使うのはほとんど無意味である(順々に増えていくものを表示するために、並列処理することはほとんど無意味だ)。

 ただし、gooutineとswitchの組み合わせは、相性がよい。例えば、非同期にリクエストを受け付けるサーバで、リクエスト内容に応じた複数の処理を記述する場合などに、両者を組み合わせて簡潔な処理記述ができるだろう。本連載では、応用編に進んだあたりで、このことにトライしたい。

「複数の場合」を扱う手法としては、チャンネルと組み合わせたselect文も存在する。これらは、Goのオブジェクト指向の解説の後に、応用編としてトライすることとしたい。

 
1/2
next

Index
Goのswitch文で解くFizzBuzz問題と構造体のイントロ
  Page1
goroutine以外の並列処理アプローチについての議論
switch文でFizzBuzz問題
  Page2
構造体とメソッドの定義

index 新世代の並列処理言語Google Goをひもとく

 Coding Edgeお勧め記事
いまさらアルゴリズムを学ぶ意味
コーディングに役立つ! アルゴリズムの基本(1)
 コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう
Zope 3の魅力に迫る
Zope 3とは何ぞや?(1)
 Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか?
貧弱環境プログラミングのススメ
柴田 淳のコーディング天国
 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く?
Haskellプログラミングの楽しみ方
のんびりHaskell(1)
 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう
ちょっと変わったLisp入門
Gaucheでメタプログラミング(1)
 Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間