数学とコンピュータサイエンスにおいて、高階関数( HOF ) とは、次のいずれか 1 つ以上を実行する 関数です。
その他の関数はすべて1 階関数です。数学では、高階関数は演算子または関数とも呼ばれます。微積分学の微分演算子は、関数をその導関数(これも関数)にマップするため、一般的な例です。高階関数は、数学全体での「関数子」という単語の他の用法と混同しないでください。関数子 (曖昧さ回避)を参照してください。
型なしラムダ計算では、すべての関数は高階です。ほとんどの関数型プログラミング言語の派生元である型付きラムダ計算では、1 つの関数を引数として取る高階関数は、形式の型を持つ値です。
一般的な例
map多くの関数型プログラミング言語に見られる関数は、高階関数の一例です。関数fと要素のコレクションを引数として受け取り、結果として、コレクションの各要素にfを適用した新しいコレクションを返します。- ソート関数は、比較関数をパラメータとして受け取り、プログラマーがソートアルゴリズムをソート対象の項目の比較から分離できるようにします。C標準関数は 、
qsortこの例です。 - フィルター
- 折り畳み
- 適用する
- 関数合成
- 統合
- 折り返し電話
- ツリートラバーサル
- 自然言語の意味理論であるモンタギュー文法は高階関数を使用する
プログラミング言語のサポート
直接サポート
これらの例はプログラミング言語を比較対照するものではなく、高階関数構文の例として役立つことを目的としています。
次の例では、高階関数はtwice関数を受け取り、その関数を何らかの値に 2 回適用します。twice同じ値に複数回適用する必要がある場合は、値ではなく関数を返す方が望ましいです。これは、「同じことを繰り返さないf」という原則に沿ったものです。
オーストラリア
2回← { ⍺⍺ ⍺⍺ ⍵ }
プラススリー← { ⍵ + 3 }
g ← {プラススリーを2回⍵ } g 7 13
あるいは暗黙的に:
2回← ⍣ 2
プラススリー← + ∘ 3
g ←プラススリーを2回g 7 13
C++
C++11std::functionでの使用:
#include <iostream> #include <function>
auto twice = []( const std :: function < int ( int ) >& f ) { return [ f ]( int x ) { return f ( f ( x )); }; };
自動plus_three = []( int i ) {戻り値i + 3 ; };
int main () { auto g = twice ( plus_three );
std :: cout << g ( 7 ) << '\n' ; // 13 }
または、C++14 で提供される汎用ラムダを使用する場合:
#include <iostream>
auto を2 回実行します( const auto & f ) { return [ f ]( int x ) { return f ( f ( x )); }; };
自動plus_three = []( int i ) {戻り値i + 3 ; };
int main () { auto g = twice ( plus_three );
std :: cout << g ( 7 ) << '\n' ; // 13 }
C#
デリゲートのみを使用する:
システムの使用;
パブリッククラスProgram { public static void Main ( string [] args ) { Func < Func < int , int > , Func < int , int >> twice = 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 (文字列[]引数) { 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 );
書き込み出力( g ( 7 )); // 13
コモンリスプ
( defun twice ( f ) ( lambda ( x ) ( funcall f ( funcall f x )))) ( defun plus-three ( i ) ( + i 3 )) ( defvar g ( twice #' plus-three )) ( print ( funcall g 7 ))
だ
stdをインポートします。stdio : writeln ;
エイリアスを2回繰り返す= ( f ) => ( int x ) => f ( f ( x ));
エイリアスplusThree = ( int i ) => i + 3 ;
void main () { auto g = twice ( plusThree );
writeln ( g ( 7 )); // 13 }
ダーツ
int Function ( int )を 2 回( int Function ( int ) f ) { return ( x ) { return f ( f ( x )); }; }
int plusThree ( int i ) {戻り値i + 3 ; }
void main () { final g = twice ( plusThree ); print ( g ( 7 )); // 13 }
エリクサー
Elixirではモジュール定義と無名関数を混在させることができます
defmodule Hof do def twice ( 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引数としてY評価されます。関数がFを返す場合、R高階関数はor_else/2を返しますR。、、および は関数になる可能性があることに注意してくださいX。Y例Rでは を返しますfalse。
ファ#
2回f = f >> fとする
plus_three = (+) 3とします。
g = 2倍プラス3とします
g 7 |> printf "%A" // 13
行く
パッケージメイン
「fmt」をインポートする
func twice ( 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 twice = { f , x -> f ( f ( x )) } def plusThree = { it + 3 } def g = twice . curry ( plusThree ) println g ( 7 ) // 13
ハスケル
2回:: ( Int -> Int ) -> ( Int -> Int ) 2回f = f . f
plusThree :: Int -> Int plusThree = ( + 3 )
main :: IO () main = print ( g 7 ) -- 13ここでg = 2 倍プラス 3
J
明確に言えば、
二度=.副詞: '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 = twice.apply ( plusThree ) ;
システム.out.println ( g.applyAsInt ( 7 ) ) ; // 13 } }
または、静的メソッドの場合も同様です。
java.util.function.*をインポートします。
クラス Main {プライベート静的IntUnaryOperator twice ( IntUnaryOperator f ) { return f . andThen ( f ); }
プライベート静的int plusThree ( int i ) { return i + 3 ; }
パブリック静的void main ( String [] args ) { var g = twice ( Main :: plusThree );
システム.out.println ( g.applyAsInt ( 7 ) ) ; // 13 } }
JavaScript
矢印関数の場合:
「厳密な使用」;
定数を2回繰り返すと、 f => x => f ( f ( x ) ) になります。
const plusThree = i => i + 3 ;
gを2回繰り返します。
コンソール.log ( g ( 7 ) ); // 13
あるいは古典的な構文では:
「厳密な使用」;
関数を2回実行します( f ) {関数( x )を戻します{ f ( f ( x )を戻します); }; }
関数plusThree ( i ) {戻り値i + 3 ; }
gを2回繰り返します。
コンソール.log ( g ( 7 ) ); // 13
ジュリア
julia> function twice ( f ) function result ( x ) return f ( f ( x )) end return result end twice (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 = twice ( :: plusThree )
println ( g ( 7 )) // 13 }
ルア
関数を 2回実行する( f )
関数を返す ( x ) fを返す( f ( x ))終了終了
関数 plusThree ( i )
戻り値 i + 3
終了
ローカル g = 2 回(プラス 3 回)
印刷( g ( 7 )) -- 13
マテリアライズド
関数 結果= twice ( f )結果= @( x ) f ( f ( x ));終了
プラス3 = @( i ) i + 3 ;
g = 2倍(プラス3 )
ディスプ(g (7 )); % 13
オカムル
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 回( callable $f ) : クロージャ {
return function ( int $x ) use ( $f ) : int {
return $f ( $f ( $x ));
};
}
関数 plusThree ( int $i ) : int {
return $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 -> ( @_ )); }; };
my $plusThree = sub { my ( $i ) = @_ ; $i + 3 ; };
私の$g = $twice -> ( $plusThree );
$g -> ( 7 )を印刷します。 "\n" ; #13
パイソン
>>> def を 2 回( f ):
... def 結果( x ):
... 戻り値 f ( f ( x ))
... 戻り 値
>>> plus_three = ラムダ i : i + 3
>>> g = 2回(プラス3 )
>>> g ( 7 ) 13
Python デコレータ構文は、関数を高階関数に渡した結果で置き換えるためによく使用されます。たとえば、関数は次のgように同等に実装できます。
>>> @twice
... def g ( i ):
... i + 3を返します
>>> (7 )13
R
2回<- \ ( f ) \ ( x ) f ( f ( x ))
plusThree <-関数( i ) i + 3
g <- 2回(プラス3 )
> (7 )[ 1 ] 13
楽
sub を 2 回( Callable:D $f ) {
return sub { $f ( $f ( $^x )) };
}
sub plusThree ( Int:D $i ) {
return $i + 3 ;
}
私の $g = 2 回( &plusThree );
$g ( 7 ); # 13と言う
Raku では、すべてのコード オブジェクトはクロージャであるため、関数内ではレキシカル変数が「閉じられている」ため、外部スコープから内部の「レキシカル」変数を参照できます。Raku は、変数に割り当てたり、匿名で呼び出したりできるラムダ式の「ポイント ブロック」構文もサポートしています。
ルビー
def twice ( 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 )) }
関数 plus_three ( i : i32 ) -> i32 {
i + 3 }
fn main () { let g = twice ( plus_three );
println! ( "{}" , g ( 7 )) // 13 }
スカラ
オブジェクトMain { def twice ( f : Int => Int ): Int => Int = f compose f
def plusThree ( i : Int ): Int = i + 3
def main ( args :配列[文字列] ): Unit = { val g = twice ( plusThree )
印刷( g ( 7 )) //13 } }
スキーム
(定義( f gを構成する) (ラムダ( x ) ( f ( g x ))))
( ( 2 回のf )を定義し( f fを合成))
( (プラス3 i ) ( + 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を適用します]}} plusThreeを設定します{{ i } { [expr $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 integration ( 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 " , integer ( square , 0 , 1 , 100 )); printf ( "%g \n " , integer ( cube , 0 , 1 , 100 )); return 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日閲覧。
