コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

「関数型プログラミング」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
SML 言語族に関する説明を少し強化
 
(47人の利用者による、間の327版が非表示)
1行目: 1行目:
{{プログラミング・パラダイム}}
{{独自研究|date=2014年4月}}
'''関数型プログラミング'''(かんすうがたプログラミング、{{lang-en-short|functional programming}})とは、[[関数 (数学)|数学的な意味での関数]]を主に使うプログラミングのスタイルである<ref name="名前なし-1">{{harvnb|本間|類地|逢坂|2017|p=3}}</ref>。 functional programming は、'''関数プログラミング'''(かんすうプログラミング)などと訳されることもある<ref name="名前なし-2">{{harvnb|本間|類地|逢坂|2017|p=2}}</ref>。
{{プログラミング言語|index=かんすうかたけんこ}}
'''関数型言語'''(かんすうがたげんご、functional language)は、'''関数型プログラミング'''に向いた特徴を持つ[[プログラミング言語]]、関数型プログラミング言語(functional programming language)の略称である。引数に関数を作用(applicate)させて計算をおこなうことから、作用型言語(applicative programming language)ともいう。[[データフロープログラミング]]言語も関数型言語の一種である<ref name="iwanami-is-dic">『岩波情報科学辞典』pp. 138-139</ref>。


{{Visible anchor|'''関数型プログラミング言語'''|関数型言語|FP}}({{lang-en-short|functional programming language}})とは、関数型プログラミングを推奨している[[プログラミング言語]]である<ref name="名前なし-1"/>。略して'''関数型言語'''({{lang-en-short|functional language}})ともいう<ref name="名前なし-1"/>。
== 関数型プログラミング ==
ここでの「関数」とは、数学でいう「[[関数 (数学)|関数]]」である。[[手続き型プログラミング]]などにおける「[[サブルーチン|関数]]」も同じ機能を持つ。原則としては[[副作用 (プログラム)|副作用]]がないものを関数と呼ぶ。副作用がある関数を認めるかどうかは、プログラミングで何がしたいかによる。空間的には副作用がないという関数も、瞬時に計算を終わることはできず、時間を浪費するという時間軸での副作用がある。時間と空間を含めた関数について考えるとよい。数学では時間を考慮しないが、プログラミングでは時間を考慮する必要がある。空間的な副作用がない関数、空間的な副作用がある関数という具合に厳密に分類するとよい。システム理論では、入力を出力に変換するものをシステムと呼び、関数と同じ機能を持つ。システムでは、出力だけある湧き出し点、入力だけあるブラックホールを仮設としてシステムに含めている。関数も、システム理論に習い、出力だけある関数、入力だけある関数も含めて、広義の関数理論を形成するとよい。


== 概要 ==
Pascalでは「手続き」と呼ぶ値を返さないルーチンもC言語では「関数」と呼ぶ。C言語の値を返さない言語は、void(何もないもの)を返すという記述をしている。0が数であるのと同様、voidも型であり、void型を返すと理解するとよい。Pascalは手続き型言語で、C言語は関数型言語というのは明らかに誤りである。<ref>[[共立出版]]『ANSI C/C++辞典』ISBN 4-320-02797-3 など</ref>。


関数型プログラミングは、[[関数 (数学)|関数]]を主軸にしたプログラミングを行うスタイルである<ref name="名前なし-1"/>。ここでの関数は、数学的なものを指し、引数の値が定まれば結果も定まるという[[参照透過性]]を持つものである<ref name="名前なし-1"/>。
関数への引数がプログラムへの入力で、関数を引数に作用させて評価して得られる値がプログラムからの出力であるとすると、コンピュータプログラムはある種の関数であると考えることができる。ここで、入力や出力は記憶装置中のファイルのようなものばかりではなく、マウスの動きの情報といった入力や、画面への表示といった出力も考えられる。極端には、一般的な[[機械語]]の命令も、レジスタやメモリのようなコンピュータの内部状態の、命令実行前の全状態を入力とし命令実行後の全状態を出力とする関数である。これはシステム理論と同じ考え方である。ここで「全状態」としているのは、入出力の線をどこで引くかによる。ある時間の全状態を入力とし、次の時間の全状態を出力とすれば、BASIC言語の <code>poke()</code> 関数によるメモリの書き換えは、メモリの状態は引数にも返す値にも含まなないが、メモリの状態そのものを全状態に含むため、副作用による作用ではなく関数である。全状態を出力に含めなければ、引数にも返す値にも含まない <code>poke()</code> 関数は全状態を考えない場合の「関数」ではない。


'''参照透過性'''とは、数学的な関数と同じように同じ値を返す式を与えたら必ず同じ値を返すような性質である<ref name="名前なし-1"/>。次の <code>square</code> 関数は、 <code>2</code> となるような式を与えれば必ず <code>4</code> を返し、 <code>3</code> となるような式を与えれば必ず <code>9</code> を返し、いかなる状況でも別の値を返すということはなく、これが参照透過性を持つ関数の一例となる<ref name="名前なし-1"/>。
複雑なコンピュータプログラムに相当する関数を作るために、[[第一級関数]]を扱えることが関数型言語に有用である。概要の節で詳説する。


<syntaxhighlight lang="python" >
他の[[プログラミングパラダイム]]との関連としては、関数型プログラミングは[[論理プログラミング|論理型プログラミング]]と同じく[[宣言型プログラミング]]・[[非手続き型言語|非手続き型プログラミング]]で、[[手続き型プログラミング]]・[[命令型プログラミング]]と分類を異にする。
def square(n):
return n ** 2
</syntaxhighlight>


