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

削除された内容 追加された内容
m "Template:" を含むテンプレート呼び出し
(同じ利用者による、間の5版が非表示)
5行目:
'''関数型言語'''({{lang-en-short|''functional language''}})は、'''関数型プログラミング'''のスタイルまたは[[プログラミングパラダイム|パラダイム]]を扱う[[プログラミング言語]]の総称である。関数型プログラミングは関数の[[写像|適用]]をベースにした[[宣言型プログラミング]]の一形態であり、関数は[[引数]]の適用から先行式の評価を後続式の適用につなげて終端の[[評価戦略|評価]]を導き出す[[式 (プログラミング)|式]]の[[ツリー構造]]として定義される。式の評価に伴う[[副作用 (プログラム)|副作用]]の発生には大きな注意が払われる。関数は引数ないし返り値として渡せる[[第一級オブジェクト]]として扱われる。
 
関数型プログラミングは[[数理論理学]]と代数系をルーツにし、[[ラムダ計算]]と[[コンビネータ論理]]を幹にして構築され、[[LISP]]言語が実装面の先駆になっている。関数の数学的な純粋性を指向したものは純粋関数型言語と個別に定義されている。[[命令型プログラミング]]言語([[手続き型プログラミング|手続き型]]や[[オブジェクト指向プログラミング|オブジェクト指向]]を指す)では単に有用な構文スタイルとして扱われている事が多い。[[高階関数]]、[[第一級関数]]、{{仮リンク|関数合成|en|Function composition (computer science)|label=}}、{{仮リンク|部分適用|en|Partial application|label=}}、[[無名関数]]、[[クロージャ]]、[[継続]]、{{仮リンク|ポイントフリー|en|Tacit programming|label=}}、[[パイプライン処理|パイプライン]]、[[イテレータ]]、[[ジェネレータ (プログラミング)|ジェネレータ]]、[[代数的データ型]]、[[型推論]]、[[パターンマッチング]]、[[ガード (プログラミング)|ガード]]、{{仮リンク|パラメトリック多相|en|Parametric polymorphism|label=}}、{{仮リンク|アドホック多相|en|Ad hoc polymorphism|label=}}、[[再帰]]、[[束縛変数]]、[[イミュータブル]]、{{仮リンク|純粋関数|en|Pure function|label=}}[[再帰]]などが{{誰範囲|date=2020年5月|関数型プログラミングのスタイル要素として挙げられる}}。
 
== 特徴 ==
{{出典の明記| date = 2020年5月6日 (水) 02:29 (UTC)| section = 1}}
ここでは関数型プログラミング本来の構文スタイルを元にして説明する。式を基本文にする関数型に対して、[[文 (プログラミング)|ステートメント]]を基本文にする[[命令型プログラミング]]言語では必要に応じて構文スタイルを変えて実装されている。代表的なのは「式に引数を適用する」に対する「関数に引数を渡す」である。値とその型付けに対するコンセプトおよびデータストラクチャの実装スタイルも異なっている。ただし双方ともアセンブリコード上では同様なものに符号化される。
 
=== 式と関数 ===
関数型プログラムの基本文は[[式 (プログラミング)|式]](''expression'')である。式は個体である値(''value'')と写像である関数(''function'')の二つから構成される。関数の定義には[[演算子]](''operator'')も含まれている。値は後節で述べる[[データ構造|データストラクチャ]]と、[[ラムダ計算]]で言われる変数(''variable'')の双方を意味する。データストラクチャの定義には数値、論理値、文字値、文字列といった基本データ型=[[プリミティブ型|プリミティブ]]も含まれている。変数は[[束縛変数]]と[[自由変数と束縛変数|自由変数]]を指す。評価(''evaluation'')される前の式は、ラムダ計算で言われるネーム(''name'')と同義になる。ネームは数学上の数式または代数式に相当するものである。式は、式内の変数部分が確定される前は評価できない[[ボトム型]]であり、これはラムダ抽象(''abstraction'')と同義である。式内の変数部分を確定するのはラムダ適用(''application'')と同義である。このネームが評価されると値になり、これはラムダ計算で言われる簡約(''reduction'')と同義である。式は値と同一視されるので、すなわち式と値は相互再帰の関係にある。式内の値は他の式の評価値である事があり、その式内にもまた他の値があるといった具合である。この仕組みは[[高階論理]]''と呼ばれる。''
 
