関数オブジェクト

関数オブジェクトは、プログラミング言語において、関数を、オブジェクトとしたものである。date=2019-04。なお、ここでのオブジェクトの語は、いわゆるオブジェクト指向のそれに限らず、「第一級オブジェクト」という語におけるのと同じ、メモリ上に領域を確保されたもの、といった意味である。関数が第一級オブジェクトである場合は特に第一級関数と言う。

date=2019-04、構文の設計によっては、y = f といったような、通常のサブルーチン呼び出しと全く同じ構文で、関数オブジェクトが保持しているサブルーチンを呼び出せる言語もある。一方、通常のサブルーチンのように呼び出すことはできず、applyといった特別な名前のメソッドを経由して呼び出す必要のある言語もある。

また、変数束縛が閉じられた関数オブジェクトはクロージャである。C#などの.NET言語には関数オブジェクトのようなものとして、オブジェクトのインスタンスとその振る舞いであるメソッドとを結びつけて管理することのできる、デリゲートがある。無名関数も参照。

1. 用途
-----

関数オブジェクトの典型的な用途は、より優れたコールバックを記述することである。C言語では、コールバックには関数へのポインタを使う他ないが、コールバックの内外で状態変数を共有できない。この制限のために、関数の動的な振る舞いやインターフェイス設計が制約されてしまう。以下に例を示す。 /* ライブラリ側のコード */ typedef void my_callback_func_t; typedef struct my_handler { my_callback_func_t* callback; } my_handler_t;

void register_callback { handler->callback = func; }

void invoke_callback { if  { handler->callback; } }

void perform_process { /* 前処理 */ /* ... */ /* コールバック関数の呼び出し */ invoke_callback; /* 後処理 */ /* ... */ }<WtXmlEndTag />

/* アプリケーション側のコード */ void callback_func { /* ... */ /* ここで後述の状態変数 state にアクセスしたくても、不可能 */ }

int main { /* 状態変数 */ static int state = 0;

my_handler_t handler = { NULL }; /* コールバックとして callback_func という関数へのポインタを登録 */ register_callback;

perform_process; }<WtXmlEndTag /> コールバック関数内で外部の状態変数を参照するためには、グローバル変数を利用したり、構造体メンバーに状態変数へのポインタを持たせたうえでコールバック関数の引数を経由して状態変数のアドレスを渡したり、といった工夫が必要になる。

関数オブジェクトは、上記のように煩雑になりがちな設計パターンを言語機能として組み込み、関数を可搬性の高いオブジェクトとして容易に利用できるようにするものである。

LISPや、Smalltalk、C++、Java、C#、Perl、Python、Rubyなどの現代的なオブジェクト指向言語は、ほとんどが関数オブジェクト、あるいは同等の機能をサポートしており、date=2019-04。

2. 起源
-----

関数オブジェクトは、LISPにおいてその初期から研究された。計算機プログラムの構造と解釈の第3章でも解説されている。

オブジェクト指向言語では、Smalltalk において、ブロックが関数オブジェクトの記法となるよう設計された。たとえば配列の各要素を通常の大小関係とは違う順序で並べ替えたい場合、比較のための関数オブジェクトを引数に取るソートメソッドを、引数としてブロックを付けて、呼び出す。ブロック内には、カスタマイズ版の比較手続きを記述する。ソートメソッド内での比較は、渡された関数オブジェクトの手続きを呼び出すことで行なわれ、期待する大小関係でのソートが行なわれる。これは、Strategyデザインパターンの完全な具現化であり、着脱可能  な振る舞いを促進するものである。

3. C++ での関数オブジェクト
-----------------

C++では、クラスあるいは構造体において関数呼び出し演算子の多重定義が可能となっている。具体的には、任意の引数を持つoperatorメンバー関数を定義することで、そのオブジェクトのインスタンスを指す変数の名前が、あたかも関数名であるかのような構文で、定義した関数を呼ぶことができる。このような C++ のオブジェクトを、C++ の用語ではファンクタ  と呼ぶ。 struct my_adder { int operator const { return a + b; } };

my_adder f; int result = f; // 以下のように呼び出すことも可能だが、通例使われない。 f.operator;<WtXmlEndTag /> C++のclass/structは、デフォルトのアクセスレベルがprivate/publicであるという違いしかないため、関数オブジェクトの記述ではclassの代わりにstructが使われることも多い。

