diff --git a/part10/chap66/chap66.md b/part10/chap66/chap66.md index e69de29..93f1029 100644 --- a/part10/chap66/chap66.md +++ b/part10/chap66/chap66.md @@ -0,0 +1,319 @@ +## 66. Stanプログラムスタイルガイド + +この章では、Stanのモデルをレイアウトするときに好まれるスタイルについて記述します。 +これは言語の規則ではなく、単純に、テキストエディターでプログラムをレイアウトする際の推奨事項です。 +こうした推奨事項は恣意的に見えるかもしれませんが、多くのプログラミング言語について多くのチームがとっているものと同様のものです。 +テキストをタイプセットするときの規則と同じように、目的は、垂直的あるいは水平的に無駄な空白文字を置かずに、可読性を獲得することにあります。 + +### 66.1. 一貫したスタイルを選択する + +スタイルのもっとも重要な点は一貫性です。 +一貫したコーディングスタイルは、ひとつのプログラムを読みやすくするだけではなく、複数のプログラムをも読みやすくします。 +ですから、このスタイルガイドから出発するにあたり、第1に推奨することは一貫することです。 + +### 66.2. 行の長さ + +行の長さは80字を超えないようにします。^[80字でも、印刷のためにレンダリングするのには多すぎるかもしれません。たとえばこのマニュアルでは、1行におさまるコードの文字数は65字ほどです。] +これは、多くのプログラミング言語のスタイルガイドで普通に推奨されているものです。その理由は、テキスト編集のウインドウを横に並べてレイアウトしやすくなることと、ウェブ上で折り返しなしで表示しやすくなること、バージョンコントロールで差分を見やすくなることなどです。 +これにより犠牲になるのは、式を1行でレイアウトすることくらいです。 + +### 66.3. ファイル拡張子 + +Stanのモデルファイルに推奨されるファイル拡張子は`.stan`です。 +Stanのデータダンプファイルでは、推奨される拡張子は`.R`あるいはもっと情報を持たせるなら`.data.R`です。 + +### 66.4. 変数名のつけ方 + +推奨される変数名のつけ方はC/C++の名前づけの慣習に従います。すなわち、変数名は小文字で、セパレーターにはアンダースコア文字(`_`)を使います。 +したがって、一緒にした`sigmay`や、キャメルケースの`sigmaY`、先頭が大文字のキャメルケースの`SigmaY`ではなく、`sigma_y`が好まれます。 +行列の変数でも小文字にすべきです。 + +例外として、小文字を推奨しないものは、これもC/C++の慣習と同様、サイズ定数です。この場合の推奨される形式は大文字1文字です。 +その理由は、ループ変数と合わせることができるからです。 +そのようにすると、$M \times N$次元の行列$a$のインデックスについてのループは以下のように書けます。 + +``` +for (m in 1:M) + for (n in 1:N) + a[m,n] = ... +``` + +### 66.5. 局所変数のスコープ + +使用するブロックで局所変数を宣言すると、プログラムが理解しやすくなります。それにより、テキストを見渡す量や、宣言と定義を再統合するのに必要な記憶を減らすことができるからです。 + +以下のStanプログラムは、BUGSモデルをそのまま移植したものに相当します。各iterationごとに異なる`mu`の要素を使っています。 + +``` +model { + real mu[N]; + for (n in 1:N) { + mu[n] = alpha * x[n] + beta; + y[n] ~ normal(mu[n],sigma); + } +} +``` + +Stanでは変数の再利用ができますし、明晰さのためには局所的に宣言すべきですから、このモデルは下のようにコーディングし直すべきでしょう。 + +``` +model { + for (n in 1:N) { + real mu; + mu = alpha * x[n] + beta; + y[n] ~ normal(mu,sigma); + } +} +``` + +この局所変数は、以下のようにまったくなくすこともできます。 + +``` +model { + for (n in 1:N) + y[n] ~ normal(alpha * x[n] + beta, sigma); +} +``` + +後の2つの実装に関しては効率性にはほとんど差はないでしょうが、双方ともBUGSの移植よりは多少効率的でしょう。 + +##### 成分ごとの代入のある複合構造のスコープ + +配列やベクトル、行列のような複合構造の局所変数の場合で、大きなまとまりではなく成分ごとに代入されるときには、その構造の局所変数を、使われるブロックの外側で宣言するとより効率的にできます。 +これにより、1度だけ割り当てが行なわれ、その後は再利用されるようになります。 + +``` +model { + vector[K] mu; + for (n in 1:N) { + for (k in 1:K) + mu[k] = ...; + y[n] ~ multi_normal(mu,Sigma); +} +``` + +この場合、ベクトル`mu`は2つのループの外側で割り当てが行なわれ、全部で`N`回使われます。 + +### 66.6. 括弧類 + +#### 1文のブロックでは括弧は任意 + +1文だけのブロックは、2通りの方法で書くことができます。 +明示的に完全に括弧をつける方法が以下です。 + +``` +for (n in 1:N) { + y[n] ~ normal(mu,1); +} +``` + +以下の括弧なしでも同じです。 + +``` +for (n in 1:N) + y[n] ~ normal(mu,1); +``` + +1文だけのブロックは1行で書くこともできます。以下はその例です。 + +``` +for (n in 1:N) y[n] ~ normal(mu,1); +``` + +後の2つは最初の例よりもずっと読みにくくなることがあります。 +このスタイルを使うのは、この例のように、文が非常に単純な場合だけにしましょう。 +同じような文がたくさんあるのでなければ、サンプリング文すべてをそれぞれ独立した行にする方がたいていはより明晰です。 + +条件文とループ文も括弧なしで書けます。 + +forループを括弧なしで使うのは危ないことがあります。 +たとえば、以下のプログラムを見てみましょう。 + +``` +for (n in 1:N) + z[n] ~ normal(nu,1); + y[n] ~ normal(mu,1); +``` + +Stanは空白文字を無視しますし、パーサーは(C++とまったく同様に)できるだけエラーにならないように文の解釈を完了させますから、上のプログラムは下のプログラムと等価になります。 + +``` +for (n in 1:N) { + z[n] ~ normal(nu,1); +} +y[n] ~ normal(mu,1); +``` + +#### ネストした演算子式の括弧 + +演算子について好まれるスタイルは、括弧を最小限にするものです。 +これにより、実際に式を読みにくくする可能性のある、コード中の雑然さが減ります。 +たとえば、`a + b * c`という式の方が、等価な`a + (b * c)`や`(a + (b * c))`よりも好まれます。 +演算子の優先順位と結合性は図4.1に示してあります。 + +同様に、比較演算子も通常は括弧を最小限にして書けます。 +たとえば、`y[n] > 0 || x[n] != 0`という形式の方が、`(y[n] > 0) || (x[n] != 0)`という括弧を使った形式よりも好まれます。 + +#### 開く波括弧は独立した行にしない + +縦方向の空白は、プログラムのどのくらいの量が見えるのかを制御するのに役立ちます。 +Stanで好まれるスタイルは前の節にあったようなものです。以下のようなものは好まれません。 + +``` +for (n in 1:N) +{ + y[n] ~ normal(mu,1); +} +``` + +これは、`parameters`ブロックや`transformed data`ブロックなどについても同様で、以下のようにします。 + +``` +transformed parameters { + real sigma; + ... +} +``` + +### 66.7. 条件 + +Stanは、C++スタイルの条件文を完全にサポートし、以下のように、実数あるいは整数の値を条件に使うことができます。 + +``` +real x; +... +if (x) { + // xが0に等しくなければ実行 + ... +} +``` + +#### 論理値以外の条件での明示的な比較 + +比較の結果や論理演算で生成されたのではない整数あるいは実数の値については、以下のように明示的に条件を書きだすのが好まれる形式です。 + +``` +if (x != 0) ... +``` + +### 66.8. 関数 + +関数は、JavaやC++のような言語と同様にレイアウトされます。以下はその例です。 + +``` +real foo(real x, real y) { + return sqrt(x * log(y)); +} +``` + +返り値は左寄せで、引数を囲む括弧は、引数および関数名に隣接して置きます。1番目以降、引数のコンマの後にはスペースを1つ入れます。 +関数本体の開く波括弧は、ループや条件文のレイアウトに従って、関数名と同じ行に置きます。 +本体自身にはインデントを付けます。ここではスペース2個を使います。 +閉じる波括弧は独立した行に置きます。 +関数名あるいは引数のリストが長いときには、以下のように書くことができます。 + +``` +matrix +function_to_do_some_hairy_algebra(matrix thingamabob, + vector doohickey2) { + ...本体... +} +``` + +この関数は、型の下、新しい行から始まります。 +引数は互いにそろえるようにします。 + +関数のドキュメンテーションは、JavadocおよびDoxygenのスタイルに従うようにします。 +以下は、23.7節の例と同じものです。 + +``` +/** + * 指定されたサイズのデータ行列を返す。行は項目に対応する。 + * 最初の列は、切片を示す値1で埋められる。 + * 残りの列は、標準正規分布からランダムに抽出した値で + * 埋められる。 + * + * @param N データの項目に対応する行の数 + * @param K 項目ごとの、切片を含めた予測変数の数 + * @return シミュレートされた予測変数の行列 + */ +matrix predictors_rng(int N, int K) { + ... +``` + +コメントを開くのは`/**`で、そのコメントの最初のアステリスクにそろえて、その下のアステリスクを並べます。コメントの終わりの`*/`もそのアステリスクにそろえます。 +`@param`タグと`@return`タグは関数の引数(すなわちパラメーター)と返り値をラベルするのに使われます。 + +### 66.9. 空白文字 + +Stanでは、プログラムの要素の間にスペースを置くことができます。 +Stanのプログラムで認められる空白文字には、スペース(ASCII `0x20`)、ラインフィード(ASCII `0x0A`)、キャリッジリターン(`0x0D`)、タブ(`0x09`)が含まれます。 +Stanでは、すべての空白文字を交換可能と扱います。空白文字がどれだけ連なっていても構文上は1個の空白文字と等価です。 +しかし、空白文字を効果的に使うことは上手なプログラムレイアウトの鍵となるものです。 + +#### 文および宣言の間には改行 + +以下の例のように、複数の文あるいは宣言を同じ行に書くことは好まれません。 + +``` +transformed parameters { + real mu_centered; real sigma; + mu = (mu_raw - mean_mu_raw); sigma = pow(tau,-2); +} +``` + +別々の4行に分けるべきです。 + +#### タブは使わない + +Stanのプログラムにはタブを含めるべきではありません。 +タブは違反にはなりませんし、ほかの空白文字を置けるところではどこでも使うことはできます。 +プログラムをレイアウトするのにタブを使うと非常に可搬性が悪くなります。理由は、1個のタブがスペース何個分になるかが、レンダリングを行なうプログラムとその設定に依存して変わるからです。 + +#### 2文字のインデント + +Stanではインデントに2個のスペースを使うよう標準化しています。これは、C/C++のコードでの標準的な慣習です。その他の実用的な選択肢としてはスペース4個がありますが、これはJavaとPythonでの慣習です。一貫するようにしてください。 + +#### `if`と条件の間のスペース + +`if`の後には1個のスペースを使います。たとえば、`if (x < y) ...`として、`if(x < y) ...`とはしません。 + +#### 関数呼び出しにスペースを入れない + +関数名と、適用する関数との間には空白は入れません。たとえば、`normal(0,1)`として、`normal (0,1)`とはしません。 + +#### 演算子の両側のスペース + +2項演算子の両側にはスペースを入れるべきです。たとえば、`y[1]=x`ではなく、`y[1] = x`を使います。`(x+y)*z`ではなく、`(x + y) * z`は使います。 + +#### 行をまたぐ式での改行 + +ときどき、式が長すぎて1行におさまらないことがあります。 +その場合に推奨される形式は、演算子の**前**で改行することです^[これは、タイプセッティングでも、ほかのプログラミング言語でもあまりない慣習です。RでもBUGSでも、式あるいは文の終端を示すものとして改行が認められていますから、演算子の前で改行することはできません。]。そしてスコープがわかるように演算子をそろえます。 +たとえば、以下のような形式を使います(内容は参考にしないでください。逆行列を取るのはたいていは悪いアイデアです)。 + +``` +target += (y - mu)' * inv(Sigma) * (y - mu); +``` + +この場合、積中の被乗数がはっきりわかるように乗算演算子(`*`)をそろえるようにします。 + +関数の引数では以下のように、コンマの後に改行し、次の引数を下でそろえます。 + +``` +y[n] ~ normal(alpha + beta * x + gamma * y, + pow(tau,-0.5)); +``` + +#### コンマの後のスペースは任意 + +任意ですが、明晰さのため、関数の引数のコンマの後でスペースを使ってもよいでしょう。たとえば、`normal(alpha * x[n] + beta,sigma)`は`normal(alpha * x[n] + beta, sigma)`とも書けます。 + +#### Unixの改行文字 + +可能なら、Stanのプログラムでは行の区切りには1個のラインフィード文字を使うべきです。 +Stanの開発者は全員(今のところ、少なくとも)Unixライクなオペレーティングシステムを使っており、標準の改行を使うことで、プログラムを読んだり、共有したりするのがより楽になります。 + +##### 改行文字のプラットホーム依存 + +改行を示すのは、LinuxやMac OS XといったUnixライクなオペレーティングシステムでは1個のラインフィード(LF)文字(ASCIIコードポイントは`0x0A`)です。Windowsでの改行は、キャリッジリターン(CR)文字(ASCIIコードポイントは`0x0D`)の後にラインフィード(LF)文字の2文字を使って示します。