19

これは、多くの人が精通していると確信しているSICPの本からのものです。これは本の初期の例ですが、私はまだ頭を動かすことができないという非常に重要な概念を感じています。ここにあります:

(define (cons x y)
 (define (dispatch m)
   (cond ((= m 0) x)
         ((= m 1) y)
         (else (error "Argument not 0 or 1 - CONS" m))))
 dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))

だからここで私はそれを理解し、carcdrはの範囲内で定義されており、それらはそれぞれ1と0にconsいくつかの引数をマップしていることがわかります(引数はいくつかです)。しかし、私が呼んでいるとしましょう...まだ指定していないいくつかの引数を取るこの内部手順にすぐに入るとき、引数3と4はどのように評価されますか?そして、おそらくもっと重要なのは、'を返すことのポイントは何ですか?私はその部分をまったく理解していません。どんな助けでもありがたいです、ありがとう!zzcons(cons 3 4)dispatchmdispatch

4

3 に答える 3

23

これは、Scheme でファーストクラスの関数を利用する奇妙な (そしておそらくより素晴らしい) 例の 1 つです。私が最初に見たリトル・シェイマーにも似たようなものがあり、何日も頭をかきむしっていたのを覚えています。分かりやすく説明していただけると助かりますが、わかりにくかったらすみません。

プリミティブconscar、およびcdrは既に Scheme で実装されているため、理解していると思いますが、念のために言っておくとcons、 はペアを構築し、ペアcarの最初のコンポーネントを選択して返しcdr、2 番目のコンポーネントを選択して返します。これらの関数を使用した簡単な例を次に示します。

> (cons 1 2)
(1 . 2)
> (car (cons 1 2))
1
> (cdr (cons 1 2))
2

貼り付けたconscar、およびのバージョンは、まったく同じように動作するはずです。cdrその方法をお見せします。

まず、carcdrは の範囲内で定義されていませんcons。コード スニペットでは、3 つすべて ( conscar、およびcdr) がトップレベルで定義されています。関数dispatchは、内部で定義されている唯一のものですcons

この関数consは 2 つの引数を取り、1 つの引数の関数を返します。これについて重要なことは、これらの 2 つの引数がdispatch返されている内部の function に見えるということです。それについてはすぐに説明します。

リマインダーで言ったようにcons、ペアを構築します。このバージョンのconsは同じことを行うはずですが、代わりに関数を返しています! 1 番目と 2 番目のコンポーネントを取得できる限り、ペアがどのようにメモリに実装または配置されるかはあまり気にしません。

したがって、この新しい関数ベースのペアではcar、ペアを呼び出して引数として渡し、最初のコンポーネントを取得できる必要があります。の定義ではcar、この引数は と呼ばれzます。consこれらの新しい、car、および関数を使用して上記と同じ REPL セッションを実行する場合cdr、 の引数zcar関数ベースのペアにバインドされconsますdispatch。ややこしいですが、よく考えてみればわかります。

の実装に基づいて、car1 つの引数の関数を取り、それを数値 に適用するように見えます0。これは に適用dispatchされ0、 の定義からわかるようにdispatch、それが私たちが望んでいることです。そこの中はandとcond比較mしてor のどちらかを返します。この場合、 を返します。これは の最初の引数、つまりペアの最初のコンポーネントです! したがって、Scheme で通常のプリミティブが行うように、最初のコンポーネントが選択されます。01xyxconscar

に対してこれと同じロジックに従うとcdr、ほとんど同じように動作することがわかりますが、ペアの 2 番目のコンポーネントである , にcons2番目の引数を返します。y

これをよりよく理解するのに役立つことがいくつかあります。1 つは、第 1 章の評価の置換モデルの説明に戻ることです。これらの関数を使用する非常に単純な例について、その置換モデルを注意深く細心の注意を払って従うと、それらが機能することがわかります。

それほど面倒ではないもう 1 つの方法はdispatch、REPL で関数を直接試してみることです。以下では、変数は、によって返される関数pを参照するように定義されています。dispatchcons

> (define p (cons 1 2))
#<function> ;; what the REPL prints here will be implementation specific
> (p 0)
1
> (p 1)
2
于 2012-09-19T14:28:57.310 に答える
7

問題のコードは、コンスセル(car と cdr の 2 つの要素のペア)consを作成するプリミティブ プロシージャを、クロージャとメッセージ ディスパッチのみを使用して再定義する方法を示しています。

このプロシージャは、 :およびdispatchに渡される引数のセレクタとして機能します。メッセージが受信されると、(セルの)の最初の引数が返されます。同様に、を受け取ると、 の 2 番目の引数(セルの ) が返されます。両方の引数は、プロシージャに対して暗黙的に定義されたクロージャ内に格納されます。このクロージャは、 と をキャプチャし、 のこの手続き型実装を呼び出した結果として返されます。consxy0conscar1conscdrdispatchxycons

thisの次の再定義carcdrビルドは、上記の定義で返されたクロージャにcar渡すプロシージャとして実装され、クロージャに渡すプロシージャとして実装され、いずれの場合も最終的には渡された元の値を返します。そしてそれぞれ。0cdr1xy

この例の本当に素晴らしい部分は、コンスセル (Lisp システムにおけるデータの最も基本的な単位) を手続きとして定義できることを示しているため、データ手続きの区別があいまいになっていることです。

于 2012-09-19T14:32:05.977 に答える
6

これは、基本的に「クロージャー/オブジェクト同型」です。

外側の関数 (cons) はクラス コンストラクターです。これは、1 つの引数の関数であるオブジェクトを返します。この引数は、メソッドの名前に相当します。この場合、メソッドはゲッターであるため、値に評価されます。コンストラクターによって返されるオブジェクトに、より多くのプロシージャーを簡単に格納することもできます。

この場合、メソッド名として選択された番号と、オブジェクト自体の外部で定義された甘い手順です。記号を使用することもできました:

(define (cons x y)
  (lambda (method)
    (cond ((eq? method 'car) x)
          ((eq? method 'cdr) y)
          (else (error "unknown method")))))

その場合、あなたが持っているものはOOにもっと似ています:

# (define p (cons 1 2))
# (p 'car)
1
# (p 'cdr)
2
于 2015-08-03T09:32:18.677 に答える