はじめに
プログラミング言語 Haskell の好きなところをまとめてみました。
この記事をきっかけに、Haskell に興味を持っていただけたら幸いです。
型システム
Haskell は強い静的型付け言語です。静的型付け言語のメリットとして、コンパイル時に型検査を行うことでプログラムを実行する前に間違いに気付ける、という点が挙げられます。
例えば、代表的な動的型付け言語である Ruby を書いたことがある人なら、以下のエラーを見たことがあるでしょう。
undefined method 'hoge' for nil:NilClass
このエラーは hoge
というメソッドを呼び出そうとしたオブジェクトが nil
であった場合に発生します。オブジェクトが nil
であることは実行時にメソッドを呼ぼうとしたその瞬間まで気付くことはできません。
一方、静的型付け言語である Haskell は事前に型検査を行うため、プログラムに問題がある場合は実行前に型エラーとして表示してくれます。動作確認やテストを実行してから間違いに気付くよりも、コンパイル時、つまり実行前に気付けた方が効率が良いのではないかと思います。
正しく型が付けられたプログラムの安全性は数学的に証明されているため、いつエラーが発生するかわからない動的型付け言語より静的型付け言語の方が安心してプログラミングできる、というのも好きなポイントの一つです。この証明については、書籍「型システム入門」等で詳しい説明が書かれているので、気になったら読んでみてください。
Haskell を書いているときに、型が合わないプログラムを書いてしまい、コンパイル時にエラーが出てしまうこともあります。ですがその時に表示されるエラーもとても分かりやすいです。例として、Haskell では以下のように型のエラーが表示されます。
<interactive>:10:23: error:
* Couldn't match expected type `Ordering' with actual type `Char'
* In the expression: 'a'
In the expression: if x > 1 then GT else 'a'
In an equation for `it': it = if x > 1 then GT else 'a'
このエラーは 「Ordering
型の値を書いて欲しいところに Char
型の 'a'
が来てしまっているよ」というエラーです。読みやすいですね。
また、エラーを直してコンパイルに成功した際に、まるでジグソーパズルの最後の1ピースがはまったかのような快感が得られるので僕は Haskell の型システムが好きです。
ポイントフリースタイル
Haskell は純粋関数型言語です。どの構文もシンプルなため、少ない行数で関数を定義できるので好きです。ここでは特に気に入っているポイントフリーについて紹介します。
ポイントフリースタイルとは、関数合成を駆使して引数を使わずに関数を定義するスタイルです。例として以下の関数をポイントフリーに変換してみます。
sumOdds xs = sum (filter odd xs)
まず初めに、関数合成の演算子 .
を使って書き換えると次のようになります。
sumOdds xs = (sum . filter odd) xs
左辺と右辺に xs
が出てきました。 Haskell ではこの xs
を省略して以下のように関数を定義できます。
sumOdds = sum . filter odd
sumOdds
は filter odd
(リストから奇数のみフィルタリング) して、 sum
(リストの総和を計算)する、ということが分かります。とてもシンプルで、美しいですね。
このように、ポイントフリースタイルはとっても素敵な関数定義方法なのですが、複雑な関数を無理にポイントフリーにしようとすると可読性が下がってしまうので、乱用は避けるべきです。もう一つ例を見てみましょう。
f x y = (x + 1) * (y + 1)
この関数をポイントフリースタイルに変換すると以下のようになります。
f = (. (1 +)) . (*) . (1 +)
何をする関数なのか、わからないですね。この場合は元の定義の方が読みやすいので、ポイントフリーにする必要はないと思います。(ですが、あえて複雑な関数に対して、どうやったらポイントフリーに変換できるかを考えるのは楽しいです。)
モナド
Haskell といえばモナドです。モナドは抽象度が高く、難しいイメージのある概念ですが、使いこなすことでより安全に様々なアプリケーションを開発することができるようになります。ここではモナドの説明は省き、いくつかのモナドとその例を紹介します。
モナドは様々な種類があります。それぞれのモナドごとに、計算に様々な文脈を持たせることが可能です。
- Maybeモナド: 失敗するかもしれいない計算
- リストモナド: 非決定性を持つ計算
- Stateモナド: 状態を持つ計算
- IOモナド: 入出力が発生する計算
例として、StateモナドとIOモナドを比較してみましょう。
increment :: State Int ()
increment = do
s <- get
put (s + 1)
main :: IO ()
main = do
name <- getLine
putStrLn ("My name is " ++ name)
increment
は Int
型の状態を持ち、get
でその値を取り出して put
で状態を変更しています。一方 main
では getLine
で標準入力からの入力を受け付け、 putStrLn
で文字列を標準出力に出力します。このように、見た目はほどんど同じですが、実行している内容は全く異なります。異なるモナドも同じように記述できるので、一度書き方を覚えれば、他のモナドも使えるようになります。
また、increment
の中で getLine
は使えません。同様に、main
の中で get
を使うこともできないです。もしやろうとしても型のエラーが発生するため実行できません。このように型で可能な操作を制限することで、誤って異なる文脈の操作をしてしまうことを防ぎ、バグを減らすことができるのも、モナドのメリットだと考えられます。
Monad Transformer や Extensible Effects により、実装したいアプリケーションの文脈に応じて複数のモナドを組み合わせることも可能です。
Haskellに入門からモナドを理解するまでの道のりは長いですが、モナドまで理解できると Haskell で様々なプログラム(Webアプリケーションなど)を書けるようになり、より Haskell の魅力が見えてきます。
さいごに
Template Haskell や型レベルプログラミングなどの話もしたかったのですが、最近はほとんどHaskellを書いておらず、うまく説明できる気がしないのでやめました。いつか書いてみたいです。
今回は Haskell の好きなところについて書きましたが、もちろん Haskell にもデメリットや課題があります。ですが、それを上回るメリットや魅力があると僕は信じています。
この記事をきっかけに、Haskell に興味を持っていただけたら幸いです。