関数も値と同一視される。関数は写像の型の値であるが、プログラム的には式に引数を結び付ける機能であり、これは式に引数を[[写像|適用]](''application'')すると呼ばれる。式内の仮引数(''parameter'')箇所に実引数(''argument'')が順次当てはめられ、式ツリーの終端式が評価値になる。引数によっては[[ボトム型]]になる関数もありこれは部分関数と呼ばれる。ボトム型は虚(''falsity'')と見なされており、式のツリーないし写像の連鎖の失敗した終着点になる。関数は、式に第1引数を適用したもの→第x引数を適用したもの→評価値、という形をとる。引数を1個ずつ適用する形態は[[カリー化]]と呼ばれる。2個以上の引数を同時適用する形態は非カリー化と呼ばれる。関数の型(''function type'')は「A→B→C」のように各引数値から評価値までの写像パターンで表現される。片方の評価値と片方の第1引数が同じ型の両関数は任意に連結して新たな関数にできる。この双方の写像のつなぎ合わせは関数合成と呼ばれる。カリー化された関数は引数の適用を途中で止めて残り引数を後から適用するように保留できる。この保留状態の関数の生成は部分適用と呼ばれる。任意のタイミングで遅延評価(''call/cc'')するために評価を保留してる関数は[[継続]]と呼ばれる。その応用に、一つの式を個々の演算子適用(関数適用)に分解して各個[[継続]]化し、それらをボトムアップで組み上げて一つの継続集合体を生成する[[継続渡しスタイル]]がある。部分適用と継続はネームと同義である。関数も当然ながら[[高階論理]]に組み込まれている。引数値または評価値として扱うことができる関数は[[第一級関数]]と呼ばれる。その第一級関数を扱うことができる関数は[[高階関数]]と呼ばれる。
 
関数は名前付きと名前無しの二通りある。名前無しの関数は専らラムダ抽象を模した構文で定義される。式内に自由変数を内包しない方は[[無名関数]]と呼ばれ、自由変数を内包する方はそれを囲い込むという意味で[[クロージャ]]と呼ばれる。自由変数は外部データへの接点になる。[[無名関数]]は引数をピュア[[写像|マッピング]]する純粋関数である。[[クロージャ]]の引数の[[写像|マッピング]]は式内の自由変数に影響され、またその自由変数に作用する事もあるという副作用要素を閉包した非純粋関数である。関数の名前は、それに結び付けられた式または式ツリーの[[不動点]]の表現になる。自式の不動点を式内に置いて新たな引数と共に[[高階論理]]の式として評価する手法は[[再帰]]と呼ばれる。関数の終端式での再帰は実引数の更新+先端式へのアドレスジャンプと同等に見なせるのでもっぱらそちらに最適化されてこれは[[末尾再帰]]と呼ばれる。末尾再帰は論理性を損なわずにスタック資源から離れた無制限ループを可能にする実装概念として重視されている。名前付き関数で、仮引数記述を省略したものはポイントフリーと呼ばれ、その省略箇所に先行式評価値が実引数として暗黙適用される。名前無し関数で、先行式評価値を実引数にする記述を省略して、その仮引数箇所に暗黙適用するのもポイントフリーと呼ばれる。この暗黙適用の式を並べて連鎖させる手法は[[パイプライン処理|パイプライン]]と呼ばれるが、言語によっては特別な演算子と併せて明示する。リスト処理時にリストの各要素に対する作用子として渡される第一級関数は[[反復子|イテレータ]]と呼ばれる。作用後の各要素を別の新生リストに向けて複製する働きを加えたものは[[ジェネレータ (プログラミング)|ジェネレータ]]と呼ばれる。これは[[イミュータブル]]重視時に多用される。イテレータはポイントフリーの無名関数、ジェネレータはポイントフリーのクロージャとして定義される事が多い。関数名は[[関手]]の識別子と同義なので、同じ識別子に個別の引数パターン候補を付けたものを列挙することで[[選言]]の関係でつなげる事ができる。これは[[多重定義|関数のパターンマッチング]]と呼ばれる。また、変数名をその場限りの[[関手]]の識別子と見なしてパターンマッチ候補を列挙して[[選言]]の関係でつなげる方は[[パターンマッチング|パターンマッチング式]]と呼ばれる。パターンマッチングは単純照合だけでなく比較的照合や任意の要素をワイルドカードにした部分的照合でも行われる
 
