関数が関数を受け入れたり返したりする場合、それは高階関数(HOF) と呼ばれます。C、C++、または Java から来た経験の浅いプログラマーにとって、高階関数は魔法のように聞こえますが、それらは非常に単純です。2 + 3 の結果を返す単純な関数を想像してください。
(define (foo) (+ 2 3)) ;; (foo) => 5
これは退屈な関数で、常に 2 を 3 に加算します。2 を 3 に加算するだけでなく、任意のユーザー提供の数値に加算するように一般化するとどうなるでしょうか。
(define (foo n) (+ 2 n)) ;; (foo 10) => 12
言語が高階関数をサポートしていない場合、関数と値 (数値、ブール値、リストなど) は 2 つの別個のものであると考えざるを得ません。しかし、関数型プログラミング(FP) では、それらの区別が曖昧になります。関数と値の唯一の違いは、 or に対してできることは何でも関数に対して実行できることを除いて、関数を呼び出すことができることだと想像してください2
:引数として与えるか、関数から返すことができます#t
。'(a b c)
または変数に格納するか、リストに入れます。たとえば、小さな関数をさらに一般化して、 に 2 を足すだけでなく、 で 2 をn
乗算しn
たり、2 つの数値を受け入れる他の関数を適用したりできるようにします。
(define (foo f n) (f 2 n))
;; (foo + 10) => 12
;; (foo * 10) => 20
;; (foo expt 10) => 1024
数値や文字列と同じように関数を処理できることがわかった場合、無名関数(FP 用語で「ラムダ」と呼ばれる) は完全に理にかなっています。無名関数は実際には、通常の名前付き関数よりも基本的で「通常の」ものです。名前付き関数は、数を変数に入れて複数回使用するのと同じように、変数に入れられる単なる匿名関数です。
(+ 2 2) ;; is no different from:
(let ((a 2)) (+ a a))
(lambda (x y) (* x y)) ;; is no different from:
(define (foo x y) (* x y)) ;; which is an abbreviation for:
(define foo (lambda (x y) (* x y))).
したがって、HOF を使用すると、関数を一般化して非常に柔軟にすることができます。関数を見て、その背後にあるロジックを確認すると、何かがデータに作用する場合、おそらく他のものも作用する可能性があることがわかります。2 つの数値を足し合わせると、それらを乗算したり、減算したり、累乗したりできます。毎回すべてのケースに対して新しい関数を記述する代わりに、関数でなければならない追加のパラメーターを受け入れることができます。
FP では、リストを操作するときなど、常に HOF を使用します。map
3 つの関数は FP: 、 、filter
のパンとバターですfoldl
。map
1 つの引数を持つ関数を受け入れ、この関数をリストのすべての要素に適用し、要素が変更された新しいリストを返します。filter
1 つの引数で述語 (ブール値を返す関数) を受け入れ、述語をリストのすべての要素に適用し、述語を満たさない要素を削除した新しいリストを返します。
(map (lambda (n) (+ n 1)) '(1 2 3 4 5) ;; '(2 3 4 5 6)
(define (foo n) (+ n 1))
(map foo '(1 2 3 4 5)) ;; '(2 3 4 5 6)
(filter (lambda (n) (> n 3)) '(1 2 3 4 5)) ;; '(4 5)
(define (bar n) (> n 3))
(filter bar '(1 2 3 4 5)) ;; '(4 5)
アリティが 1 の関数のリストがあるとします。ここでも、関数を使ってやりたいことが何でもでき、それをデータ構造にも格納できます。それらすべてを同じ数値に適用して、リストを取得したいとします。結果の。
(let ((xs (list (lambda (x) (+ x 1))
(lambda (x) (* x 2))
(lambda (x) (- x)))))
(map (lambda (f) (f 10)) xs)) ;; => (11 20 -10)
結論:プログラミング言語が関数型プログラミングの概念を適切にサポートしている場合、高階関数により柔軟性と汎用性が可能になり、コードがより強力になり (さまざまなユースケースで同じ関数を使用できます)、簡潔になります (10 バージョンを記述する必要はありません)。 1つの機能の)。一部の高階関数は関数型プログラミングで頻繁に使用されるため、低レベルで冗長な for ループを取り除き、代わりにすべてを実行するワンライナーを記述します。
注: foldl
「左折り」または「左縮小」と同じである は、さらに強力です。本当に興味があり、時間があれば、 reduce を使用して私の回答の前半を読んでください。これは Scheme/Racket 用に書かれたものではなく、Common Lisp/Emacs Lisp 用に書かれたものですが、fold/reduce の背後にある考え方は理解できます。