第一級関数とは
計算機科学における
第一級関数(ファーストクラスファンクション)とは、関数を他のデータ型と同様に扱うことができる
プログラミング言語の特性を指します。具体的には、以下の様な操作が可能です。
変数への代入: 関数を変数に格納することができます。
引数としての受け渡し: 関数を別の関数の
引数として渡すことができます。
戻り値としての返却: 関数から関数を返すことができます。
データ構造への格納: 関数をリストや辞書などの
データ構造に格納することができます。
第一級関数は、関数型プログラミングの中核をなす概念であり、
高階関数を実現する上で不可欠です。
高階関数とは、関数を
引数に取ったり、関数を戻り値として返したりする関数のことを指します。
例えば、リストの各要素に関数を適用する`map`関数は、第一級関数を必要とする典型的な例です。`map`関数は、関数とリストを
引数として受け取り、リストの各要素にその関数を適用した結果を新しいリストとして返します。
scheme
(map (lambda (x) (
x 2)) '(1 2 3)) ; => (2 4 6)
第一級関数の重要性
第一級関数は、プログラムの柔軟性と再利用性を高める上で非常に重要です。これにより、プログラムの部品を関数として抽象化し、それらを組み合わせてより複雑な処理を記述することが可能になります。
また、第一級関数は、関数型プログラミングにおける重要な要素であるクロージャを構成する上でも不可欠です。クロージャとは、関数とその関数が定義された環境(自由変数)を一緒に保持する仕組みのことです。
型理論と第一級関数
型理論では、型 `A` の値を受け取り、型 `B` の値を返す関数を `A → B` と表記します。これは、論理における含意 `P ⇒ Q` と対応しており、カリー・ハワード同型対応によって、関数型と論理的推論との間の深い関係が示されています。
圏論と第一級関数
圏論においては、第一級関数は閉圏に対応します。特に、単純型付きラムダ計算は、カルテシアン閉圏の言語として解釈することができます。
多くのプログラミング言語が第一級関数をサポートしています。以下にいくつかの例を挙げます。
関数型言語:
Haskell
Lisp
ML
Scheme
その他:
JavaScript
Python
Ruby
Scala
PHP
C++では関数ポインタをサポートしていますが、これは第一級関数とはみなされません。しかし、
C++では
関数オブジェクト(`operator()`を持つクラス)や
C++11以降ではラムダ式をサポートしており、これらは他の
第一級オブジェクトと同様に扱うことができます。
cpp
include
include
int add(int x, int y) {
return x + y;
}
int main() {
std::function
func = add; // 関数を格納
std::cout << func(3, 5) << std::endl; // 関数呼び出し
auto lambda = [](int x, int y) { return x y; }; // ラムダ式
std::cout << lambda(4, 6) << std::endl;
return 0;
}
JavaScriptでは、関数は第一級オブジェクトです。関数を変数に代入したり、引数として渡したり、戻り値として返すことが可能です。
javascript
function add(x, y) {
return x + y;
}
const myFunc = add; // 関数を変数に代入
console.log(myFunc(2, 3)); // 関数呼び出し
function applyFunc(func, x, y) {
return func(x, y);
}
console.log(applyFunc(add, 5, 10)); // 関数を引数として渡す
Pythonでは、`def`文や`lambda`式で関数を定義でき、これらは第一級関数として扱われます。
python
def add(x, y):
return x + y
my_func = add # 関数を変数に代入
print(my_func(3, 7))
def apply_func(func, x, y):
return func(x, y)
print(apply_func(add, 10, 20)) # 関数を引数として渡す
lambda_func = lambda x, y: x y # ラムダ式
print(lambda_func(5, 6))
Luaの例では、第一級関数がIPアドレスのテーブルのソート戦略を指定するために使用されています。
lua
local ips = {
{"192.168.1.1", "hostB"},
{"192.168.1.2", "hostA"},
{"192.168.1.3", "hostC"}
}
table.sort(ips, function(a,b) return a[2] > b[2] end) --ホスト名を逆順でソート
for i, ip in ipairs(ips) do
print(ip[1], ip[2])
end
ALGOL 68 では、`PROC` で関数を定義し、それを引数に渡したり、変数に代入したりすることができます。ニュートン法を実装する例が以下に示されています。
algol68
PROC newton = (REAL x, error, PROC (REAL) REAL f, f prime) REAL:
ニュートン法 #
IF f(x) <= error
THEN x
ELSE newton (x - f(x) / f prime (x), error, f, f prime)
FI;
print(( newton(0.5, 0.001, (REAL x) REAL: x*3 - 2x*2 + 6, (REAL x) REAL: 3x2 - 4x), newline ));
第一級関数と関連する概念
無名関数: 名前を持たない関数のことです。ラムダ式などで定義されます。
クロージャ: 関数とその関数が定義された環境(自由変数)を一緒に保持する仕組みです。
関数オブジェクト: オブジェクトのように振る舞うことができる関数のことです。
第一級オブジェクト: データ型として扱えるオブジェクトのことです。
まとめ
第一級関数は、プログラミング言語における重要な概念であり、関数型プログラミングの基盤をなしています。関数をデータとして扱うことで、プログラムの柔軟性、再利用性、可読性を向上させることができます。多くのプログラミング言語が第一級関数をサポートしており、現代のプログラミングにおいて必須の知識と言えるでしょう。