F#とは?
「F#とは何か」というのは漠然としていて曖昧な質問のようですが、まずF#がどういったプログラミング言語に分類されるのかということから解説してみたいと思います。
関数型言語F#
F#が最もベースとするプログラミング特性はおそらく関数型言語ですが、厳密に言うとF#は、関数型プログラミング言語をメインパラダイムとしながら、
- スクリプティング
- 命令型言語
- オブジェクト指向
- 言語指向
などの機能を備えたマルチパラダイムプログラミング言語、です。
オブジェクト指向プログラミング言語(以降、OOP言語)を含むほとんどの主流言語は主に「命令型(手続き型)プログラミング」を標準サポートするように設計されています。これに対して、F#は命令型とは対照的な「宣言型プログラミング」に分類される関数型プログラミングを標準サポートします。
MSDNでは、下記のように、命令型と関数型を比較しています。以下の表は、「Functional Programming vs. Imperative Programming」の内容を著者なりに抜粋日本語訳したものです。
特徴 | 命令型の方法 | 関数型の方法 |
プログラミングの焦点 | どのように(how)タスク(アルゴリズム)を実行するか | 求められている情報は何か(what) |
どのように(how)状態の変化を追跡するか | 必要な変換は何か(what) | |
状態の変化 | 重要 | 存在しない |
実行の順序 | 重要 | あまり重要ではない |
主要なフロー制御 | ループ、条件、および関数(メソッド)呼び出し | 関数呼び出し(再帰を含む) |
主要な操作単位 | 構造体またはクラスのインスタンス | ファーストクラス オブジェクトとしての関数とデータコレクション |
OOPでは、状態(オブジェクト)を作成してそれを操作することが主な目的であり、プログラムはおなじみの「printf ****
(****を表示しなさい)」というようなコンピューターが実行すべき命令の列で構成されます。
これに対し、関数型プログラミングでは、基本的に、状態(変数やオブジェクトなど)やそれらの変化という概念はなく、プログラムは数学の式のような関数で表現され、すべての計算は関数の評価によって行われます(実際には、C#やC++で関数型の機能を使用すること、F#で命令型の機能を使用することは可能ですので、正確にはどちらがより多用されるかという区別になります)。
関数型プログラミングでは、関数やデータコレクションはファーストクラスオブジェクトとして扱われます。ファーストクラスオブジェクトというのは、言語内で他のオブジェクトとの比較において固有の制限を持つことなく使用できるオブジェクトのことです。例えば、他のビルトインの型のオブジェクト同様に、関数をパラメータとして渡すことが可能ですし、戻り値として返すことも可能です。
参考までにどんな関数型言語があるかリストにしてみました。珍しい用語もあるかもしれませんが、以降で説明しますので、簡単に目を通してください。
言語 | 純粋関数型 | 遅延評価 | 静的型付け | その他の特徴 |
Haskell | 純粋型 | 遅延評価 | 静的型付け | 無限長のリストの処理などにおいて効力を発揮します。 |
Erlang | 非純粋型 | 先行評価 | 動的型付け | 並列処理に適したプログラミング言語。プロトコルの変換やスイッチを管理したりするテレコミュニケーションシステムで多用されています。 |
Scala | 非純粋型 | 先行評価 | 静的型付け | Javaプラットフォーム(Java仮想マシン)上で動作し、既存のJavaのプログラムと容易に連携させることができます。 |
LISP | 非純粋型 | 先行評価 | 動的型付け | 全てのプログラミング言語の中でも2番目に古い高級言語であり、また、実装が比較的容易なため、非常に多くの方言が存在します。人工知能の分野で幅広く利用されています。 |
Scheme | 非純粋型 | 先行評価 | 動的型付け | Lispの方言のひとつであり、コンパクトな設計が特徴です。 |
Ocaml | 非純粋型 | 先行評価 | 静的型付け | 強力な型推論を最大の特徴とします。関数型言語としてはかなり高速に動作します。 |
F# | 非純粋型 | 先行評価 | 静的型付け | .NET Frameworkに対応した関数型言語。コア言語部分においてはC#、Ocaml、Haskellと互換性があります。 |
関数型言語は、さらに、純粋関数型と非純粋関数型に分類できます。純粋関数型においては、状態という概念はまったく存在しないため、状態の変化は起こりません。つまり、一度変数に値が定義されると、その変数にほかの値が代入されることはなく、値は不変です。したがって、関数は同じ変数を引数として与えられれば常に同じ値を返すことになります。一方、一般的な関数型言語には状態を操作する機能が備わっており、外部API、OS、他言語とのスムーズな相互運用のためにその機能が使用されます。これらを非純粋関数型と呼び、F#もこれに分類されます。
関数型言語の基本は、関数の評価ですが、これらの関数が引数のすべてを利用するとは限らず、条件次第で不用になることもあります。そこで、式の評価結果(関数の引数など)が本当に必要になるまで、計算が遅延される遅延評価というものが存在します。
正格評価(あるいは先行評価)とは、その逆の考え方で、関数の引数が常にその関数に引き渡される前に完全に評価されることを意味します。F#は正格評価を行います。遅延評価を、効率よく実装するのは難しく、また、遅延された式がいつ計算されるかタイミングを予測できない、デバッグが難しいなどの弱点があり、遅延評価が本格的に普及するのはしばし先のことになりそうです。
その他、関数型言語の主要機能の一つに、型推論というものがあります。本連載の後半でも例を用いて説明しますが、簡単に言うと識別子の型を宣言しなくても関数の評価プロセスにおいて使われた型シグネチャなどからコンパイラが自動的に型を決定してくれる仕組みのことです。静的型付けでは、すべての識別子のデータ型をプログラムの定義(コンパイルする)時点で決定します。F#も静的型付けを行います。これに対し動的型付けでは、プログラムの定義時点では型の限定を行わず、実行時に渡されるデータの値が適当であるかどうかを判定し、必要に応じて変換を施したり別の機能に委譲します。
F#におけるOOP
F#はオブジェクト指向の構造をサポートします。例えば、他.NET Framework対応言語(以降、.NET言語)と同様に、F#においても「クラス」がオブジェクト指向プログラミングをサポートする上で主要コンセプトになります。
Typeキーワードを使用して作成するF#のクラスは、単一継承など、C#やVisual Basicのそれとほぼ同様の機能を持ちます。あらゆる.NET Framework(最新.NET Framework 4.0を含む)へのフルアクセスが可能なため、他の.NET言語とスムーズな相互運用が可能です。強力な静的型付けシステムと型推論機能から、F#で記述されたコードは他.NET言語より、シンプルになる傾向があります(F#特有機能を含む、F#におけるオブジェクト指向詳細については次回以降の連載で詳しく紹介する予定です)。
F#における言語指向プログラミング
関数型プログラミング、OOPの他に、F#のマルチパラダイムの一面として欠かせないのは、言語指向プログラミング(LOP:Language Oriented Programming)です。LOPとは、一般的なプログラミング言語(汎用言語)を使用せずに、DSL(ドメイン固有言語)を作成して問題を解決するプログラミングのスタイルのことです。
DSLとは、特定のタスク(ドメイン)にターゲットを絞って設計された言語のことで、DSLを用いてコードを記述することでそのタスクに関するコードはよりシンプルになります。表計算ソフトにおけるマクロなどはDSLのよい例として挙げられます。一般的なプログラミング言語より、カスタマイズされた言語(DSL)の方が関係者にとって分かりやすく、かつ、その曖昧さを的確に表現できることもしばしばあります。F#にはそういった抽象的なデータを自然な形で直感的に、かつ、正確に表現するためのデータ型が多々用意されていて、それらをDSLとして機能させることが可能です。また、XML設定ファイルのように外部にDSLを作成して、読み込むのもLOPのアプローチです。アクティブパターンなどのF#機能を用いて、パターンマッチングを拡張するような定義もLOPのコンセプトによって可能になります。以下の例も参照ください。
リスト1では、int型に別名をつけて、プログラムをより直感的に操作できるようにしています。
> type sampleMei = int let sampleA = 100312 : sampleMei ;;
▼
type sampleMei = int val sampleA : sampleMei = 100312
F#のLOPは次回以降の回で詳しく紹介予定です。