技術戦略部より、tipsやノウハウなど技術的な話題を発信いたします。(2)golang開発の松尾です。前回は環境面について解説しましたが、今回はGo言語の文法と特徴について解説してみたいと思います。Go言語のシンプルさと特殊さGoは言語の文法という側面から見ると実に「シンプル」な言語です。型推論による型指定の省略、ループのための構文は"for"のみ、GCを備えメモリ管理が不要・・・・・・などなどプログラムを最低限のコード量で記述できるように設計されています。Goは他言語経験者の視線から眺めると少々変わった分かりにくい要素をいくつか備えています。「ポインタ型はあるがポインタ演算は無い」、「channelやmapやsliceといった特殊な組み込み参照型」、「変数の値がstackとheapのどちらに配置されるかはコンパイラが適切に決めてくれる」などなどと私自身もGoの言語仕様を理解する途上でいくつか躓くポイントがありました。このようにGoには「シンプルで分かりやすい」部分と「Go独特の言語仕様で分かりにくい」箇所が混在しています。このような性質を踏まえつつ言語仕様を見ていくことにしましょう。main()C言語で書かれたプログラムがmain関数より実行が開始されるように、またJavaの場合は指定したclass内に含まれるstaticなmainメソッドより実行が開始されるのと似て、Goはmainパッケージに定義されたmain関数より実行が開始されます。したがって下記のようなmain関数を含まないソースコードは実行できません。build"で実行可能ファイルを作成することができます。定数はconstを使って定義することができます。inner)}パッケージ下および関数内で定義することが可能です。様々な型を定数として定義することができます。ちなみに上記のコードでは日本語文字列を使用していますがGoが対応するマルチバイト文字列のリテラル表現はUTF-8エンコーディングに対応しています。(それ以外のエンコーディングである場合はコンパイル時のエラーとなります。)また、Goにはenum(列挙型)に該当する文法はありませんが、iotaを使うことでenumに似た定数定義を行うことができます。0}iotaの挙動はちょっと分かりづらいのですが「コンテキストが異なるiotaの値は0に戻る」(ソース内のX、Yの挙動)、「同じコンテキスト内では0から1ずつ増加する」、「iotaを使った式がそれ以下の定数に対して自動的に適用される」(ソース内のA-Eの挙動)あたりがポイントになります。reflectパッケージここで突然reflectパッケージについて説明します。reflectパッケージは主としてGoのリフレクション機能を提供します。いってもその機能は多岐に渡ってしまうのでここではreflectパッケージを利用することで変数の型情報を得ることができる利便性について解説します。Goの型システムには少々分かりづらいところがあるため、具体的に実行時の型がどのようになっているのかを得る手段を知ることが大変有用だと思われるためです。fmt.Println(reflect.TypeOf(a))}このプログラムの実行結果は下記のようになります。false)型指定の無い数値の定数はint型となるシングルクォーテーションで囲んだ「ルーンリテラル」はUnicodeの1文字を表す書き方でint32型となる組込みでstring型をもつ配列型は[3]intのように要素数も含めてひとつの型を表す上記のようなポイントを読み取ることができます。goでは「:=」の構文を使うことで型指定の省略を行うことができます。何らかのコードを読んでいて変数の実際の型を知りたいなどの場合にはreflectパッケージの機能が有効に利用できます。ひとつ気をつけるべきポイントがあります。goでは組込み型としてuint32(符号なし32bit)、int8(符号あり8bit)などの厳密な数値型が用意されているのですが、数値定数がデフォルトでとるint型ではプラットフォーム次第でその内容が異なることがあります。具体例を示すと、下記のコードは64bit環境であれば問題なく動作しますが、32bit環境ではコンパイルエラーとなります。fmt.Println(reflect.TypeOf(n))}ここまでは明示的にreflectパッケージの機能を使用してみましたが、goではfmt.Printfの書式に「%T」を指定することで同様の出力を得ることができます。3})}変数の型情報を見るには上記の書き方で十分でしょうが、goでは実行時に変数の型情報を取り出すことができるところがポイントです。Javaで言えばinstanceof演算子のような振る舞いを実現するのに利用することができます。先に型推論を利用した変数定義とreflect.TypeOfについて解説しましたが型指定も含めた変数の定義は下記のように書きます。fmt.Println(a)}CやJavaの経験者であれば変数名の後に型指定があるところや一見奇妙に見える配列型の定義箇所が少々気味が悪く感じるかもしれません。これがgoの構文ですので慣れましょう(笑)また、goの慣例として定義する変数名を「短く簡潔に」定義する傾向があります。Javaなどのように「長く具体的な」命名の慣習を持つ文化に馴染みが深いと違和感が大きいとは思いますが、郷に入っては郷に従え、できるだけ学ぼうとする言語の慣習で書くべきでしょう。それによってコードを読む際にも大いに役に立ちます。ポインタ型goにはCに良く似たポインタ型が用意されています。fmt.Println(*np)}「&変数名」でポインタの取り出し、「*変数名」で値の取り出しとCと大きく違うところはありません。goのポインタで一番重要なのは「ポインタ型はあるがポインタ演算が無い」という特性です。言語仕様からポインタ演算を取り除くことで得られる安全性を重視した結果とも言えるでしょう。ポインタについての説明は本気でやると際限なく長くなりそうなのでこの程度にとどめておきます。構造体(Struct)goにはCによく似た構造体の定義を行うことができます。m.age)}Cの構造体と異なってフィールドの初期化に便利なリテラルが利用できます。上記ではname、ageといったフィールドに対して変数の定義と同時に初期化を行っています。goの構造体の作成には「new([構造体型])」という書き方もあります。m.age)}newによる初期化は構造体の領域のみを作成して「構造体のポインタ型」を返すことに注意してください。上記のコードを実行するとMember型のフィールドnameは空文字列、ageは数値の0になっていることが確認できます。このような文法が用意されているのでしょうか?私見ですがプログラムにおいて構造体で定義されるデータ構造は比較的「長生き」する性質があります。Cにおいてヒープに作成された構造体のポインタを持ち回り各所で処理を行っていくなどの流れは極めて基本的な手順です。この「構造体を作成してそのポインタを取り出す」といった頻出するパターンを簡略化したのがこのnewによる構造体の作成の手法なのだと思われます。ちなみにgoでは実行時にオプションを指定してどの変数がヒープに格納されるかを確認する手段が用意されています。m.age)}上記2パターンを下記のオプションを付加して実行してみてください。異なる結果が得られます。下記はnewを使用したバージョンの実行例です。heap」の箇所からヒープに割り当てられたことが分かりました。goのプログラム内の変数はその参照範囲を解析することでスタックに配置されるかヒープに配置されるかが自動的に判別されてコンパイルされます。非常に便利な機能なのですが反面、知らずに書くと非効率的なコードを書いてしまう危険性をはらみます。最低限、データがスタックに置かれるかヒープに確保されるかについて多少なりとも意識的にコーディングする必要があります。構造体の出力についてはfmt.Printfに便利な書式が用意されています。age:37}構造体のフィールドとデータを一括して表示させたい場合に役に立つでしょう。関数goの関数定義についてはそれほど変わったところはありません。fmt.Println(power(10))}あまり他の言語に見られない文法としては関数が「多値」を返すように定義できるところでしょうか。c)}ちょっと無理矢理ですがint型の数値を3つ戻す関数の定義です。関数の呼び出し側の代入部分で分かるように_(アンダースコア)で変数名を省略して戻り値を無視することができます。goはC++やJavaのような例外機構は備えていません。多値で返すという下記のようなパターンが頻出します。file)}このようにgoのエラー処理はCにおけるエラー処理の不便さとJavaなどの例外処理のようなコストの高い仕組みの中間ぐらいを狙って設計されていることが分かります。forgoにおけるループのための構文はforのみと大変シンプルです。}}forにいくつかの書き方パターンがあるところが独特ですね。defergoの特徴的な構文にdeferがあります。deferで定義された関数呼び出しは実行されている関数の「脱出直前に呼び出される」という動作をします。fmt.Println("5")}Javaなどのtry-catch-finallyといった文法のfinallyに近い動作イメージです。オープンしたファイルを関数の完了時に確実にクローズするなどのリソース処理などを簡潔に記述するのに役に立ちます。continued長くなってきたので第2回めはここでいったん終わります。次回は少し分かりづらいgoの「参照型」や非同期処理のためのgoroutineという仕組みについて解説してみようと思います。