Haskell - 個人的な(そして異端的な)コーディングスタイル

Haskell - 個人的な(そして異端的な)コーディングスタイル
=================================================

はじめに
-------

書くときの効率、美しさ、読みやすさを考えながら、コードを書いています。その結果として、実を結んだ、個人的なコーディングスタイルを紹介します。

この文書について
--------------

この文書は拙書[「Haskell - 教養としての関数型プログラミング」](http://amzn.asia/4vNqxxP)の「補足資料D 個人的なコーディングスタイル」の内容を公開したものです。

どこが異端的か
------------

Haskell界では、OS間での可搬性を重視してか、「タブを使わないようにしよう」という「紳士協定」がある。それに対して、Linux文化に長いことひたっている著者は、すくなくとも、「タブを使うかどうか、自分で決めさせてくれ」というスタンスをとる。

それ以外については、まあまあ、妥当で穏当なことを書いた。

字下げ
-----

8文字タブを使います。これについては、Linuxカーネルの創始者である、リーナスの言葉を引用しておきます。彼は、つぎのように、言います。

> Tabs are 8 characters, and thus indentations are also 8 characters. There are heretic movements that try to make indentations 4 (or even 2!) characters deep, and that is akin to trying to define the value of PI to be 3.
>
> タブは8文字なので、字下げも8文字だ。字下げを4文字に(さらには、2文字に!)しようという、異端者たちも、いるようだけど、これは、円周率を3にするようなものだ。
>
> Rationale: The whole idea behind indentation is to clearly define where a block of control starts and ends. Especially when you've been looking at your screen for 20 straight hours. you'll find it a lot easier to see how the indentation works if you have large indentations.
>
> 論理的根拠 - そもそも、字下げってのは、ブロックのはじまりとおわりとを、はっきりさせるためのものだ。とくに、20時間ぶっつづけで、モニタを見つめたあとなら、大きな字下げが役に立つことが、よくわかるだろう。
>
> Now, some people will claim that having 8-character indentation makes the code move too far to the right, and makes it hard on a 80-character terminal screen. The answer to that is that if you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
>
> ここで、「8文字の字下げだと、コードが右によりすぎて、80文字の画面で読みにくくなる」と、文句を言う人がいるかもしれない。答えはこうだ。「もし、3段階よりも深い字下げが必要だとしたら、どっちにしたって、コードはぐちゃぐちゃになってるんだから、コードを直すべきだ」。
>
> In short, 8-char indents make things easier to read, and have the added benefit of warning you whern you're nesting your functions too deep. Heed that warning.
>
> つまり、8文字字下げはコードを読みやすくして、さらに、関数を深くネストしすぎたときの、警告にもなるってことだ。警告には、耳をかたむけよう。

これで、言いつくされています。GHCでは、宗教戦争を避けるために、「タブ文字を使わないようにする」という方向での、解決を図っているようです。

4文字字下げ派に気をつかった、公平な解決というつもりなのでしょうが、そもそも、タブを本当に必要としているのは、8文字字下げ派です。4文字字下げ派にとっては、タブをスペースに置き換えることに、大きな問題はないはずです。しかし、8文字字下げ派にとっては、大きな問題です。タブを1回打ちこむ代わりに、スペースを8回打ちこまされることを、考えてみましょう。エディタの設定を変えればいいと言うかもしれません。それでは、字下げを浅くするときのことを、考えてください。「タブ」は8文字字下げ派のために、とっておくべきです。

いまのところは、つぎのオプションで、なんとかしておきましょう。

    -fno-warn-tabs

スペース
-------

### 演算子の前後

演算子の前後には、スペースを置きます。これは、とくに、Haskellでは必要です。Haskellでは、つぎの表記は、「変数valueに修飾名Someがついたもの」です。

```hs
Some.value
```

それにたいして、つぎの式は、「値構築子Someと関数valueとが、関数合成されたもの」と、解釈されます。

```hs
Some . value
```

「演算子(.)では、前後のスペースが必須である場合がある」と、いうことです。これとの統一のために、演算子の前後に、スペースを置くべきでしょう。

### 演算子の部分適用

演算子の部分適用のときにも、おなじように、演算子と値とのあいだにスペースを置いて、つぎのようにします。

```hs
(3 +)
(/ 8)
```

### カンマのあと

カンマのまえには、スペースを置きません。カンマのあとには、スペースを置きます。これについては、英文タイプのルールにしたがうということです。

```hs
["Hello", "Haskell", "Welcome", "to", "Haskell"]
```

ただし、例外があります。この本では解説しませんでしたが、言語拡張TupleSectionsを使うと、タプルの部分適用ができます。タプルの部分適用のときには、つぎのようにします。

```hs
(3 ,)
(, 4)
```

演算子の部分適用との、統一感を出すために、カンマのまえにスペースを置きます。

### 括弧のまえ

括弧のまえには、スペースを置きます。ほかの言語では、「関数適用」を意味するために、つぎのように、関数と括弧とを、くっつけます。

```hs
f(x)
```

しかし、Haskellでは、関数適用に括弧は必要なく、つぎのように書けます。

```hs
f x
```

括弧をつけるのは、結合のしかたを変えるためです。つぎの例を見てみましょう。

```hs
f (g x)
```

このように、「値gに、関数fを適用する」のではなく、「値xに、関数gを適用する」ということを、示すためです。よって、括弧のまえに、スペースがあるほうが、論理的な統一感があります。

### 括弧のなか

基本的に、開き括弧のあとや、閉じ括弧のまえには、スペースを入れません。たとえば、つぎのようにはしません。

```hs
( 3, 4 )
```

つぎのようにします。

```hs
(3, 4)
```

ただし、例外がみっつあります。

#### レコード構文

レコード構文については、開き括弧のあとと、閉じ括弧のまえに、スペースを入れます。理由は、とくにありません。個人的な感覚ですが、つぎの、ふたつを比較すると、うえのかたちのほうが、きれいな感じがします。

```hs
Some { slot1 = value1, slot2 = value2, slot3 = value3 }
Some {slot1 = value1, slot2 = value2, slot3 = value3}
```

したのかたちだと、すこし窮屈な感じがします。

#### 内包表記

リスト内包表記などでも、開き括弧のあとと、閉じ括弧のまえに、スペースを入れます。つぎの、ふたつを見てみましょう。

```hs
[ f x y | x <- [1, 2, 3], y <- [4, 5, 6] ]
[f x y | x <- [1, 2, 3], y <- [4, 5, 6]]
```

したのかたちだと、すこし窮屈な感じがします。

#### 複数行に、またがる括弧

リストの要素が多くなったときなどに、つぎのようにします。

```hs
list1 = [
        item1,
        item2,
        item3,
        item4 ]
```

ここで、閉じ括弧の行を変えると、閉じ括弧だけの行ができてしまい、無駄な感じがします。かといって、スペースを入れずに、おわりの要素とくっつけると、「改行によって、はじめの要素と、大きくわけられている開き括弧」にたいして、バランスが悪くなります。よって、すこしでも、バランスをとるために、スペースでわけることにしています。

1行の幅
------

1行の幅は、80文字までとします。歴史的な理由ではありますが、まあまあ、妥当なところかと思います。さらに、本当ならば、あとの編集にそなえて、あらかじめ、75文字くらいで、改行しておいたほうがいいでしょう。

演算子と改行
-----------

### 通常の演算子

ひとつの式のなかで、改行が必要になったときには、通常の演算子については、演算子が行のおわりにくるようにします。

```hs
hello = "Hello, hello, hello, hello, hello, hello, ..., " ++
        "world!"
```

### 関数適用や、それに類似した機能をもつ演算子

関数適用や、それに類似した機能をもつ演算子については、演算子が行のはじめにくるようにします。

```hs
foo = fun1
        . fun2
        . fun3
        $ fun4 x

bar = monad1
        >>= monad2
        >>= monad3
        >>= monad4

baz = fun
        <$> app1
        <*> app2
        <*> app3
        <*> app4
```

このような演算子では、「適用の連鎖」や「処理の流れ」を、あらわすことが多いので、このような書きかたのほうが、読みやすく感じます。

変数名
------

### 変数名の長さ

スコープのせまい変数には、短い名前をつけます。スコープのひろい変数には、長い名前をつけます。モジュールから、そとの世界に公開する変数には、できるだけ、4文字以上の名前をつけます。引数など、関数のなかでローカルな変数には、できるだけ、3文字以下の名前をつけます。モジュールのなかを、スコープとする変数には、長い名前と短い名前とを、場面に応じて使いわけます。

### 複数の単語からなる変数

「複数の単語から、できている変数」を表現するのに、スネークケースを使うか、キャメルケースを使うかも、長年のテーマです。スネークケースとは、つぎのような、書きかたです。

```hs
some_long_variable
```

キャメルケースは、つぎのように、なります。

```hs
someLongVariable
```

Haskellでは、おもにキャメルケースを、採用しています。しかし、スネークケースのほうが、美しく思える場面も、ときどき、あります。いまのところは、それぞれの場面によって、使いわけています。個人的には、「キャメルケースを基本として、イレギュラーなことをするときに、スネークケースを使う」という、使いわけをしています。たとえば、C言語の関数を呼ぶときに、つぎのような名前をつけるなどです。

```hs
c_someFunction
```

演算子($)の運続
--------------

演算子($)が、つぎのように、連続することがあります。

```hs
f $ g $ h $ ... y $ z a
```

その場合には、つぎのように、おわり以外を、演算子(.)に置き換えます。

```hs
f . g . h . ... y $ z a
```

見ための美しさと、意味論的にも「関数fからyまでを、関数合成したもの」を「値aに、関数zを適用したもの」に適用するというほうが、きれいなように思います。つぎのように、しないのは、演算子の数が増えて冗長になるからです。

```hs
f . g . h . ... y . z $ a
```

複数の関数をまとめて型宣言するとき
------------------------------

たとえば、つぎのように、関数について、まとめて型宣言することを考えます。

```hs
f, g, h :: Char -> Bool -> Integer
```

すべての関数が1行におさまれば、つぎのように、行間をあけずに定義します。

```hs
f, g, h :: Char -> Bool -> Integer
f c b = ...
g c b = ...
h c b = ...
```

ひとつでも、複数行にまたがる関数定義があれば、つぎのように、関数定義のあいだ、すべてに、1行ずつ空行を入れます。

```hs
f, g, h :: Char -> Bool -> Integer
f c b = ...

g c b = ...
        ...

h c b = ...
```

where節
-------

where節のなかみが、1行で80文字におさまるときは、つぎのように、改行をせずに、予約語whereのあとに、そのまま、変数などの定義を続けます。

```hs
fun x = ...
        where y = ...; z = ...
```

1行にすると80文字におさまらないときは、つぎのように、改行したうえで、続く、変数などの定義について、予約語whereとインデントをそろえます。

```hs
fun x = ...
        where
        y = ...
        z = ...
```

let-in式
--------

let-in式は、1行で80文字におさまるなら、そのまま、つぎのように続けます。

```hs
let x = ...; y = ... in exp
```

1行にすると80文字におさまらないときは、予約語letにタブを続けて、つぎのように、ローカル変数の定義と、予約語inに続く式の、インデントをそろえます。

```hs
let     x = ...
        y = ... in
        exp
```

予約語letを含む行が、8文字以上あるときは、つぎのように、予約語letのあとで改行したうえで、インデントを一段、深くします。あとは、おなじように、インデントをそろえます。

```hs
some = let
        x = ...
        y = ... in
        exp
```

ワイルドカードを使う/使わない
--------------------------

### ワイルドカードを使う

#### 変数を、あとで使わない

そのあとの式で使うことのない変数を、パターンマッチのところに置くのは、あまり、いいスタイルではありません。ワイルドカードを使いましょう。ただし、捨てられる値の、意味がわかるようにしておきたいときには、_ではじまる変数名を使います。つぎのようには、しません。

```hs
fun x y = y * 3
```

つぎの、どちらかの、書きかたをします。

```hs
fun _ y = y * 3
fun _x y = y * 3
```

#### do記法で

do記法で、モナドをかえす値が、ユニット値ではないにもかかわらず、値を捨てる場合があります。このときは、「わかってて、捨ててるんだよ」という気持ちを示すために、_ <-をつけます。たとえば、値m1からm4までの、モナドであるような文脈つきの値を、考えます。モナドm2は、ユニット値をかえします。それ以外は、ユニット値ではない値をかえすとします。モナドm3が、かえす値は使わないので、変数に束縛せずに、捨てるとします。このとき、つぎのようには、しません。

```hs
do      x <- m1
        m2
        m3
        m4
```

つぎのように、書きます。

```hs
do      x <- m1
        m2
        _ <- m3
        m4
```

### ワイルドカードを使わない

単一の値構築子の代わりに、ワイルドカードを、使うことは可能です。つぎのようにできます。

```hs
fun (Just x) = ...
fun _ = ...
```

しかし、これは、よくないスタイルです。Maybe値であれば、値構築子が追加されることはありませんが、自作の型では、値構築子が追加されることがありえます。そのようなときに、うえのような表現があると、修正もれが起きます。単一の値構築子であれば、ワイルドカードを使わずに、ちゃんと書きましょう。

```hs
fun (Just x) = ...
fun Nothing = ...
```

いくつかの選択肢があったとしても、数個であり、冗長になりすぎなければ、きちんと列挙したほうが安全です。つぎのようには、できるだけしないようにしましょう。

```hs
data A = X | Y | Z | W

fun X = ...
fun _ = ...
```

つぎのようにしましょう。

```hs
fun X = ...
fun Y = ...
fun Z = ...
fun W = ...
```

このようにしておけば、型Aに、たとえば、値構築子Vが追加されたときに、未修正の箇所を、処理系が警告してくれます。

モジュールの導入
--------------

インポート文は、直観的に、より一般的なものから、より具体的なものになるように、並べます。直観的、かつ、主観的なルールですが

```hs
* Control
* Data
* System
* Text
* Network
```

のような、順とします。より細かくは

```hs
* Control.Functor
* Control.Applicative
* Control.Monad
```

のように、制約が厳しくなっていくような順で、並べています。