配列の中から特定の条件に該当する要素の個数を数えるルーチンの例を考えてみよう。ただし条件の判定処理は自由にカスタマイズできるものとする。

関数へのポインタを使用する C のプログラムは、たとえば下記のようになる。

include /* 引数が条件に該当する場合は 1 を、該当しない場合は 0 を返す */ typedef int PredicateFunc;

size_t countIf { size_t count = 0; size_t i; for  { if ) { count++; } } return count; }

int isPositive { return x > 0; }

int isEven { return x % 2 == 0; }

int main { const int data[] = { 0, 1, 2, 3, 4, 5, 6, 0, -1, -2, -3, -4 }; printfcountIf / sizeof, &isPositive)); printfcountIf / sizeof, &isEven)); }<WtXmlEndTag />

一方、C++の関数オブジェクトを利用すると下記のようになる。

include template size_t countIf { size_t count = 0; for  { if ) { count++; } } return count; }

struct IsPositiveFunctor { bool operator const { return x > 0; } };

struct IsEvenFunctor { bool operator const { return x % 2 == 0; } };

int main { const int data[] = { 0, 1, 2, 3, 4, 5, 6, 0, -1, -2, -3, -4 }; std::printfcountIf / sizeof, IsPositiveFunctor)); std::printfcountIf / sizeof, IsEvenFunctor)); }<WtXmlEndTag /> コールバックを述語  として countIf 関数テンプレートに渡す際、関数へのポインタではなく構造体のインスタンスを渡していることに注意されたい。

上記の例において関数オブジェクトによるカスタマイズを可能にしているのは、テンプレートと関数呼び出し演算子のオーバーロードによる静的ダック・タイピングである。関数テンプレートを実体化する際の字句解析に適合しさえすれば、述語にはどんなオブジェクトでも渡せる。

コールバック関数が実行されると、他のメンバー関数と同様に働き、すなわちオブジェクトの他のメンバーに対して完全にアクセスすることができる。

date=2019-04

date=2019-04

C++ の Standard Template Library  では、テンプレートの述語  として関数オブジェクトを多用している。

3.1. C++のファンクタとファンクショノイド
------------------------

以上のように C++ では、関数オブジェクトは、「関数呼び出しと同じ構文で、メンバ関数を呼ぶことができるオブジェクト」として実装されている。これを C++ の用語ではファンクタと呼んでいるが、これはStandard MLのfunctorや、数学における関手とは関係ない。

C++ では、主要なメソッド一つを持つオブジェクトを「ファンクショノイド」と言い、その一種で、その主要なメソッドが operator であるオブジェクトが「ファンクタ」である、と説明される。これは C++ の用語であり、C++ を離れた文脈では、関数オブジェクトすなわちファンクタ、ではないので注意。

3.2. 性能
-------

C++ における関数オブジェクトの利点のひとつは、関数ポインタと異なり、インライン化できるためパフォーマンスが良い点である。たとえば、引数をインクリメントさせるシンプルな関数は関数オブジェクトとして実装できる: struct IncrementFunctor { void operator { ++i; } }; 通常の関数: void increment_function { ++i; } STL 関数 std::for_each を用いると下記のようになる: template Function for_each { for  f; return f; } ここに std::for_each を適用すると下記のようになる: int A[] = {1, 4, 2, 8, 5, 7}; const size_t N = sizeof / sizeof; for_each); for_each;

いずれの for_eachも期待通りの動作をするが、最初の方法では以下のように展開される。 IncrementFunctor for_each<int*, IncrementFunctor> 二番目の方法では以下のように展開される。 void for_each<int*, void>)

for_each<int*, IncrementFunctor>の場合には関数が既知であるためコンパイラがインライン化できるが、for_each<int*, void> の場合にはコンパイル時に関数が不定でありインライン化できない。

現実には、コンパイラに指示すれば簡単に関数を既知にすることができる。コンパイラが関数の定義を認識しており、それがクラスの内外いずれでも同じように行われていさえすればよい。インライン化しない場合、リンカは関数がクラスの関数だとの指示さえあれば同じ関数の別のコンパイル単位の複数回の定義をエラーを生成せずに黙って見過ごす。リンカは同じ関数の定義がクラスの関数でない場合には複数回の定義を許容しないためである。

