セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

関数型言語で書くと何が嬉しいのか?

Scala Advent Calendar 2015(Qiita版)14日目の記事です。

昨日の記事はこちらです。

d.hatena.ne.jp

それでは・・・。

毎年、苦し紛れの記事を上げるのですが、今年は特にネタが思いつかないので、マサカリ覚悟で苦し紛れの妄言を吐きます。

タイトルは、Scala勉強会などで聞かれることの多い質問の一つです。

恐らく、オブジェクト指向プログラミングが流行り始めた当初も「オブジェクト指向で書くと(ry」という質問が飛び交ったと思いますので、「誰もが新しい概念に対する不安」というものを抱えて日々暮らしているのだと思います。

まず、関数型言語で一番重要な概念というのが、関数が純粋であるということだと思います。

純粋であるというのは、引数と戻り値以外に関数は何ら影響を受けないし与えないということです。

つまり、関数の世界は、下記の図のようにとてもシンプルです。

このことによる、メリット・デメリットは様々ありますが、先に「関数を純粋に保つ」こと前提として決めてしまって、それから「そのためにはどうするか?」を考えてきたのが、関数型言語であり、関数型言語コミュニティだと言えると思います。

対する、手続き型言語や、それに続くオブジェクト指向プログラミングという思想は「純粋であること」にこだわらなかったグループが導いてきた世界です。

両方共、コンピューティングの発展に大いに寄与してきているわけですが、それぞれのアプローチというのは、根本のところで袂を分かった訳です。

まるで、手でボールを扱うことの是非で、フットボールがサッカーとラグビーに分かれていったように、それぞれの道で膨大な思索と体験を重ね、今日の発展があるわけです。

関数を純粋に保つことのメリット

副作用が無いことのメリットというのは、各方面で色々言われていると思いますが、僕は「プログラムを綺麗なツリーで表せる」という点を推したいと思います。

なぜ、ツリーで表せるのか?ということと、ツリーで表すと何が嬉しいのか?ということになると思いますが、まずプログラムをツリーとして見るためには、カリー化について知らなければなりません。

カリー化というのは、関数の引数を分解して、「他引数関数」から「関数を返す関数」への変換を行うことです。

Scalaでは、下記のような記法で自動的にカリー化することができます。

def f(x: Int)(y: Int) = x + y

これは、このように書くのと同義です。*1

def f(x: Int) = (y:Int) => x + y

このことにより、すべての関数は「引数を一つ取り、何らかの値を返すもの」と言い切ることができます。*2

つまり、この世の中の関数というのは以下の4パターンしか存在しないわけです。

つまり、関数というのは、何らかの値を取るか関数を取り、値か関数を返す存在です。

そして、引数や戻り値になる関数も同じ構造をしているので、以下の様なツリーが構成されるわけです。

おおよそ正しくないですが、Scalaのコードで書くとこんな感じです。一応Scalaの記事なんで。(^^;

sealed trait Program[T]
case class Value[T](v: T) extends Program[T]
case class Func[T](in: Program[T], out: Program[T]) extends Program[T]

これで、純粋な関数がツリー構造になるということが、解ったかと思います。

ツリー構造によるメリット

比較対象として、純粋さにこだわらない、非関数型的なアプローチを見てみますと、例えば下記の関数群があった場合、その構造はグラフ構造となります。

var x = 0
def f = x += 1
def g = x += 1
def h = x += 1
def i = x += 1
def j = x += 1

図に表すとわかりますが、各関数がお互いに影響を与え合うことにより、影響度合いがドンドン膨らんでいきます。

そして、ノードの増加に対する影響の増加が加速度的だということは直感的に想像できると思います。

実際計算すると、2000ノード大しての影響するパスの数は実に200万本近くになります。

これに対して、ツリー構造であればパス数は、常にN-1です。2000ノードであれば、1999本のパスで済みます。

非純粋なケースの実に1000倍の開きがあるのです。

システムが巨大化すればするほど、効果が大きく現れるという事になります。

実に大きなメリットがありますね。

関数を純粋に保つことは、何も関数型言語だけの機能ではありません。

一つ一つのコードでどんな言語でも今すぐ実践可能な、プログラミングテクニックです。

ただし、純粋に保ちつつアレヤコレヤするのは、非純粋なアプローチとは、やり方や考え方、心構えが違ってきます。

そのために、関数型プログラミングの方法論を学ぶことは、とても大きなメリットがあります。

学んだことを今書いているプログラムに少し応用させるだけで、バグを大幅に抑えたり、記述をすっきりさせたりすることができます。

すぐに関数型プログラミングをはじめましょう。^^

*1:ふう。Scalaのコード例が出てきたので、とりあえずAdvent Calendarといての体裁は辛うじてクリア!?(^^;

*2:「何らかの値」に関数が含まれる