関数型プログラミング(かんすうがたプログラミング、)とは、数学的な意味での関数を主に使うプログラミングのスタイルである。 functional programming は、関数プログラミング(かんすうプログラミング)などと訳されることもある。
()とは、関数型プログラミングを推奨しているプログラミング言語である。略して関数型言語()ともいう。
概要
関数型プログラミングは、関数を主軸にしたプログラミングを行うスタイルである。ここでの関数は、数学的なものを指し、引数の値が定まれば結果も定まるという参照透過性を持つものである。
参照透過性とは、数学的な関数と同じように同じ値を返す式を与えたら必ず同じ値を返すような性質である。次の square 関数は、 2 となるような式を与えれば必ず 4 を返し、 3 となるような式を与えれば必ず 9 を返し、いかなる状況でも別の値を返すということはなく、これが参照透過性を持つ関数の一例となる。
def square(n): return n ** 2
次の countup 関数は、同じ 1 を渡しても、それまでに countup 関数がどのような引数で呼ばれていたかによって、返り値が 1, 2, 3, ... と変化するため、引数の値だけで結果の値が定まらないような参照透過性のない関数であり、数学的な関数とはいえない。
counter = 0 def countup(n): global counter counter += n return counter
関数型プログラミングは、参照透過性を持つような数学的な関数を使って組み立てた式が主役となる。別の箇所に定義されている処理を利用することを、手続き型プログラミング言語では「関数を実行する」や「関数を呼び出す」などと表現するが、関数型プログラミング言語では「式を評価する」という表現も良く使われる。
参照透過性
参照透過性とは、同じ値を与えたら返り値も必ず同じになるような性質である。参照透過性を持つことは、その関数が状態を持たないことを保証する。状態を持たない数学的な関数は、並列処理を実現するのに適している。関数型プログラミング言語の内で、全ての関数が参照透過性を持つようなものを純粋関数型プログラミング言語という。
入出力
関数型プログラミングでは、数学的な関数を組み合わせて計算を表現するが、それだけではファイルの読み書きのような外界とのやり取りを要する処理を直接的に表現できない。このような外界とのやり取りを I/O (入出力) と呼ぶ。数学的な計算をするだけ、つまり 1 + 1 のようなプログラム内で完結する処理ならば、入出力を記述できなくても問題ないが、現実的なプログラムにおいてはそうでない。
非純粋な関数型プログラミング言語においては、式を評価すると同時に I/O が発生する関数を用意することで入出力を実現する。たとえば、 F# 言語では、printfn "Hi." が評価されると、 () という値が戻ってくると同時に、画面に Hi. と表示される I/O が発生する。
Haskell では、評価と同時に I/O が行われる関数は存在しない。たとえば、 putStrLn "Hi." という式が評価されると IO () 型を持つ値が返されるが画面には何も表示されず、この値が Haskell の処理系によって解釈されて初めて画面に Hi. と表示される。 I/O アクションとは、ファイルの読み書きやディスプレイへの表示などのような I/O を表現する式のことである。 IO a という型は、コンピュータへの指示を表す I/O アクションを表現している。ここでの IO はモナドと呼ばれるものの一つである。
Clean では、一意型を用いて入出力を表す。
手法
最初に解の集合となる候補を生成し、それらの要素に対して1つ(もしくは複数)の解にたどり着くまで関数の適用とフィルタリングを繰り返す手法は、関数型プログラミングでよく用いられるパターンである。
Haskell では、関数合成の二項演算子を使ってポイントフリースタイルで関数を定義することができる。関数をポイントフリースタイルで定義すると、データより関数に目が行くようになり、どのようにデータが移り変わっていくかではなく、どんな関数を合成して何になっているかということへ意識が向くため、定義が読みやすく簡潔になることがある。関数が複雑になりすぎると、ポイントフリースタイルでは逆に可読性が悪くなることもある。
言語
関数型プログラミング言語とは、関数型プログラミングを推奨しているプログラミング言語である。略して関数型言語ともいう。全ての関数が参照透過性を持つようなものを、特にという。そうでないものを非純粋であるという。
関数型プログラミング言語の多くは、言語の設計において何らかの形でラムダ計算が関わっている。ラムダ計算はコンピュータの計算をモデル化する体系の一つであり、記号の列を規則に基づいて変換していくことで計算が行われるものである。
+ 関数型プログラミング言語 名前 型付け 純粋性 評価戦略 理論的背景 Clean 静的型付け 純粋 遅延評価 Elm 静的型付け 純粋 正格評価 Erlang 動的型付け 非純粋 正格評価 F# 静的型付け 非純粋 正格評価 Haskell 静的型付け 純粋 遅延評価 型付きラムダ計算 Idris 静的型付け 純粋 正格評価 型付きラムダ計算 Lazy K 型なし 純粋 遅延評価 コンビネータ論理 LISP 1.5SchemeCommon LispClojure 動的型付け 非純粋 正格評価 型無しラムダ計算 LISPの各種方言 方言による 方言による 方言による Miranda 静的型付け 純粋 遅延評価 MLStandard MLOCaml 静的型付け 非純粋 正格評価 Scala 静的型付け 非純粋 正格評価 Unlambda 型なし 非純粋 正格評価 コンビネータ論理Lean静的型付け純粋正格評価型付きラムダ計算
手続き型プログラミングとの比較
C 言語や Java 、 JavaScript 、 Python 、 Ruby などの2017年現在に使われている言語の多くは、手続き型の文法を持っている。そのような言語では、文法として式 (expression) と文 (statement) を持つ。ここでの式は、計算を実行して結果を得るような処理を記述するための文法要素であり、加減乗除や関数呼び出しなどから構成されている。ここでの文は、何らかの動作を行うようにコンピュータへ指示するための文法要素であり、条件分岐の if 文やループの for 文と while 文などから構成されている。手続き型の文法では、式で必要な計算を進め、その結果を元にして文でコンピュータ命令を行うという形で、プログラムを記述する。このように、手続き型言語で重要なのは文である。
それに対して、関数型言語で重要なのは式である。関数型言語のプログラムはたくさんの式で構成され、プログラムそのものも一つの式である。たとえば、 Haskell では、プログラムの処理の記述において文は使われず、外部の定義を取り込む import 宣言も処理の一部として扱えない。関数型言語におけるプログラムの実行とは、プログラムを表す式の計算を進めて、その結果として値 (value) を得ることである。式を計算することを、評価する (evaluate) という。
手続き型言語ではコンピュータへの指示を文として上から順に並べて書くのに対して、関数型言語では数多く定義した細かい式を組み合わせてプログラムを作る。手続き型言語では文が重要であり、関数型言語では式が重要である。
式と文の違いとして、型が付いているかどうかというのがある。式は型を持つが、文は型を持たない。プログラム全てが式から構成されていて、強い静的型付けがされているのならば、プログラムの全体が細部まで型付けされることになる。このように細部まで型付けされているようなプログラムは堅固なものになる。
歴史 1930年代 関数型言語の開発において、アロンゾ・チャーチが1932年と1941年に発表したラムダ計算の研究ほど基本的で重要な影響を与えたものはない。ラムダ計算は、それが考え出された当時はプログラムを実行するようなコンピュータが存在しなかったためにプログラミング言語として見なされなかったにもかかわらず、今では最初の関数型言語とされている。1989年現在の関数型言語は、そのほとんどがラムダ計算に装飾を加えたものとして見なせる。
1960年代 1960年にジョン・マッカーシー等が発表した LISP は関数型言語の歴史において重要である。ラムダ計算は LISP の基礎であると言われるが、マッカーシー自身が1978年に説明したところによると、匿名関数を表現したいというのが最初にあって、その手段としてマッカーシーはチャーチのラムダ計算を選択したに過ぎない。
歴史的に言えば、 LISP に続いて関数型プログラミングパラダイムへ刺激を与えたのは、1960年代半ばのの成果である。ランディンの成果はハスケル・カリーとアロンゾ・チャーチに大きな影響を受けていた。ランディンの初期の論文は、ラムダ計算と、機械および高級言語 (ALGOL 60) との関係について議論している。ランディンは、1964年に、 SECD マシンと呼ばれる抽象的な機械を使って機械的に式を評価する方法を論じ、1965年に、ラムダ計算で ALGOL 60 の非自明なサブセットを形式化した。1966年にランディンが発表した ISWIM(If You See What I Mean の略)という言語(群)は、間違いなく、これらの研究の成果であり、構文や意味論において多くの重要なアイデアを含んでいた。 ISWIM は、ランディン本人によれば、「 LISP を、その名前にも表れたリストへのこだわり、手作業のメモリ割り当て、ハードウェアに依存した教育方法、重い括弧、伝統への妥協、から解放しようとする試みとして見ることができる」。関数型言語の歴史において ISWIM は次のような貢献を果たした。
構文についての革新 演算子を前置記法で記述するのをやめて中置記法を導入した。 let 節と where 節を導入して、さらに、関数を順序なく同時に定義でき、相互再帰も可能なようにした。 宣言などを記述する構文に、インデントに基づいたオフサイドルールを使用した。 意味論についての革新 非常に小さいが表現力があるコア言語を使って、構文的に豊かな言語を定義するという戦略を導入した。 等式推論 (equational reasoning) を重視した。 関数によるプログラムを実行するための単純な抽象機械としての SECD マシンを導入した。
ランディンは「それをどうやって行うか」ではなく「それの望ましい結果とは何か」を表現することに重点を置いており、そして、 ISWIM の宣言的なプログラミング・スタイルは命令的なプログラミング・スタイルよりも優れているというランディンの主張は、今日まで関数型プログラミングの賛同者たちから支持されてきた。その一方で、関数型言語への関心が高まるまでは、さらに10年を要した。その理由の一つは、 ISWIM ライクな言語の実用的な実装がなかったことであり、実のところ、この状況は1980年代になるまで変わらなかった。
ケネス・アイバーソンが1962年に発表した APL は、関数型プログラミング言語ではないが、その関数型的な部分を取り出したサブセットがラムダ式に頼らずに関数型プログラミングを実現する方法の一例であるという点で、関数型プログラミング言語の歴史を考察する際に言及する価値はある。実際に、アイバーソンが APL を設計した動機は、配列のための代数的なプログラミング言語を開発したいというものであり、アイバーソンのオリジナル版は基本的に関数型的な記法を用いていた。その後の APL では、いくつかの命令型的な機能が追加されている。
1970年代 1970年代には、David A. Turner の SASL(w:SASL (programming language))が1972年に開発され、1976年に non-strict (lazy) に再設計された。1973年に発足した Edinburgh LCF のプロジェクトにおいて、証明の道筋を関数として記述するためのメタ言語としてML (Meta Language) が考案され、1975~76年にエディンバラ大学で実装された(ML (プログラミング言語)#歴史)。1975年に誕生した Scheme と、Lambda Papers と呼ばれる関連 paper の一群The Lambda Papersは、Common Lisp など後の Lisp に大きな影響を与えた。1977年には、ジョン・バッカスがチューリング賞記念講演で Can Programming be Liberated from the von Neumann Style? A Functional Style and its Algebra of Programs を述べたhttps://ipsj.ixsq.nii.ac.jp/records/64324。
1980年代 1980年代には、w:Hope (programming language)(最初は正格評価)、KRC、w:Lennart Augustsson らの Lazy ML(LML)、Miranda、1984年にちなむ w:Orwell (programming language) など、遅延評価の関数型言語が続々と提案され、1987年には十二以上存在していたともされる。Functional Programming Languages and Computer Architecture '87 において、オープンな標準を作成するための委員会が発足されるべき、という強い合意が参加者のあいだで形成され、Haskell の制定が始まった。
1990年代 1990年に Haskell 1.0 が作成される(Haskell#歴史)。
脚注
注釈
出典
参考文献
外部リンク
なぜ関数プログラミングは重要か lang|en|The Functional Programming Language XSLT - A proof through examples(PDF)
関連項目
カリー化