演算子はデフォルトの式内容を持ち、その引数が単項演算子なら1個、二項演算子なら2個に限定された関数と同義である。引数を部分適用された演算子はセクションと呼ばれる。演算子は任意の型をフックした、又は任意の型に演算子をフックさせた再定義および追加定義ができる。前者の演算子に型をフックしたするという前者は関数のパターンマッチングと同じ仕組みで、後者の型に演算子をフックさせるという後者は抽象データ型の静的メンバ関数と同じ仕組みで実装される。双方ともアドホック多相に該当するものである。
関数名は[[関手]]の識別子と同義なので、同じ識別子に個別の引数パターン候補を付けたものを列挙することで[[非交和]]または[[選言]]の関係でつなげる事ができる。これは[[多重定義|関数のパターンマッチング]]と呼ばれる。また、変数名を直接[[関手]]の識別子と見なしてパターンマッチ候補を列挙して非交和の関係でつなげる方は[[パターンマッチング|パターンマッチング式]]と呼ばれる。パターンマッチングは等価性照合、等値性照合、任意の要素をワイルドカードにした部分的等値性照合のいずれかで行われる。パターンマッチで特定された実引数値ないし変数値を更に任意の等値式または比較式で真偽判定し、その結果に従った写像の二択分岐や、偽の場合はパターンマッチをそこで無効にして写像をキャンセルする事もできる。これは[[ガード (プログラミング)|ガード]]と呼ばれる。
 
演算子はデフォルトの式内容を持ち、その引数が単項演算子なら1個、二項演算子なら2個に限定された関数と同義である。部分適用された演算子はセクションと呼ばれる。演算子は任意の型をフックした、又は任意の型にフックさせた再定義および追加定義ができる。前者のフックしたは関数のパターンマッチングと同じ仕組みで、後者のフックさせたは抽象データ型の静的メンバ関数と同じ仕組みで実装される。双方ともアドホック多相に該当するものである。
 
=== 値とデータストラクチャ ===
37 ⟶ 35行目:
[[参照透過性]](''referential transparency'')とは関数は同じ引数値に対して必ず同じ評価値を恒久的に導出し、その評価過程において現行計算枠外の情報資源に一切の作用を及ぼさないというプロセス上の枠組みを意味する。現行計算枠外のいずれかの情報資源が変化するのと同時にいずれかの関数の評価過程も変化してしまう現象が[[副作用 (プログラム)|副作用]]と呼ばれる。参照透過性はこの副作用の論理的排除も同時に意味している。参照透過性に則した関数実装は関数の純粋化と呼ばれる。副作用の論理的排除は関数の純粋化の他、あらゆる再代入処理をプログラムから排除する事で成立する。それによってプログラム内に存在するあらゆる値の写像の履歴が[[有向グラフ]]化されて、プログラム開始時に宣言(''declarative'')された初期値まで遡れるようになる。宣言値からあらゆる存在値をつなぐ写像の履歴であるプロセス[[有向グラフ]]の解析と模型化は、[[プロセス計算|プロセス微積分]]ないし[[プロセス代数]]と呼ばれ[[並行プログラミング]]などの支柱になる。関数型プログラミングの世界で再代入がタブーとされるのは、それが写像の履歴の改ざんにつながるからである。従ってある時点の写像をただ書き留めておく[[束縛変数]]と、旧値の更新を新値の産出で代替した[[イミュータブル]]が重視される。[[構造化プログラミング|制御フロー]]の反復(ループ)は関数の再帰で表現され、選択(分岐)は非交和の関係でつなげた写像で表現される。再代入処理は自由変数の他、リスト更新、クロージャ、継続、オプション型生成、ボトム型処理、システムコール、各種I/O作業なども指しており、参照透過性を維持しつつそれらを実装するための仕組みが[[型理論]]由来の派生構造型であり、[[圏論]]由来の[[モナド (プログラミング)|モナド]]である。
 
