LoginSignup
21
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-07-05

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

はじめに

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

この文書について

この文書は拙書「Haskell - 教養としての関数型プログラミング」の「補足資料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がついたもの」です。

Some.value

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

Some . value

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

演算子の部分適用

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

(3 +)
(/ 8)

カンマのあと

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

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

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

(3 ,)
(, 4)

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

括弧のまえ

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

f(x)

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

f x

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

f (g x)

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

括弧のなか

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

( 3, 4 )

つぎのようにします。

(3, 4)

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

レコード構文

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

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

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

内包表記

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

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

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

複数行に、またがる括弧

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

list1 = [
        item1,
        item2,
        item3,
        item4 ]

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

1行の幅

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

演算子と改行

通常の演算子

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

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

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

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

foo = fun1
        . fun2
        . fun3
        $ fun4 x

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

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

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

変数名

変数名の長さ

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

複数の単語からなる変数

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

some_long_variable

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

someLongVariable

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

c_someFunction

演算子($)の運続

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

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

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

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

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

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

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

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

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

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

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

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

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

g c b = ...
        ...

h c b = ...

where節

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

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

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

fun x = ...
        where
        y = ...
        z = ...

let-in式

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

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

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

let     x = ...
        y = ... in
        exp

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

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

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

ワイルドカードを使う

変数を、あとで使わない

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

fun x y = y * 3

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

fun _ y = y * 3
fun _x y = y * 3

do記法で

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

do      x <- m1
        m2
        m3
        m4

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

do      x <- m1
        m2
        _ <- m3
        m4

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

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

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

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

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

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

data A = X | Y | Z | W

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

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

fun X = ...
fun Y = ...
fun Z = ...
fun W = ...

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

モジュールの導入

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

* Control
* Data
* System
* Text
* Network

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

* Control.Functor
* Control.Applicative
* Control.Monad

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

21
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
15