カテゴリ / テンプレート

関数型プログラミング言語: functional programming language)は、関数型プログラミングのパラダイムを扱うプログラミング言語の総称である。純粋関数型purely functional)と非純粋関数型impure functional)の二つに大別され、後者の方が幅広く用いられている。マルチパラダイム言語に組み込まれているものは例外なく非純粋である。関数型プログラミングは構文(syntax)と設計(design)の二つの視点から解釈される。構文スタイルと設計原則の双方に忠実なのが純粋関数型である。構文面を中心に導入し設計面にはそれほど忠実でないのが非純粋関数型である。

特徴編集

構文面編集

  • 数理論理学ラムダ計算の哲学が土台になっている。
  • プログラムの基本文expression)とされ、それ自体が値(value)と見なされている。
  • 式の結果値の後続式への反映は変数への代入ではなく、束縛変数への結び付けにするのが本来の在り方である。
  • 関数(function)も値と同一視されている。前述通り式も値なので、関数のフロー終端式がそのまま結果値になる。
  • 関数は概念上、式の引数への適用(applying)と解釈される。その対義概念として反適用(unapplying)の仕組みも存在する。これは式の引数への適用を差し戻して元の引数を抽出する。
  • 関数は、式を第1引数に適用したもの→第2引数に適用したもの→第x引数に適用したもの→結果値、という形をとる。関数の型は、各引数値から結果値までの型の連鎖として表現される。
  • 関数は、ラムダ抽象を模したクロージャまたは無名関数としても表現され、式の中に組み込める。
  • 関数は値と同義なので、関数の引数値を関数にする事も可能であり、また関数の結果値を関数にする事も可能である。関数を引数値または結果値として扱える関数は高階関数と呼ばれる。引数値または結果値として扱われる関数は第一級関数と呼ばれる。
  • 値は型(type)によって分類される。無数に存在する型同士の等価計算を円滑化する為に、型もまたメタ型(type class)によって分類される。メタ型は比較対象の型同士に各種演算子を適用した際の論理値を定義する。これらによる型の体系化は型システムと呼ばれる。
  • 値の型宣言は積極的に省略される。省略された型を導き出す手法は型推論と呼ばれる。これは決して簡略化のためではなく、与えられた初期値または引数値からの変換作業を履行するという関数型の性格上、省略する方が自然なスタイルになるからである。
  • 値は代数的データ(algebraic data)として表現される。代数的データは、atom(実値)nil(不在)cons(実値+連結点)の連鎖体であり、その構成はゼロから複数以上の実値を内包する事になる。atomもまた代数的データであり入れ子構造になる。
  • 全実値が同型の代数的データはlistと定義される。一つの代数的データへの式の適用は、内包する全実値への適用と同義になる。これが関数型プログラミングの代表的利点であるリスト処理(イテレーション)の自然な表現につながっている。
  • 代数的データは構造体data structure)の原始的表現であり、ラッピングやコンポジションなどの形態でアナザー値またはサブセット値を内包できる。前述の反適用=抽出の仕組みにより内包値の取り出しも定義できる。これはモナドを実装する上で重要である。
  • 再帰が重視されている。関数の再帰呼び出しだけでなく、データ定義の中でも同型データの再帰定義または相互再帰がごく自然に行われる。
  • カウンタ変数を増減するループ文は用いられないのが本来の在り方である。同様の反復フローはカウンタ値を束縛変数にした関数の再帰で行う。
  • 条件分岐と、引数値または式値による多分岐節(パターンマッチング)がやや非構造的に多用される。