参照透過性が保証されたプロセス[[有向グラフ]]は、一定の[[証明論]]に基づいたプルーフアシスタントによる[[正当性 (計算機科学)|プログラム正当性]]の[[形式的検証]]および[[数学的証明]]を可能にする。プルーフアシスタントはソフトウェアツールである。純粋関数型言語はその為に参照透過性をプログラム全体の枠組みにしている。プログラム全体に参照透過性を適用するには関数の純粋化と再代入処理の排除の他に、プログラムレベルでは回避できない各種I/O作業に伴う必然的副作用の論理的排除も必要になるので専用のランタイム環境上での動作が必須になる。ランタイム環境は「コンテキスト」を走行プログラムとの仲介にする。プログラム内の各関数は、ライナー型引数値として渡されたコンテキストに作用するという形で各種I/O作業を行う。その仮想的I/O作業はランタイム環境側で実際に代行され、そのI/O作業で変化したコンピュータ環境はその都度コンテキストに反映される。関数はコンテキストをライナー型返り値として渡し返す。ライナ―型(''linear type'')は[[型理論]]の派生構造型(''substructural type'')の一形態であり、[[線形合同法]]に似たユニーク値生成アルゴリズムによってプロセス有向グラフの正当性を維持するための[[型システム]]である。これはユニークネス型とも呼ばれる。コンテキストに「関連値」を注入する仕組みはアフィン型(''affine type'')、抽出する仕組みは関連型(''relevant type'')と呼ばれる。双方とも派生構造型の一形態である。このように各種I/O作業をコンテキストへの作用という形にする事で副作用を論理的に排除し、ライナー型の疑似乱数列に似た仕組みで参照透過性を論理的に維持している。常にユニークな値に生成されるライナー型値は、I/O作業の副作用によって実際には変化しているランタイム環境の時系列状態を完全に抽象化して、それらを理論上各個照会可能にしているマッピングキーである。これによってランタイム環境の変化もプロセス有向グラフで論理的に辿れるようにしている。なお、型システム理論の代わりに[[圏論]]を利用しに基づいてプロセス有向グラフの正当性を維持するための[[デザインパターン (ソフトウェア)|デザインパターン]]手法が[[モナド (プログラミング)|モナド]]である。参照透過性を維持する以上の機能を持たない派生構造型に対して、モナドの方は関連値とコンテキストを組み合わせた写像(=関手)をより簡潔作用す表現できモナド演算子は数学問題における[[公理]]や[[公式]]と同等存在あり決められた効率的なアールに従っゴリズムの実装手段とし用いるだけで副作用の排除と参照透過性の維持を論理的に表現できられる。
 
=== 型システム ===
関数型プログラミングの[[型システム]](''type system'')は、[[型付きラムダ計算|型付けラムダ計算]]ベースの[[型理論]]に基づいたスタイルで実装されている。型システムの分類に従った対比で述べると、[[命令型言語]]では明示的型付け(''manifest typing'')が多用されるのに対し、関数型では推論的型付け(''inferred typing'')が多用される。また関数型では、所属する部品に注目して全体を識別する構造的型付け(''structural typing'')よりも、記名から全体を識別しその文脈で所属する部品も識別する記名的型付け(''nominal typing'')の方がよく用いられる。これはアドホック多相に相当するものであり、実例は[[型クラス]]と型アノテーションである。型クラスは型理論における”文脈”を形式化したものでインターフェースのように用いられる。型アノテーションはそれ自体が構造体化される実装もあり、この場合は様々な情報が付加されてやや無節操に応用される。なお、構造的型付けは[[ダックタイピング]]の考え方と同義である。
 
