Haskellをなぜ学ぶのか

Last Edited: 6/10/2024

このブログはHaskellに嫌気が差してきた頃に改めてなぜHaskellを学ぶのかを見つめ直し、学習を続けるために書いたものです。

Haskell & Haskell Curry

Haskell(ハスケル)は、関数型プログラミングの創始に大きな貢献をした有名な論理学者 Haskell Curry(ハスケル・カリー)にちなんで名付けられた、ラムダ計算に基づいた純粋な関数型プログラミング言語です。 ハスケルには、C、Python、Javaのようなオブジェクト指向プログラミング言語には必ずしも備わっていない様々な特性があり、 特定の問題に対して適したツールとなります。

純粋関数

「純粋関数型」という名前が示すように、Haskellには関数は純粋で、同じ入力に対して同じ出力を生成し、 外部の状態を変更したり副作用を生じさせないべきであるという理念があります。これが具体的に何を意味し、なぜ重要なのかを理解するために、 入力を変更し副作用を生じる不純な関数の例を見てみましょう。

counter = 0
 
def hello(names: List[string]):
    last_person = names.pop()
    counter += 1
    if counter == 100:
     return f'こんにちは、{name}さん! あなたは100番目のお客様です!'
    return f'こんにちは、{name}さん!'
 
hello(['Alice', 'Bob'])
# => 'こんにちは、Bobさん!'

上記の関数は、リストの最後の人に挨拶をします。しかし、この関数は、pop関数を使ってリストの最後の人を削除してしまい(入力を変更)、 さらにcounterに1を足しながら挨拶をします(副作用)。また、カウントが100の場合、その人が100番目の顧客であることを祝います。 小さなコードベースではこのような関数を扱うのは難しくないかもしれませんが、大規模なコードベースでは、 変更や副作用を追跡するのが非常に難しくなります。

names = ['Alice', 'Bob']
hello(names) # => こんにちは、Bobさん!, names=['Alice']
hello(names) # => こんにちは、Aliceさん!, names=[]
hello(names) # => Err!

上記の例を見れば、なぜこれが問題になるかがわかります。関数の出力は毎回異なる結果を生成し、 counterを明確な指示なしに更新します。他の場所でこの関数を使用すると非常に混乱を招く可能性があります。 不純な関数は関数のテストでも頭痛の種です。なぜなら、異なる入力と外部状態の組み合わせで関数をテストする必要があるからです。 もし関数が純粋であれば、異なる入力で関数を単純にテストして期待通りに動作するか確認するだけで済みます。 上述のように、Haskellの純粋関数のアプローチは信頼性、スケーラビリティ、テスト可能性、可読性を向上させ、 多くの学術および産業のシナリオで好まれています。

遅延評価

Haskellのもう一つの独自性は、必要になったときにのみ計算が行われる遅延評価にあります。 その反対は厳密評価で、これはPython、Java、Rubyなど多くの言語で使用され、出力を導出するために必要かどうかに関係なく、 順番に計算が実行されます。これを理解するため、1つの例を見てみましょう。

def example(arg):
    x = func1(arg)
    y = func2(arg)
    z = func3(arg)
    if z:
     return x
    else:
     return y

厳密に評価される言語では、if文に進む前に、xyzがすべて評価されます。もしそれぞれを求めるのに10秒かかるとしたら、 if文に進む前に30秒かかる計算になります。一方、Haskellのような遅延評価される言語では、まずzが評価され、 その後にxまたはyを評価するかどうかを決定します。そのため、計算に要する時間は30秒ではなく20秒しかかからない訳です。

スキルの問題?

今、この記事を読んで「スキルの問題だ!」と叫びたくなっているかもしれません。確かに、 オブジェクト指向プログラミング言語でも純粋関数を書くことはでき、すべての変数をすべてのケースで評価する必要はありません。 上記の関数を次のようにリファクタリング(書き換え)することができます。

def hello(names: List[string], counter: int):
    if counter == 100:
      return f'Hello, {names[-1]}! You are 100th customer!'
    return f'Hello, {names[-1]}'
 
def example(arg):
   z = func3(arg)
   if z:
    return func1(arg)
   else:
    return func2(arg)

結局のところ、すべての確立されたプログラミング言語は実質的にチューリング完全です。 しかし、重要なポイントは、関数型プログラミング言語は不純な関数を許さず(エラーを出力)、デフォルトで遅延評価を行うため、 意図せずに予期しない動作、副作用、不必要な評価を持つ関数を書いてしまうことに対するストレスを軽減することができるということです。 これらの点を確保することが非常に重要な場合もあり、そうした状況ではHaskellが価値のあるツールとなります。

プロダクション

Haskellの特徴のいくつかを説明しましたが、これらはなぜ特定の状況で好まれるのかを示しています。上記の他にも、Haskellは宣言型言語であり、 コードも読みやすい上、Hindley-Milner型システムを持ち静的型付け言語でもあるため、強力な型推論とポリモーフィズム、タイプセーフティを可能にします。 (非常に重要)これについてもっと知りたい場合は、「宣言型 vs 命令型 言語」や「Haskell 型システム」をそれぞれググってみてください。

さらに素晴らしいのは、Haskellがすでに様々な学術および産業のアプリケーションで実績のあるプロダクションレディな言語であることです。 MetaはHaskellを使って高性能なスパムフィルターを構築し、GitHubはHaskellでSemanticを実装し、MicrosoftはHaskellでBondを構築しました。 そのリストはまだまだ続きます。Haskell周りのツールは世界中の素晴らしいHaskell愛好家のおかげで一貫して改善されています。

Be Different

このようにHaskellには多くの利点があるにもかかわらず、その人気は比較的低く、プログラミング言語の人気を評価するTIOBEインデックスでは29位にランクされており、 2024年のGitHubのソースコードリポジトリでのアクティブユーザーの割合はわずか0.132%です。これは、もしあなたがHaskellの達人になれば、 この過剰に飽和した開発者の世界で希少なスキルを持つことになるということです。(これは100%私の個人的な意見です) オブジェクト指向プログラミングに慣れた後に関数型プログラミングを学ぶことは、知的にも満足でき、意味のあることです。同じ問題に取り組む際に、 異なる思考プロセスを必要とすることが多いからです。

結論

Haskellを学ぶべきかどうかは、完全にあなた自身とあなたの状況に依存します(不変データに関する問題があるため)が、 私は個人的にこの言語を学ぶことをお勧めします。私自身もHaskellの学習を始めたばかりなので、Haskellをツールボックスに加えることで、 より優れた開発者になれることを願っています。

 リソース