設計面編集

  • 数理論理学コンビネータ論理圏論が主な土台になっている。
  • 関数型プログラミングは宣言型プログラミングに属している。宣言型と対照的関係にあるのが命令型プログラミング手続き型オブジェクト指向が属す)である。この両者を分ける一つの基準は副作用side-effect)に対する扱い方である。副作用とは、現行プロセスの閉包領域の外にある様々な情報資源の状態である「環境」の変化と、その変化によって各プロセスの処理過程もまた影響を受ける事を指す。ここでの情報資源とは、プログラムの各種入出力を担うOSの現行状態と、プログラムを走行させているランタイム環境が提供する各種データリソースを意味する。命令型プログラミングは前述の「環境」に対して自由に作用を及ぼせるパラダイムであり、それが”imperative”の由来にもなっている。それに対して宣言型プログラミングは作用を及ぼせる対象と、自身がその影響を受ける事になる対象が、あらかじめ初期値または入力値として”declarative”された情報資源のみに限られている。加えて関数型プログラミングは原則的に、宣言されたデータに対しても作用を及ぼさず、他の値を導き出す為の因子として扱う。
  • 参照透過性が根幹原則になっている。
  • 評価戦略(evaluation strategy
  • 純粋関数(pure function

歴史編集

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)をランタイムとする関数型プログラミング言語を実装しようという動きが現れ、ScalaClojureF#などが登場した。

代表的な関数型言語編集

言語 純粋さ 型付け
Clean 純粋 強い、静的
Clojure 非純粋 動的
Erlang 非純粋 動的
F# 非純粋 強い、静的
Haskell 純粋 強い、静的
Idris 純粋 強い、静的
Lazy K 純粋 型なし
LISP 非純粋 動的
Miranda 純粋 強い、静的
ML 非純粋 強い、静的
SML# 非純粋 強い、静的
Standard ML 非純粋 強い、静的
OCaml 非純粋 強い、静的
Scala 非純粋 強い、静的
Scheme 非純粋 動的
Unlambda 非純粋 型なし

純粋関数型言語では、参照透過性が常に保たれるという意味において、全てのや関数の評価時に副作用を生まない。純粋関数型言語であるHaskellCleanは非正格な評価を基本としており、引数はデフォルトで遅延評価される。一方、Idrisは純粋だが正格評価を採用している。入出力などを参照透過性を保ったまま実現するために、たとえば Haskell ではモナドClean では一意型英語版という特殊な型を通して一貫性のある表現を提供する。

非純粋関数型言語では、参照透過性を壊す、副作用があるような式や関数も存在する。LISPなどでデータ構造の破壊的変更などの副作用を多用したプログラミングを行うと、それはもはや手続き型プログラミングである。多くの場合、非純粋関数型言語の評価戦略は正格評価(先行評価)であるが、遅延評価する部分を明示することで、無限リストなどを扱えるものもある。

従来の手続き型と分類されるプログラミング言語においても、関数型プログラミングを行ないやすくなる機能を備えているものもある。C言語およびC++関数へのポインタをサポートし、関数をオブジェクトのように扱うことができるが、関数ポインタによって第一級関数をサポートしているとみなされてはいない。なお、C# 3.0、C++11、Java 8など、後発の規格においてラムダ式(無名関数)をサポートするようになった言語もある。

JavaScriptJavaなど近年[いつ?]高水準言語には、関数型言語の機能や特徴を取り入れているものがあるが、変数の値やオブジェクトの状態を書き換えるプログラミングスタイルを通常とするため、関数型言語とは分類されない。一方LISPは、その多くが副作用のある式や関数が多数あり、手続き型スタイルでのプログラミングがされることも多いが、理論的なモデル(「LISP」)の存在や副作用を使わないプログラミングが基本であること、ないし主には歴史的理由から、関数型言語だとされることが多い。なお、Pascalでは「手続き」と呼ばれるような値を返さないサブルーチンを、C言語では「関数」と呼んでいるが、これは単にルーチンについて、細分類して別の呼称を付けているか、細分類せず総称しているか、という分類と呼称の違いに過ぎず、「Pascalは手続き型言語で、C言語は関数型言語」[2]という一部の書籍に見られる記述は明確に誤りである。また、OCamlHaskellなどでは、「自明な値(例えば())を返す」と、値を返さない(Voidなど)は違うものであり、後者は停止しないか例外を出す(そのため結果がない)ようなプログラムを表す。

なお、「関数型言語である」と「関数型プログラミングをする」は同値ではなく、関数型には分類されない言語で関数型プログラミングをすること[注釈 1]や、関数型プログラミングを基本とする言語の上で他のパラダイムを実現する例もある[3]データフロープログラミング言語も関数型言語と共通した特徴を部分的に持つ。

その他の関数的性質を持つ言語

関数型プログラミングの例編集

関数型プログラミングは「計算とは何か」という数学の理論を基礎にしており、関数型プログラミングがもつ計算モデル関数モデルである[4]。たとえば、1 から 10 までの整数を足し合わせるプログラムを考える[注釈 2]命令型プログラミングでは以下のようにループ文を使って変数に数値を足していく(計算機の状態を書き換える)命令を繰り返し実行するという形を取る。

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のようにループ構文をサポートせず、従来の手続き型プログラミングが難しいケースもある。

逆に手続き型言語を使って関数型プログラミングを行なうことも可能であるが、末尾再帰呼び出しの最適化がサポートされるかどうかはコンパイラ次第である。

脚注編集

[ヘルプ]

注釈編集

  1. ^ 関数型プログラミングのエッセンスとして、MISRA CのようにC言語でも副作用を極力用いないプログラミングを推奨しているコーディング標準もある。
  2. ^ 本来は等差数列の和の公式を用いて定数時間で問題を解く方法が最適解だが、ここではプログラミングスタイルの比較のため数値計算的手法を用いる。

出典編集

  1. ^ 「プログラミングはフォン・ノイマン・スタイルから解放されうるか?: 関数型プログラミング・スタイルとそのプログラム代数」、米澤明憲訳『ACMチューリング賞講演集』(共立出版)pp. 83-156
  2. ^ 共立出版ANSI C/C++辞典』ISBN 4-320-02797-3 など
  3. ^ Oleg Kiselyov, Ralf Lämmel. “Haskell's overlooked object system”. 2005年9月10日閲覧。
  4. ^ 計算モデル2 関数モデル. (中略) 関数モデルに基づくプログラミング言語. 関数型言語. Lisp 犬塚信博 (2007)「プログラミング言語論 第1回 イントロダクション」名古屋工業大学
  5. ^ 関数 (F#) | MSDN

外部リンク編集