もうひとつの例として、std::sort関数テンプレートを挙げる。

include
include
include
include
include void createRandomNumbers { std::srand; for  { x = std::rand; } }

inline bool compareFunc { return a < b; }

struct CompareFunctor { bool operator const { return a < b; } };

template void performFunc { const auto startTime = std::chrono::system_clock::now; func; const auto endTime = std::chrono::system_clock::now; std::cout << "Elapsed time [ms] = " << std::chrono::duration_cast.count << std::endl; }

int main { std::vector array; std::cout << "* std::qsort:" << std::endl; createRandomNumbers; performFunc { std::qsort, array.size, sizeof, [] { return *static_cast - *static_cast; }); }); std::cout << "* std::sort and function pointer:" << std::endl; createRandomNumbers; performFunc { std::sort, array.end, compareFunc); }); std::cout << "* std::sort and functor:" << std::endl; createRandomNumbers; performFunc { std::sort, array.end, CompareFunctor); }); std::cout << "* std::sort and lambda:" << std::endl; createRandomNumbers; performFunc { std::sort, array.end, [] { return a < b; }); }); }<WtXmlEndTag />std::qsortおよびstd::sortの実装はいずれもクイックソートとは限らず、処理系依存のため、直接性能を比較することに意味はないが、通例インライン展開が利用できるstd::sort
のほうが性能が良くなる。また、述語に関数ポインタを利用するよりも、関数オブジェクトを利用したほうが性能が良くなる。C++11で追加されたラムダ式は、コンパイラによって内部的には関数オブジェクトの定義に展開されるため、通例関数オブジェクトを利用した場合と同等の性能になる。

3.3. 状態の保持
----------

関数オブジェクトの利点の一つは、関数の呼び出しをまたいで状態を保持できる点である。たとえば、下記のコードは10以上の数を数えるジェネレータを定義し、11 回呼び出し結果を出力している。

include
include
include class countfrom { private: int count; public: countfrom : count {} int operator { return count++; } };

int main { std::generate_n, 11, countfrom); return 0; }<WtXmlEndTag /> 関数オブジェクトで状態を保持する場合、明示的にメンバー変数を定義したうえで、関数オブジェクトのメンバーとして操作を記述しなければならなかった。C++11で導入されたラムダ式では、外部の変数のキャプチャをサポートし、クロージャを実現することができる。ラムダ式によってキャプチャされた変数は、コンパイラによって暗黙的に生成される関数オブジェクトのメンバー変数となる。

4. C#の関数オブジェクト
--------------

5. D言語の関数オブジェクト
---------------

D言語には関数オブジェクトとして、デリゲートとクロージャの両方がある。 bool find needle_test) { foreach  { if  ) return true; } return false; }

void main { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123;

bool needleTest { return n == needle; }

assert); }<WtXmlEndTag /> D言語におけるデリゲートとクロージャの違いは、コントロールが変数のスコープから一旦抜けても、変数の寿命が続いているか、そうでないかである。コンパイラにより保守的に、自動的に決定される。

D言語は、関数リテラルやラムダ式もサポートしている。 void main { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123; assert { return n == needle; })); assert => n == needle)); } コンパイラがインライン化できるようにするため、関数オブジェクトをC++形式の演算子のオーバーロードを用いて宣言することもできる。しかし、D言語ではテンプレート引数として関数等を渡す手法が一般的である。 bool find { foreach  { if  ) return true; } return false; }

void main { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123;

struct NeedleTest { int needle; this { needle = n; } bool opCall { return n == needle; } }

assert)); }<WtXmlEndTag />

6. Java における関数オブジェクト
--------------------

Javaでは関数が第一級オブジェクトでないため、関数オブジェクトの代わりとして、一つのメソッドを持つインタフェースが使われる。代表的なインタフェースとしては、runメソッドを持つname=java.lang.Runnableが挙げられる。実際のコーディングにおいては、そのようなインタフェースを実装するクラスの定義には、内部クラス  や、メソッド内の変数をキャプチャしてクロージャのように振る舞うことのできるローカルクラス  あるいは匿名クラス  がしばしば使われる。

