Go (プログラミング言語)
| Go | |
|---|---|
| パラダイム | コンパイラ言語、並列プログラミング |
| 登場時期 | 2009年 |
| 設計者 | Robert Griesemer、ロブ・パイク、ケン・トンプソン |
| 開発者 | |
| 最新リリース | version 1.0.3 / 2012年09月24日 |
| 型付け | 強い型付け |
| 主な処理系 | GC(C言語で記述。構文解析にyacc/bisonを使用)、Gccgo(再帰下降パーサを持つC++フロントエンド、バックエンドに標準GCC)[1]。 |
| 影響を受けた言語 | C言語、Limbo、Modula、Newsqueak、Oberon、Pascal[2] |
| 影響を与えた言語 | Dart |
| プラットフォーム | Linux、Mac OS X、FreeBSD、Windows |
| ライセンス | BSD |
| ウェブサイト | http://golang.org |
Goはプログラミング言語のひとつ。Google社によって開発されており[3]、設計にロブ・パイク、ケン・トンプソンらが関わっている。
主な特徴として、軽量スレッディングのための機能、Pythonのような動的型付け言語のようなプログラミングの容易性、などがある。Go処理系としてはコンパイラのみが開発されている。
発表当初はLinuxとMac OS Xのみしかサポートしていなかったが[4]、2012年3月にリリースされたversion 1からはWindowsもサポートされている[5]。また、2011年5月10日に公開された Google App Engine 1.5.0 でも、Go言語がサポートされている[6]。
目次 |
特徴 [編集]
Goの構文は様々な言語に部分的に類似している。変数の定義における型の記法はLimboと同様の後置でALGOLやPascalに類似し、ブロックの区切りに波括弧を使う記法はC言語に類似している。for文やif文では条件式を丸括弧で括らず帰結部分には波括弧が必須である。メモリ管理はガベージコレクションに一任され、連想配列も備える。並列処理はOccamやLimboと同様、アントニー・ホーアによるCSPのプロセス代数をモデルとし、[2]、Limboと同様チャンネルによるスレッド間通信機能がある。
クラスの継承、ジェネリックプログラミング、アサーション、オーバーロードといった機能が存在しないことも特徴として挙げられる。インターフェースを用いたポリモーフィズム(多態性)が実現されている。try-catchはないがそれに代わる機能としてpanicとrecoverを用いた例外処理機能をサポートしている。[2]FAQにおいて、ジェネリックプログラミングは一部導入が表明されているが、オーバーロードは効率的見地から排除されたことが述べられている。関数は多値を返すことができるので、それによりエラーの報告は容易である、としている。
Hello World [編集]
package main import "fmt" func main() { fmt.Printf("Hello, World\n") }
他言語との比較 [編集]
ここでは、Goと同じシステム記述言語であるC++を主な比較対象に機能面についての違いを説明する。
オブジェクトの実装 [編集]
Goにおけるオブジェクトの実装は下記の様に記述する。:
// 型(構造体)の定義 type Something struct { member int } // メソッドの定義 func ( this *Something ) Function() { fmt.Printf( "%d", this.member ); }
上記のコードはC++における下記の記述と同等である。:
// 型の定義 struct Something { int member; void Function(); }; // メンバー関数の定義 Something::Function() { printf( "%d", member ); }
Goにおける大きな違いは、型定義の中にメソッド名が登場しない事と( this *Something ) と記述したレシーバーの存在である。
レシーバーは( 変数名 型名 )と記述する。レシーバー内の変数は、C++におけるthis変数に相当する。C++と異なり変数名は自由に指定が可能であり、受け取り方も値渡し、ポインター渡しの2種類から指定出来る。
Goでは、型からメソッドが切り離されており、型は一切メソッドに依存しない。 メソッドと型は、メソッドの追加対象となる型名をレシーバーの型名に指定して関連付ける。 型からメソッドが切り離された事により、型本体の変更無しでメソッド追加可能となり同パッケージ内限定ではあるが、 一種のオープンクラスを実現している。(Goでは型について、クラスという用語は使わず単に型という)
Goにはコンストラクター、デストラクターに当たる機能が存在しない。 コンストラクターは型に所属しない関数や、他の型のメソッドで代用する。
型定義 [編集]
GoではC++のtypedefの様な形で新しい型を定義できる。
次の例では、int型を元にMathIntという新しい型を定義している。:
type MathInt int
一見typedefとよく似ているが、単に別名をつけている訳ではなく元のint型と別の型を定義している点が大きく異なる。
新しく定義した型であれば、元の型が構造体型でなくともメソッドを追加することが出来る。例えばint型やマップ型といった事前定義済み型(組み込み型)に対してもメソッドを追加することが可能である。
事前定義済み型から新しい型を定義した場合、下記の様な記述が可能となる。:
func main() { var value MathInt; value = -10 fmt.Printf( "%d", value.Abs() ); // -10の絶対値を表示する }
多態性の表現 [編集]
GoにはC++や他の言語における仮想関数を持ったクラスが存在しない。型に追加したメソッドは一種の非仮想関数である。Goにおいての多態はinterface型を使用して実現する。
interface型は実装を一切持たずメソッドの形式だけを定義した型であり、その点はC#など最近の言語のinterfaceと同じである。但し、Goのinterface型は、代入できる型との関連付けが不要である。interfaceに定義しているメソッドを全て持ってさえいればどんなオブジェクトでも代入可能である。
Goにおけるinterfaceの例を下記に示す。:
type Container interface { Begin() Iterator End() Iterator }
C++ではC++0xにおいてconceptが存在したが、interfaceはconceptの動的版とも言える。 また、かつてg++においてsignatureというC++拡張が存在したがGoのinterfaceは全く同じ機能である。
継承 [編集]
Goは建前上継承機能を持っていない。しかし、匿名フィールドというメンバーを定義する事で 実質継承を記述することが可能である。
Goにおける多重継承の例を下記に示す。:
type BaseA int func ( _ *BaseA ) Function() { } func ( _ *BaseA ) FunctionA() { } type BaseB int func ( _ *BaseB ) Function() { } func ( _ *BaseB ) FunctionB() { } type Derived struct { BaseA BaseB }
呼び出し例:
var derived Derived derived.FunctionA() derived.FunctionB()
Goは構造体に変数名を省き型名だけ記述すことにより匿名フィールドを定義できる。 匿名フィールドは派生型からセレクターによるメソッド呼び出しが暗黙に移譲される。 実質C++などでいう基底クラスとして機能するのである。ただし、派生型から暗黙に 匿名フィールドと同じ型にキャストする事はできない。
上記の例に示した通りGoはC++同様実質多重継承が可能である。 多重継承には基底型のメンバーが重複するという問題がつきまとう。 Goにおいても同様で上記の例ではderived.Function()という呼び出しができない。 また、上記の例に登場するDerivedのオブジェクトはFunction()を持つinterfaceに代入する事ができない。
Goは、この解決方法に次の2つの方法を提供している。
- 匿名フィールドを明示的に指定する
- 派生型にも同じメソッドを作り明示的に移譲する
1.の匿名フィールドを明示的に指定した例を下記に示す。:
var derived Derived derived.BaseA.Function() derived.BaseB.Function()
1.の解決方法はC++の明示的な基底クラスメンバー呼び出しとよく似ている。 Goの場合あくまでも基底型ではなく匿名フィールドを明示的に指定しているという点が大きく異なる。 例えば匿名フィールドを明示的に操作する場合にも同様の方法を使用する。
下記に例を示す。:
var derived Derived var base *BaseA = &derived.BaseA derived.BaseA = 100 derived.BaseA += 100 base.Function()
1.の方法はセレクターによるメソッドの呼び出しは解決できるもののinterfaceへの代入については解決できない。 interfaceへの代入まで可能にするには2.の方法で解決する。
2.の派生型で明示的に移譲する例を下記に示す。:
type Derived struct { BaseA BaseB } func ( this *Derived ) Function() { // 明示的にBaseAのFunction()に移譲する this.BaseA.Function() }
上記の例を使用したinterfaceへの代入例:
type Something interface { Function() } var derived Derived var something Something = &derived something.Function()
委譲による継承の特性 [編集]
C++などの従来の継承と異なりGoにおける継承はあくまで委譲の発展である。 この方式は、C++であれば基底クラスとなる型にあらゆる型を指定できるという特徴を持っている。 構造体の派生型は勿論の事、型を修飾したポインター型、interfaceも匿名フィールドとして 基底クラス代わりに使用出来る。この特徴によりBridgeパターンを簡単に実装でき、実装継承の 問題の一つである基底クラスが固定される問題を手軽に回避できる。
下記にGoによるBridgeパターンの例を示す。:
type Base interface { Function() } type Derived struct { Base } func ( this *Derived ) WithBase( base Base ) { this.Base = base }
呼び出し例:
// 新しい実装の定義 type Implement int func ( _ Implement ) Function() { } var derived Derived var implement Implement // 基底となる型の実装を置き換える derived.WithBase( &implement ) // ImplementのFunction()が呼び出される derived.Function()
アクセス指定子 [編集]
Goには、型レベルのアクセス指定子が存在しない。 Goにはprivateやprotectedに相当するクラスレベルの アクセス指定子が存在せず全てpublicとなる。 ただし、パッケージレベルでのアクセス制御が存在する。 パッケージレベルでのアクセス制限はシンボル名(変数名、メソッド名、 関数など)の先頭1文字が大文字か小文字かで決定する。 大文字であればパッケージに対しpublicであり、小文字であればprivateとなる。
リソース開放 [編集]
Goではデストラクターやfinally構文など従来の例外安全な強制実行構文を採用していない。代わりとしてdefer文という例外安全な強制実行のしくみを採用している。
deferはdeferキーワードに続けて、関数呼び出しかセレクターによる呼び出しを記述することで 関数終了時に指定した処理の呼び出しを実行する。
deferは下記の様に記述する。:
defer 関数呼び出しまたはセレクター呼び出し
deferは、finallyの様にブロックを持たず入れ子が深くなるようなことはない。ブロックを気にする必要がないため概ねデストラクターの様に使用できる。ただし、デストラクターと異なり関数以外のブロックを無視する点に注意が必要である。例えばループ中でdeferを使用した場合、ループ内では指定した関数が呼ばれず関数終了時にdeferで指定した全ての関数呼び出しが行われる。
deferには、関数呼び出しかセレクターによる呼び出ししか記述できず、式や文を直接記述することはできないが、下記のように無名関数を利用して、任意の式や文の実行も可能である。:
defer func() { message := "error" fmt.Println( message ) }()
例外処理 [編集]
冒頭でも述べたように、goは一般的な例外処理への使用を推奨しないものの、 例外処理用の専用構文を備えている。一般的な例外にはあくまで戻り値を使う。
C++のthrowに当たる構文としてはpanic関数がある。
panicは下記のように記述する:
panic( /* 各種値 */ )
panic関数の引数には、C++のthrowと同様、数値や関数、構造体といった 各種値を指定する事が出来る。Javaの様なThrowableクラスの派生型でなければ 使えないといった制限はない。 また、C++やその他のthrowと異なり、panicは組み込み関数である。 有用性は無いが、deferなど式が使えず関数呼び出ししか記述できない場所でも直接記述できる。
panic関数を実行するとthrow同様、panic呼び出し以降の処理を中断し、コールスタックを巻き戻す。 ただし、panic呼び出し以前にdeferされた関数やセレクターについては呼び出しが実行される。
goでは、C++のcatchに直接対応する構文は存在しない。recover関数と、 deferを組み合わせて類似した動作を実現する。recover関数は、 panic関数の引数を返し、コールスタックの巻き戻しを停止する。
recoverの使用例を下記に示す。:
func Example() { defer func() { /* recoverを呼んだ時点でコールスタックの巻き戻しが終了する */ cause := recover() fmt.Fprintln( os.Stderr, cause ) }() panic( "error" ); /* panic以降Example内の処理は実行されない */ } func main() { Example() /* Example()の呼び出し側はrecover関数が呼び出されたため処理を継続できる */ }
recoverは関数であるためdeferにdefer recover()と直接記述することもできる。 ただし、recoverが機能するのはdeferで呼び出した関数の内部だけであり、 deferに直接記述した場合は機能しない。
recoverはdeferで実行される関数内であればどこでも機能する。 これを利用し例外が発生する関数に直接recoverを記述せず、例外処理用関数を 用意してそこにrecoverを記述し、共通する例外処理を一つの関数にまとめる事もできる。 C++でも値を取らないthrowを記述する事で同様に処理をまとめることができるが記述が冗長となる。
C++ではデストラクター内で例外を投げるとcatchしようが無いため即時停止を招き危険である。 goのdeferとpanic関数/recover関数の組み合わせでは、deferで起きたpanicもrecoverで捕まえられるため類似する問題は発生しない。
動的変換 [編集]
Goはインターフェース型の値から、基底型の値(インターフェース型に変換される前の型の値)を動的に安全に得る「型アサーション」と呼ばれる機能を備えており、メソッドを持たない空のインターフェースを用いて簡単なジェネリックプログラミングが可能である。
// getValue の返値はどんな型でも構わない var i interface{} = getValue() /* i の基底型を string と仮定し、 実際にそうであれば s には変換結果が、 ok には true がセットされ、 そうでなければ s にはstring型の初期値(ゼロ値)が、 ok には false がセットされる。 以下の様に2つ目の返値を無視すると、i の基底型が string でなければ暗黙的にランタイムパニックとなる s := i.(string) */ if s, ok := i.(string); ok { fmt.Println("文字列: ", s) } else { var kind string // 型switch switch i.(type) { case int: kind = "数値" default: kind = "その他" } fmt.Printf("%s: %v\n", kind, i) }
標準パッケージのreflectを使うことで[7]、対象のプリミティブ型やその値などの詳細を得ることができる。
参考文献 [編集]
関連項目 [編集]
外部リンク [編集]
- Go言語公式ウェブサイト (英語)
- Go言語 FAQ (英語)
- The Go Programming Language - YouTube (英語)
- IRC : #go-nuts on irc.freenode.net (英語)
- メーリングリスト : http://groups.google.com/group/golang-nuts (英語)