私はSchemeクラスにいて、defineを使用せずに再帰関数を作成することに興味がありました。もちろん、主な問題は、名前がないと関数を呼び出すことができないことです。
私はこの例を見つけました:それはラムダだけを使用する階乗ジェネレータです。
((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
しかし、最初の呼び出しを理解することすらできません(lambda(x)(x x)):それは正確には何をしますか?そして、階乗を取得したい値をどこに入力しますか?
これはクラス向けではなく、好奇心からです。
(lambda (x) (x x))
は、それ自体で引数xを呼び出す関数です。
投稿したコードのブロック全体が、1つの引数の関数になります。あなたはそれをこのように呼ぶことができます:
(((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
5)
それは5でそれを呼び出し、120を返します。
これを高レベルで考える最も簡単な方法は、最初の関数(lambda (x) (x x))
は、x自身への参照を与えているので、xはそれ自体を参照できるため、再帰します。
表現 (lambda (x) (x x))
は、1つの引数(関数である必要があります)で評価されたときに、それ自体を引数としてその関数を適用する関数を作成します。
指定された式は、1つの数値引数を取り、その引数の階乗を返す関数に評価されます。それを試すには:
(let ((factorial ((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))))
(display (factorial 5)))
あなたの例にはいくつかの層があります。段階的に作業し、それぞれが何をするのかを注意深く調べることは価値があります。
次のように定義します。
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
これがletrec
が実際に機能する方法です。 Christian Queinnecによる [〜#〜] lisp [〜#〜] を参照してください。
あなたが質問している例では、自己アプリケーションコンビネータは "[〜#〜] u [〜#〜]と呼ばれていますコンビネータ " 、
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
((U h) 5))
ここでの微妙な点は、let
のスコープ規則のため、ラムダ式は定義されている名前を参照できないことです。
((U h) 5)
が呼び出されると、let
フォームによって作成された環境フレーム内で((h h) 5)
アプリケーションに縮小されます。
ここで、h
をh
に適用すると、その上の環境でg
がh
を指す新しい環境フレームが作成されます。
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
( (let ((g h))
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))
5))
ここでの(lambda (n) ...)
式は、その環境フレーム内から返されます。このフレームでは、g
がその上のh
を指します closureオブジェクト 。つまり1つの引数n
の関数。これは、g
、h
、およびU
のバインディングも記憶しています。
したがって、このクロージャが呼び出されると、n
に5
が割り当てられ、if
フォームが入力されます。
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
(let ((g h))
(let ((n 5))
(if (zero? n)
1
(* n ((g g) (sub1 n)))))))
g
はクロージャオブジェクトが作成された環境の上の環境フレームで定義されたh
を指しているため、(g g)
アプリケーションは(h h)
アプリケーションに縮小されます。つまり、一番上のlet
フォームにあります。しかし、すでに(h h)
呼び出しの削減が見られました。これにより、クロージャーが作成されました。つまり、1つの引数n
の関数が、次の反復でfactorial
関数として機能します。 4
、次に3
などで呼び出されます。
それが新しいクロージャオブジェクトになるか、同じクロージャオブジェクトが再利用されるかは、コンパイラによって異なります。これはパフォーマンスに影響を与える可能性がありますが、影響はありません。再帰のセマンティクス。
(lambda (x) (x x))
は関数オブジェクトを受け取り、関数オブジェクト自体という1つの引数を使用してそのオブジェクトを呼び出します。
次に、これは別の関数で呼び出され、その関数オブジェクトをパラメーター名fact-gen
で受け取ります。実際の引数n
をとるラムダを返します。これが((fact-gen fact-gen) (sub1 n))
の仕組みです。
従うことができれば、 The Little Schemer のサンプルの章(第9章)を読む必要があります。このタイプの関数を作成し、最終的にこのパターンを Y Combinator (一般的に再帰を提供するために使用できます)に抽出する方法について説明します。
私はこの質問が好きです。 「Schemeプログラミング言語」は良い本です。私の考えはその本の第2章からです。
まず、私たちはこれを知っています:
(letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))
letrec
を使用すると、関数を再帰的に作成できます。そして、(fact 5)
を呼び出すと、fact
はすでに関数にバインドされていることがわかります。別の関数がある場合は、この方法で(another fact 5)
を呼び出すことができ、another
はbinary関数(my英語が苦手です、ごめんなさい)。 another
は次のように定義できます。
(let ((another (lambda (f x) .... (f x) ...))) (another fact 5))
fact
をこのように定義してみませんか?
(let ((fact (lambda (f n) (if (= n 1) 1 (* n (f f (- n 1))))))) (fact fact 5))
fact
がbinary関数の場合、関数f
と整数n
、この場合、関数f
はたまたまfact
自体です。
上記のすべてを取得した場合は、[〜#〜] y [〜#〜]コンビネータを記述して、let
をlambda
に置き換えることができます。
基本的にあなたが持っているのはYコンビネータに似た形です。階乗固有のコードをリファクタリングして再帰関数を実装できるようにした場合、残りのコードはYコンビネータになります。
理解を深めるために、私はこれらの手順を自分で実行しました。
https://Gist.github.com/z5h/238891
私が書いたものが気に入らない場合は、Y Combinator(関数)をグーグルで検索してください。
Defineを使用せずに再帰関数を作成することに興味がありました。もちろん、主な問題は、名前がないと関数を呼び出すことができないことです。
ここでは少し話題から外れていますが、上記のステートメントを見て、「defineを使用せずに」は「名前がない」という意味ではないことをお知らせしたいと思います。何かに名前を付けて、Schemeで定義せずに再帰的に使用することができます。
(letrec
((fact
(lambda (n)
(if (zero? n)
1
(* n (fact (sub1 n)))))))
(fact 5))
あなたの質問が特に「匿名再帰」と言っていると、より明確になります。
この質問を見つけたのは、defineを使用できないマクロ内に再帰的なヘルパー関数が必要だったためです。
(lambda (x) (x x))
とY-combinatorを理解したいのですが、letという名前は、観光客を怖がらせることなく仕事を成し遂げます。
((lambda (n)
(let sub ((i n) (z 1))
(if (zero? i)
z
(sub (- i 1) (* z i)) )))
5 )
このようなコードで十分な場合は、(lambda (x) (x x))
とY-combinatorの理解を延期することもできます。 HaskellやMilkyWayのようなスキームは、その中心に巨大なブラックホールを抱えています。以前は生産的だったプログラマーの多くは、これらのブラックホールの数学的美しさに魅了され、二度と見られなくなります。