「関数型プログラミング」の版間の差分
4行目: | 4行目: | ||
'''関数型言語'''({{lang-en-short|''functional language''}})は、'''関数型プログラミング'''のスタイルまたは[[プログラミングパラダイム|パラダイム]]を扱う[[プログラミング言語]]の総称である。関数型プログラミングは関数の[[写像|適用]]と[[関数の合成|合成]]から組み立てられる[[宣言型プログラミング]]の一種であり、関数は[[引数]]の適用から先行式の[[評価戦略|評価]]を後続式の適用に繋げて終端評価に到る[[式 (プログラミング)|式]]の[[ツリー構造|ツリー]]として定義される。関数は引数ないし返値として渡せる[[第一級関数]]として扱われる。 |
'''関数型言語'''({{lang-en-short|''functional language''}})は、'''関数型プログラミング'''のスタイルまたは[[プログラミングパラダイム|パラダイム]]を扱う[[プログラミング言語]]の総称である。関数型プログラミングは関数の[[写像|適用]]と[[関数の合成|合成]]から組み立てられる[[宣言型プログラミング]]の一種であり、関数は[[引数]]の適用から先行式の[[評価戦略|評価]]を後続式の適用に繋げて終端評価に到る[[式 (プログラミング)|式]]の[[ツリー構造|ツリー]]として定義される。関数は引数ないし返値として渡せる[[第一級関数]]として扱われる。 |
||
関数型プログラミングは[[数理論理学]]と[[圏論]]を主にした数学分野をルーツにし、関数[[形式体系]]の[[ラムダ計算]]と[[コンビネータ論理]]を幹にして構築され、[[LISP]]言語が実装面の先例になっている。関数の数学的な純粋性を追求した純粋関数型言語も存在する。純粋関数型パラダイムでは[[参照透過性]]が最重視され[[モナド (プログラミング)|モナド]]などの特別な[[型システム]]が導入されている。また{{誰範囲|date=2020年5月|{{要出典範囲|date=2020年5月|[[並行計算]]パラダイムでは純粋関数が重視されている}}}}。[[マルチパラダイムプログラミング言語|マルチパラダイム]]言語での導入例では、単に有用な構文スタイルとして扱われている事が多い。[[高階関数]]と[[第一級関数]]、[[クロージャ]]または[[無名関数]]、関数合成と部分適用、{{要出典範囲|ポイントフリーまたは[[パイプライン処理|パイプライン]]|date=2020年5月}}、[[イテレータ|イテレーション]]またはリスト処理、[[型推論]]、[[多態性|パラメータ多相]]とアドホック多相、[[パターンマッチング]]、[[イミュータブル]] |
関数型プログラミングは[[数理論理学]]と[[圏論]]を主にした数学分野をルーツにし、関数[[形式体系]]の[[ラムダ計算]]と[[コンビネータ論理]]を幹にして構築され、[[LISP]]言語が実装面の先例になっている。関数の数学的な純粋性を追求した純粋関数型言語も存在する。純粋関数型パラダイムでは[[参照透過性]]が最重視され[[モナド (プログラミング)|モナド]]などの特別な[[型システム]]が導入されている。また{{誰範囲|date=2020年5月|{{要出典範囲|date=2020年5月|[[並行計算]]パラダイムでは純粋関数が重視されている}}}}。[[マルチパラダイムプログラミング言語|マルチパラダイム]]言語での導入例では、単に有用な構文スタイルとして扱われている事が多い。[[高階関数]]と[[第一級関数]]、[[クロージャ]]または[[無名関数]]、関数合成と部分適用、{{要出典範囲|ポイントフリーまたは[[パイプライン処理|パイプライン]]|date=2020年5月}}、[[イテレータ|イテレーション]]またはリスト処理、[[型推論]]、[[多態性|パラメータ多相]]とアドホック多相、[[パターンマッチング]]、[[再帰]]の最適化、[[束縛変数]]と[[イミュータブル]]などが関数型プログラミングのスタイル要素として挙げられる。 |
||
== 特徴 == |
== 特徴 == |
||
<!-- 本節は一般的でない記述過多。この記事にはHaskellやLISPに偏重しない内容を記載すること --> |
<!-- 本節は一般的でない記述過多。この記事にはHaskellやLISPに偏重しない内容を記載すること <=このページは「関数型言語」ですのでそのマジョリティ(Scheme、ML、Miranda系)になるべく共通した項目を記述してるつもりです。また関数型に限った特徴のみを書き、他でも見られるものは意図的に省いてます。 --> |
||
ここでは関数型プログラミング本来の構文スタイルを元にして説明する。式を基本文にする関数型に対して、[[文 (プログラミング)|ステートメント]]を基本文にする[[手続き型プログラミング|手続き型]]や[[オブジェクト指向プログラミング|オブジェクト指向]]などの[[命令型プログラミング]]言語では必要に応じて構文スタイルを変えて実装されている。代表的なのは「式の引数への適用」に対する「引数を関数に渡す」である。ただし双方ともアセンブリコード上では同様に符号化される。[[代数的データ型]]も[[構造体]]や[[連結リスト]]で置き換えられているのが通例である。 |
ここでは関数型プログラミング本来の構文スタイルを元にして説明する。式を基本文にする関数型に対して、[[文 (プログラミング)|ステートメント]]を基本文にする[[手続き型プログラミング|手続き型]]や[[オブジェクト指向プログラミング|オブジェクト指向]]などの[[命令型プログラミング]]言語では必要に応じて構文スタイルを変えて実装されている。代表的なのは「式の引数への適用」に対する「引数を関数に渡す」である。ただし双方ともアセンブリコード上では同様に符号化される。[[代数的データ型]]も[[構造体]]や[[連結リスト]]で置き換えられているのが通例である。 |
||
=== 式と関数 === |
=== 式と関数 === |
||
{{出典の明記|date=2020年5月6日 (水) 02:29 (UTC)|section=1}} |
{{出典の明記|date=2020年5月6日 (水) 02:29 (UTC)|section=1}} |
||
*関数型プログラムの基本文は[[式 (プログラミング)|式]](''expression'')である。式は[[第一級オブジェクト]]と |
*関数型プログラムの基本文は[[式 (プログラミング)|式]](''expression'')である。式は[[第一級オブジェクト]]と同等である。 |
||
*式は、値(''value'')と演算子(''operator'')と関数(''function'')で構成される。式内の代数部分が確定される前の式は抽象値と同義であり、確定後の式は実値と同義になる。ここでの代数とは式内の各束縛変数と、同じく式内の各関数の引数の双方を指す。実値の導出過程は評価(''evaluation'')と呼ばれる。 |
*式は、値(''value'')と演算子(''operator'')と関数(''function'')で構成される。式内の代数部分が確定される前の式は抽象値と同義であり、確定後の式は実値と同義になる。ここでの代数とは式内の各束縛変数と、同じく式内の各関数の引数の双方を指す。実値の導出過程は評価(''evaluation'')と呼ばれる。 |
||
*式は値と同一視されるので、上述の式と値は相互再帰の関係にある。式内の値は他の式の評価値である事があり、その式内にもまた他の値があるといった具合である。 |
|||
*式評価値の後続への反映は変数への代入ではなく、[[束縛変数]]で定数化するのが{{要出典範囲|本来の在り方である|date=2020年5月}}。 |
*式評価値の後続への反映は変数への代入ではなく、[[束縛変数]]で定数化するのが{{要出典範囲|本来の在り方である|date=2020年5月}}。 |
||
* |
*任意の後続式の[[自由変数と束縛変数|自由変数]]の記述を省略して自動的に引数または先行式の評価値を適用する構文がしばしば用いられる。これはポイントフリーまたは[[パイプライン処理|パイプライン]]と呼ばれる。 |
||
*関数も値と同一視される。関数は引数(''parameter'') |
*関数も値と同一視される。関数は式に引数(''parameter'')を結び付けるものである。式の代数部分に引数値が順次束縛され、式ツリーの終端式が評価値になる。 |
||
*関数は、式の引数への適用(''application'')と解釈される。{{要出典範囲|その対義概念として反適用('' |
*関数は、式の引数への適用(''application'')と解釈される。{{要出典範囲|その対義概念として反適用(''unapply'')の仕組みも存在する。これは式の引数への適用を差し戻して元の引数を抽出する|date=2020年5月}}。 |
||
*関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→評価値、という形をとる。関数の型は各引数値から評価値までの型の連鎖として表現され、 |
*関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→評価値、という形をとる。関数の型は各引数値から評価値までの型の連鎖として表現され、これはカインド(''kind'')とも呼ばれる。上述のように引数を1個ずつ適用する形態は[[カリー化]]と呼ばれる。 |
||
*2個以上の引数を同時適用する非カリー化の関数も用いられる。無名関数がしばしばそれになる。この場合は部分適用やポイントフリーが制限される。 |
*2個以上の引数を同時適用する非カリー化の関数も用いられる。無名関数がしばしばそれになる。この場合は部分適用やポイントフリーが制限される。 |
||
*[[カリー化]]は関数の断片化と連結を可能にする。前者は部分適用と呼ばれ、例えば式を第1引数に適用しただけのものを後続の式で使い回せる。後者は関数合成と呼ばれ、評価値と第1引数が同じ型の関数をつなげたものを後続の式で使い回せる。 |
|||
*関数は名前付きと名前無しの二通りある。後者はラムダ抽象を模した構文で式中に直接定義される。これは[[クロージャ]]または[[無名関数]]と呼ばれる。 |
*関数は名前付きと名前無しの二通りある。後者はラムダ抽象を模した構文で式中に直接定義される。これは[[クロージャ]]または[[無名関数]]と呼ばれる。 |
||
*関数の引数値を関数にする事も可能であり、また関数の評価値を関数にする事も可能である。他の関数を引数値または評価値として扱える関数は[[高階関数]]と呼ばれる。他の関数から引数値または評価値として扱われる関数は[[第一級関数]]と呼ばれる。 |
*関数の引数値を関数にする事も可能であり、また関数の評価値を関数にする事も可能である。他の関数を引数値または評価値として扱える関数は[[高階関数]]と呼ばれる。他の関数から引数値または評価値として扱われる関数は[[第一級関数]]と呼ばれる。 |
||
*関数式の引数適用を任意の段階で保留して残り引数の適用を待つ第一級関数を生成できる。これは部分適用と呼ばれる。 |
|||
*片方の評価値と片方の第1引数が同じ型の両関数を合わせて双方の写像をつなげた第一級関数を生成できる。これは関数合成と呼ばれる。 |
|||
*言語によるが、演算子はデフォルトの式内容を持ち、引数が1~2個に限定された関数と同義である。演算子の式内容は任意に再定義できる。部分適用された演算子はセクションと呼ばれる第一級関数になる。 |
*言語によるが、演算子はデフォルトの式内容を持ち、引数が1~2個に限定された関数と同義である。演算子の式内容は任意に再定義できる。部分適用された演算子はセクションと呼ばれる第一級関数になる。 |
||
29行目: | 31行目: | ||
*値(''value'')は代数的データ型(''algebraic data type'')として表現される。これは[[直積集合|直積]]、[[非交和]]、[[再帰]]の構造を持ち、単体値を兼ねたあらゆる値集合の汎用表現になる。代数的データによって単体値と値集合を同等に扱うスタイルが関数型プログラミングの代表的利点であるリスト処理([[イテレータ|イテレーション]])に繋がっている。 |
*値(''value'')は代数的データ型(''algebraic data type'')として表現される。これは[[直積集合|直積]]、[[非交和]]、[[再帰]]の構造を持ち、単体値を兼ねたあらゆる値集合の汎用表現になる。代数的データによって単体値と値集合を同等に扱うスタイルが関数型プログラミングの代表的利点であるリスト処理([[イテレータ|イテレーション]])に繋がっている。 |
||
<!-- *代数的データ型は、''atom(プリミティブ)nil(無)cons(内包値+リンク)''の要素で実装される。''atom''は数値、論理値、文字、文字列を指す。''cons''の''内包値''は''atom''または次の''cons(=''入れ子の代数的データ)を指し、''cons''のリンクは次の''cons''または''nil''(=終端)を指す。代数的データは''cons''の再帰で構成されてゼロ個から複数以上の値を内包する事になる。この再帰ツリー構造は[[S式]]と呼ばれる。 --> <!-- LISP系に限定しすぎ。 --> |
<!-- *代数的データ型は、''atom(プリミティブ)nil(無)cons(内包値+リンク)''の要素で実装される。''atom''は数値、論理値、文字、文字列を指す。''cons''の''内包値''は''atom''または次の''cons(=''入れ子の代数的データ)を指し、''cons''のリンクは次の''cons''または''nil''(=終端)を指す。代数的データは''cons''の再帰で構成されてゼロ個から複数以上の値を内包する事になる。この再帰ツリー構造は[[S式]]と呼ばれる。 --> <!-- LISP系に限定しすぎ。 --> |
||
*代数的データ型は、''atom(プリミティブ)nil(無)cons(内包値+リンク)の要素で実装されるのが一般的である。atom''は数値、論理値、文字、文字列を指す。''cons''の''内包値''は''atom''または次の''cons(=''入れ子の代数的データ)を指し、''cons''のリンクは次の''cons''または''nil''(=終端)を指す。代数的データは''cons''の再帰で構成されてゼロ個から複数以上の値を内包する事になる。この再帰ツリー構造は[[S式]]と呼ばれる。 |
|||
*値は型(''type'')によって分類される。 |
*値は型(''type'')によって分類される。 |
||
*全ての値が同じ型の代数的データは''list''と呼ばれ、異なる場合は''tuple''と呼ばれる。''list''は用法的に[[線形リスト]]と同義であり、''tupleは用法によっては''[[構造体]]の近似物になる。後者は前述の[[直積集合|直積]]である。 |
*全ての値が同じ型の代数的データは''list''と呼ばれ、異なる場合は''tuple''と呼ばれる。''list''は用法的に[[線形リスト]]と同義であり、''tupleは用法によっては''[[構造体]]の近似物になる。後者は前述の[[直積集合|直積]]である。 |
||
*型選択している代数的データは''variants''などと呼ばれ、[[共用体]]ないし[[列挙型]]の類似物になる。これは前述の[[非交和]]である。 |
*型選択している代数的データは''variants''などと呼ばれ、[[共用体]]ないし[[列挙型]]の類似物になる。これは前述の[[非交和]]である。 |
||
*前述の再帰は代数的データの[[ネスティング|入れ子構造]]を表現できる。その入れ子はパラメータ多相で[[総称型]]にできる。 |
*前述の再帰は代数的データの[[ネスティング|入れ子構造]]を表現できる。その入れ子はパラメータ多相で[[総称型]]にできる。 |
||
* |
*値は式評価の束縛と解釈される傾向から値の型宣言は積極的に省略される。型を自動的に導き出す機能は[[型推論]]と呼ばれる。型推論と[[多態性|パラメータ多相]]はよく併用される。 |
||
=== 再帰と評価戦略とパターンマッチング === |
=== 再帰と評価戦略とパターンマッチング === |
||
47行目: | 50行目: | ||
== 歴史 == |
== 歴史 == |
||
初の関数型プログラミング言語とされる「[[LISP]]」は、1950年代に[[マサチューセッツ工科大学]]の[[ジョン・マッカーシー]]によって開発された。数々の後発言語の手本にされた[[マルチパラダイムプログラミング言語|マルチパラダイム]]言語であるLISPは同時に、[[ラムダ計算]]を |
初の関数型プログラミング言語とされる「[[LISP]]」は、1950年代に[[マサチューセッツ工科大学]]の[[ジョン・マッカーシー]]によって開発された。数々の後発言語の手本にされた[[マルチパラダイムプログラミング言語|マルチパラダイム]]言語であるLISPは同時に、[[ラムダ計算]]をモデルにして再帰可能に拡張された関数など数々の関数型プログラミングのスタイルを備えていた。ラムダ計算は1930年代に[[アロンゾ・チャーチ]]によって発明された[[計算模型]]であり、1937年に[[チューリング完全]]である事が示されて[[チューリングマシン]]と等価な計算[[形式体系]]である事が証明されている。この経緯からラムダ計算は関数型プログラミングの基礎理論に位置付けられている。同じく1930年代にラムダ計算と並ぶ[[計算模型]]の[[コンビネータ論理]]を考案し、[[カリー化]]の語源にもなった[[ハスケル・カリー]]がいる。LISPは多くの派生言語を生んでいるが、1975年公開の「[[Scheme]]」は関数型プログラミングとしての特徴をより明確にした言語になっている。 |
||
現代的な関数型プログラミング言語の祖としてはアイディアが1966年に発表された |
現代的な関数型プログラミング言語の祖としてはアイディアが1966年に発表された[[ISWIM]]が挙げられるが、1970年前後までは関数型プログラミング言語の歴史はLISPの発展が主である。1970年代にプロジェクトが開始された[[ロジック・フォー・コンピュータブル・ファンクションズ]](英語版)のための言語として[[ML (プログラミング言語)|ML]]が作られている。またLISPにおいて、変数のスコープに静的スコープを採用したSchemeが誕生したのが1975年である。 |
||
1977年、 |
1977年、FORTRANの設計と[[バッカス・ナウア記法]]の発明の業績でこの年の[[チューリング賞]]を受賞した[[ジョン・バッカス]]は、''Can Programming Be Liberated From the von Neumann Style?: A Functional Style and Its Algebra of Programs''と題した受賞記念講演で関数型プログラミングの重要性を訴えた。講演では[[FP (プログラミング言語)|FP]]という関数型プログラミング言語の紹介もした(サブタイトルの後半の「プログラムの代数」はこれを指す)が、これは[[APL]](特に、[[高階関数]]の意味がある記号(APLの用語ではオペレーター([[作用素]])という))の影響を受けている。 |
||
バッカスの |
バッカスのFPは広く使用されることはなかったが、この後関数型プログラミング言語の研究・開発は広まることとなった。1985年に[[Miranda]]が登場した。1987年に、遅延評価の純粋関数型プログラミング言語の標準の必要性が認識されHaskellの策定が始まった。1990年にHaskell 1.0仕様がリリースされた。同じく1990年にはMLの標準である[[Standard ML]]もリリースされている。Cleanは1987年に登場したが、発展の過程でHaskellの影響を受けている。1996年に、ML処理系のひとつであったCamlに[[オブジェクト指向]]を追加したOCamlが登場した。また日本ではSMLに独自の拡張を施した[[SML#]]が開発されている。 |
||
21世紀に入ると、[[Java |
21世紀に入ると、[[Java仮想マシン]]や[[共通言語基盤]](CLI)をランタイムとする関数型プログラミング言語を実装しようという動きが現れ、[[Scala]]・[[Clojure]]・[[F Sharp|F#]]などが登場した。 |
||
== 代表的な関数型言語 == |
== 代表的な関数型言語 == |
2020年5月8日 (金) 13:24時点における版
![]() | この記事には独自研究が含まれているおそれがあります。 |
![](http://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/Orange_lambda.svg/160px-Orange_lambda.svg.png)
関数型言語(英: functional language)は、関数型プログラミングのスタイルまたはパラダイムを扱うプログラミング言語の総称である。関数型プログラミングは関数の適用と合成から組み立てられる宣言型プログラミングの一種であり、関数は引数の適用から先行式の評価を後続式の適用に繋げて終端評価に到る式のツリーとして定義される。関数は引数ないし返値として渡せる第一級関数として扱われる。
関数型プログラミングは数理論理学と圏論を主にした数学分野をルーツにし、関数形式体系のラムダ計算とコンビネータ論理を幹にして構築され、LISP言語が実装面の先例になっている。関数の数学的な純粋性を追求した純粋関数型言語も存在する。純粋関数型パラダイムでは参照透過性が最重視されモナドなどの特別な型システムが導入されている。また並行計算パラダイムでは純粋関数が重視されている[要出典][誰?]。マルチパラダイム言語での導入例では、単に有用な構文スタイルとして扱われている事が多い。高階関数と第一級関数、クロージャまたは無名関数、関数合成と部分適用、ポイントフリーまたはパイプライン[要出典]、イテレーションまたはリスト処理、型推論、パラメータ多相とアドホック多相、パターンマッチング、再帰の最適化、束縛変数とイミュータブルなどが関数型プログラミングのスタイル要素として挙げられる。
特徴
ここでは関数型プログラミング本来の構文スタイルを元にして説明する。式を基本文にする関数型に対して、ステートメントを基本文にする手続き型やオブジェクト指向などの命令型プログラミング言語では必要に応じて構文スタイルを変えて実装されている。代表的なのは「式の引数への適用」に対する「引数を関数に渡す」である。ただし双方ともアセンブリコード上では同様に符号化される。代数的データ型も構造体や連結リストで置き換えられているのが通例である。
式と関数
![]() |
- 関数型プログラムの基本文は式(expression)である。式は第一級オブジェクトと同等である。
- 式は、値(value)と演算子(operator)と関数(function)で構成される。式内の代数部分が確定される前の式は抽象値と同義であり、確定後の式は実値と同義になる。ここでの代数とは式内の各束縛変数と、同じく式内の各関数の引数の双方を指す。実値の導出過程は評価(evaluation)と呼ばれる。
- 式は値と同一視されるので、上述の式と値は相互再帰の関係にある。式内の値は他の式の評価値である事があり、その式内にもまた他の値があるといった具合である。
- 式評価値の後続への反映は変数への代入ではなく、束縛変数で定数化するのが本来の在り方である[要出典]。
- 任意の後続式の自由変数の記述を省略して自動的に引数または先行式の評価値を適用する構文がしばしば用いられる。これはポイントフリーまたはパイプラインと呼ばれる。
- 関数も値と同一視される。関数は式に引数(parameter)を結び付けるものである。式の代数部分に引数値が順次束縛され、式ツリーの終端式が評価値になる。
- 関数は、式の引数への適用(application)と解釈される。その対義概念として反適用(unapply)の仕組みも存在する。これは式の引数への適用を差し戻して元の引数を抽出する[要出典]。
- 関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→評価値、という形をとる。関数の型は各引数値から評価値までの型の連鎖として表現され、これはカインド(kind)とも呼ばれる。上述のように引数を1個ずつ適用する形態はカリー化と呼ばれる。
- 2個以上の引数を同時適用する非カリー化の関数も用いられる。無名関数がしばしばそれになる。この場合は部分適用やポイントフリーが制限される。
- 関数は名前付きと名前無しの二通りある。後者はラムダ抽象を模した構文で式中に直接定義される。これはクロージャまたは無名関数と呼ばれる。
- 関数の引数値を関数にする事も可能であり、また関数の評価値を関数にする事も可能である。他の関数を引数値または評価値として扱える関数は高階関数と呼ばれる。他の関数から引数値または評価値として扱われる関数は第一級関数と呼ばれる。
- 関数式の引数適用を任意の段階で保留して残り引数の適用を待つ第一級関数を生成できる。これは部分適用と呼ばれる。
- 片方の評価値と片方の第1引数が同じ型の両関数を合わせて双方の写像をつなげた第一級関数を生成できる。これは関数合成と呼ばれる。
- 言語によるが、演算子はデフォルトの式内容を持ち、引数が1~2個に限定された関数と同義である。演算子の式内容は任意に再定義できる。部分適用された演算子はセクションと呼ばれる第一級関数になる。
代数的データ型
![]() |
- 値(value)は代数的データ型(algebraic data type)として表現される。これは直積、非交和、再帰の構造を持ち、単体値を兼ねたあらゆる値集合の汎用表現になる。代数的データによって単体値と値集合を同等に扱うスタイルが関数型プログラミングの代表的利点であるリスト処理(イテレーション)に繋がっている。
- 代数的データ型は、atom(プリミティブ)nil(無)cons(内包値+リンク)の要素で実装されるのが一般的である。atomは数値、論理値、文字、文字列を指す。consの内包値はatomまたは次のcons(=入れ子の代数的データ)を指し、consのリンクは次のconsまたはnil(=終端)を指す。代数的データはconsの再帰で構成されてゼロ個から複数以上の値を内包する事になる。この再帰ツリー構造はS式と呼ばれる。
- 値は型(type)によって分類される。
- 全ての値が同じ型の代数的データはlistと呼ばれ、異なる場合はtupleと呼ばれる。listは用法的に線形リストと同義であり、tupleは用法によっては構造体の近似物になる。後者は前述の直積である。
- 型選択している代数的データはvariantsなどと呼ばれ、共用体ないし列挙型の類似物になる。これは前述の非交和である。
- 前述の再帰は代数的データの入れ子構造を表現できる。その入れ子はパラメータ多相で総称型にできる。
- 値は式評価の束縛と解釈される傾向から値の型宣言は積極的に省略される。型を自動的に導き出す機能は型推論と呼ばれる。型推論とパラメータ多相はよく併用される。
再帰と評価戦略とパターンマッチング
- 反復構造は変数代入したカウンタを増減するループ文ではなく再帰で表現するのが好まれる。カウンタは再帰呼び出し時に、引数として与えて操作するか、イテレーションを用いる。
- 選択構造では関数の引数値によるパターンマッチングが多用される。真理値による単純な比較など、パターンマッチングでは大げさな場合はif文も用いられる。
参照透過性
純粋関数型言語は、参照透過性の遵守をプログラムの枠組みにしている。参照透過性の意味自体はシンプルであり、関数は同じ引数値に対して必ず同じ評価値を恒久的に導出し、その評価過程においてプログラムの認知内における一切の情報資源に作用を及ぼさない、というものである。プログラムが認知する範囲内のいずれかの情報資源が変化するのと同時にいずれかの関数の評価過程も変化してしまう現象が副作用と呼ばれる。
型システム
純粋関数とイミュータブルと並行計算
歴史
初の関数型プログラミング言語とされる「LISP」は、1950年代にマサチューセッツ工科大学のジョン・マッカーシーによって開発された。数々の後発言語の手本にされたマルチパラダイム言語であるLISPは同時に、ラムダ計算をモデルにして再帰可能に拡張された関数など数々の関数型プログラミングのスタイルを備えていた。ラムダ計算は1930年代にアロンゾ・チャーチによって発明された計算模型であり、1937年にチューリング完全である事が示されてチューリングマシンと等価な計算形式体系である事が証明されている。この経緯からラムダ計算は関数型プログラミングの基礎理論に位置付けられている。同じく1930年代にラムダ計算と並ぶ計算模型のコンビネータ論理を考案し、カリー化の語源にもなったハスケル・カリーがいる。LISPは多くの派生言語を生んでいるが、1975年公開の「Scheme」は関数型プログラミングとしての特徴をより明確にした言語になっている。
現代的な関数型プログラミング言語の祖としてはアイディアが1966年に発表されたISWIMが挙げられるが、1970年前後までは関数型プログラミング言語の歴史はLISPの発展が主である。1970年代にプロジェクトが開始されたロジック・フォー・コンピュータブル・ファンクションズ(英語版)のための言語としてMLが作られている。またLISPにおいて、変数のスコープに静的スコープを採用したSchemeが誕生したのが1975年である。
1977年、FORTRANの設計とバッカス・ナウア記法の発明の業績でこの年のチューリング賞を受賞したジョン・バッカスは、Can Programming Be Liberated From the von Neumann Style?: A Functional Style and Its Algebra of Programsと題した受賞記念講演で関数型プログラミングの重要性を訴えた。講演ではFPという関数型プログラミング言語の紹介もした(サブタイトルの後半の「プログラムの代数」はこれを指す)が、これはAPL(特に、高階関数の意味がある記号(APLの用語ではオペレーター(作用素)という))の影響を受けている。
バッカスの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#などが登場した。
代表的な関数型言語
言語 | 純粋さ | 型付け |
---|---|---|
Clean | 純粋 | 強い、静的 |
Clojure | 非純粋 | 動的 |
Erlang | 非純粋 | 動的 |
F# | 非純粋 | 強い、静的 |
Haskell | 純粋 | 強い、静的 |
Idris | 純粋 | 強い、静的 |
Lazy K | 純粋 | 型なし |
LISP | 非純粋 | 動的 |
Miranda | 純粋 | 強い、静的 |
ML | 非純粋 | 強い、静的 |
SML# | 非純粋 | 強い、静的 |
Standard ML | 非純粋 | 強い、静的 |
OCaml | 非純粋 | 強い、静的 |
Scala | 非純粋 | 強い、静的 |
Scheme | 非純粋 | 動的 |
Unlambda | 非純粋 | 型なし |
純粋関数型言語では、参照透過性が常に保たれるという意味において、全ての式や関数の「評価時」に副作用を生まない。純粋関数型言語であるHaskellやCleanは非正格な評価を基本としており、引数はデフォルトで遅延評価される。一方、Idrisは純粋だが正格評価を採用している。入出力などを参照透過性を保ったまま実現するために、たとえば Haskell ではモナド、Clean では一意型という特殊な型を通して一貫性のある表現を提供する。
非純粋関数型言語では、参照透過性を壊す、副作用があるような式や関数も存在する。LISPなどでデータ構造の破壊的変更などの副作用を多用したプログラミングを行うと、それはもはや手続き型プログラミングである。多くの場合、非純粋関数型言語の評価戦略は正格評価(先行評価)であるが、遅延評価する部分を明示することで、無限リストなどを扱えるものもある。
従来の手続き型と分類されるプログラミング言語においても、関数型プログラミングを行ないやすくなる機能を備えているものもある。C言語およびC++は関数へのポインタをサポートし、関数をオブジェクトのように扱うことができるが、関数ポインタによって第一級関数をサポートしているとみなされてはいない。なお、C# 3.0、C++11、Java 8など、後発の規格においてラムダ式(無名関数)をサポートするようになった言語もある。
JavaScriptやJavaなど近年[いつ?]の高水準言語には、関数型言語の機能や特徴を取り入れているものがあるが、変数の値やオブジェクトの状態を書き換えるプログラミングスタイルを通常とするため、関数型言語とは分類されない。一方LISPは、その多くが副作用のある式や関数が多数あり、手続き型スタイルでのプログラミングがされることも多いが、理論的なモデル(「純LISP」)の存在や副作用を使わないプログラミングが基本であること、ないし主には歴史的理由から、関数型言語だとされることが多い。なお、Pascalでは「手続き」と呼ばれるような値を返さないサブルーチンを、C言語では「関数」と呼んでいるが、これは単にルーチンについて、細分類して別の呼称を付けているか、細分類せず総称しているか、という分類と呼称の違いに過ぎず、「Pascalは手続き型言語で、C言語は関数型言語」[1]という一部の書籍に見られる記述は明確に誤りである。また、OCamlやHaskellなどでは、「自明な値(例えば()
)を返す」と、値を返さない(Void
など)は違うものであり、後者は停止しないか例外を出す(そのため結果がない)ようなプログラムを表す。
なお、「関数型言語である」と「関数型プログラミングをする」は同値ではなく、関数型には分類されない言語で関数型プログラミングをすること[注釈 1]や、関数型プログラミングを基本とする言語の上で他のパラダイムを実現する例もある[2]。データフロープログラミング言語も関数型言語と共通した特徴を部分的に持つ。
その他の関数的性質を持つ言語
関数型プログラミングの例
関数型プログラミングは「計算とは何か」という数学の理論を基礎にしており、関数型プログラミングがもつ計算モデルは関数モデルである[3]。たとえば、1 から 10 までの整数を足し合わせるプログラムを考える[注釈 2]。命令型プログラミングでは以下のようにループ文を使って変数に数値を足していく(計算機の状態を書き換える)命令を繰り返し実行するという形を取る。
- Pascalによる例:
program test;
var total, i : Integer;
begin
total := 0;
for i := 1 to 10 do
total := total + i;
WriteLn(total)
end.
一方、関数型プログラミングでは、繰り返しには一時変数およびループを使わず、関数の再帰呼び出しを使う。
- F#による例:
printfn "%d" (let rec sum x = if x > 0 then x + sum (x - 1) else 0
sum 10)
ただし再帰呼び出しはスタックオーバーフローの危険性やオーバーヘッドを伴うため、注意深く使用しなければならない[4]。通例、関数型言語では、末尾再帰呼び出し (tail-recursive call) の形で書かれた関数をループに展開する末尾呼出し最適化により、スタックオーバーフローの危険性および再帰のオーバーヘッドを解消できる。Schemeなど、関数型言語の中には末尾再帰呼び出しの最適化を仕様で保証するものもある。再帰関数を末尾再帰に書き換えることが難しいケースも有り、そのような場合は一般的なループが採用される。
また、関数型言語は文 (statement) よりも式 (expression) を中心とした言語仕様となっていることも特徴である。前述の例において、再帰関数sum
を束縛するlet
は式である。また、条件分岐のif-then-else
も式である。文よりも式で書けることが多いほうが都合がよい。
関数型言語は関数型プログラミングをサポートする言語ではあるが、手続き型プログラミングを行なうことも可能である。例えばF#では以下のようなPascal風の書き方もできる。
let mutable total = 0
for i = 1 to 10 do
total <- total + i
printfn "%d" total
ただしHaskellのようにループ構文をサポートせず、従来の手続き型プログラミングが難しいケースもある。
逆に手続き型言語を使って関数型プログラミングを行なうことも可能であるが、末尾再帰呼び出しの最適化がサポートされるかどうかはコンパイラ次第である。
脚注
注釈
出典
- ^ 共立出版『ANSI C/C++辞典』ISBN 4-320-02797-3 など
- ^ Oleg Kiselyov, Ralf Lämmel. “Haskell's overlooked object system”. Sep 10, 2005閲覧。
- ^ 計算モデル2 関数モデル. (中略) 関数モデルに基づくプログラミング言語. 関数型言語. Lisp 犬塚信博 (2007)「プログラミング言語論 第1回 イントロダクション」名古屋工業大学
- ^ 関数 (F#) | MSDN
外部リンク