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

第6回 Goでドメイン特化言語も作成可能!?

赤坂 けい
チームWordProgress

2010/4/19

任意の型を格納可能なinterface{}

 静的なコンパイル言語であるGoでは、mapのキー・バリューの型を指定する必要がある(map [int] string、map [string] stringなど)。しかし、Goのmapは、バリューに任意の型の値を格納するコレクションも作成できる。それは、Goでは、すべての型が空のインターフェイス「interface{}」を持っているとされることを利用する(すなわち、バリューの型に、「interface{}」を指定する)。

 「interface{}」をバリューとして持つ以下の例を見ていこう。

●mapint1.go
package main
import ("fmt")
    //ユーザー定義のsymbol型
    type symbol string
    var Age ,Name ,Weight symbol
    
func main() {
    yamada := map[*symbol] interface{} {
            &Age : 17,
            &Name : "山田太郎",
            &Weight : 98.3
        };
    fmt.Print(yamada)
} 
●実行結果
map[0x4513f4:17 0x447654:98.3 0x4476d0:山田太郎]

 main関数内で、「map[*symbol] interface{}」というmapを定義している。このmapは、キーがsymbol型へのポインタ、バリューが空のインターフェイス「interface{}」である。

 このうち、symbol型とはプログラムの冒頭部で定義されたものであり、Age ,Name ,Weightの3つの変数がsymbol型として宣言されている。

 mapを初期化する複合リテラルの部分を見てほしい。

   {
     &Age : 17,
     &Name : "山田太郎",
     &Weight : 98.3
   }

 キーの部分にはsymbol型のポインタ&Age ,&Name ,&Weightが、バリューの部分には整数、文字列、実数が、それぞれ記述されている。この部分だけ見ると、Goのmapは、動的言語であるRubyのハッシュに負けない簡潔な表現ができることが分かるだろう。ちなみにRubyのハッシュで同等の表現を書くと以下の通りとなる。

●Rubyのハッシュによる同等の表現
    {
      :age => 17,
      :name => '山田太郎',
      :weight => 98.3
    }

 実行結果では、ハッシュを代入した変数yamadaの内容がプリントされている。その内容から、map型であること、キーの中身はポインタが指すアドレス(「0x」から始まる十六進法のアドレス)であること、バリューは整数、文字列、実数がそのまま格納されていることが分かるだろう。

 さて、空のインターフェイス「interface{}」が、任意の型を持つ値が格納できる点は、どのように理解すれば良いのだろうか? 継承ベースのオブジェクト指向プログラミングに親しんでいる方は、全ての型の源となるルート型のようなものを想像したいかもしれない。しかし、クラス階層と継承の仕組みを持たないGoでは、そのように理解することはできない。

 筆者は、インターフェイスをその字義通りに「外部との接点」と捉えた上で、「空のインターフェイス」とは、「外部との接点を持ちうること」と考えるのが良いのではと考えている。すなわち、Goでは、全ての型から生成されるオブジェクトは、自身のメモリ空間内にそれぞれの型に応じた内部状態(値)を持つと共に、外部から何らかのアクセスを受け入れると「期待」される。この「期待」が、空のインターフェイス「interface{}」として表現されているため、空のインターフェイスには任意の型を代入できると、理解するのである(*3)。

 むろん、単に、「interface{}は、任意の型を入れることのできる入れ物である」と理解していても、実用上は問題ない。

[*3] オブジェクト指向パラダイムで考えたい方のための補足 : Goのinterface{}のアプローチを考える上で、ScalaのNothing型が参考になるのではと考えている。ScalaのNothing型は、全ての型のサブタイプであり、Scalaのジェネリクス・プログラミングにおいて重要な役割を担っている(Goのメーリングリストでは、ジェネリクスを導入すべきかという議論がしばしばなされているが、Scalaのジェネリクス・プログラミング・モデルは非常に洗練されており、何らかの形でGoにも導入してもらえないかと筆者は期待している)。

interface{}から型情報を抽出する

 さて、上記のプロクラムにはいくつか気持ちが悪い点がある。

 第1に、symbol型の変数を定義し、そのポインタを用いているにも関わらず、中身は全く代入されていない点である。第2に、mapからキー・バリューを取り出していない点である。

 次のプログラムでこの点を解決しよう。

●mapint2.go
package main
import ("fmt")
//ユーザー定義のsymbol型
type symbol string
//symbol型のメソッドGet
func (this symbol) Get(in interface{}) string {
    return fmt.Sprintf("%v%v",in,this)
}
var Age ,Name ,Height ,Weight symbol
= "歳","さん","(cm)","(Kg)"
    
//mapデータ attr
var attr = map[*symbol] interface{} {
        &Name : "山田太郎",
        &Age : 17,
        &Height : 177.3,
        &Weight : 98.3
    }
func isUnderAge(age interface{}) bool{
    if val,ok :=age.(int); ok {
        if val < 20 {return true}
    }
    return false
}
func main() {
    for k,v := range attr {
        fmt.Println(k.Get(v))
    }
    if isUnderAge(attr[&Age]) {
        fmt.Printf("%vは未成年\n",Name.Get(attr[&Name]))
    }
}  
●実行結果
177.3(cm)
98.3(Kg)
山田太郎さん
17歳
山田太郎さんは未成年

 今度のプログラムでは、symbol型の変数にそれぞれの「単位」のようなものを代入している。

 Goでは、同一の型を持つ複数の変数を、以下のように、一気に初期化できる。

   var Age ,Name ,Height ,Weight symbol
    = "歳","さん","(cm)","(Kg)"

 また、symbol型のメソットGetも定義している。Getの引数はinterface{}であるため、任意の型を受け取れる。Getの返り値は、これをfmt.Sprintf関数で、%vオプションを用いて文字列へと変換し、それと、それぞれの変数が持つ「単位」を結合したものである(実行結果を見れば一目瞭然だろう)。

 main関数内では、for〜rangeを用いて、それぞれの値をプリントしている。k.Get(v)というところで、symbol型のメソットGetを活用している。また、ポインタ&Ageに年齢を入れるという約束の上で、&Ageに対応する値が20以下、すなわち、未成年かどうかを判断する関数isUnderAgeも用意している。関数isUnderAgeは、以外と複雑だ。これは、関数isUnderAgeの引数がinterface{}であるため、引数が数値と比較可能である(すなわち引数自身も数値である)ことが期待できないためだ。そこで、変数の型がint型であるかどうかを調べ、値を代入する(int)メソッドを呼出し、型の判別結果を変数okに、対応する値をvalに代入することを試みている(ここでは、Goの特徴のひとつである「複数代入」が行われている)。ここで変数okはbool型であり、okがtrueの時にはvalには値が存在する。その場合のみ、valが20以下であるか(未成年かどうか)を判断している。

おわりに

 今回のGoのインターフェイスの用い方は、慣れないうちはやや戸惑うようなものかもしれない。ただ、Goは、スクリプト言語に近い記述ができることは体験してもらえたのてはなかろうか。

 次回以降、Goのインターフェイスを用いたオブジェクト指向プログラミングの解説をしていきたい。また、「スライス」も取り上げたい。

prev
3/3
Index
Goでドメイン特化言語も作成可能?
  Page1
GoでDSL(ドメイン特化言語)?
  Page2
Goの組込コレクション機能mapを学ぶ
  Page3
任意の型を格納可能なinterface{}
おわりに

 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 記事ランキング

本日 月間