関数型初期の[[LISP]]系の[[S式]]は、二項型構築子(コンス)の実行時の連結でパターンを形成する潜在的型付け(''latent typing'')であり、これは形式化されていない推論的型付けと言えるもので[[動的型付け]](''dynamic typing'')に分類されている。[[ML (プログラミング言語)|ML]]系を境にしてパターンを事前形成する[[静的型付け]](''static typing'')が主流になった。その実装の[[代数的データ型]]は多項な型構築子の組み合わせであり、パラメトリック多相で[[ジェネリックプログラミング|ジェネリック化]]された。''Hindley–Milner''型体系は、このパラメトリック多相に対応した[[型推論]]機能を提供して強い静的型付けの実装をサポートした。値をメモリ上のビット列ではなく、理論上または建前上は構造的な代数式と見なすのが関数型プログラミングの特徴である。前者はただのビット列を識別するためにコンパイラがその識別情報を他に持ち、それを参照する為に予め型名を明示(''manifest'')せねばならないが、後者は値自体が識別情報を兼ねていることになっているので常時その推論(''inferred'')で型名を識別できる。この時に用いられる[[型推論]]は、代数的データ型のパターンを等価性を審査できる形まで簡約するという型理論に沿った用法だけでなくに留まらず大抵はソースコードの前後の文脈までシークを解析して型を導き出せるという実用重視も特最適化された機能である。これによって関数型言語での型注釈と型宣言はもっぱら省略される。値を構造的な代数式と見なす仕組みは、メモリ上のビット列の幅拡張や恣意解釈を利用した[[型変換]]を全排除するので結果的に強い型付け(''strong typing'')に結び付く。しかし随所で多用されるユニオン型(等価性による非交和型)の値は弱い型付け(''weak typing'')相当である。関数型プログラミングでは強を基軸にしつつも弱のジョイントで解釈の選択が図られている。
 
[[量化]]可能なパターン(型)はプロパータイプ(''proper type'')と呼ばれる。量化されたパターンはターム(型付け値)である。プリミティブではないプロパータイプは1個以上のタイプ(''type'')から形成される。型構築子はプロパータイプとタイプの依存関係を決めて同時にそのプロパータイプの識別子になる。タイプはプリミティブまたは型構築子(入れ子)を指す。タイプAとタイプBからなるプロパータイプCは、AとBに依存(''dependent'')している事になりその関係は「A→B→C」と表現される。型構築子内の任意のタイプを型変数にして[[ジェネリックプログラミング|ジェネリック化]](=パラトメトリック多相)すると、プロパータイプ未満の存在になって量化不可になる。これを量化可能なプロパータイプ存在にするには、その型構築子への型引数の指定が別途必要になる。プロパータイプ存在とその未満存在を区別する仕組みが[[型理論]]の[[カインド (型理論)|カインド]]である。カインドではタイプを*と総称表現する。カインドでは、プロパータイプ存在は「*」と表現される。型引数1個必要のプロパータイプ未満存在は「*→*」になる。2個必要なら「*→*→*」になり、これに型引数が1つ適用されると「*→*」になる。カインドは量化の可否の他、型構築子が設定する依存関係の要素箇所に当てはめられるかどうかの判別になる。
 
タイプに依存してプロパータイプを導出する仕組みが型構築子と呼ばれるのに対し、タームに依存してプロパータイプを導出する仕組みの方は[[型理論]]の[[依存型]]と呼ばれる。依存型の導出は、依存値×写像の直積で表現される。写像は依存関数とも呼ばれる。依存関数から導出される「型」とは、量化前のプロパータイプか、未確定の変数部分=量化されてない部分を内包しているネーム存在である。この型としてのネーム存在は簡約されるなどして柔軟な等価性照合を可能にする。型としてのネーム存在は量化されるとタームとしての値存在になり、それをまた依存値にした別の型の導出も可能である。依存関数は、依存値によって量化部分は異なるが常に同じ等価性の型を導出する[[全称量化子|全称量]](''for all'')と、依存値によって異なる等価性の型導出されることもある[[存在量化子|存在量]](''there exists'')に分かれる。全称量は特定の関数ないし演算子への計算可能性が全面保証されるのに対して存在量はそれが部分保証されることを意味する。依存型は、型構築子をメインにするそれとは異なる型システムの下で実装される事になった。
 