次の <code>countup</code> 関数は、同じ <code>1</code> を渡しても、それまでに <code>countup</code> 関数がどのような引数で呼ばれていたかによって、返り値が <code>1</code>, <code>2</code>, <code>3</code>, ... と変化するため、引数の値だけで結果の値が定まらないような参照透過性のない関数であり、数学的な関数とはいえない<ref name="名前なし-1"/>。
== 概要 ==
関数型プログラミング言語には、関数型プログラミングのために、[[第一級関数]]を定義する。[[カリー化]]などの機能を持つ。静的型付けの言語は[[型推論]]の機能がある。理論的な計算モデルとしては[[ラムダ計算]]や[[項書き換え]]を基礎とする。


<syntaxhighlight lang="python" >
純粋関数型プログラミング言語では、[[参照透過性]]が常に保たれるという意味において、全ての[[式 (プログラミング)|式]]や関数は[[副作用 (プログラム)|副作用]]を持たない。入出力などのために、多くの純粋関数型プログラミング言語は正格性に関して非正格であり、引数はデフォルトで[[遅延評価]]である。また入出力などを[[参照透過性]]を保ったまま実現するために、たとえば[[Haskell]]では[[モナド (プログラミング)|モナド]]、[[Clean]]では{{仮リンク|一意型|en|Uniqueness type}}という特殊な型を通してでのみ入出力などを扱えるようにしている。
counter = 0
def countup(n):
global counter
counter += n
return counter
</syntaxhighlight>


関数型プログラミングは、参照透過性を持つような数学的な関数を使って組み立てた'''式'''が主役となる<ref name="名前なし-1"/>。別の箇所に定義されている処理を利用することを、手続き型プログラミング言語では「関数を実行する」や「関数を呼び出す」などと表現するが、関数型プログラミング言語では「式を評価する」という表現も良く使われる<ref name="名前なし-3">{{harvnb|本間|類地|逢坂|2017|p=4}}</ref>。
非純粋関数型言語では、参照透過性を壊す、副作用があるような式や関数も存在する。Lispなどでデータ構造の破壊的変更などの副作用を多用したプログラミングをおこなうと、それはもはや手続き型プログラミングと同じである。非純粋関数型言語は多くが正格性に関して正格であり、[[評価戦略]]としては正格評価(先行評価)である。無限リストのような技巧を使うために、明示して遅延評価をおこなわせるための機構を持つ。


=== 参照透過性 ===
[[JavaScript]]など近年の[[高水準言語]]には、関数型言語の機能や特徴を取り入れているものがある。変数の値やオブジェクトの状態を書き換えるプログラミングスタイルを通常とするため、関数型言語とはしない。一方[[LISP]]は、その多くが副作用のある式や関数が多数あり、手続き型スタイルでのプログラミングも可能だが、理論的なモデル(「[[純LISP]]」)の存在や副作用を使わないプログラミングが基本であること、歴史的理由などから、関数型プログラミング言語に含む。

{{main|参照透過性}}

参照透過性とは、同じ値を与えたら返り値も必ず同じになるような性質である<ref name="名前なし-1"/>。参照透過性を持つことは、その関数が'''状態を持たない'''ことを保証する<ref name="名前なし-4">{{harvnb|本間|類地|逢坂|2017|p=5}}</ref>。状態を持たない数学的な関数は、並列処理を実現するのに適している<ref name="名前なし-4"/>。関数型プログラミング言語の内で、全ての関数が参照透過性を持つようなものを純粋関数型プログラミング言語という<ref name="名前なし-4"/>。

=== 入出力 ===

関数型プログラミングでは、数学的な関数を組み合わせて計算を表現するが、それだけではファイルの読み書きのような外界とのやり取りを要する処理を直接的に表現できない<ref name="名前なし-5">{{harvnb|本間|類地|逢坂|2017|p=6}}</ref>。このような外界とのやり取りを '''I/O (入出力)''' と呼ぶ<ref name="名前なし-5"/>。数学的な計算をするだけ、つまり <code>1 + 1</code> のようなプログラム内で完結する処理ならば、入出力を記述できなくても問題ないが、現実的なプログラムにおいてはそうでない<ref name="名前なし-5"/>。

