技術戦略部より、tipsやノウハウなど技術的な話題を発信いたします。(3)golang開発の松尾です。言語が異なると、同じ問題でも違った解決方法が採用されます。Year」といったペースは果たせていませんが、それでも複数の言語を学ぶ効力はそれなりに知っているつもりです。今年はKotlinあたりが楽しそうだなあと思いつつ。前回まででgoには値型と構造体型にそれぞれのポインタ型が用意されていることがわかりました。goにはさらに「参照型」と呼ばれる特殊な組込型が用意されています。不正確な表現になりますが単純化するとgoの参照型とは主として「make」を使って作成するデータ構造を参照する型になります。具体的には「map」と「slice」と「channel」という3種類の特殊なデータ構造が参照型として用意されています。mapgoのmapはいわゆる「ハッシュテーブルです」。JavaにおけるHashMap、RubyにおけるHashといったようにキーと値の組み合わせを複数格納するための便利なデータ構造ですがgoではmapという型でサポートしています。m[3])}「map[キーの型]値の型」という慣れていないと大変気持ちの悪い書き方に見えてしまいますが、任意の型を組み合わせたmap型を定義することができます。下記のコードのように使い勝手の良い初期化リテラルやforを使用したイテレーションもサポートされています。}}intなどの値型や構造体型はそのままで関数に引数で渡すとスタック上にコピーが発生して別の領域に格納されたデータとして扱われるため呼び出し側のデータそのものを参照させたい場合にはポインタ型を利用する必要がありました。参照型については他の関数にわたしてもコピーされることはなく参照としてそのまま引き渡すことができます。}}map型を定義する文法は少々奇妙な印象も受けますが、頻繁に使用されるデータ構造をできるだけシンプルに書きやすくなるように設計されていることがうかがえます。sliceわたしがgoに触れた時に一番混乱したのは「配列型とsliceの違い」でした。便利な仕組みではあるもののgoの言語仕様でもっとも混乱を招きやすいのは相変わらずこのあたりだと思っています。いくつかのsliceの例を見てみましょう。[...]で要素数を省略した配列型。b[0])}上記のコードで出てくるlen()は配列またはsliceの要素数を取り出すための組込み関数です。cap()は配列構造を格納するために用意されているcapacityを取り出すための組込み関数です。平易に「[5]int{...}」といった配列型を定義すれば、len=5、cap=5と要素数もキャパシティも同じ数値を戻します。上記のコード例の変数pのように配列aの一部分を参照するsliceは要素数、キャパシティともに異なることが確認できます。a[1:3]という書き方は配列型であるaのa[1]、a[2]に該当する範囲に限定されたsliceを作成します。従ってlen=2となります。errorを表示して終了します。len=2であるsliceの3番目の要素に値を書き込もうとしたためです。goの配列型は型が厳密であり可変長配列のような要素数が変動するような操作はできません。sliceであれば組み込み関数appendを利用することで可変長配列のような振る舞いを実現できます。cap(p))}上記のコードを実行した出力は下記のようになりました。length=2,capacity=4length=3,capacity=4length=6,capacity=8length=9,capacity=16appendで要素を追加するにしたがってlenの返す値が増加するのは見ての通りなのですが、あるタイミングでcapの返す値が大きくなっているのがわかります。ここで要素数とキャパシティは異なるものであることが理解できます。配列のサイズを表す要素数と配列の末尾に効率的に要素を追加するために予め確保されたメモリ領域のサイズを表すキャパシティという2つの要素があるのです。上記の実行例ではappendによってキャパシティを超える要素が配列に追加されるタイミングで元のキャパシティの倍の値に拡張されていることがわかります。(内部的にcap=4のメモリ領域から、cap=8のメモリ領域へコピーされていると想定できます)goのsliceは特に効率を気にしなければガンガンとappendを使用してスクリプト言語の配列のように柔軟な使い方が可能になっています。cap(s2))}当然のことながらappendによってキャパシティを自動的に増加させるのはそれ相応のコストがかかります。上記の小さなコード程度ではほとんどパフォーマンスに差は出ませんが、予め取り扱うデータ量が想定される場合はキャパシティを適切な値に設定することが重要になります。goにおける配列とsliceは全く違う構造を持っています。(これが最も混乱の元なのですが)文法上は比較的同じように操作できます。goでは自分が配列を扱っているのか、sliceを扱っているのかについてある程度意識的にコーディングする必要があります。channelchannelは並列処理間でデータの受け渡しを行うためのデータ構造です。いった「型」を受け渡すのかという情報と何個のデータをバッファリング可能かという「バッファサイズ」の2つの情報で構成されています。goはgoroutineという並列処理のための機構を備えています。channelは複数のgoroutine間のデータ共有のための中心的な存在です。goroutineは抜きにしてchannel単体で見てみましょう。channelはmapやsliceのようにmakeで作成します。n)}上記のコードはchannelを作成して、そのチャンネルに「<-」演算子を使って数値の1を書き込んでいます。次に「<-」演算子を使ってchannelに書き込まれた値を受け取って最後にその値を表示するという流れになっています。コンパイルは問題ないものの上記のコードは期待通りに動きません。2「全てのgoroutineが眠っている」=「デッドロックを検出」というエラーメッセージが表示されました。これは一体どういうことなのでしょうか?goはOSが提供するthreadとは異なるgoroutineを処理の単位として持ちます。goroutineを生成しないこの記事のコード例のような単純なコードでもmainが動作している以上はひとつのgoroutineが動作していることになります。1」とデータを入力した時点でその動作主体であるgoroutineが待ち状態になります。結果的にプログラムの実行状態に唯一存在するgoroutineが停止したため処理を進行させるgoroutineが無くなったことによりデッドロックと判断されました。n)}実験的にバッファサイズに1を設定したchannelに修正してみましょう。今度は意図通りに動作することが確認できるでしょう。channelからデータを取り出す前のバッファが埋まった状態でさらにchannelにデータを入力すると前段のコードと同様deadlockによってプログラムは停止してしまいます。channelへのデータの入力と同様にデータの受信時に受け取り可能なデータが存在しない場合はgoroutineが停止します。n)}channelは多数の並列処理間で安全にデータを共有する仕組みであり、かつ各並列処理の実行や停止を調整するロック機能も提供している機能であると言えるでしょう。goroutineさてgoroutineについて見てみましょう。goでは「goステートメント」を使って与えた関数を非同期で実行するgoroutineを作成します。World!」の出力が出たり出なかったりとまちまちな動作をします。*/}「goステートメント」には無名関数を与えることができます。int」であるchannelを作成しそれをgoroutineに与えて処理の完了の合図としてchannelに特に意味をなさない1という数値を入力しています。main関数のラストでchannelからデータを受信する箇所が結果的に作成したgoroutineの完了を待つ処理として動作します。マルチスレッド・プログラミングなどでよく見る同期処理とは趣が大きく異なりますが、goではchannelを駆使することで効率的な非同期処理を実現することができる仕掛けが施されていることがわかります。した応用ですが、goroutineとchannelを組み合わせることでPythonにおけるgeneratorのような処理も簡単に実現できます。102}生成したgoroutineは関数numsの動作を開始してコード内の「A」の位置で停止します。以後「<-generator」のようにchannelから読み出しを行うたびに処理を再開して「A」の位置で停止を繰り返します。このように協調して動作するコルーチン的な処理方法も簡単に実現できるところがgoの強みになっています。interfacegoでは型推論による型指定の省略によって大抵は変数の型を書かずに済む反面、関数の定義などでは厳密に引数の型を明示する必要があります。ここで疑問が湧いてきます。fmt.Printfのような関数は第1引数にフォーマット文字列をとり、残りを可変長引数としてbool型、int型、string型、構造体型などなどあらゆる型を渡すことができます。これはどういう仕組みで実現できているのでしょうか?論より証拠、fmt.Printfの定義を見てみましょう。os.Error)第1引数のformatは特に問題ありません。...」という書き方も可変長引数を配列として受け取るための書き方だと推定できます。可変長引数aの型指定のinterface{}とはどんな型なのでしょうか?説明を前にざっくりとした答えをいえばinterface{}はgoのありとあらゆる型を表す汎用的な型指定です。Go言語はとりたてて「オブジェクト指向」な言語ではありません。JavaのClassのような機能は無くJavaScriptのprototypeのような機能もありません。goには「interface」と「method」という機能は存在します。「interface」といってもJavaのそれとは大きく異なります。goのmethodとはどういうものでしょうか。fmt.Printf("%sです。m.greet()}Memberという構造体を定義しています。greetという挨拶を出力する関数を定義していますが関数名の前にMember型のselfという引数が定義されています。ここではMember型専用のメソッドgreetを定義していると読むことができます。JavaやRubyのようにレシーバをthisなりselfなりで参照できる言語しか触ったことがない場合は少々分かりづらいかもしれませんが、PerlやPythonのオブジェクト指向機能のようにmethodの第1引数にレシーバをとるタイプの言語であれば馴染みやすい書き方ではないかと思います。interfaceの説明に戻ります。上記でMember型へ定義したgreetメソッドをもつ型を表すinterfaceを作成してみましょう。fmt.Printf("%sです。fmt.Printf("グレード%sの%sだ。}}メソッドgreetを備えるinterfaceとしてGreetableという型を定義しています。ポリモルフィズムの参考としてMember型とは別の型としてMemberEx型を定義してみました。それぞれの型が内容の異なるgreetを実装しています。Javaのinterfaceと異なり各々の型が明示的にinterfaceをimplementsする必要はありません。interfaceで定義されたmethod定義を満たしている型であればinterface型へのダウンキャストが可能です。(interfaceの定義を満たしていない場合はコンパイル時にエラーになります)サンプルコードではGreetable型のsliceにMemberとMemberExの2つを並べていることで異なる型を共通するinterfaceでまとめることができることを示しています。このようにgoではinterfaceとmethodを使用してポリモルフィズムを実現することができます。interface{}という書き方が「特にメソッド指定のない空のinterfaceを表す」ことから結果として「全ての型を表す」という流れが見えてくるのではないでしょうか。例外処理goは他の言語で良く見るtry〜catch型の例外処理機構は備えていませんが、独特な大域脱出のための仕組みを持ちます。World!")}組込み関数panicにエラーメッセージを渡すとそこで関数の実行が停止します。World!」は出力されずにランタイムエラーを出力してプログラムが停止します。World!」の出力後にランタイムエラーでプログラムが終了します。panicによる関数の巻き戻しを停止させるには組込み関数recoverを使用します。原則としてrecoverはdeferによって遅延実行させなければ意味を持ちません。panic("error!")}deferにrecoverを使用してpanicによるエラーを補足しつつ関数の巻き戻しを停止させています。fmt.Println(err)}特に意味を持ちませんがrecoverはどこに書いても動作しますが、deferによるpanicが起きたコンテキスト以外では単にnilを返します。終わり3回に渡ってgoを書く上でポイントになりそうなところをまとめてみました。初めはもう少し簡単に書けるかと思ったのですがけっこう大変でした。プログラミング言語を解説するって難しいですね。ラクーンはメインでJava、PHP、最近Rubyを導入しつつといった環境でサービスを展開していますが、開発メンバーに対してどういう説明の仕方をすれば効率的に他言語を学ぶことができるだろうか?という試行錯誤の果てに初心者向けでもなければ上級者向けでも無い個人的には「職業プログラマ向け」というスタンスで書いた解説になっているつもりです。不正確なところや誤解を招きそうなところがありましたらご指摘いただければ幸いです。