5

したがって、言語が高次の手続きを提供する場合、手続きを返す手続きを使用できます。何かのようなもの:

(define (Proc a b c)
  (lambda (x) ( #| method body here in terms of a b c and x |# )))

新しい手順を作成するには、次のようにします。

(define ProcA (Proc a1 b1 c1)) ; Would create ProcA that has 1 argument

Proc同様のタスクは、次のように、3 つの引数ではなく 4 つの引数を取る定義を定義し、このプロシージャを呼び出して定義することにより、高次プロシージャをサポートしない言語で実行できますProcA

(define (Proc a b c x) ( #| method body -- does not return any procedure |# )
(define (ProcA x) (Proc a1 b1 c1 x))

では、なぜ高階手続きについてそれほど曖昧なのか? 何か不足していますか?

4

8 に答える 8

3

ここで議論を要約しようとはしませんが、Why Functional Programming Mattersの中で、John Hughes は高階関数が有用であると主張しています。コードを再利用します。例は、あまり使用されなくなった非常に古い言語で書かれていますが、それでも理解しやすく、かなり説得力があります。ジョンの論文を読むことは、「なぜ高階手続きについてそれほど曖昧なのか」というあなたの質問に対する詳細な答えを得る良い方法です。

于 2009-05-07T03:24:01.303 に答える
1

変換関数または配列による並べ替えアルゴリズムを考えてみてください。ここで、関数のユーザーが引数として関数を渡せるようにすることで、関数の動作を指定できるように、非常に柔軟にしたいと考えています。

たとえば、次の手続き型プロトタイプを使用して並べ替えアルゴリズムを作成するとします。

sort(Array a, void (*fn)(a::element_type, a::element_type));

その関数のユーザーは、適切な fn を渡すことによって、降順または昇順を指定できます。

于 2009-05-07T03:13:52.250 に答える
1

これは、実現可能性よりもマインドセットに関するものです。関数を第一級市民として扱い、関数を操作して他の関数を作成する関数の観点から考えることができます。

明らかに、これを他の言語で実行またはシミュレートできますが、それが構文メカニズムでない場合は、追加またはハックとして扱われます。

于 2009-05-07T02:47:47.240 に答える
1

わかりましたが、2 番目の例では、コンパイル時にa1b1、およびの事前に定められたリストを使用してそのプロシージャを作成していますc1。最初の例では、実行時に を呼び出したときにそれを作成していて、ProcA好きなだけ異なるものを作成できるので、もっと興味深いことができます。

于 2009-05-07T02:51:56.780 に答える
0

関数が関数を受け入れたり返したりする場合、それは高階関数(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 を使用します。map3 つの関数は FP: 、 、filterのパンとバターですfoldlmap1 つの引数を持つ関数を受け入れ、この関数をリストのすべての要素に適用し、要素が変更された新しいリストを返します。filter1 つの引数で述語 (ブール値を返す関数) を受け入れ、述語をリストのすべての要素に適用し、述語を満たさない要素を削除した新しいリストを返します。

(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 の背後にある考え方は理解できます。

于 2014-12-26T12:44:23.627 に答える