非純粋な関数型プログラミング言語においては、式を評価すると同時に I/O が発生する関数を用意することで入出力を実現する<ref name="名前なし-5"/>。たとえば、 [[F Sharp|F# 言語]]では、<code>printfn "Hi."</code> が評価されると、 <code>()</code> という値が戻ってくると同時に、画面に <code>Hi.</code> と表示される I/O が発生する<ref name="名前なし-5"/>。

[[Haskell]] では、評価と同時に I/O が行われる関数は存在しない<ref name="名前なし-5"/>。たとえば、 <code>putStrLn "Hi."</code> という式が評価されると <code>IO ()</code> 型を持つ値が返されるが画面には何も表示されず、この値が Haskell の処理系によって解釈されて初めて画面に <code>Hi.</code> と表示される<ref name="名前なし-5"/>。 '''I/O アクション'''とは、ファイルの読み書きやディスプレイへの表示などのような I/O を表現する式のことである<ref name="名前なし-5"/><ref>{{harvnb|本間|類地|逢坂|2017|p=23}}</ref>。 <code>IO a</code> という型は、コンピュータへの指示を表す I/O アクションを表現している<ref name="名前なし-5"/><ref>{{harvnb|本間|類地|逢坂|2017|p=31}}</ref>。ここでの <code>IO</code> は[[モナド (プログラミング)|モナド]]と呼ばれるものの一つである<ref>{{harvnb|本間|類地|逢坂|2017|p=32}}</ref>。

[[Clean]] では、一意型を用いて入出力を表す。

=== 手法 ===

{{節スタブ|1=[[モナド (プログラミング)|モナド]]・[[永続データ構造]]|date=2021年3月}}

最初に解の集合となる候補を生成し、それらの要素に対して1つ(もしくは複数)の解にたどり着くまで関数の適用とフィルタリングを繰り返す手法は、関数型プログラミングでよく用いられるパターンである<ref name="名前なし-6">{{harvnb|Lipovača|2012|p=22}}</ref>。

Haskell では、関数合成の二項演算子を使って'''ポイントフリースタイル'''で関数を定義することができる<ref name="名前なし-6"/>。関数をポイントフリースタイルで定義すると、データより関数に目が行くようになり、どのようにデータが移り変わっていくかではなく、どんな関数を合成して何になっているかということへ意識が向くため、定義が読みやすく簡潔になることがある<ref name="名前なし-6"/>。関数が複雑になりすぎると、ポイントフリースタイルでは逆に可読性が悪くなることもある<ref name="名前なし-6"/>。

=== 言語 ===

関数型プログラミング言語とは、関数型プログラミングを推奨している[[プログラミング言語]]である<ref name="名前なし-1"/>。略して関数型言語ともいう<ref name="名前なし-1"/>。全ての関数が参照透過性を持つようなものを、特に{{仮リンク|純粋関数型プログラミング言語|en|purely functional programming language}}という<ref name="名前なし-4"/>。そうでないものを非純粋であるという<ref name="名前なし-5"/>。

関数型プログラミング言語の多くは、言語の設計において何らかの形で[[ラムダ計算]]が関わっている<ref name="名前なし-3"/>。ラムダ計算はコンピュータの計算をモデル化する体系の一つであり、記号の列を規則に基づいて変換していくことで計算が行われるものである<ref name="名前なし-3"/>。

{| class="wikitable sortable"
|+ 関数型プログラミング言語
|-
! 名前
! 型付け
! 純粋性
! 評価戦略
! 理論的背景
|-
| [[Clean]]
| 静的型付け
| 純粋
| 遅延評価
|
|-
| [[Elm (プログラミング言語)|Elm]]
| 静的型付け
| 純粋
| 正格評価
|
|-
| [[Erlang]]
| 動的型付け
| 非純粋
| 正格評価
|
|-
| [[F Sharp|F#]]
| 静的型付け
| 非純粋
| 正格評価
|
|-
| [[Haskell]]<ref name="名前なし-2"/>
| 静的型付け<ref name="名前なし-2"/>
| 純粋<ref name="名前なし-2"/>
| 遅延評価<ref name="名前なし-2"/>
| 型付きラムダ計算<ref name="名前なし-3"/>
|-
| [[Idris (プログラミング言語)|Idris]]
| 静的型付け
| 純粋
| 正格評価
| 型付きラムダ計算
|-
| [[Lazy K]]
| 型なし
| 純粋
| 遅延評価
| コンビネータ論理
|-
| [[LISP|LISP 1.5]]<br>[[Scheme]]<br>[[Common Lisp]]<br>[[Clojure]]
| 動的型付け
| 非純粋
| 正格評価
| 型無しラムダ計算<ref name="名前なし-3"/>
|-
| [[LISP]]の各種方言<ref name="名前なし-3"/>
| 方言による
| 方言による
| 方言による
|
|-
| [[Miranda]]
| 静的型付け
| 純粋
| 遅延評価
|
|-
| [[ML (プログラミング言語)|ML]]<br>[[Standard ML]]<br>[[OCaml]]
| 静的型付け
| 非純粋
| 正格評価
|-
| [[Scala]]
| 静的型付け
| 非純粋
| 正格評価
|
|-
| [[Unlambda]]
| 型なし
| 非純粋
| 正格評価
| コンビネータ論理
|-
|[[Lean (証明アシスタント)|Lean]]
|静的型付け
|純粋
|正格評価
|型付きラムダ計算
|}

=== 手続き型プログラミングとの比較 ===

[[C|C 言語]]や [[Java]] 、 [[JavaScript]] 、 [[Python]] 、 [[Ruby]] などの2017年現在に使われている言語の多くは、手続き型の文法を持っている<ref name="名前なし-7">{{harvnb|本間|類地|逢坂|2017|p=22}}</ref>。そのような言語では、文法として式 (expression) と文 (statement) を持つ<ref name="名前なし-7"/>。ここでの式は、計算を実行して結果を得るような処理を記述するための文法要素であり、加減乗除や関数呼び出しなどから構成されている<ref name="名前なし-7"/>。ここでの文は、何らかの動作を行うようにコンピュータへ指示するための文法要素であり、条件分岐の [[if文|if 文]]やループの [[for文|for 文]]と [[while文|while 文]]などから構成されている<ref name="名前なし-7"/>。手続き型の文法では、式で必要な計算を進め、その結果を元にして文でコンピュータ命令を行うという形で、プログラムを記述する<ref name="名前なし-7"/>。このように、[[手続き型言語]]で重要なのは文である<ref name="名前なし-7"/>。

それに対して、[[関数型言語]]で重要なのは式である<ref name="名前なし-7"/>。関数型言語のプログラムはたくさんの式で構成され、プログラムそのものも一つの式である<ref name="名前なし-7"/>。たとえば、 Haskell では、プログラムの処理の記述において文は使われず、外部の定義を取り込む import 宣言も処理の一部として扱えない<ref name="名前なし-7"/>。関数型言語におけるプログラムの実行とは、プログラムを表す式の計算を進めて、その結果として値 (value) を得ることである<ref name="名前なし-7"/>。式を計算することを、'''評価する''' (evaluate) という<ref name="名前なし-7"/>。

手続き型言語ではコンピュータへの指示を文として上から順に並べて書くのに対して、関数型言語では数多く定義した細かい式を組み合わせてプログラムを作る<ref name="名前なし-7"/>。手続き型言語では文が重要であり、関数型言語では式が重要である<ref name="名前なし-8">{{harvnb|本間|類地|逢坂|2017|pp=22–23}}</ref>。

式と文の違いとして、型が付いているかどうかというのがある<ref name="名前なし-8"/>。式は型を持つが、文は型を持たない<ref name="名前なし-8"/>。プログラム全てが式から構成されていて、強い静的型付けがされているのならば、プログラムの全体が細部まで型付けされることになる<ref name="名前なし-8"/>。このように細部まで型付けされているようなプログラムは堅固なものになる<ref name="名前なし-8"/>。


== 歴史 ==
== 歴史 ==
=== 1930年代 ===
[[FORTRAN]]には、関数(ここでは副作用のあるものを含む意味で)を定義できる、「ADD A, 1」のような形ではなく「A = A + 1」のように式で計算を指示できる、といった特徴もあったが、基本的には手続き型プログラミング言語であった。
関数型言語の開発において、[[アロンゾ・チャーチ]]が1932年<ref group="注釈">{{harv|Church|1932}}</ref>と1941年<ref group="注釈">{{harv|Church|1941}}</ref>に発表した[[ラムダ計算]]の研究ほど基本的で重要な影響を与えたものはない<ref name="名前なし-9">{{harvnb|Hudak|1989|p=363}}</ref>。ラムダ計算は、それが考え出された当時は[[プログラム (コンピュータ)|プログラム]]を実行するような[[コンピュータ]]が存在しなかったために[[プログラミング言語]]として見なされなかったにもかかわらず、今では最初の関数型言語とされている<ref name="名前なし-9"/>。1989年現在の関数型言語は、そのほとんどがラムダ計算に装飾を加えたものとして見なせる<ref name="名前なし-9"/>。


=== 1960年代 ===
[[LISP]]は、その基本機能のモデルとして関数型の[[純LISP]]を持つなどといった特徴がある、最初の関数型言語である。今日広く使われているLispのうち特に[[Scheme]]は関数型としての特徴が強い。
1960年に[[ジョン・マッカーシー]]等が発表した [[LISP]] は関数型言語の歴史において重要である<ref>{{harvnb|Hudak|1989|p=367}}</ref>。ラムダ計算は LISP の基礎であると言われるが、マッカーシー自身が1978年<ref group="注釈">{{harv|McCarthy|1978}}</ref>に説明したところによると、[[匿名関数]]を表現したいというのが最初にあって、その手段としてマッカーシーはチャーチのラムダ計算を選択したに過ぎない<ref>{{harvnb|Hudak|1989|pp=367–368}}</ref>。


歴史的に言えば、 [[LISP]] に続いて関数型プログラミングパラダイムへ刺激を与えたのは、1960年代半ばの{{仮リンク|ピーター・ランディン|en|Peter Landin}}の成果である<ref name="名前なし-10">{{harvnb|Hudak|1989|p=371}}</ref>。ランディンの成果は[[ハスケル・カリー]]と[[アロンゾ・チャーチ]]に大きな影響を受けていた<ref name="名前なし-10"/>。ランディンの初期の論文は、ラムダ計算と、機械および高級言語 ([[ALGOL 60]]) との関係について議論している<ref name="名前なし-10"/>。ランディンは、1964年<ref group="注釈">{{harv|Landin|1964}}</ref>に、 [[SECDマシン|SECD マシン]]と呼ばれる抽象的な機械を使って機械的に式を評価する方法を論じ、1965年<ref group="注釈">{{harv|Landin|1965}}</ref>に、ラムダ計算で ALGOL 60 の非自明なサブセットを形式化した<ref name="名前なし-10"/>。1966年<ref group="注釈">{{harv|Landin|1966}}</ref>にランディンが発表した [[ISWIM]](If You See What I Mean の略)という言語(群)は、間違いなく、これらの研究の成果であり、[[構文]]や[[プログラム意味論|意味論]]において多くの重要なアイデアを含んでいた<ref name="名前なし-10"/>。 ISWIM は、ランディン本人によれば、「 LISP を、その名前にも表れた[[リスト (抽象データ型)|リスト]]へのこだわり、手作業のメモリ割り当て、ハードウェアに依存した教育方法、[[S式|重い括弧]]、伝統への妥協、から解放しようとする試みとして見ることができる」<ref name="名前なし-10"/>。関数型言語の歴史において ISWIM は次のような貢献を果たした<ref name="名前なし-11">{{harvnb|Hudak|1989|pp=371–372}}</ref>。
現代的な関数型プログラミング言語の祖としてはアイディアが1966年に発表された[[ISWIM]]が挙げられるが、1970年前後までは関数型プログラミング言語の歴史はLispの発展が主である。1970年代にプロジェクトが開始された[[w:Logic for Computable Functions]]のための言語として[[ML (プログラミング言語)|ML]]が作られている。


* 構文についての革新<ref name="名前なし-10"/>
またLISPにおいて、変数のスコープに静的スコープを採用した[[Scheme]]が誕生したのが1975年である。
** 演算子を前置記法で記述するのをやめて中置記法を導入した<ref name="名前なし-10"/>。
** let 節と where 節を導入して、さらに、関数を順序なく同時に定義でき、相互再帰も可能なようにした<ref name="名前なし-10"/>。
** 宣言などを記述する構文に、インデントに基づいたオフサイドルールを使用した<ref name="名前なし-10"/>。
* 意味論についての革新<ref name="名前なし-11"/>
** 非常に小さいが表現力があるコア言語を使って、構文的に豊かな言語を定義するという戦略を導入した<ref name="名前なし-10"/>。
** 等式推論 (equational reasoning) を重視した<ref name="名前なし-10"/>。
** 関数によるプログラムを実行するための単純な抽象機械としての SECD マシンを導入した<ref name="名前なし-11"/>。


ランディンは「それをどうやって行うか」ではなく「それの望ましい結果とは何か」を表現することに重点を置いており、そして、 ISWIM の宣言的なプログラミング・スタイルは命令的なプログラミング・スタイルよりも優れているというランディンの主張は、今日まで関数型プログラミングの賛同者たちから支持されてきた<ref name="名前なし-12">{{harvnb|Hudak|1989|p=372}}</ref>。その一方で、関数型言語への関心が高まるまでは、さらに10年を要した<ref name="名前なし-12"/>。その理由の一つは、 ISWIM ライクな言語の実用的な実装がなかったことであり、実のところ、この状況は1980年代になるまで変わらなかった<ref name="名前なし-12"/>。
1977年、FORTRANの設計と[[バッカス・ナウア記法|BNF]]の発明の業績でこの年の[[チューリング賞]]を受賞した[[ジョン・バッカス]]は、''Can Programming Be Liberated From the von Neumann Style?: A Functional Style and Its Algebra of Programs''<ref>「プログラミングはフォン・ノイマン・スタイルから解放されうるか?: 関数型プログラミング・スタイルとそのプログラム代数」、[[米澤明憲]]訳『ACMチューリング賞講演集』([[共立出版]])pp. 83-156</ref>と題した受賞記念講演で関数型プログラミングの重要性を訴えた。講演では[[w:FP (programming language)|FP]]という関数型プログラミング言語の紹介もした(サブタイトルの後半の「プログラムの代数」はこれを指す)が、これは[[APL]](特に、[[高階関数]]の意味がある記号(APLの用語ではoperator([[作用素]])という))の影響を受けている(APL自体はふつう関数型とはされない)。


[[ケネス・アイバーソン]]が1962年<ref group="注釈">{{harv|Iverson|1962}}</ref>に発表した [[APL]] は、純粋な関数型プログラミング言語ではないが、その関数型的な部分を取り出したサブセットがラムダ式に頼らずに関数型プログラミングを実現する方法の一例であるという点で、関数型プログラミング言語の歴史を考察する際に言及する価値はある<ref name="名前なし-12"/>。実際に、アイバーソンが APL を設計した動機は、配列のための代数的なプログラミング言語を開発したいというものであり、アイバーソンのオリジナル版は基本的に関数型的な記法を用いていた<ref name="名前なし-12"/>。その後の APL では、いくつかの命令型的な機能が追加されている<ref name="名前なし-12"/>。
バッカスのFPは広く使用されることはなかったが、この後関数型プログラミング言語の研究・開発は広まることとなった。1985年に[[Miranda]]が登場した。1987年に、遅延評価の純粋関数型プログラミング言語の標準の必要性が認識され[[Haskell]]の策定が始まった。1990年にHaskell 1.0仕様がリリースされた。同じく1990年にはMLの標準である[[Standard ML]]もリリースされている。


== 脚注 ==
[[Clean]]は1987年に登場したが、発展の過程で[[Haskell]]の影響を受けている。1996年に、ML処理系のひとつであったCamlに[[オブジェクト指向]]を追加した[[OCaml]]が登場した。また日本ではSMLに独自の拡張をほどこした[[SML#]]が開発されている。


{{脚注ヘルプ}}
21世紀に入ると、[[Java仮想マシン]]や[[共通言語基盤|CLI]]に関数型言語を実装しようという動きがあらわれ、[[Scala]]・[[Clojure]]・[[F Sharp|F#]]などが登場した。


=== 注釈 ===
== 代表的な関数型言語 ==
=== 純粋関数型言語 ===
* 強い静的型付け
** [[Clean]]
** [[Haskell]]
** [[Miranda]]
* 型なし
** [[Lazy K]]


{{Notelist}}
=== 非純粋関数型言語 ===
* 強い静的型付け
** [[プログラミング言語ML|ML]]
** [[Standard ML]], [[SML#]]
** [[OCaml]]
** [[F Sharp|F#]]
* 動的型付け
** [[Erlang]]
** [[LISP]]
*** [[Scheme]]
* 型なし
** [[Unlambda]]


=== 出典 ===
=== その他の関数的性質を持つ言語など ===

* [[APL]]
{{Reflist}}
* [[XSL Transformations|XSLT]]

== 参考文献 ==

* {{Cite Q|Q55890017|last=Church|first=Alonzo}}
* {{Cite Q|Q105884272|last=Church|first=Alonzo}}
* {{Cite Q|Q55871443|last=Hudak|first=Paul}}
* {{Cite Q|Q105954505|last=Iverson|first=Kenneth}}
* {{Cite Q|Q56048080|last=McCarthy|first=John}}
* {{Cite Q|Q30040385|last=Landin|first=Peter}}
* {{Cite Q|Q105941120|last=Landin|first=Peter}}
* {{Cite Q|Q54002422|last=Landin|first=Peter}}
* {{Cite Q|Q105845956|edition=1st (1st printing)|last=Lipovača|first=Miran}}
* {{Cite Q|Q105833610|edition=1st (1st printing)|last=本間|first=雅洋|last2=類地|first2=孝介|last3=逢坂|first3=時響}}


== 外部リンク ==
== 外部リンク ==

* [http://www.sampou.org/haskell/article/whyfp.html なぜ関数プログラミングは重要か]
* [http://www.sampou.org/haskell/article/whyfp.html なぜ関数プログラミングは重要か]
* [http://www.topxml.com/xsl/articles/fp/ The Functional Programming Language XSLT - A proof through examples]
* [https://fxsl.sourceforge.net/articles/FuncProg/Functional%20Programming.html lang|en|The Functional Programming Language XSLT - A proof through examples]([http://alamos.math.arizona.edu/courses/rychlik/CourseDir/589/Assignments/a3/fp.pdf PDF])


== 参考文献 ==
== 関連項目 ==

{{reflist}}
* [[カリー化]]

{{プログラミング言語の関連項目}}

{{Normdaten}}


{{DEFAULTSORT:かんすうかた}}
{{DEFAULTSORT:かんすうかたふろくらみ}}
[[Category:関数型言語|*]]
[[Category:関数型プログラミング|*]]
[[Category:プログラミングパラダイム]]
[[Category:プログラミングパラダイム]]

2024年11月3日 (日) 14:27時点における最新版

関数型プログラミング(かんすうがたプログラミング、: functional programming)とは、数学的な意味での関数を主に使うプログラミングのスタイルである[1]。 functional programming は、関数プログラミング(かんすうプログラミング)などと訳されることもある[2]

関数型プログラミング言語: functional programming language)とは、関数型プログラミングを推奨しているプログラミング言語である[1]。略して関数型言語: functional language)ともいう[1]

概要

[編集]

関数型プログラミングは、関数を主軸にしたプログラミングを行うスタイルである[1]。ここでの関数は、数学的なものを指し、引数の値が定まれば結果も定まるという参照透過性を持つものである[1]

参照透過性とは、数学的な関数と同じように同じ値を返す式を与えたら必ず同じ値を返すような性質である[1]。次の square 関数は、 2 となるような式を与えれば必ず 4 を返し、 3 となるような式を与えれば必ず 9 を返し、いかなる状況でも別の値を返すということはなく、これが参照透過性を持つ関数の一例となる[1]

def square(n):
  return n ** 2

次の countup 関数は、同じ 1 を渡しても、それまでに countup 関数がどのような引数で呼ばれていたかによって、返り値が 1, 2, 3, ... と変化するため、引数の値だけで結果の値が定まらないような参照透過性のない関数であり、数学的な関数とはいえない[1]

counter = 0
def countup(n):
  global counter
  counter += n
  return counter

関数型プログラミングは、参照透過性を持つような数学的な関数を使って組み立てたが主役となる[1]。別の箇所に定義されている処理を利用することを、手続き型プログラミング言語では「関数を実行する」や「関数を呼び出す」などと表現するが、関数型プログラミング言語では「式を評価する」という表現も良く使われる[3]

参照透過性

[編集]

参照透過性とは、同じ値を与えたら返り値も必ず同じになるような性質である[1]。参照透過性を持つことは、その関数が状態を持たないことを保証する[4]。状態を持たない数学的な関数は、並列処理を実現するのに適している[4]。関数型プログラミング言語の内で、全ての関数が参照透過性を持つようなものを純粋関数型プログラミング言語という[4]

入出力

[編集]

関数型プログラミングでは、数学的な関数を組み合わせて計算を表現するが、それだけではファイルの読み書きのような外界とのやり取りを要する処理を直接的に表現できない[5]。このような外界とのやり取りを I/O (入出力) と呼ぶ[5]。数学的な計算をするだけ、つまり 1 + 1 のようなプログラム内で完結する処理ならば、入出力を記述できなくても問題ないが、現実的なプログラムにおいてはそうでない[5]

非純粋な関数型プログラミング言語においては、式を評価すると同時に I/O が発生する関数を用意することで入出力を実現する[5]。たとえば、 F# 言語では、printfn "Hi." が評価されると、 () という値が戻ってくると同時に、画面に Hi. と表示される I/O が発生する[5]

Haskell では、評価と同時に I/O が行われる関数は存在しない[5]。たとえば、 putStrLn "Hi." という式が評価されると IO () 型を持つ値が返されるが画面には何も表示されず、この値が Haskell の処理系によって解釈されて初めて画面に Hi. と表示される[5]I/O アクションとは、ファイルの読み書きやディスプレイへの表示などのような I/O を表現する式のことである[5][6]IO a という型は、コンピュータへの指示を表す I/O アクションを表現している[5][7]。ここでの IOモナドと呼ばれるものの一つである[8]

Clean では、一意型を用いて入出力を表す。

手法

[編集]

最初に解の集合となる候補を生成し、それらの要素に対して1つ(もしくは複数)の解にたどり着くまで関数の適用とフィルタリングを繰り返す手法は、関数型プログラミングでよく用いられるパターンである[9]

Haskell では、関数合成の二項演算子を使ってポイントフリースタイルで関数を定義することができる[9]。関数をポイントフリースタイルで定義すると、データより関数に目が行くようになり、どのようにデータが移り変わっていくかではなく、どんな関数を合成して何になっているかということへ意識が向くため、定義が読みやすく簡潔になることがある[9]。関数が複雑になりすぎると、ポイントフリースタイルでは逆に可読性が悪くなることもある[9]

言語

[編集]

関数型プログラミング言語とは、関数型プログラミングを推奨しているプログラミング言語である[1]。略して関数型言語ともいう[1]。全ての関数が参照透過性を持つようなものを、特に純粋関数型プログラミング言語英語版という[4]。そうでないものを非純粋であるという[5]

関数型プログラミング言語の多くは、言語の設計において何らかの形でラムダ計算が関わっている[3]。ラムダ計算はコンピュータの計算をモデル化する体系の一つであり、記号の列を規則に基づいて変換していくことで計算が行われるものである[3]

関数型プログラミング言語
名前 型付け 純粋性 評価戦略 理論的背景
Clean 静的型付け 純粋 遅延評価
Elm 静的型付け 純粋 正格評価
Erlang 動的型付け 非純粋 正格評価
F# 静的型付け 非純粋 正格評価
Haskell[2] 静的型付け[2] 純粋[2] 遅延評価[2] 型付きラムダ計算[3]
Idris 静的型付け 純粋 正格評価 型付きラムダ計算
Lazy K 型なし 純粋 遅延評価 コンビネータ論理
LISP 1.5
Scheme
Common Lisp
Clojure
動的型付け 非純粋 正格評価 型無しラムダ計算[3]
LISPの各種方言[3] 方言による 方言による 方言による
Miranda 静的型付け 純粋 遅延評価
ML
Standard ML
OCaml
静的型付け 非純粋 正格評価
Scala 静的型付け 非純粋 正格評価
Unlambda 型なし 非純粋 正格評価 コンビネータ論理
Lean 静的型付け 純粋 正格評価 型付きラムダ計算

手続き型プログラミングとの比較

[編集]

C 言語JavaJavaScriptPythonRuby などの2017年現在に使われている言語の多くは、手続き型の文法を持っている[10]。そのような言語では、文法として式 (expression) と文 (statement) を持つ[10]。ここでの式は、計算を実行して結果を得るような処理を記述するための文法要素であり、加減乗除や関数呼び出しなどから構成されている[10]。ここでの文は、何らかの動作を行うようにコンピュータへ指示するための文法要素であり、条件分岐の if 文やループの for 文while 文などから構成されている[10]。手続き型の文法では、式で必要な計算を進め、その結果を元にして文でコンピュータ命令を行うという形で、プログラムを記述する[10]。このように、手続き型言語で重要なのは文である[10]

それに対して、関数型言語で重要なのは式である[10]。関数型言語のプログラムはたくさんの式で構成され、プログラムそのものも一つの式である[10]。たとえば、 Haskell では、プログラムの処理の記述において文は使われず、外部の定義を取り込む import 宣言も処理の一部として扱えない[10]。関数型言語におけるプログラムの実行とは、プログラムを表す式の計算を進めて、その結果として値 (value) を得ることである[10]。式を計算することを、評価する (evaluate) という[10]

手続き型言語ではコンピュータへの指示を文として上から順に並べて書くのに対して、関数型言語では数多く定義した細かい式を組み合わせてプログラムを作る[10]。手続き型言語では文が重要であり、関数型言語では式が重要である[11]

式と文の違いとして、型が付いているかどうかというのがある[11]。式は型を持つが、文は型を持たない[11]。プログラム全てが式から構成されていて、強い静的型付けがされているのならば、プログラムの全体が細部まで型付けされることになる[11]。このように細部まで型付けされているようなプログラムは堅固なものになる[11]

歴史

[編集]

1930年代

[編集]

関数型言語の開発において、アロンゾ・チャーチが1932年[注釈 1]と1941年[注釈 2]に発表したラムダ計算の研究ほど基本的で重要な影響を与えたものはない[12]。ラムダ計算は、それが考え出された当時はプログラムを実行するようなコンピュータが存在しなかったためにプログラミング言語として見なされなかったにもかかわらず、今では最初の関数型言語とされている[12]。1989年現在の関数型言語は、そのほとんどがラムダ計算に装飾を加えたものとして見なせる[12]

1960年代

[編集]

1960年にジョン・マッカーシー等が発表した LISP は関数型言語の歴史において重要である[13]。ラムダ計算は LISP の基礎であると言われるが、マッカーシー自身が1978年[注釈 3]に説明したところによると、匿名関数を表現したいというのが最初にあって、その手段としてマッカーシーはチャーチのラムダ計算を選択したに過ぎない[14]

歴史的に言えば、 LISP に続いて関数型プログラミングパラダイムへ刺激を与えたのは、1960年代半ばのピーター・ランディン英語版の成果である[15]。ランディンの成果はハスケル・カリーアロンゾ・チャーチに大きな影響を受けていた[15]。ランディンの初期の論文は、ラムダ計算と、機械および高級言語 (ALGOL 60) との関係について議論している[15]。ランディンは、1964年[注釈 4]に、 SECD マシンと呼ばれる抽象的な機械を使って機械的に式を評価する方法を論じ、1965年[注釈 5]に、ラムダ計算で ALGOL 60 の非自明なサブセットを形式化した[15]。1966年[注釈 6]にランディンが発表した ISWIM(If You See What I Mean の略)という言語(群)は、間違いなく、これらの研究の成果であり、構文意味論において多くの重要なアイデアを含んでいた[15]。 ISWIM は、ランディン本人によれば、「 LISP を、その名前にも表れたリストへのこだわり、手作業のメモリ割り当て、ハードウェアに依存した教育方法、重い括弧、伝統への妥協、から解放しようとする試みとして見ることができる」[15]。関数型言語の歴史において ISWIM は次のような貢献を果たした[16]

  • 構文についての革新[15]
    • 演算子を前置記法で記述するのをやめて中置記法を導入した[15]
    • let 節と where 節を導入して、さらに、関数を順序なく同時に定義でき、相互再帰も可能なようにした[15]
    • 宣言などを記述する構文に、インデントに基づいたオフサイドルールを使用した[15]
  • 意味論についての革新[16]
    • 非常に小さいが表現力があるコア言語を使って、構文的に豊かな言語を定義するという戦略を導入した[15]
    • 等式推論 (equational reasoning) を重視した[15]
    • 関数によるプログラムを実行するための単純な抽象機械としての SECD マシンを導入した[16]

ランディンは「それをどうやって行うか」ではなく「それの望ましい結果とは何か」を表現することに重点を置いており、そして、 ISWIM の宣言的なプログラミング・スタイルは命令的なプログラミング・スタイルよりも優れているというランディンの主張は、今日まで関数型プログラミングの賛同者たちから支持されてきた[17]。その一方で、関数型言語への関心が高まるまでは、さらに10年を要した[17]。その理由の一つは、 ISWIM ライクな言語の実用的な実装がなかったことであり、実のところ、この状況は1980年代になるまで変わらなかった[17]

ケネス・アイバーソンが1962年[注釈 7]に発表した APL は、純粋な関数型プログラミング言語ではないが、その関数型的な部分を取り出したサブセットがラムダ式に頼らずに関数型プログラミングを実現する方法の一例であるという点で、関数型プログラミング言語の歴史を考察する際に言及する価値はある[17]。実際に、アイバーソンが APL を設計した動機は、配列のための代数的なプログラミング言語を開発したいというものであり、アイバーソンのオリジナル版は基本的に関数型的な記法を用いていた[17]。その後の APL では、いくつかの命令型的な機能が追加されている[17]

脚注

[編集]

注釈

[編集]

出典

[編集]

参考文献

[編集]
  • Church, Alonzo (1932年4月), “A Set of Postulates for the Foundation of Logic” (英語), Annals of Mathematics 33 (2): 346, doi:10.2307/1968337, ISSN 0003-486X, JSTOR 1968337, https://jstor.org/stable/1968337 , Wikidata Q55890017
  • Church, Alonzo (1941年) (英語), The Calculi of Lambda Conversion, プリンストン大学出版局 , Wikidata Q105884272
  • Hudak, Paul (1989年9月1日), “Conception, evolution, and application of functional programming languages” (英語), ACM Computing Surveys 21 (3): 359–411, doi:10.1145/72551.72554, ISSN 0360-0300 , Wikidata Q55871443
  • Iverson, Kenneth (1962年12月1日) (英語), A Programming Language, ジョン・ワイリー・アンド・サンズ, ISBN 978-0-471-43014-8, OL 26792153M , Wikidata Q105954505
  • McCarthy, John (1978年), History of LISP, doi:10.1145/800025.808387 , Wikidata Q56048080
  • Landin, Peter (1964年1月1日), “The Mechanical Evaluation of Expressions” (英語), The Computer Journal 6 (4): 308-320, doi:10.1093/COMJNL/6.4.308, ISSN 0010-4620 , Wikidata Q30040385
  • Landin, Peter (1965年), “Correspondence between ALGOL 60 and Church's Lambda-notation” (英語), Communications of the ACM 8, ISSN 0001-0782 , Wikidata Q105941120
  • Landin, Peter (1966年3月1日), “The next 700 programming languages” (英語), Communications of the ACM 9 (3): 157-166, doi:10.1145/365230.365257, ISSN 0001-0782 , Wikidata Q54002422
  • Lipovača, Miran 田中英行, 村主崇行訳 (2012年5月25日), すごいHaskellたのしく学ぼう! (1st (1st printing) ed.), オーム社, ISBN 978-4-274-06885-0 , Wikidata Q105845956
  • 本間雅洋; 類地孝介; 逢坂時響『Haskell入門 関数型プログラミング言語の基礎と実践』(1st (1st printing))技術評論社、2017年10月10日。ISBN 978-4-7741-9237-6 , Wikidata Q105833610

外部リンク

[編集]

関連項目

[編集]