Javaの標準ライブラリの例では、java.util.Collections.sort はリストと、意味的には関数を引数にとる。この関数はリスト内のオブジェクトを比較する役割を持つ。しかし、Javaは関数が第一級オブジェクトでないため、name=java.util.Comparatorインタフェースを実装したオブジェクトを渡す。下記の例のように使用する：

List list = Arrays.asList;

// Comparator を実装する匿名クラスを定義して利用。 Collections.sort { public int compare { return Integer.valueOf.compareTo); } } );<WtXmlEndTag /> Java 8からは匿名クラスの糖衣構文としてラムダ式をサポートする。ラムダ式を利用すると、以下のように簡潔に記述することができる。 Collections.sort -> Integer.valueOf.compareTo) ); また、Java 8はメソッド参照もサポートするようになった。メソッド参照は、内部的にはインタフェースを利用して実装されている。

7. Python における関数オブジェクト
----------------------

Pythonでは、関数は、文字列や数値、リストなどをはじめとする他の任意のデータと同様のオブジェクトである。つまり、Pythonは第一級関数を扱うことができる言語である。また、__call__ メソッドを持つ任意のオブジェクトを関数呼び出しの構文で呼び出すことができる。

例として、Accumulator クラス  を挙げる。 class Accumulator: def __init__: self.n = n def __call__: self.n += x return self.n 下記のように使用する: >>> a = Accumulator >>> a 9 >>> a 11 >>> b = Accumulator >>> b 49 Python で関数オブジェクトを定義するもう一つの方法として、ネストした関数定義、といった形の構文を使う方法がある。 def Accumulator: def inc: inc.n += x return inc.n inc.n = n return inc 一方で Python には、その内部に式しか書くことができないという強い制限のある lambda form しか関数リテラルに相当するものがなく、手続き的なものは名前を付けて定義しなければならない。これはそうするのが良いプラクティスとされているからである。

8. Lisp における関数オブジェクト
--------------------

Lisp においても、関数は、文字列やベクトル・リスト・数値と同様に変数に入れたり関数から返したりできる第一級オブジェクトであり、第一級関数を扱うことができる言語である。

Lisp はその最初から  という特殊形式による関数リテラルをはじめとして、第一級関数を扱うことができる言語であったが、1960年代から1970年前後までの実装では、動的スコープのためクロージャにはなっておらず、funarg問題が認識されることとなった。Scheme で、静的スコープと無限エクステントによる解決が示され、Common Lisp をはじめとする現代的な Lisp の多くは静的スコープを採用しており、lambda 特殊形式ではクロージャが作られる。しかし Emacs Lisp のように動的スコープの Lisp もまだ広く残っている。

Scheme では変数と関数で名前空間が分かれておらず、変数名を関数名と同様に使ってプログラムを書くことができる。  ) )



)  )<WtXmlEndTag /> これを実行すると、 hello, world hello, Scheme のように出力される。

これに対し、伝統的な Lisp の多くや Common Lisp は変数と関数で名前空間が分かれている。同様のプログラムを Common Lisp で書いた例を示す。  ) )



)  )<WtXmlEndTag /> これを実行すると、 hello, world hello, Common Lisp のように出力される。

Common Lisp では、名前に対して、変数としての値と、関数とが別々に結びつけられていて、文脈によりどちらかがアクセスされる。F という変数に関数値を束縛する所では、通常の文脈において関数にアクセスするために #' を名前の前に付けている。一方、並びの先頭の位置に、この例では F と書いても、その名前の関数がないというエラーになる。Common Lisp では一般に、FUNCALL か APPLY 関数を使って呼び出す。

変数と関数の名前空間を分けるか同じにするか、という議論は、Lisp に限らず現代的なプログラミング言語の設計において話題になる。ML などの現代的な関数型言語では当然のように同じ名前空間である。スクリプティング言語では、Python や JavaScript は同じ名前空間としたが、Ruby ではローカル変数とメソッドで別の名前空間とした。Lisp では「Lisp-1 対 Lisp-2 の議論」などと呼ばれる。

9. Ruby における関数オブジェクト
--------------------

Ruby では、便宜上 Object クラスのローカルインスタンスメソッドを「グローバル関数」と呼んでいるといった例外はあるが、関数は存在せず、全てメソッドである。メソッドはオブジェクトではなく、変数とメソッドで名前空間が違う。

