高階関数
数学とコンピュータ サイエンスにおいて、高階関数( HOF ) とは、次のいずれか 1 つ以上を実行する関数です。
その他の関数はすべて一階関数です。数学では、高階関数は演算子または汎関数とも呼ばれます。微積分学における微分演算子は、関数をその導関数(これも関数)に写すため、一般的な例です。高階関数は、数学における「関数子」の他の用法と混同しないでください。「関数子(曖昧さ回避)」を参照してください。
型なしラムダ計算では、すべての関数は高階です。ほとんどの関数型プログラミング言語の派生元である型付きラムダ計算では、1 つの関数を引数として取る高階関数は、 の形式の型を持つ値です。
一般的な例
- 多くの関数型プログラミング言語で見られるmap関数は、高階関数の一例です。関数fと要素のコレクションを引数として受け取り、コレクションの各要素にfを適用した新しいコレクションを返します。
- sortは比較関数を引数として受け取り、ソートアルゴリズムとソート対象項目の比較処理を分離することができます。C言語の標準関数が
qsortその例です。 - フィルター
- fold( foldlとfoldrを含む)
- スキャン
- 適用する
- 関数合成
- 統合
- 折り返し電話
- ツリートラバーサル
- 自然言語の意味理論であるモンタギュー文法は高階関数を使用する
プログラミング言語のサポート
直接サポート
これらの例は、プログラミング言語を比較対照するものではなく、高階関数の構文の例として役立つことを目的としています。
以下の例では、高階関数はtwice関数を受け取り、その関数をある値に2回適用します。twice同じ値に複数回適用する必要がある場合は、値ではなく関数を返すことが望ましいです。これは「 don't repeat yourself(同じことを繰り返さないf)」の原則に沿っています。
APL
2回← { ⍺⍺ ⍺⍺ ⍵ } プラススリー← { ⍵ + 3 } g ← {プラススリーを2回⍵ } g 7 13 あるいは暗黙的に:
2回← ⍣ 2 プラススリー← + ∘ 3 g ←プラス3を2回g 7 13 C++
C++11std::functionでの使用:
stdをインポートします。 auto を2 回実行して[ ]( const std :: function < int ( int ) >& f ) -> auto { return [ f ]( int x ) -> int { return f ( f ( x )); }; }; 自動plusThree = []( int i ) -> int {戻り値i + 3 ; }; int main () { auto g = 2回( plusThree ); std :: println ( "{}" , g ( 7 )); // 13 } または、C++14 で提供される汎用ラムダを使用します。
stdをインポートします。 auto を2 回実行して[]( const auto & f ) -> auto { return [ f ]( int x ) -> int { return f ( f ( x )); }; }; 自動plusThree = []( int i ) -> int {戻り値i + 3 ; }; int main () { auto g = 2回( plusThree ); std :: println ( "{}" , g ( 7 )); // 13 } C#
デリゲートのみを使用する:
システムを使用します。 パブリッククラスProgram { public static void Main ( string [] args ) { Func < Func < int , int > , Func < int , int >> two = f => x => f ( f ( x )); Func < int , int > plusThree = i => i + 3 ; var g = 2回( plusThree ) ; コンソール.WriteLine ( g ( 7 )); // 13 } } または、静的メソッドの場合も同様です。
システムを使用します。 パブリッククラスProgram {プライベート静的Func < int , int > Twice ( Func < int , int > f ) { return x => f ( f ( x )); } プライベート静的int PlusThree ( int i ) => i + 3 ; パブリック静的void Main (文字列[] args ) { var g = Twice ( PlusThree ); コンソール.WriteLine ( g ( 7 )); // 13 } } クロージュア
( defn を2 回[ f ] ( fn [ x ] ( f ( f x )))) (定義プラス3 [ i ] ( + i 3 )) ( def g ( 2倍プラス3 )) ( println ( g 7 )) ; 13 ColdFusion マークアップ言語 (CFML)
2回 = 関数( f ) { 関数( x ) {戻り 値f ( f ( x )); }; }; plusThree = function ( i ) { return i + 3 ; };g = 2回(プラス3 ) ;writeOutput ( g ( 7 )); // 13コモンリスプ
( defun を2 回( f ) ( lambda ( x ) ( funcall f ( funcall f x )))) ( defunプラス 3 ( i ) ( + i 3 )) ( defvar g ( #' を2 回プラス 3 )) ( print ( funcall g 7 )) D
std . stdioをインポートします: writeln ; エイリアスを2回実行する= ( f ) => ( int x ) => f ( f ( x )); エイリアスplusThree = ( int i ) => i + 3 ; void main () { auto g = 2回( plusThree ); writeln ( g ( 7 )); // 13 } ダーツ
int関数( int ) を2 回( int関数( int ) f ) { return ( x ) { return f ( f ( x )); }; } int plusThree ( int i ) { i + 3を返します; } void main () {最終g = 2回( plusThree );印刷( g ( 7 )); // 13 } エリクサー
Elixirではモジュール定義と無名関数を混在させることができる
defmodule Hof do def を2 回( f ) do fn ( x ) -> f . ( f . ( x ))で実行 end end end plus_three = fn ( i ) -> i + 3終了 g =ホフ. 2倍(プラス3 ) IO .置くg . ( 7 ) # 13 あるいは、純粋な匿名関数を使用して構成することもできます。
2回= fn ( f ) -> fn ( x ) -> f . ( f . ( x ))終了終了 plus_three = fn ( i ) -> i + 3終了 g = 2回。(プラス3 ) IO .置くg . ( 7 ) # 13 アーラン
or_else ([], _) -> false ; or_else ([ F | Fs ], X ) -> or_else ( Fs , X , F ( X ))。 or_else ( Fs 、X 、false ) -> or_else ( Fs 、X ) 。or_else ( Fs 、_、{ false 、Y }) -> or_else ( Fs 、Y ) 。or_else (_、_、R ) -> R 。 or_else ([ fun erlang : is_integer / 1 , fun erlang : is_atom / 1 , fun erlang : is_list / 1 ], 3 . 23 )。 このErlangの例では、高階関数はor_else/2関数のリスト(Fs)と引数(X)を受け取ります。関数FをX引数として評価します。関数がFfalseを返す場合、 の次の関数Fsが評価されます。関数がFを返す場合{false, Y}、 の次の関数が評価されます。関数がFsを返す場合、高階関数は を返します。、、 は関数になり得ることに注意してください。例では を返します。YFRor_else/2RXYRfalse
F#
f = f >> fを2回繰り返す plus_three = (+) 3とします g = 2倍プラス3とする g 7 |> printf "%A" // 13 行く
パッケージメイン 「fmt」をインポート func を2 回( f func ( int ) int ) func ( int ) int { return func ( x int ) int { return f ( f ( x )) } } func main () { plusThree := func ( i int ) int { return i + 3 } g := 2回(プラス3 ) fmt . Println ( g ( 7 )) // 13 } 関数リテラルは、識別子 ( twice) を使用して定義することも、匿名 (変数 に割り当てられるplusThree) で定義することもできることに注意してください。
グルーヴィー
def two = { f , x -> f ( f ( x )) } def plusThree = { it + 3 } def g = two . curry ( plusThree ) println g ( 7 ) // 13 ハスケル
2回:: ( Int -> Int ) -> ( Int -> Int ) 2回f = f . f プラススリー:: Int -> Intプラススリー= ( + 3 ) main :: IO () main = print ( g 7 ) -- 13ここでg = 2倍プラス3 J
明確に言えば、
2回=.副詞: 'uu y' plusthree =.動詞: 'y + 3' g =. plusthree を2回g 7 13 あるいは暗黙のうちに、
2倍=. ^: 2 プラススリー=. +& 3 g =.プラススリーを2回g 7 13 Java (1.8+)
関数インターフェースのみを使用する:
java.util.function.*をインポートします。 クラス Main { public static void main ( String [] args ) { Function < IntUnaryOperator , IntUnaryOperator > twice = f -> f . andThen ( f ); IntUnaryOperator plusThree = i -> i + 3 ; var g = 2回.apply ( plusThree ) ; システム.out.println ( g.applyAsInt ( 7 ) ) ; // 13 } } または、静的メソッドの場合も同様です。
java.util.function.*をインポートします。 クラス Main {プライベート静的IntUnaryOperator を2 回( IntUnaryOperator f ) {戻り値f . andThen ( f ); } プライベート静的int plusThree ( int i ) { return i + 3 ; } パブリック静的void main ( String [] args ) { var g = 2回( Main :: plusThree ); システム.out.println ( g.applyAsInt ( 7 ) ) ; // 13 } } JavaScript
矢印関数の場合:
「厳密なものを使用する」;const を2 回繰り返すとf => x => f ( f ( x )); const plusThree = i => i + 3 ; const g = 2回( plusThree ); コンソール.log ( g ( 7 ) ); // 13 あるいは古典的な構文では:
「厳密なものを使用する」;関数を2回実行します( f ) {関数( x )を返します{ f ( f ( x )を返します); }; } 関数plusThree ( i ) {戻り値i + 3 ; } const g = 2回( plusThree ); コンソール.log ( g ( 7 ) ); // 13 ジュリア
julia> function を2 回( f )関数の結果( x )を1回( f ( x ))で返し、結果を2 回 ( 1 つのメソッドを持つ汎用関数 ) で終了します。 julia> plusthree ( i ) = i + 3 plusthree (1つのメソッドを持つジェネリック関数) julia> g = twice ( plusthree ) (::var"#result#3"{typeof(plusthree)}) (1つのメソッドを持つジェネリック関数) ジュリア> g ( 7 ) 13 コトリン
fun を2 回実行します( f : ( Int ) -> Int ): ( Int ) -> Int { return { f ( f ( it )) } } 楽しいplusThree ( i : Int ) = i + 3 fun main () { val g = 2回( :: plusThree ) println ( g ( 7 )) // 13 } ルア
関数を2回実行する( f )関数を返す( x )関数を返す( f ( x ))終了終了 関数plusThree ( i )戻り値i + 3終了 ローカルg = 2 回(プラス 3 回) 印刷( g ( 7 )) -- 13 MATLAB
関数 結果= 2回( f )結果= @( x ) f ( f ( x ));終了 プラススリー= @( i ) i + 3 ; g = 2倍(プラス3 ) ディスプ(g (7 )); % 13 OCaml
f x = f ( f x )を2回 繰り返す plus_three = (+) 3としますlet () = let g = 2回 plus_three in print_int ( g 7 ); (* 13 *) print_newline ()PHP
<?php宣言します( strict_types = 1 );function を 2 回(呼び出し可能 $f ) : クロージャ { return function ( int $x ) use ( $f ) : int { return $f ( $f ( $x )); }; }関数 plusThree ( int $i ) : int { 戻り値 $i + 3 ; }$g = 2回( 'plusThree' ) ;echo $g ( 7 ), " \n " ; // 13またはすべての関数を変数に入れる:
<?php宣言します( strict_types = 1 );$twice = fn (呼び出し可能 $f ) : クロージャ => fn ( int $x ) : int => $f ( $f ( $x ));$plusThree = fn ( int $i ) : int => $i + 3 ;$g = $twice ( $plusThree );echo $g ( 7 ), " \n " ; // 13矢印関数は親スコープから取得した変数を暗黙的に取得しますが、[1]匿名関数ではuse同じことを行うためにキーワードが必要であることに注意してください。
パール
strictを使用します。warningsを使用します。 sub を2 回実行します{ my ( $f ) = @_ ; sub { $f -> ( $f -> ( @_ )); }; } サブプラススリー{ my ( $i ) = @_ ; $i + 3 ; } 私の$g = 2 回( \& plusThree ); $g -> ( 7 ), "\n"を印刷する; # 13 またはすべての関数を変数に入れる:
strictを使用します。warningsを使用します。 my $twice = sub { my ( $f ) = @_ ; sub { $f -> ( $f -> ( @_ )); }; }; 私の$plusThree = sub {私の( $i ) = @_ ; $i + 3 ; }; 私の$g = $twice -> ( $plusThree ); $g -> ( 7 ), "\n"を印刷する; # 13 パイソン
def を2 回( f : Callable [ Any ]) -> Any : def結果( x : Any ) -> Any : return f ( f ( x ))結果を返す plus_three : 呼び出し可能[ int ] = lambda i : i + 3g : int = 2倍( plus_three ) print ( g ( 7 )) # 13 を出力しますPythonのデコレータ構文は、関数を高階関数に渡した結果で置き換えるためによく使われます。例えば、この関数はg以下のように実装することもできます。
@twice def g ( i : int ) -> int : i + 3を返す print ( g ( 7 )) # 13 を出力しますR
2回<- \ ( f ) \ ( x ) f ( f ( x )) plusThree <-関数( i ) i + 3 g <- 2回(プラス3 ) > g ( 7 ) [ 1 ] 13 楽
sub を 2 回実行します( Callable:D $f ) { return sub { $f ( $f ( $^x )) };}sub plusThree ( Int:D $i ) { $i + 3を返します ;}私の $g = 2回( &plusThree ); $g ( 7 ); # 13と言うRakuでは、すべてのコードオブジェクトはクロージャであるため、関数内ではレキシカル変数が「閉じられている」ため、外側のスコープから内側のレキシカル変数を参照できます。Rakuはラムダ式用の「pointy block」構文もサポートしており、変数に代入したり匿名で呼び出したりすることができます。
ルビー
def 2回( f ) -> ( x ) { f . call ( f . call ( x )) }終了 プラス3 = -> ( i ) { i + 3 } g = 2倍(プラス3 ) gを置く。( 7 ) # 13を呼び出す さび
fn を2 回( f : impl Fn ( i32 ) -> i32 ) -> impl Fn ( i32 ) -> i32 { move | x | f ( f ( x )) } fn plus_three ( i : i32 ) -> i32 { i + 3 } fn main () { let g = 2回( plus_three ); println! ( "{}" , g ( 7 )) // 13 } スカラ
オブジェクトMain { def を2 回( f : Int => Int ): Int => Int = fをfで構成する def plusThree ( i : Int ): Int = i + 3 def main ( args :配列[文字列] ): Unit = { val g = 2回( plusThree ) 印刷( g ( 7 )) // 13 } } スキーム
(定義( f gを合成) (ラムダ( x ) ( f ( g x )))) ( ( 2回f )を定義し( f fを合成)) (定義(プラス3i ) ( + i 3 )) ( g ( 2倍 + 3 )を定義) (ディスプレイ( g 7 )) ; 13 (ディスプレイ" \n " ) 迅速
func をtwice ( _ f : @ escaping ( Int ) -> Int ) -> ( Int ) -> Int { return { f ( f ( $0 )) } } plusThree = { $0 + 3 }とします。 g = 2回( plusThree )とします。 印刷( g ( 7 )) // 13 Tcl
{{ f x } を 2 回設定します{ $fを適用します[ $f $xを適用します]}} 3 つを加算して設定します{{ i } { [式$i + 3を返します]}} # 結果: 13 puts [apply $twice $plusThree 7 ] Tcl は、apply コマンドを使用して匿名関数を適用します (8.6 以降)。
XACML
XACML 標準では、属性バッグ内の複数の値に関数を適用するための高階関数が標準で定義されています。
ルールallowEntry {許可条件anyOfAny(function [ stringEqual ],市民権、allowedCitizenships ) } XACML の高階関数のリストについては、こちらを参照してください。
XQuery
関数をlocal:twice ( $ f , $ x ) { $ f ( $ f ( $ x )) }と宣言します。 関数local:plusthree ( $ i ) { $ i + 3 }を宣言します。 ローカル:twice (ローカル:plusthree # 1 , 7 ) (: 13 :) 代替案
関数ポインタ
C、C++、Fortran、Pascalなどの言語では、関数ポインタを使うことで、プログラマーは関数への参照を渡すことができる。以下のCコードは、任意の関数の積分の近似値を計算する。
#include <stdio.h> ダブルスクエア(ダブルx ) { return x * x ; } ダブルキューブ(ダブルx ) { return x * x * x ; } /* 区間 [a,b] 内で f() の積分を計算します */ double integral ( double f ( double x ), double a , double b , int n ) { int i ; double sum = 0 ; double dt = ( b - a ) / n ; for ( i = 0 ; i < n ; ++ i ) { sum += f ( a + ( i + 0.5 ) * dt ); } return sum * dt ; } int main () { printf ( "%g \n " , integral ( square , 0 , 1 , 100 )); printf ( "%g \n " , integral ( cube , 0 , 1 , 100 )); 0を返す; } C 標準ライブラリのqsort関数は、関数ポインターを使用して高階関数の動作をエミュレートします。
マクロ
マクロは高階関数の効果の一部を実現するためにも使用できます。ただし、マクロは変数の捕捉の問題を容易に回避することはできません。また、大量の重複コードが生成され、コンパイラによる最適化が困難になる場合があります。マクロは一般的に強い型付けではありませんが、強い型付けのコードを生成することもあります。
動的コード評価
他の命令型プログラミング言語では、評価スコープ内で動的にコードを実行する( EvalまたはExecute操作と呼ばれることもある)ことで、高階関数で得られるアルゴリズム結果の一部を実現できます。このアプローチには、次のような重大な欠点があります。
- 実行される引数コードは通常、静的に型付けされていません。これらの言語では一般に、実行されるコードの整形式性と安全性を判断するために動的型付けに依存しています。
- 引数は通常文字列として与えられ、その値は実行時まで分からない場合があります。この文字列は、プログラム実行中にコンパイルされるか(ジャストインタイムコンパイルを使用)、解釈によって評価される必要があります。この場合、実行時にオーバーヘッドが発生し、通常は効率の悪いコードが生成されます。
オブジェクト
高階関数をサポートしないオブジェクト指向プログラミング言語では、オブジェクトが効果的な代替手段となります。オブジェクトのメソッドは本質的に関数のように動作し、メソッドはオブジェクトをパラメータとして受け取り、オブジェクトを戻り値として生成します。ただし、オブジェクトは純粋関数に比べて実行時のオーバーヘッドが大きくなることが多く、オブジェクトとそのメソッドを定義およびインスタンス化するための定型コードも必要になります。スタックベース(ヒープベースではなく)のオブジェクトまたは構造体を使用できる言語では、この方法により柔軟性が高まります。
関数を返す関数を使用して、 Free Pascalで単純なスタック ベースのレコードを使用する例:
プログラム例; type int = integer ; Txy = record x , y : int ; end ; Tf = function ( xy : Txy ) : int ; function f ( xy : Txy ) : int ; begin Result : = xy.y + xy.x ; end ; 関数g ( func : Tf ) : Tf ; begin result := func ; end ; 変数a : Tf ; xy : Txy = ( x : 3 ; y : 7 ) ; begin a := g ( @ f ) ; // "a" に関数を返すwriteln ( a ( xy )) ; // 10 を出力しますend . この関数はレコードを入力としてa()受け取りTxy、レコードxとyフィールドの合計 (3 + 7) の整数値を返します。
機能解除
非関数化は、第一級関数がない言語で高階関数を実装するために使用できます 。
// 機能解除された関数データ構造template < typename T > struct Add { T value ; }; template < typename T > struct DivBy { T value ; }; template < typename F , typename G > struct Composition { F f ; G g ; }; // 機能解除された関数適用の実装template < typename F , typename G , typename X > auto apply ( Composition < F , G > f , X arg ) { return apply ( f . f , apply ( f . g , arg )); } テンプレート< typename T , typename X > auto apply ( Add < T > f , X arg ) { return arg + f . value ; } テンプレート< typename T , typename X > auto apply ( DivBy < T > f , X arg ) { return arg / f . value ; } // 高階合成関数template < typename F , typename G > Composition < F , G > compose ( F f , G g ) { return Composition < F , G > { f , g }; } int main ( int argc , const char * argv []) { auto f = compose ( DivBy < float > { 2.0f }, Add < int > { 5 }); apply ( f , 3 ); // 4.0f apply ( f , 9 ); // 7.0f return 0 ; } この場合、関数オーバーロードによって異なる型が使用され、異なる関数が呼び出されます。この例のオーバーロードされた関数のシグネチャは ですauto apply。
参照
- ファーストクラス関数
- 組み合わせ論理
- 関数レベルプログラミング
- 関数型プログラミング
- カッパ計算-高階関数を除外する関数の形式論
- 戦略パターン
- 高階メッセージ
参考文献
- ^ 「PHP: アロー関数 - マニュアル」www.php.net . 2021年3月1日閲覧。