web-dev-qa-db-ja.com

Schemeでは、ラムダをどのように使用して再帰関数を作成しますか?

私は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)):それは正確には何をしますか?そして、階乗を取得したい値をどこに入力しますか?

これはクラス向けではなく、好奇心からです。

25
NcAdams

(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はそれ自体を参照できるため、再帰します。

15

表現 (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)))

あなたの例にはいくつかの層があります。段階的に作業し、それぞれが何をするのかを注意深く調べることは価値があります。

11
Greg Hewgill

次のように定義します。

(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)アプリケーションに縮小されます。

ここで、hhに適用すると、その上の環境でghを指す新しい環境フレームが作成されます。

(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の関数。これは、gh、およびUのバインディングも記憶しています。

したがって、このクロージャが呼び出されると、n5が割り当てられ、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などで呼び出されます。

それが新しいクロージャオブジェクトになるか、同じクロージャオブジェクトが再利用されるかは、コンパイラによって異なります。これはパフォーマンスに影響を与える可能性がありますが、影響はありません。再帰のセマンティクス。

5
Will Ness

(lambda (x) (x x))は関数オブジェクトを受け取り、関数オブジェクト自体という1つの引数を使用してそのオブジェクトを呼び出します。

次に、これは別の関数で呼び出され、その関数オブジェクトをパラメーター名fact-genで受け取ります。実際の引数nをとるラムダを返します。これが((fact-gen fact-gen) (sub1 n))の仕組みです。

従うことができれば、 The Little Schemer のサンプルの章(第9章)を読む必要があります。このタイプの関数を作成し、最終的にこのパターンを Y Combinator (一般的に再帰を提供するために使用できます)に抽出する方法について説明します。

5

私はこの質問が好きです。 「Schemeプログラミング言語」は良い本です。私の考えはその本の第2章からです。

まず、私たちはこれを知っています:

(letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))

letrecを使用すると、関数を再帰的に作成できます。そして、(fact 5)を呼び出すと、factはすでに関数にバインドされていることがわかります。別の関数がある場合は、この方法で(another fact 5)を呼び出すことができ、anotherbinary関数(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))

factbinary関数の場合、関数fと整数n、この場合、関数fはたまたまfact自体です。

上記のすべてを取得した場合は、[〜#〜] y [〜#〜]コンビネータを記述して、letlambdaに置き換えることができます。

4
user1578562

基本的にあなたが持っているのはYコンビネータに似た形です。階乗固有のコードをリファクタリングして再帰関数を実装できるようにした場合、残りのコードはYコンビネータになります。

理解を深めるために、私はこれらの手順を自分で実行しました。
https://Gist.github.com/z5h/238891

私が書いたものが気に入らない場合は、Y Combinator(関数)をグーグルで検索してください。

4
z5h

Defineを使用せずに再帰関数を作成することに興味がありました。もちろん、主な問題は、名前がないと関数を呼び出すことができないことです。

ここでは少し話題から外れていますが、上記のステートメントを見て、「defineを使用せずに」は「名前がない」という意味ではないことをお知らせしたいと思います。何かに名前を付けて、Schemeで定義せずに再帰的に使用することができます。

(letrec
  ((fact
     (lambda (n)
       (if (zero? n)
         1
         (* n (fact (sub1 n)))))))
  (fact 5))

あなたの質問が特に「匿名再帰」と言っていると、より明確になります。

2
user102008

この質問を見つけたのは、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やMil​​kyWayのようなスキームは、その中心に巨大なブラックホールを抱えています。以前は生産的だったプログラマーの多くは、これらのブラックホールの数学的美しさに魅了され、二度と見られなくなります。

1
Syzygies