「関数型プログラミング」の版間の差分
m Internal link written as an external link |
|||
(同じ利用者による、間の12版が非表示) | |||
2行目: | 2行目: | ||
{{プログラミング言語|index=かんすうかたけんこ}} |
{{プログラミング言語|index=かんすうかたけんこ}} |
||
[[ファイル:Orange_lambda.svg|代替文=|境界|右|フレームなし|167x167ピクセル]] |
[[ファイル:Orange_lambda.svg|代替文=|境界|右|フレームなし|167x167ピクセル]] |
||
'''関数型 |
'''関数型言語'''({{lang-en-short|''functional language''}})は、関数型プログラミングの[[プログラミングパラダイム|パラダイム]]を扱う[[プログラミング言語]]の総称である。'''純粋関数型'''(''purely functional'')と'''非純粋関数型'''(''impure functional'')の二つに大別され、後者の方が幅広く用いられている。[[マルチパラダイムプログラミング言語|マルチパラダイム言語]]に組み込まれているものは例外なく非純粋である。関数型プログラミングは構文(''syntax'')と設計(''design'')の二つの視点から解釈される。構文スタイルと設計原則の双方に忠実なのが純粋関数型である。構文面を中心に導入し設計面にはそれほど忠実でないか又はほとんど度外視しているのが非純粋関数型である。 |
||
⚫ | |||
非純粋関数型は、関数を値として扱える[[高階関数]]、値として扱われる[[第一級関数]]、型を体系化ないし抽象化する[[型システム]]をそれぞれ連携させた計算式の構文スタイルに着目している部分が大きい。それに加えて純粋関数型は、プログラム概念上の[[副作用 (プログラム)|副作用]]をイメージ的に排除する[[参照透過性]]をプロセスの原則にしたものであり、その仕様実現の為に専用のランタイム環境上でプログラムは走行される。 |
|||
[[数理論理学]]の[[ラムダ計算]]と[[LISP]]言語が主な土台になっている。 |
|||
== |
=== 式と関数 === |
||
*関数型プログラムの[[文 (プログラミング)|基本文]]は[[式 (プログラミング)|式]](''expression'')である。式は値と同一視される。 |
|||
=== 構文視点 === |
|||
*式は、値(''value'')と演算子(''operator'')と関数(''function'')で構成される。式内の代数部分が確定される前の式は抽象値と同義であり、確定後の式は実値と同義になる。ここでの代数とは式内の各束縛変数と、同じく式内の各関数の引数の双方を指す。実値の導出過程は評価(''evaluation'')と呼ばれる。 |
|||
関数型プログラミングの基本要素である関数(''function'')とは、与えられた入力値から任意の出力値を導き出す変換式を意味するが、[[数理論理学]]の[[型付きラムダ計算]]がモデルにされてる背景からそれに加えた性質を持つ。関数はそれ自体が型付きの値として扱われるが、その「関数の型」は各引数値一つ一つへの適用から結果値までのパターンである型の連鎖として表現される。関数を値として扱える関数の仕組みは[[高階関数]](''higher-order function'')、値そのものとして扱われる関数の仕組みは[[第一級関数]](''first-class function'')と呼ばれる。型(''type'')を体系化ないし抽象化する仕組みは[[型システム]](''type system'')と呼ばれる。引数になった関数もまた引数を持つといった連鎖は広義の[[相互再帰]]になり[[複雑系]]のプロセスを自然に表現する。それらの連携による計算式のスタイルが関数型プログラミングをユニークなものにしている。 |
|||
*式を評価した値の後続式への反映は変数への代入ではなく、[[束縛変数]]で定数化するのが本来の在り方である。 |
|||
*{{仮リンク|暗黙プログラミング|en|tacit programming|label=}}が指向されており、直前の式の評価値はそのまま直後の式の適用値にする事もできる。これは[[パイプライン処理|パイプライン]]またはポイントフリーと呼ばれる。 |
|||
*関数も値と同一視される。関数は引数(''argument'')と式を結び付けるユニットである。式の代数部分に引数値が順次束縛される。式も値なので、関数のフロー終端式がそのまま結果値になる。 |
|||
*関数は、式の引数への適用(''applying'')と解釈される。その対義概念として反適用(''unapplying'')の仕組みも存在する。これは式の引数への適用を差し戻して元の引数を抽出する。 |
|||
*関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→結果値、というような形をとる。関数の型は、各引数値から結果値までの型の連鎖として表現される。 |
|||
*型の連鎖は、さながらパズルのような関数の断片化と連結を可能にする。前者は部分適用と呼ばれ、例えば式を第1引数に適用しただけのものを後続の式で使い回せる。後者は関数合成と呼ばれ、結果値と第1引数が同じ型の関数をつなげたものを後続の式で使い回せる。 |
|||
*関数は名前付きと名前無しの二通りある。後者はラムダ抽象を模した構文で式中に直接定義される。これは[[クロージャ]]または[[無名関数]]と呼ばれる。 |
|||
*関数は値と同義なので、関数の引数値を関数にする事も可能であり、また関数の結果値を関数にする事も可能である。関数を引数値または結果値として扱える関数は[[高階関数]]と呼ばれる。引数値または結果値として扱われる関数は[[第一級関数]]と呼ばれる。 |
|||
*演算子はデフォルト式内容を持ち引数が1~2個に限定された関数と同義である。演算子の式内容は任意に変更できる。巷の演算子オーバーロードは関数型プログラミング由来と言える。 |
|||
=== 値と型 === |
|||
この[[ラムダ計算]]の論理を背景にした関数の用法を、一般的な手続き型プログラミングの視点から眺めると、手続き型では関数定義を識別する[[シグネチャ]]でしかなかった関数の「引数欄」を、関数型の方ではその順序構成に厳格に意味を持たせたプログラム要素にして「関数の型」の表現法に組み込んでいる点が大きな違いになる。高階、第一級関数に加えて、この引数欄構造の明確な有意義化が、[[カリー化]]と合わせたラムダ計算視点の式分析と、その論理に則った各種変換および各種簡約による式の再構成を可能にし、また部分適用や関数合成による式の組み換えも可能にして、式の平易化と関数アルゴリズムの最適化を実現している。 |
|||
*値(''value'')は型(''type'')によって分類される。無数に存在する型同士の等価計算を円滑化する為に、型もまた型クラス(''type class'')あるいは型ヴァリアント(''type variant'')といった機能で分類される。これらの習合は[[型システム]]と呼ばれる。 |
|||
⚫ | |||
*型クラスは自身に属する型(=型インスタンス)の値が適用される演算子と関数を定義する。その式内容は型インスタンスの方で実装される。型クラスによる定義と実装の分離は型の多相を表現できる。 |
|||
⚫ | 関数型プログラミングは[[宣言型プログラミング]] |
||
*型ヴァリアントは[[共用体]]に似た仕組みでそれぞれ異なる型の値をグループ化できる。これは[[パターンマッチング]]時に活用され柔軟(場合によっては無節操)な多分岐節を表現できる。 |
|||
*値の型宣言は積極的に省略される。省略された型を導き出す機能は[[型推論]]と呼ばれる。これは決して簡略化のためではなく、与えられた初期値または引数値からの変換作業を履行するという関数型の性格上、省略する方が自然なスタイルになるからである。 |
|||
*型は「A B」のように直列表現もされこれはモナド型(''monadic type'')と呼ばれる。AはB型値の背景情報(''context'')の型であり大抵の場合後述の[[副作用 (プログラム)|副作用]](''side-effect'')を包括する。型の直列は関数型プログラミングの要点になる[[モナド (プログラミング)|モナド]]の仕組みを表現する。 |
|||
*モナド型からは概念的に反適用の仕組みによって値が抽出される。「A B」型の値からB型値を抜き出して次の式に適用させるといった具合である。パイプラインと同様に抽出も{{仮リンク|暗黙プログラミング|en|tacit programming|label=}}に則って自動解釈される事が多い。 |
|||
*値は代数的データ(''algebraic data'')として表現される。代数的データは、''atom(プリミティブ)nil(無)cons(値+リンク)''の要素からなる。代数的データは''cons''の再帰で構成されており、ゼロ個から複数以上の値を内包する事になる。''cons''の値は''atom''または入れ子の代数的データを指し、''cons''のリンクは次の''cons''または''nil''を指す。''atom''は数値、論理値、文字、文字列を指す。この再帰ツリー構造は[[S式]]と呼ばれる。 |
|||
*代数的データは単体値の表現でもあり、あらゆる値集合の原始的表現でもある。代数的データによって単値と値集合を同等に扱うスタイルが関数型プログラミングの代表的利点であるリスト処理([[イテレーション]])に繋がっている。 |
|||
*代数的データは''cons''の再帰構造であり、先頭''cons''の値+リンクを常時分離表現できる。そのリンクは後続全''consを''表現する。そのリンクが指す''cons''を再帰関数の引数にして値+リンクを再度分離しそのまた再帰を繰り返していくと、結果的に代数的データの全内包値に作用を及ぼせる事になる。 |
|||
*全ての値が同型の代数的データは''list''と呼ばれ、上述の''cons''の値+リンク分離と再帰関数の合わせ技による反復作用の典型的対象とされる。 |
|||
*異なる型の値を内包した代数的データは''tuple''と呼ばれ、用法によっては[[構造体]](''data structure'')と同等になる。 |
|||
=== 分岐と再帰 === |
|||
命令型プログラミングは前述の「環境」に対して自由に作用を及ぼせるパラダイムであり、それが”''imperative''”の由来にもなっている。それに対して宣言型プログラミングは作用を及ぼせる対象と、自身がその影響を受ける事になる対象が、あらかじめ初期値または入力値として”''declarative''”された情報資源(=データ)のみに限られている。加えて関数型プログラミングは原則的に、宣言されたデータに対しても作用を及ぼさず、他の値を導き出す為の因子として扱う。 |
|||
*[[再帰]]が重視されている。関数の再帰呼び出しと[[相互再帰]]が多用される。データ定義でも、データ名に対する定義式の中で自身データ名が記述される再帰定義が多用される。 |
|||
⚫ | |||
*カウンタ変数を増減するループ文は用いられないのが本来の在り方である。同様の反復フローはカウンタ値を束縛変数にした関数の再帰で行う。 |
|||
関数型プログラミングを構成する考え方には以下のものがある。 |
|||
*引数値による多分岐節([[パターンマッチング]])が多用される。関数型プログラミングにおける選択フローは、引数の多分岐から始まる関数に集約される。 |
|||
⚫ | |||
=== 参照透過性(''referential transparency'') === |
|||
[[数理論理学]]の[[コンビネータ論理]]と[[圏論]]が主な土台になっている。 |
|||
=== パラダイム === |
|||
=== データ機構(''data structure'') === |
|||
⚫ | 関数型プログラミングは[[宣言型プログラミング]]のカテゴリに属している。宣言型と対照的関係にあるのが[[命令型プログラミング]]であり、そのカテゴリには[[手続き型プログラミング|手続き型]]や[[オブジェクト指向プログラミング|オブジェクト指向]]が属している。この両者を分ける一つの基準は[[副作用 (プログラム)|副作用]](''side-effect'')に対する扱い方である。副作用とは、現行プロセスの閉包領域の外にある様々な情報資源の状態である「環境」の変化と、その変化によって各プロセスの処理過程もまた影響を受ける事を指す。ここでの情報資源とは、プログラムの各種入出力を担う[[オペレーティングシステム|OS]]の現行状態と、プログラムを走行させている[[仮想マシン|ランタイム環境]]が提供する各種データリソースを意味する。命令型プログラミングは前述の「環境」に対して自由に作用を及ぼせるパラダイムであり、それが”''imperative''”の由来にもなっている。それに対して宣言型プログラミングは作用を及ぼせる対象と、自身がその影響を受ける事になる対象が、あらかじめ初期値または入力値として”''declarative''”された情報資源のみに限られている。加えて関数型プログラミングは原則的に、宣言されたデータに対しても作用を及ぼさず、他の値を導き出す為の因子として扱う。 |
||
=== |
=== 参照透過性 === |
||
=== 評価戦略 |
=== 再帰と評価戦略 === |
||
⚫ | |||
=== 型の体系(''type system'') === |
|||
=== 高階関数と第一級関数(''higher-order and first-class function'') === |
|||
⚫ | |||
== 歴史 == |
== 歴史 == |
2020年4月18日 (土) 19:06時点における版
この記事には独自研究が含まれているおそれがあります。 |
関数型言語(英: functional language)は、関数型プログラミングのパラダイムを扱うプログラミング言語の総称である。純粋関数型(purely functional)と非純粋関数型(impure functional)の二つに大別され、後者の方が幅広く用いられている。マルチパラダイム言語に組み込まれているものは例外なく非純粋である。関数型プログラミングは構文(syntax)と設計(design)の二つの視点から解釈される。構文スタイルと設計原則の双方に忠実なのが純粋関数型である。構文面を中心に導入し設計面にはそれほど忠実でないか又はほとんど度外視しているのが非純粋関数型である。
特徴 - 構文面
数理論理学のラムダ計算とLISP言語が主な土台になっている。
式と関数
- 関数型プログラムの基本文は式(expression)である。式は値と同一視される。
- 式は、値(value)と演算子(operator)と関数(function)で構成される。式内の代数部分が確定される前の式は抽象値と同義であり、確定後の式は実値と同義になる。ここでの代数とは式内の各束縛変数と、同じく式内の各関数の引数の双方を指す。実値の導出過程は評価(evaluation)と呼ばれる。
- 式を評価した値の後続式への反映は変数への代入ではなく、束縛変数で定数化するのが本来の在り方である。
- 暗黙プログラミングが指向されており、直前の式の評価値はそのまま直後の式の適用値にする事もできる。これはパイプラインまたはポイントフリーと呼ばれる。
- 関数も値と同一視される。関数は引数(argument)と式を結び付けるユニットである。式の代数部分に引数値が順次束縛される。式も値なので、関数のフロー終端式がそのまま結果値になる。
- 関数は、式の引数への適用(applying)と解釈される。その対義概念として反適用(unapplying)の仕組みも存在する。これは式の引数への適用を差し戻して元の引数を抽出する。
- 関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→結果値、というような形をとる。関数の型は、各引数値から結果値までの型の連鎖として表現される。
- 型の連鎖は、さながらパズルのような関数の断片化と連結を可能にする。前者は部分適用と呼ばれ、例えば式を第1引数に適用しただけのものを後続の式で使い回せる。後者は関数合成と呼ばれ、結果値と第1引数が同じ型の関数をつなげたものを後続の式で使い回せる。
- 関数は名前付きと名前無しの二通りある。後者はラムダ抽象を模した構文で式中に直接定義される。これはクロージャまたは無名関数と呼ばれる。
- 関数は値と同義なので、関数の引数値を関数にする事も可能であり、また関数の結果値を関数にする事も可能である。関数を引数値または結果値として扱える関数は高階関数と呼ばれる。引数値または結果値として扱われる関数は第一級関数と呼ばれる。
- 演算子はデフォルト式内容を持ち引数が1~2個に限定された関数と同義である。演算子の式内容は任意に変更できる。巷の演算子オーバーロードは関数型プログラミング由来と言える。
値と型
- 値(value)は型(type)によって分類される。無数に存在する型同士の等価計算を円滑化する為に、型もまた型クラス(type class)あるいは型ヴァリアント(type variant)といった機能で分類される。これらの習合は型システムと呼ばれる。
- 型クラスは自身に属する型(=型インスタンス)の値が適用される演算子と関数を定義する。その式内容は型インスタンスの方で実装される。型クラスによる定義と実装の分離は型の多相を表現できる。
- 型ヴァリアントは共用体に似た仕組みでそれぞれ異なる型の値をグループ化できる。これはパターンマッチング時に活用され柔軟(場合によっては無節操)な多分岐節を表現できる。
- 値の型宣言は積極的に省略される。省略された型を導き出す機能は型推論と呼ばれる。これは決して簡略化のためではなく、与えられた初期値または引数値からの変換作業を履行するという関数型の性格上、省略する方が自然なスタイルになるからである。
- 型は「A B」のように直列表現もされこれはモナド型(monadic type)と呼ばれる。AはB型値の背景情報(context)の型であり大抵の場合後述の副作用(side-effect)を包括する。型の直列は関数型プログラミングの要点になるモナドの仕組みを表現する。
- モナド型からは概念的に反適用の仕組みによって値が抽出される。「A B」型の値からB型値を抜き出して次の式に適用させるといった具合である。パイプラインと同様に抽出も暗黙プログラミングに則って自動解釈される事が多い。
- 値は代数的データ(algebraic data)として表現される。代数的データは、atom(プリミティブ)nil(無)cons(値+リンク)の要素からなる。代数的データはconsの再帰で構成されており、ゼロ個から複数以上の値を内包する事になる。consの値はatomまたは入れ子の代数的データを指し、consのリンクは次のconsまたはnilを指す。atomは数値、論理値、文字、文字列を指す。この再帰ツリー構造はS式と呼ばれる。
- 代数的データは単体値の表現でもあり、あらゆる値集合の原始的表現でもある。代数的データによって単値と値集合を同等に扱うスタイルが関数型プログラミングの代表的利点であるリスト処理(イテレーション)に繋がっている。
- 代数的データはconsの再帰構造であり、先頭consの値+リンクを常時分離表現できる。そのリンクは後続全consを表現する。そのリンクが指すconsを再帰関数の引数にして値+リンクを再度分離しそのまた再帰を繰り返していくと、結果的に代数的データの全内包値に作用を及ぼせる事になる。
- 全ての値が同型の代数的データはlistと呼ばれ、上述のconsの値+リンク分離と再帰関数の合わせ技による反復作用の典型的対象とされる。
- 異なる型の値を内包した代数的データはtupleと呼ばれ、用法によっては構造体(data structure)と同等になる。
分岐と再帰
- 再帰が重視されている。関数の再帰呼び出しと相互再帰が多用される。データ定義でも、データ名に対する定義式の中で自身データ名が記述される再帰定義が多用される。
- カウンタ変数を増減するループ文は用いられないのが本来の在り方である。同様の反復フローはカウンタ値を束縛変数にした関数の再帰で行う。
- 引数値による多分岐節(パターンマッチング)が多用される。関数型プログラミングにおける選択フローは、引数の多分岐から始まる関数に集約される。
特徴 - 設計面
パラダイム
関数型プログラミングは宣言型プログラミングのカテゴリに属している。宣言型と対照的関係にあるのが命令型プログラミングであり、そのカテゴリには手続き型やオブジェクト指向が属している。この両者を分ける一つの基準は副作用(side-effect)に対する扱い方である。副作用とは、現行プロセスの閉包領域の外にある様々な情報資源の状態である「環境」の変化と、その変化によって各プロセスの処理過程もまた影響を受ける事を指す。ここでの情報資源とは、プログラムの各種入出力を担うOSの現行状態と、プログラムを走行させているランタイム環境が提供する各種データリソースを意味する。命令型プログラミングは前述の「環境」に対して自由に作用を及ぼせるパラダイムであり、それが”imperative”の由来にもなっている。それに対して宣言型プログラミングは作用を及ぼせる対象と、自身がその影響を受ける事になる対象が、あらかじめ初期値または入力値として”declarative”された情報資源のみに限られている。加えて関数型プログラミングは原則的に、宣言されたデータに対しても作用を及ぼさず、他の値を導き出す為の因子として扱う。
参照透過性
再帰と評価戦略
純粋関数と並行計算
歴史
LISPは、その基本機能のモデルとして関数型の純LISPを持つなどといった特徴がある、最初の関数型言語である。今日広く使われているLISP方言のうち特に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[1]と題した受賞記念講演で関数型プログラミングの重要性を訴えた。講演では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言語は関数型言語」[2]という一部の書籍に見られる記述は明確に誤りである。また、OCamlやHaskellなどでは、「自明な値(例えば()
)を返す」と、値を返さない(Void
など)は違うものであり、後者は停止しないか例外を出す(そのため結果がない)ようなプログラムを表す。
なお、「関数型言語である」と「関数型プログラミングをする」は同値ではなく、関数型には分類されない言語で関数型プログラミングをすること[注釈 1]や、関数型プログラミングを基本とする言語の上で他のパラダイムを実現する例もある[3]。データフロープログラミング言語も関数型言語と共通した特徴を部分的に持つ。
その他の関数的性質を持つ言語
関数型プログラミングの例
関数型プログラミングは「計算とは何か」という数学の理論を基礎にしており、関数型プログラミングがもつ計算モデルは関数モデルである[4]。たとえば、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)
ただし再帰呼び出しはスタックオーバーフローの危険性やオーバーヘッドを伴うため、注意深く使用しなければならない[5]。通例、関数型言語では、末尾再帰呼び出し (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のようにループ構文をサポートせず、従来の手続き型プログラミングが難しいケースもある。
逆に手続き型言語を使って関数型プログラミングを行なうことも可能であるが、末尾再帰呼び出しの最適化がサポートされるかどうかはコンパイラ次第である。
脚注
注釈
出典
- ^ 「プログラミングはフォン・ノイマン・スタイルから解放されうるか?: 関数型プログラミング・スタイルとそのプログラム代数」、米澤明憲訳『ACMチューリング賞講演集』(共立出版)pp. 83-156
- ^ 共立出版『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
外部リンク