F#のリストや{ }が絡む式のコーディング規約

# F#のフォーマット

F# にはフォーマットのガイドライン( [GitHub](https://github.com/fsprojects/fantomas/blob/master/docs/FormattingConventions.md) | [MSDN](https://docs.microsoft.com/ja-jp/dotnet/fsharp/style-guide/formatting) )がありますが、公式の翻訳がおかしかったり、一部に曖昧さを残している点があります。この記事はその中でも特にMSDNの記述に曖昧さが残る以下の式のコーディング規約を調べてきたので記事にしました。
プロジェクトで統一したコーディング規約を設定したい！という際に参考にして下さい。

- レコード (record)
- リスト (list)
- 配列 (array)
- シーケンス式 (seq式 / sequence expression)
- コンピュテーション式 (computation expression)
- オブジェクト式 (object expression)

原則としてこれらの式には**共通のコーディング規約**が適用できます。

## 式が十分に短い場合は1行で書く

基本ルールです。F#では**情報量の少ない行は嫌われます。**
共通ルールとして、波括弧 `{` `}` や角括弧 `[` `]` の内側には1つスペースをあけます。[^0]

```fsharp:1行フォーマット
// レコード
{ X = 10; Y = 20 }

// リスト(配列も同じ)
[ 1; 2; 3; 4 ]

// シーケンス式(コンピュテーション式も同じ)
seq { for i in 1..10 -> i * 5 }

// オブジェクト式 (MSDNには1行の例がある。個人的には非推奨)
{ new IDisposable with member _.Dispose() = printfn "Bananas!" }
```

## 長い場合は複数行で書く

ややこしいのはここから。
最初に書いたようにF#のフォーマットガイドには [Anh-Dung Phan が書いた GitHub上にある基本ガイド](https://github.com/fsprojects/fantomas/blob/master/docs/FormattingConventions.md)と、[それを元に拡張されたMSDNのガイド](https://docs.microsoft.com/ja-jp/dotnet/fsharp/style-guide/formatting) が存在するのですが、 **それぞれで内容が異なります。**

### 複数行の書き方(Anh-Dung Phan式)

- 開き括弧の前に必ず改行
- 開き括弧の次に1つスペースをあけて最初のトークンを配置
- その後の行はインデントを最初のトークンに合わせる[^1]
- **閉じ括弧は最後のトークンと同じ行に、1つスペースをあけて配置**

ただし、**1行目にwithがある式**ではインデントを最初の行に揃えずに1段深くします。

```fsharp
// レコード(各フィールドごとに必ず改行する)
{ X = sin v + cos w
  Y = cos v - sin w
  Z = v * v + w * w }
//^インデントはこのラインに合わせる

// NG: 同じ行に2つのフィールドを書かない
{ X = sin v + cos w; Y = cos v - sin w
  Z = v * v + w * w }

// NG: 前の行の続きから書かない
let point = { X = sin v + cos w
              Y = cos v - sin w
              Z = v * v + w * w }

// リスト(1要素ごとか、意味のあるまとまりごとに改行する)
[ SuperLongLongColorName.Red
  SuperLongLongColorName.Green
  SuperLongLongColorName.Blue ]

// 配列の場合、2行目以降のインデントはスペース3個分になる
[| { Id = 0; Color = "Red" }
   { Id = 1; Color = "Green" }
   { Id = 2; Color = "Blue" } |]

// レコードwith式やオブジェクト式では、インデントを1行目に揃えずに1段深くする
// (これらの式はwithの後に1段深いインデントを必要とするため)
{ point with
    X = sin v + cos w
    Y = cos v - sin w }

{ IDisposable with
    member _.Dispose() = printfn "Bananas!" }
```

**6行以上になる場合**は閉じ括弧を新しい行に配置してもよいです。

```fsharp
{ Color1 = "Red"
  Color2 = "Orange"
  Color3 = "Yellow"
  Color4 = "Green"
  Color5 = "Cyan"
  Color6 = "Blue"
  Color7 = "Magenta"
}
```

なお、こちらのガイドラインにはシーケンス式やコンピュテーション式に関する記述はありません。
このフォーマットのメリットは**行数が少なくなること**ですが、**中途半端なインデントが生まれる**デメリットもあります。

### 複数行の書き方(MSDN式)

MSDNでは、Anh-Dung Phan式の書き方も提示した上で、多段インデントを含む場合や関数に適用する場合は次のようなフォーマットを推奨しています。

* 開き括弧の後( `with` 句があるならその後)に改行
* インデントを1段深くして最初のトークンを配置する
* **閉じ括弧は新しい行に配置し、開き括弧がある行とインデントを揃える**

```fsharp
// レコード
{
    X = sin v + cos w
    Y = cos v - sin w
    Z = v * v + w * w
}

// レコードのwith式
{ point with
    X = sin v + cos w
    Y = cos v - sin w
}

// リスト(配列も同じ)
[
    SuperLongLongColorName.Red
    SuperLongLongColorName.Green
    SuperLongLongColorName.Blue
]

// シーケンス式・コンピュテーション式
seq {
    for i in superLongLongNameParameter do
    if i % 2 = 0 then
        i * 10 + superLongLongNameParameter.Length
}

// オブジェクト式
{ new IDisposable with
    member _.Dispose() = printfn "Bananas!"
}
```

これらの式が**十分短い関数の最後の引数**である場合、開き括弧は**前の行の最後**に配置します。[^2]
シーケンス式やコンピュテーション式もこのパターンの扱いです。

```fsharp
Some {
    X = sin v + cos w
    Y = cos v - sin w
}

// 関数が長い・多い場合はパイプ演算子を使う
{
    X = sin v + cos w
    Y = cos v - sin w
}
|> superLongLongFunctionName
|> HyperLongLongType.LongNameMethod
```

こちらを使うメリットは**中途半端なインデントが生まれない**ことです。そのため、これらの式の中に更に深いインデントを含む場合にコードが書きやすくなります。[^3]
ただし、Anh-Dung Phan式に比べて最大2行増えるというデメリットもあります。

```fsharp:多段インデントを含む例
// MSDN式の書き方なら、下式のValueのようにインデントが深くなっても書きやすい
{
    X = sin v + cos w
    Y = cos v - sin w
    Value =
        if w > 0.0 then
            cos v * cos w
        else
            sin v * sin w
}
```

### 結局どっちがいいのか?

**好みです。** が、多人数開発でコーディング規約として使うなら**他言語使いから見ても親しみやすいMSDN式**をお勧めします。
私も基本的にはMSDN式で書きますが、「ブロックの最後の行がこれらの式だけて構成されていて、各行が十分に単純な場合」にはAnh-Dung Phan式も使います。

また、この2つを混ぜたようなフォーマットはやめたほうがいいでしょう。少なくとも**開き括弧か閉じ括弧のどちらかは行頭にあるべき**です。

```fsharp
// 開き括弧も閉じ括弧も行頭にない書き方はやめたほうがいいと思う
Some {
    X = sin v + cos w
    Y = cos v - sin w }
```

## まとめ

コーディング規約は書きやすく読みやすいように決めましょう。

[^0]: 角括弧の内側にスペースをあけるのはMSDNのみの記述です。

[^1]: このため、中途半端なインデントが生まれることがあります。

[^2]: MSDNの表記が特に曖昧で、原文では「値を括弧なしでoption型にラップするなどの一部の特殊なケースでは、…」となっています。具体的すぎるやろ。

[^3]: 大抵のコードエディタやIDEはインデント数を4の倍数とか3の倍数に揃えるので、7文字のような中途半端なインデントは書きにくいです。