Scalaで関数型プログラミングをする#1 関数型プログラミングとは

2012.12.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

関数型プログラミングとは何か

Ⅰ.はじめに

Web系のプログラマーとして仕事をはじめたころ、
Java Servlet + JSPを用いたWebアプリケーションが注目をあびていたころで、
最近まで仕事で使用してきた言語は、ほとんどJavaでした。
数年前にはじめてScala言語を学び、そこではじめて「関数型プログラミング」という考え方を知りました。
私自身が関数型プログラミングの初心者であるため、
この記事ではScalaを用いて関数型プログラミングというものがどういうものなのかを、
他のサイトの解説等を参考にしつつ、自身の学習を兼ねて基本から学んでいきます。

Ⅱ.「関数型プログラミング」について確認

まずは関数型プログラミングがどういうものか確認しておきます。
関数型プログラミングでいう「関数」とは、数学でいうところの関数であって
手続き型プログラミングでいう意味の関数では使われないようです。
ここでいう関数の特徴は以下のようなものです。

1.副作用がない(参照透過性が高い)

関数型プログラミングでは、原則として副作用を許しません。
副作用とは、ある機能がコンピュータの論理的状態を変化させ、
その後得ることができる結果に影響を与えることをいいます。
例:変数への値の代入
副作用がない場合、次の条件が成立します。

  • 同じ条件を与えれば必ず同じ結果が得られる
  • 他のいかなる機能の結果にも影響を与えない

このような性質を「参照透過性」といいます。

ここでそれぞれ簡単な例を見てみましょう。
最初は副作用をのない関数の例です。(ScalaのREPLで確認)

scala> def add(x:Int,y:Int):Int = x + y
scala> add(1,2)
res17: Int = 3

同じ引数を渡せばどんな状況でも同じ結果を返しますし、他に何も影響を与えません。
次は副作用がある例です。

scala> var x = 10;
x: Int = 10

scala> def add2(y:Int):Unit = x += y
add2: (y: Int)Unit

scala> add2(5)

scala> x
res14: Int = 15

scala> add2(5)

scala> x
res16: Int = 20

最初に変数xをvarで宣言し、add2関数の中で変数xの値を書き換えています。
これは「他のいかなる機能の結果にも影響を与えない」という条件を破っています。
このため、このadd2関数は副作用を持っていることがわかります。

副作用がないということは、参照透過性が高いということになります。
繰り返しになりますが、参照透過性が高いということは、
関数はどんなときでも返値は引数によってのみ決定され、関数がどんな状態で呼ばれても入力される値が同じものであれば、
取得結果は同じになるということです。

補足:なお、副作用を伴う機能の例としてはこれ以外にもIO機能(標準出力や外部装置等)があります。
例えば、コンソールに文字を表示するためのprintln関数は標準出力の状態を変更するため、副作用があるといえます。
Haskell等の純粋な関数型プログラミング言語では副作用が一切許されていないので、こうしたIO機能については
モナドを用いて参照投透過性を実現しています。
Scalaでは副作用を許可しているため、副作用を伴う機能を使用することは言語上問題ありません。

参考:wikipedia
関数型プログラミング
副作用

2.言語内において関数が第一級の身分を持つ

「関数をファーストクラスオブジェクトとして扱う」とも言われます。
関数自身を通常のクラス/オブジェクトと同じように、引数や戻り値として使用できるということです。
例えば、Listクラスのmap関数は関数自体を引数に取ります。

scala> val list = List(1,2,3)
list: List[Int] = List(1, 2, 3)

scala> val f = (i:Int) => i + 1
f: Int => Int = <function1>

scala> list.map(f)
res40: List[Int] = List(2, 3, 4)

また、下記のように関数を返す関数を定義することも可能です。

scala> def returnFunction(x:Int) = (y:Int) => x + y
returnFunction: (x: Int)Int => Int

scala> val func = returnFunction(1)
func: Int => Int = <function1>

scala> func(2)
res41: Int = 3

他には、無名リテラルとして表現可能、部分適用が可能、といった条件を持つことが多いです。
要するに関数も他の変数やオブジェクトと同じように扱うことができるという意味です。

以上、これら2つの条件を満たすことで、関数型プログラミングが可能になります。
これで関数型プログラミングというものがどんなものか再確認できました。
次回は純粋関数型言語というものについて解説する予定です。