コンピュータプログラミングにおいて、関数オブジェクト[a]とは、オブジェクトを通常の関数と同じように呼び出すことができる構造であり、通常は同じ構文(関数パラメーターも関数になることができる)を使用します。一部の言語、特に C++ では、関数オブジェクトはファンクタと呼ばれることがよくあります(関数型プログラミングの概念とは関係ありません)。
説明
関数オブジェクトの典型的な用途は、コールバック関数を記述することです。Cなどの手続き型言語でのコールバックは、関数ポインタを使用して実行できます。[2]しかし、コールバック関数との間で状態を受け渡すのは困難または扱いにくい場合があります。この制限により、関数のより動的な動作も妨げられます。関数オブジェクトは、関数が実際には完全なオブジェクトのファサードであり、独自の状態を保持するため、これらの問題を解決します。
C++、Eiffel、Groovy、Lisp、Smalltalk、Perl、PHP、Python、Ruby、Scalaなど、多くの最新(および一部の古い)言語は、ファーストクラス関数オブジェクトをサポートしており、それらを大いに活用する場合もあります。[3] 関数型プログラミング言語は、さらにクロージャ、つまり作成時に周囲の環境の変数を「閉じる」ことができるファーストクラス関数をサポートしています。コンパイル時に、ラムダリフティングと呼ばれる変換によって、クロージャが関数オブジェクトに変換されます。
CとC++の場合
コールバック関数を使用して 1 組の項目間の順序関係を定義するソート ルーチンの例を考えてみましょう。次の C/C++ プログラムでは関数ポインターが使用されています。
#include <stdlib.h>
/* qsort() コールバック関数。a < b の場合は < 0、a > b の場合は > 0、a == b の場合は 0 を返します */
int compareInts ( const void * a , const void * b ) { return ( * ( int * ) a - * ( int * ) b ); } ... // qsort のプロトタイプは// void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *)); ... int main ( void ) { int items [] = { 4 , 3 , 1 , 2 }; qsort ( items , sizeof ( items ) / sizeof ( items [ 0 ]), sizeof ( items [ 0 ]), compareInts ); return 0 ; }
C++ では、メンバー関数を定義して関数呼び出し演算子をオーバーロードするクラスを定義することで、通常の関数の代わりに関数オブジェクトを使用できます。C++ では、これは次のように表示されます。
operator()
// 比較述語: a < b の場合は true を返し、それ以外の場合は false を返します
struct IntComparator { bool operator ()( const int & a , const int & b ) const { return a < b ; } };
int main () { std :: vector < int > items { 4 , 3 , 1 , 2 }; std :: sort ( items . begin ()、items . end ()、IntComparator ()); return 0 ; }
関数にコールバックを提供する構文はstd::sort()同じですが、関数ポインターの代わりにオブジェクトが渡されることに注意してください。呼び出されると、コールバック関数は他のメンバー関数と同様に実行されるため、オブジェクトの他のメンバー (データまたは関数) に完全にアクセスできます。もちろん、これは単なる簡単な例です。ファンクタが通常の関数よりも優れている点を理解するには、特定のフィールドでオブジェクトを並べ替える一般的な使用例を検討してください。次の例では、ファンクタを使用して、単純な従業員データベースを各従業員の ID 番号で並べ替えます。
struct CompareBy { const std :: string SORT_FIELD ; CompareBy ( const std :: string & sort_field = "name" ) : SORT_FIELD ( sort_field ) { /* sort_field を検証 */ } bool operator ()( const Employee & a , const Employee & b ) const { if ( SORT_FIELD == "name" ) return a . name < b . name ; else if ( SORT_FIELD == "age" ) return a . age < b . age ; else if ( SORT_FIELD == "idnum" ) return a . idnum < b . idnum ; else /* 例外などをスローする */ } };
int main () { std :: vector < Employee > emps ; /* データベースにデータを入力するコード */ // 従業員 ID 番号でデータベースをソートしますstd :: sort ( emps . begin (), emps . end (), CompareBy ( "idnum" )); return 0 ; }
C++11では、ラムダ式により同じことをより簡潔に実行できるようになりました。
int main () { std :: vector < Employee > emps ; /* データベースにデータを入力するコード */ const std :: string sort_field = "idnum" ; std :: sort ( emps . begin (), emps . end (), [ & sort_field ]( const Employee & a , const Employee & b ) const { /* フィールドを選択して比較するコード */ }); return 0 ; }
関数オブジェクトをコールバック関数以外の状況で使用することも可能である。この場合、関数オブジェクトについて
短縮形のファンクタは通常使用されない。例を続けると、
IntComparator cpm ; bool結果= cpm ( a 、b );
クラス型ファンクタに加えて、他の種類の関数オブジェクトも C++ で使用できます。これらは、C++ のメンバー ポインタまたはテンプレート機能を利用できます。テンプレートの表現力により、関数オブジェクトを他の関数オブジェクトの観点から定義する (関数合成など) など、いくつかの関数プログラミング手法を使用できます。C++標準テンプレート ライブラリ(STL) の多くは、テンプレート ベースの関数オブジェクトを多用しています。
C++ で関数オブジェクトを作成する別の方法は、関数ポインタ型、関数参照型、または関数ポインタ型への参照への非明示的な変換関数を定義することです。変換によってcv 修飾子が破棄されないと仮定すると、これにより、その型のオブジェクトを、変換先の型と同じシグネチャを持つ関数として使用できます。これを使用するように以前の例を変更すると、次のクラスが得られ、そのインスタンスは関数ポインタのように呼び出すことができます。[4]
// 比較述語: a < b の場合は true を返し、それ以外の場合は false を返します
。struct IntComparator { static bool compare ( const int & a , const int & b ) { return a < b ; } using T = decltype ( compare ); operator T * () const { return compare ; } };
int main () { std :: vector < int > items { 4 , 3 , 1 , 2 }; std :: sort ( items . begin ()、items . end ()、IntComparator ()); return 0 ; }
状態を維持する
関数オブジェクトのもう 1 つの利点は、呼び出し間で影響する状態を維持できることですoperator()。たとえば、次のコードは、10 からカウントして 11 回呼び出される
ジェネレーターを定義します。
#include <アルゴリズム> #include <iostream> #include <イテレータ>
クラスCountFrom { public : CountFrom ( int count ) : count_ ( count ) {} int演算子()() { return count_ ++ ; }
プライベート:
int count_ ; };
int main () { const int state ( 10 ); std :: generate_n ( std :: ostream_iterator < int > ( std :: cout , " \n " ), 11 , CountFrom ( state )); }
C++14 以降では、上記の例は次のように書き直すことができます。
#include <アルゴリズム> #include <iostream> #include <イテレータ>
int main () { std :: generate_n ( std :: ostream_iterator < int > ( std :: cout , " \n " ), 11 , [ count = 10 ]()可変{ return count ++ ; }); }
C#の場合
C#では、関数オブジェクトはデリゲートを介して宣言されます。デリゲートは、名前付きメソッドまたはラムダ式を使用して宣言できます。以下は、名前付きメソッドを使用した例です。
Systemを使用します。System.Collections.Genericを使用します。
パブリッククラスComparisonClass1 {パブリック静的int CompareFunction ( int x 、int y ) {戻り値x - y ; }
public static void Main () { var items = new List < int > { 4 , 3 , 1 , 2 } ; Comparison < int > del = CompareFunction ; items.Sort ( del ) ; } }
以下はラムダ式を使用した例です。
Systemを使用します。System.Collections.Genericを使用します。
パブリッククラスComparisonClass2 {パブリック静的void Main () { var items = new List < int > { 4 , 3 , 1 , 2 }; items . Sort (( x , y ) => x - y ); } }
Dで
D では、関数オブジェクトを宣言する方法がいくつか用意されています。それぞれ、クロージャを介した Lisp/Python スタイルと、デリゲートを介した C# スタイルです。
bool find ( T )( T [] haystack 、bool delegate ( T ) Needle_test ) { foreach ( straw ; haystack ) { if ( Needle_test ( straw )) trueを返します。} falseを返します。}
void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int Needle = 123 ; bool NeedleTest ( int n ) { return n == Needle ; } assert ( find ( haystack , & NeedleTest )); }
D におけるデリゲートとクロージャの違いは、コンパイラによって自動的にかつ慎重に決定されます。D は関数リテラルもサポートしており、ラムダ形式の定義が可能です。
void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int Needle = 123 ; assert ( find ( haystack , ( int n ) { return n == Needle ; })); }
コンパイラがコードをインライン化できるようにするには (上記を参照)、演算子オーバーロードを介して関数オブジェクトを C++ スタイルで指定することもできます。
bool find ( T , F )( T [] haystack , F Needle_test ) { foreach ( straw ; haystack ) { if ( Needle_test ( straw )) trueを返します。} falseを返します。}
void main () { int [] haystack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int Needle = 123 ; class NeedleTest { int Needle ; this ( int n ) { Needle = n ; } bool opCall ( int n ) { return n == Needle ; } } assert ( find ( haystack , new NeedleTest ( Needle ))); }
エッフェル塔
Eiffelソフトウェア開発方法と言語では、操作とオブジェクトは常に別の概念として扱われます。ただし、エージェントメカニズムにより、操作をランタイム オブジェクトとしてモデル化することが容易になります。エージェントは、プロシージャ呼び出しで引数として渡されたり、コールバック ルーチンとして指定されるなど、関数オブジェクトに帰属する適用範囲を満たします。Eiffel のエージェント メカニズムの設計では、方法と言語のオブジェクト指向の性質を反映するように努めています。エージェントは、通常、Eiffel の 2 種類のルーチン (PROCEDUREと )をモデル化する 2 つのライブラリ クラスのいずれかの直接インスタンスであるオブジェクトですFUNCTION。これら 2 つのクラスは、より抽象的な から派生していますROUTINE。
ソフトウェア テキスト内では、言語キーワードagentによってエージェントをコンパクトな形式で構築できます。次の例では、ボタンがクリックされたときに実行されるアクションのリストに、ゲージを前進させるアクションを追加することが目標です。
my_button.select_actions.extend (エージェントmy_gauge.step_forward )
上記の例で参照されているルーチンは、イベント駆動型プログラミングextend機能を提供するグラフィカル ユーザー インターフェイス (GUI) ライブラリのクラスの機能です。
他のライブラリ クラスでは、エージェントはさまざまな目的で使用されることが知られています。たとえば、データ構造をサポートするライブラリでは、線形構造をモデル化するクラスは、引数としてエージェント ( のインスタンス) を受け入れる型の関数を使用して普遍量化を実行します。したがって、次の例では、のすべてのメンバーに文字 '!' が含まれている
場合にのみ が実行されます。for_allBOOLEANFUNCTIONmy_actionmy_list
my_list : LINKED_LIST [ STRING ] ... if my_list . for_all ( agent { STRING }. has ( '!' )) then my_action end ...
エージェントを作成すると、モデル化するルーチンの引数や、適用されるターゲット オブジェクトも、閉じた状態にすることも開いた状態にすることもできます。閉じた引数とターゲットには、エージェントの作成時に値が割り当てられます。開いた引数とターゲットの値の割り当ては、エージェントの作成後のある時点まで延期されます。ルーチンは、for_all引数として、構造体の実際のジェネリック パラメーターに準拠する 1 つの開いた引数またはターゲットを持つ関数を表すエージェントを想定しています (STRINGこの例では)。
エージェントのターゲットが未指定の場合、agent {STRING}.has ('!')上記の例のテキストに示されているように、予想されるターゲットのクラス名が中括弧で囲まれてオブジェクト参照に置き換えられます。引数が未指定の場合、疑問符文字 ('?') が未指定引数のプレースホルダーとしてコード化されます。
ターゲットと引数を閉じたり開いたままにしたりする機能は、エージェント メカニズムの柔軟性を向上させることを目的としています。新しい行の後に標準出力に文字列を出力する次の手順を含むクラスについて考えます。
print_on_new_line ( s : STRING ) -- 改行の前に `s' を出力します。do print ( "%N" + s ) end
同じクラス内にあると想定される次のスニペットは、print_on_new_line同じルーチンへの引数として使用されるエージェント内のオープン引数とオープン ターゲットの混在を示すために使用します。
my_list : LINKED_LIST [ STRING ] ... my_list . do_all ( agent print_on_new_line ( ? )) my_list . do_all ( agent { STRING }. to_lower ) my_list . do_all ( agent print_on_new_line ( ? )) ...
do_allこの例では、構造内の各項目に対してエージェントによってモデル化されたルーチンを実行する線形構造の
手順を使用します。
3 つの命令のシーケンスは、 内の文字列を出力しmy_list、文字列を小文字に変換してから、再度出力します。
プロシージャは、do_all構造体全体を反復処理し、現在の項目をオープン引数 ( に基づくエージェントの場合print_on_new_line) またはオープン ターゲット ( に基づくエージェントの場合to_lower) に置き換えてルーチンを実行します。
オープンおよびクローズ引数とターゲットを使用すると、必要な数の引数以外をすべてクローズすることで、必要以上の引数を呼び出すルーチンを使用することもできます。
my_list . do_all (エージェントmy_multi_arg_procedure ( closed_arg_1 、? 、closed_arg_2 、closed_arg_3 )
Eiffel エージェント メカニズムの詳細については、Eiffel ISO/ECMA 標準ドキュメントを参照してください。
ジャワ島
Java にはファーストクラス関数がないため、関数オブジェクトは通常、単一のメソッドを持つインターフェース (最も一般的にはCallableインターフェース) によって表現され、その実装は一般に匿名内部クラス、または Java 8 以降ではラムダになります。
Java の標準ライブラリの例では、 は、リスト内のオブジェクトを比較する役割を持つ関数と関数java.util.Collections.sort()を取りますList。ファーストクラス関数がない場合、関数は Comparator インターフェースの一部になります。これは次のように使用できます。
List < String > list = Arrays . asList ( "10" , "1" , "20" , "11" , "21" , "12" ); Comparator < String > numStringComparator = new Comparator < String > () { public int compare ( String str1 , String str2 ) { return Integer . valueOf ( str1 ). compareTo ( Integer . valueOf ( str2 ) ); } };
コレクション.sort ( list , numStringComparator ) ;
Java 8 以降では、次のように記述できます。
List < String > list = Arrays . asList ( "10" , "1" , "20" , "11" , "21" , "12" ); Comparator < String > numStringComparator = ( str1 , str2 ) -> Integer . valueOf ( str1 ). compareTo ( Integer . valueOf ( str2 ) );
コレクション.sort ( list , numStringComparator ) ;
JavaScriptの場合
JavaScriptでは、関数はファーストクラスオブジェクトです。JavaScript はクロージャもサポートしています。
次の例を後続の Python の例と比較してください。
関数Accumulator ( start ) { var current = start ; return function ( x ) { return current += x ; }; }
使用例:
var a = Accumulator ( 4 ); var x = a ( 5 ); // x の値は 9 ですx = a ( 2 ); // x の値は 11 です
var b = Accumulator ( 42 ); x = b ( 7 ); // x の値は 49 (クロージャ b の現在の値 = 49) x = a ( 7 ); // x の値は 18 (クロージャ a の現在の値 = 18)
ジュリア
Juliaでは、メソッドは型に関連付けられているため、その型にメソッドを追加することで、任意の Julia オブジェクトを「呼び出し可能」にすることができます。(このような「呼び出し可能」オブジェクトは、「ファンクタ」と呼ばれることもあります。)
一例として、このアキュムレータ可変構造体があります(ポール・グラハムのプログラミング言語の構文と明瞭性に関する研究に基づく):[5]
julia>可変構造体Accumulator n :: Int終了
julia>関数( acc :: Accumulator )( n2 ) acc . n += n2終了
julia> a =アキュムレータ( 4 )アキュムレータ(4)
ジュリア> a ( 5 ) 9
ジュリア> a ( 2 ) 11
julia> b =アキュムレータ( 42 )アキュムレータ(42)
ジュリア> b ( 7 ) 49
このようなアキュムレータはクロージャを使用して実装することもできます。
julia> function Accumulator ( n0 ) n = n0 function ( n2 ) n += n2 end end Accumulator (1 つのメソッドを持つ汎用関数)
julia> a = Accumulator ( 4 ) (::#1) (1つのメソッドを持つ汎用関数)
ジュリア> a ( 5 ) 9
ジュリア> a ( 2 ) 11
julia> b = Accumulator ( 42 ) (::#1) (1つのメソッドを持つ汎用関数)
ジュリア> b ( 7 ) 49
LispとSchemeでは
Common Lisp、Schemeなどの Lisp 系言語では、関数は文字列、ベクトル、リスト、数値などのオブジェクトです。クロージャ構築演算子は、プログラムの一部から関数オブジェクトを作成します。演算子に引数として渡されるコード部分は関数の一部であり、字句環境も関数の一部です。字句的に可視な変数の束縛は、関数オブジェクト(より一般的にはクロージャと呼ばれる)にキャプチャされ、格納されます。キャプチャされた束縛はメンバー変数の役割を果たし、クロージャのコード部分は、C++ の演算子 () と同様に、 匿名メンバー関数の役割を果たします。
クロージャ コンストラクタの構文は です(lambda (parameters ...) code ...)。(parameters ...)部分では、関数が宣言されたパラメータを受け取るようにインターフェイスを宣言できます。 部分code ...は、ファンクタが呼び出されたときに評価される式で構成されます。
C++ などの言語でのファンクタの使用の多くは、単に、欠落しているクロージャ コンストラクターをエミュレートするだけです。プログラマはクロージャを直接構築できないため、必要なすべての状態変数とメンバー関数を持つクラスを定義する必要があります。次に、代わりにそのクラスのインスタンスを構築し、すべてのメンバー変数がそのコンストラクターによって初期化されるようにします。値は、クロージャによって直接キャプチャされるべきローカル変数から正確に導出されます。
Common Lisp のクラス システムを使用した関数オブジェクト。クロージャは使用しません。
( defclass counter () ((値:初期化引数:値:アクセサ値)))
( defmethod関数呼び出し(( cカウンター)) ( incf ( cの値)))
( defun make-counter (初期値) ( make-instance 'counter :value初期値))
;;; カウンターを使用します:
( defvar *c* ( make-counter 10 )) ( functor-call *c* ) --> 11 ( functor-call *c* ) --> 12
Common Lisp には funcallable オブジェクトを作成する標準的な方法がないため、FUNCTOR-CALL という汎用関数を定義することでそれを模倣します。これは、どのようなクラスにも特化できます。標準の FUNCALL 関数は汎用的ではなく、関数オブジェクトのみを受け取ります。
この FUNCTOR-CALL 汎用関数によって、関数オブジェクトが得られます。関数オブジェクトは、通常、同じ構文を使用して、オブジェクトを通常の関数であるかのように呼び出したり呼び出したりできるコンピュータ プログラミング構造です。構文はほぼ同じで、FUNCALL ではなく FUNCTOR-CALLです。一部の Lisp では、単純な拡張機能として、関数呼び出し可能なオブジェクトが提供されています。オブジェクトを関数と同じ構文を使用して呼び出し可能にすることは、かなり簡単な作業です。クラス オブジェクトであろうとクロージャであろうと、関数呼び出し演算子をさまざまな種類の関数で動作させることは、整数、実数、複素数などのさまざまな種類の数値で動作する + 演算子を作成することほど複雑ではありません。
さて、クロージャを使用して実装されたカウンターです。これははるかに簡潔で直接的です。MAKE-COUNTERファクトリー関数の INITIAL-VALUE 引数がキャプチャされ、直接使用されます。コンストラクターを介して補助クラス オブジェクトにコピーする必要はありません。これがカウンターです。補助オブジェクトが作成されますが、それは舞台裏で行われます。
( defun make-counter (値) ( lambda ( ) ( incf値)))
;;; カウンターを使う
( defvar *c* ( make-counter 10 )) ( funcall *c* ) ; --> 11 ( funcall *c* ) ; --> 12
Scheme ではクロージャがさらにシンプルになり、Scheme コードではこのような高階プログラミングがより慣用的に使用される傾向があります。
( define ( make-counter value ) ( lambda () ( set! value ( + value 1 )) value )) ;;; カウンターを使う( define c ( make-counter 10 )) ( c ) ; --> 11 ( c ) ; --> 12
同じ語彙環境で複数のクロージャを作成できます。それぞれが特定の種類の操作を実装するクロージャのベクトルは、一連の仮想操作を持つオブジェクトを非常に忠実にエミュレートできます。このタイプのシングルディスパッチオブジェクト指向プログラミングは、クロージャで完全に実行できます。
つまり、いわゆる山の両側から掘られているトンネルのようなものが存在します。OOP 言語のプログラマーは、オブジェクトがそのオブジェクトの機能目的を実行するための1 つのメイン関数を持つように制限することで関数オブジェクトを発見し、さらにその名前を削除して、オブジェクトが呼び出されているように見えるようにします。クロージャを使用するプログラマーは、オブジェクトが関数のように呼び出されることに驚きませんが、同じ環境を共有する複数のクロージャが、単一ディスパッチ型 OOP の仮想テーブルのような完全な抽象操作セットを提供できることを発見します。
Objective-Cの場合
Objective-Cでは、クラスから関数オブジェクトを作成できますNSInvocation。関数オブジェクトの構築には、メソッド シグネチャ、ターゲット オブジェクト、およびターゲット セレクタが必要です。現在のオブジェクトの への呼び出しを作成する例を次に示しますmyMethod。
// 関数オブジェクトを構築します
SEL sel = @selector ( myMethod ); NSInvocation * inv = [ NSInvocation invocationWithMethodSignature : [ self methodSignatureForSelector : sel ]]; [ inv setTarget : self ]; [ inv setSelector : sel ];
// 実際の呼び出しを実行します
[ inv invoke ];
の利点はNSInvocation、ターゲット オブジェクトを作成後に変更できることです。 を 1 つNSInvocation作成し、任意の数のターゲットごとに、たとえば観測可能なオブジェクトから呼び出すことができます。 はNSInvocationプロトコルのみから作成できますが、簡単ではありません。こちらを参照してください。
Perlの場合
Perlでは、関数オブジェクトは、クラスのコンストラクターから作成され、オブジェクトのインスタンス データに対して閉じられた関数を返し、クラスに渡されます。
パッケージAcc1 ; sub new { my $class = shift ; my $arg = shift ; my $obj = sub { my $num = shift ; $arg += $num ; }; bless $obj , $class ; } 1 ;
または、演算子をオーバーロードして、&{}オブジェクトを関数として使用できるようにします。
パッケージAcc2 ;オーバーロード'&{}'を使用=> sub { my $self = shift ; sub { my $num = shift ; $self -> { arg } += $num ; } };
sub new { my $class = shift ; my $arg = shift ; my $obj = { arg => $arg }; bless $obj 、$class ; } 1 ;
どちらの場合も、関数オブジェクトは、逆参照矢印構文$ref->(@arguments)を使用して使用できます。
use Acc1 ; my $a = Acc1 -> new ( 42 ); print $a -> ( 10 ), "\n" ; # 52 が出力されますprint $a -> ( 8 ), "\n" ; # 60 が出力されます
または、コード参照逆参照構文&$ref(@arguments)を使用します。
Acc2を使用します。my $a = Acc2 -> new ( 12 ); print & $a ( 10 ), "\n" ; # 22 が出力されます。print & $ a ( 8 ), " \n" ; # 30 が出力されます。
PHPの場合
PHP 5.3 以降には、たとえば usort() 関数のパラメータとして使用できる ファーストクラス関数があります。
$a = 配列( 3 , 1 , 4 );
usort ( $a , 関数 ( $x , $y ) { return $x - $y ; });
PHP 5.3 以降では、ラムダ関数とクロージャもサポートされます。
function Accumulator ( $start )
{
$current = $start ;
return function ( $x ) use ( & $current )
{
return $current += $x ;
};
}
使用例:
$a = アキュムレータ( 4 );
$x = $a ( 5 );
echo "x = $x <br/>" ; // x = 9
$x = $a ( 2 );
echo "x = $x <br/>" ; // x = 11
PHP 5.3以降では、クラスにマジックメソッド__invoke()を追加することで、オブジェクトを呼び出せるようにすることも可能です。[6]
クラス Minus
{
public 関数 __invoke ( $x , $y )
{
return $x - $y ;
}
}
$a = 配列( 3 , 1 , 4 );
usort ( $a , 新しい Minus ());
PowerShellの場合
Windows PowerShell言語では、スクリプト ブロックは単一の単位として使用できるステートメントまたは式のコレクションです。スクリプト ブロックは引数を受け入れ、値を返すことができます。スクリプト ブロックは、Microsoft .NET Framework型 System.Management.Automation.ScriptBlock のインスタンスです。
関数 Get-Accumulator ( $x ) {
{
param ( $y )
return $x += $y
}. GetNewClosure ()
}
PS C:\> $a = Get-Accumulator 4
PS C:\> & $a 5
9
PS C:\> & $a 2
11
PS C:\> $b = Get-Accumulator 32
PS C:\> & $b 10
42
Pythonの場合
Pythonでは、関数は文字列、数値、リストなどと同様にファーストクラス オブジェクトです。この機能により、多くの場合、関数オブジェクトを記述する必要がなくなります。メソッドを持つオブジェクトはすべて、__call__()関数呼び出し構文を使用して呼び出すことができます。
一例として、このアキュムレータクラスがあります(ポール・グラハムのプログラミング言語の構文と明瞭性に関する研究に基づいています)[7]
クラス Accumulator :
def __init__ ( self , n ) -> None :
self . n = n
def __call__ ( self , x ):
self . n += x
戻り値 self . n
使用例(対話型インタープリターを使用):
>>> a = アキュムレータ( 4 )
>>> a ( 5 )
9
>>> a ( 2 )
11
>>> b = アキュムレータ( 42 )
>>> b ( 7 )
49
関数はオブジェクトなので、ローカルに定義したり、属性を与えたり、他の関数から返したりすることもできます。[8]次の例で示すとおりです。
def Accumulator ( n ):
def inc ( x ):
非ローカル n
n += x
戻り値 n
戻り値 inc
ルビーでは
Rubyでは、いくつかのオブジェクト、特に Method オブジェクトと Proc オブジェクトを関数オブジェクトと見なすことができます。Ruby には、UnboundMethod と block という、半関数オブジェクトと見なせる 2 種類のオブジェクトもあります。UnboundMethod は、関数オブジェクトとして使用する前に、まずオブジェクトにバインド (つまり Method になる) する必要があります。ブロックは関数オブジェクトのように呼び出すことができますが、オブジェクトとして他の用途で使用するには (たとえば、引数として渡す)、まず Proc に変換する必要があります。最近では、シンボル (リテラル単項指示子 を介してアクセス:) も s に変換できるようになりました。Ruby の単項演算子 (オブジェクトを呼び出すことと同等で、メソッドが存在すると仮定Proc)を使用して、Ruby Extensions Project は簡単なハックを作成しました。
&to_proc
クラスSymbol def to_proc proc { | obj , * args | obj.send ( self , * args ) } end end
現在、メソッドは を介しfooて関数オブジェクト、つまりProcとなり&:foo、 を介して使用されるようになりましたtakes_a_functor(&:foo)。はSymbol.to_proc、2006年6月11日のRubyKaigi2006で正式にRubyに追加されました。[1]
さまざまな形式があるため、Functor という用語は Ruby では Function オブジェクトを意味するために一般的には使用されません。Ruby Facets プロジェクトによって導入されたディスパッチ委譲の一種がFunctor と名付けられています。その最も基本的な定義は次のとおりです。
class Functor defInitialize ( & func ) @ func = funcenddefmethod_missing ( op , * args , & blk ) @func . call ( op , * args , & blk )終了終了
この用法は、 MLなどの関数型プログラミング言語や元の数学用語 で使用される用法に似ています。
その他の意味
より理論的な文脈では、関数オブジェクトは、関数のクラスの任意のインスタンスであると考えることができます。特に、関数がファーストクラスオブジェクトであるCommon Lispなどの言語ではそうです。
関数型プログラミング言語の ML ファミリーでは、関数子という用語を使用して、モジュールからモジュールへ、または型から型へのマッピングを表します。これは、コードを再利用するための手法です。このように使用される関数子は、カテゴリ理論における関数子の元の数学的な意味、または C++、Java、 Adaでの汎用プログラミングの使用に類似しています。
Haskellでは、関数子という用語は、圏論における 関数子の意味に関連する概念にも使用されます。
Prologおよび関連言語では、 functor はfunction symbolの同義語です。
参照
注記
- ^ C++では、関数オブジェクトは1つの主要なメソッドを持つオブジェクトであり、関数オブジェクトは関数オブジェクトの特殊なケースです。[1]これらは関数オブジェクトに似ていますが、同じではありません。
参考文献
- ^ 関数型と関数子の違いは何ですか?
- ^ Silan Liu. 「C++ チュートリアル パート I - 基本: 5.10 関数ポインターは主にコールバック テクニックを実現するために使用されます。これについては後で説明します」。TRIPOD: プログラミング チュートリアル Copyright © Silan Liu 2002 。2012 年 9 月 7 日取得。
関数ポインターは主にコールバック テクニックを実現するために使用されます。これについては後で説明します。
- ^ Paweł Turlejski (2009-10-02). 「C++ チュートリアル パート I - 基本: 5.10 関数ポインターは主にコールバック テクニックを実現するために使用されます。これについては後で説明します」。Just a Few Lines 。2012-09-07取得。PHP
5.3 では、他の多くの機能とともにクロージャが導入されました。これで、Ruby / Groovy / Scala / any_modern_language の開発者が実行できるすべての優れた機能を実行できるようになりました。そうですよね? まあ、実行できますが、おそらく実行しないでしょう... 理由は次のとおりです。
- ^ 「オーバーロード解決§クラス オブジェクトの呼び出し」cppreference.com。
- ^ アキュムレータジェネレータ
- ^ マジックメソッドに関する PHP ドキュメント
- ^ アキュムレータジェネレータ
- ^ Python リファレンスマニュアル - 関数定義
さらに読む
- David Vandevoorde & Nicolai M Josuttis (2006). C++ テンプレート: 完全ガイド、ISBN 0-201-73484-2 : 具体的には、第 22 章は関数オブジェクトについて説明しています。
外部リンク
- ポートランドパターンリポジトリからの説明
- C++ の高度な設計問題 - 非同期 C++ 2020-09-22 にWayback Machineでアーカイブされました ( Kevlin Henney著)
- Lars Haendel 著「関数ポインタ チュートリアル」(2000/2001)
- Herb Sutterによる記事「一般化された関数ポインタ」
- Java の汎用アルゴリズム
- PHP ファンクタ - PHP の関数オブジェクト
- functionoid とは何ですか? また、なぜそれを使用するのでしょうか? (C++ FAQ)