[[多態性]]三種の三番目であるサブタイプ多相は、データの[[セマンティクス]]または[[メソッド (計算機科学)|振る舞い]]の多相を扱う性質から関数型とは相容れない部分が多い。関数型の[[代数的データ型]]と[[オブジェクト指向プログラミング|オブジェクト指向]]の[[抽象データ型]]は対象的なデータストラクチャと見なされている。前者がデータのみの表現体であるのに対して、後者はセマンティクスを主にしたデータの表現体である。しかしオブジェクト指向との連携が模索される中で数々の手法も提示されている。動的型付けメインのLISP系ではS式の代わりに、各スロットに任意の変数を[[動的束縛|動的バインディング]]できるフレームレコードを用いる。その動的バインディング用レコードを1個以上引数にできる関数によって[[多重ディスパッチ]]が表現される。動的バインディング用レコードの型チェックは[[ダックタイピング]]で行われる。静的型付けメインのML系ではパラメトリック多相による[[ジェネリックプログラミング|ジェネリック]]抽象デ[[クラス (コンピュータ型が用いら)|クラス]](パラメトリック多相に分類される。その型引数=型変数には総称的抽象データ型が代入される。関)の型変型プログラミングのサブタイプ多相はジェネリッククラスの[[継承共変性と反変性 (プログラミング計算機科学)|継承関係バリアンス]](''variance'')を用いる。バリアンスとなく、型変数の方の継承派生関係を用いるのが特徴である。有効にして、型変数の方の継承関係に適用結ばれたジェネリッきるインスタンスの機構はヴァリアンス(''variance'')と呼ばれ幅を持たせことを指すヴァリアンスこれジェネリッククラスのメンバ関数の引数と返り値中間媒体にしてサブタイプ多相のための継承構造対象にすること扱う仕組みであ明確にしている。ヴァ派生関係を無効にした型変数は不変性(''invariance'')となる。型変数のバリアンスには[[共変性と反変性 (計算機科学)|共変性]](''covariance'')と[[共変性と反変性 (計算機科学)|反変性]](''contravariance'')がある。共変性は指定の型変数には適用クラスとその派生クラス抽象軸当てはめることができる。それたその子孫て反変性の型変数たちよる横並びは適用クラスとそサブタイピング基底クラスを当てはめることがる。反変性は指定用途はやや想像しにくいが、型変数からその祖先を主要メンバ変数の型注釈にしてクラスを特化し、そのメンバ変数までを扱うメンバ関数階層的方は汎化させる事縦並びのサブタイピングであどに使われる。また、関数写像アドホッおいて基底多相に該当すラスの引数→派生クラスの返り値は共変性、派生クラスの引数→基底クラスの返り値は反変性とな。バリアンスは境界的定量化(''typebounded boundquantification'')またの手法でも行われる。これ型制約(''type constraint'')で型引数または型変数を、共変ないし反変の基準とする注釈クラスで記号修飾させて静するものである。共変である上限境界は、クラスのコンストラクタ(総称抽象データチェッの型構築子)の型引数を注釈ラスの派生クラスの適用に限定できる。反変である下限境界は、クラス内の型変数サポートさ注釈クラスの基底クラス範囲に差し戻せる事が多い
 
== 歴史 ==
59 ⟶ 57行目:
'''1960年代'''
 
1964年に計算機科学者[[ケネス・アイバーソン]]が開発した「[[APL]]」は、数多く定義された関数記号に多次元配列データを適用する機能を中心にした言語であり、取り分け[[スプレッドシート]]処理に対する効率性が見出されて、1960年代以降の商業分野と産業分野に積極導入された。APLは関数型言語ではなく配列プログラミング言語に位置付けられているが、配列を始めとするデータ集合に対する関数適用の有用性を特に証明した言語になった。そのデータ集合処理の可能性に注目した「J」「K」「Q」といった派生言語が後年に登場している。また後年の「[[FP (プログラミング言語)|FP]]」にも影響を与えている。続く1966年に発表された「[[ISWIM]]」は関数型を有用な構文スタイルとして扱うマルチパラダイム言語の原点とされ、[[ALGOL]]を参考にした構造化プログラミングに高階関数とwhereスコープが加えられていた。60年代の関数型プログラミングの歴史はもっぱらLISPの発展を中心にしていたが、ISWIMは後年の「ML」「Scheme」のモデルにされている。
 
'''1970年代'''