Ruby には、メソッドの他に手続きの表現としてブロックがある。Ruby のブロックは、メソッド呼び出しにオプショナルに付加できるもので、暗黙の引数といったような感じで呼び出されるメソッドに渡される。ブロックからクロージャが作られ、呼び出された側からは yield という特殊なグローバル関数により、そのクロージャを呼ぶことができる。

ブロックは直接にはオブジェクトではない。しかし、メソッド定義の仮引数の記述の最後に、&foo のように & を先頭に付けた引数を付けるなどすることで、簡単に Proc オブジェクトとして得ることができる。また、メソッド呼び出しの最後の実引数として、引数の前に & を付けることで、Proc オブジェクトをブロックの代わりに渡すこともできる。Proc オブジェクトの手続きは call というインスタンスメソッドにより呼ぶことができる。

Proc オブジェクトは文脈という環境を持つ関数オブジェクト状のものである。これに対し、レシーバという環境を持つ関数オブジェクト状のものが Method オブジェクトである。Method オブジェクトはメソッドそのものではなく、リフレクションなどのためのオブジェクトであり、感じとしては java.lang.reflect.Method に似ている。UnboundMethod はレシーバが切り離された Method であり、実行するためにはまずレシーバを bind して Method にしなければならない。

Ruby Extensions Project は、[2]。 class Symbol def to_proc proc { |obj, *args| obj.send } end end Symbol にこのような to_proc メソッドがあれば、foo メソッドを呼び出すような Proc オブジェクトが、:foo.to_proc というコードで得られる。& が付けられた実引数が、Proc オブジェクトでない場合は、to_proc メソッドが呼ばれるというコントラクトになっているので、たとえば配列の要素の合計を得る、というコードが [2, 3, 5, 7].inject :+ のように簡潔に書ける。Symbol#to_proc は、RubyKaigi 2006 の期間中、2006年6月11日に正式に Ruby に追加された[3]。

また、Ruby においてファンクタという名前があるものとして、[4] プロジェクトによって導入された委譲の実装がある。委譲の最も基本的な定義は下記のようなものである:

class Functor def initialize @func = func end def method_missing @func.call end end

10. Smalltalkにおける関数オブジェクト
-------------------------

冒頭にも述べたようSmalltalkでは関数オブジェクトに類似する機能としてブロックが存在する。他の言語同様関数的な使い方も可能であるが、複数の実行方法を備えており多機能になっている。Smalltalkでは言語構文としてif文を始め殆どの制御構文が存在せず、メッセージとこのブロックの組み合わせによってあらゆる制御を実現しておりブロックはSmalltalkの根幹をなしている。

下記に一般的なブロックの使用例を示す。[から]までがブロックである。 Number methodsFor:'comparing' ! max: aNumber "レシーバーと引数を比較して大きい方を返す。" self < aNumber ifTrue: [ ^ aNumber. "ブロック内ではブロックの外側にある変数も参照できる" ] ifFalse: [ ^ self. ]. !!上記における^は他の言語のreturnに当たる復帰文であるが、この復帰文はブロックの呼び出し階層を無視しブロックを含むメソッドの呼び出し元まで一気に戻るという特殊な働きをしており、更に他の言語と一線を隔てる特徴となっている。この復帰文によって、ブロックはより制御構文としての性格が強くなっている。

11. ファンクタ
---------

ここで説明するのは C++ 用語のファンクタではない。

より形式化したものとして、数学における関手と同様のものを持つ言語もある。たとえば、Standard MLのfunctorは、モジュールからモジュールへのマッピング である。

HaskellのFunctorは以下のような型クラスである。 class Functor f where fmap ::  -> f a -> f b

-- さらに、型 f α と f α に対して定義された fmap は、以下を満たさなければならない。 -- 恒等関数を恒等関数にうつす -- fmap id == id -- 関数合成との関係 -- fmap  == fmap func1 . fmap func2<WtXmlEndTag />Prologでは、関数のシンボルをファンクタと呼んでいる。

12. 文献
------

Vandevoorde, David, & Josuttis, Nicolai M. //C++ Templates: The Complete Guide//, ISBN 0-201-73484-2


13. 参考
------

14. 外部リンク
---------

[5]
"[6]" by Kevlin Henney
[7] by Lars Haendel 
Article "[8]" by Herb Sutter
[9]