13

Graham の本「On Lisp」を読んでいるのですが、37 ページにある次の例を理解できません。

その戻り値が
引用されたリストを組み込み、

(defun エクスクレーム (式)
  (式 '(oh my)) を追加)

> (叫ぶ '(ライオンとトラとクマ))
(ライオンズとトラとベアーズ OH MY)
> (nconc * '(良さ))
(ライオンズとトラとベアーズ オーマイグッドネス)

関数内のリストを変更できます。

> (exclaim '(fixnums and bignums and floats))
(FIXNUMS と BIGNUMS とフロート OH MY GOODNESS)

このような問題に対する明確な証拠を作成するには、次のように記述します。
(defun エクスクレーム (式)
  (式を追加 (list 'oh'my)))

誰かがここで何が起こっているのか理解していますか? これは、引用が何をするかについての私のメンタルモデルをひどく台無しにしています.

4

3 に答える 3

7

引用のメンタル モデルに欠陥がある可能性があるという観察結果は素晴らしいものです。ただし、そのメンタル モデルが何であるかによって、当てはまる場合と当てはまらない場合があります。

まず、プログラムの実行にはさまざまな段階があることを覚えておいてください。Lisp 環境は、最初にプログラム テキストをデータ構造 (リスト、シンボル、および文字列や数値などのさまざまなリテラル データ) に読み込まなければなりません。次に、それらのデータ構造をマシンコードまたは何らかの中間フォーマットにコンパイルする場合としない場合があります。最後に、結果のコードが評価されます(もちろん、マシン コードの場合、これは単に適切なアドレスにジャンプすることを意味する場合があります)。

ここではコンパイルの問題を脇に置き、評価者の入力がリーダーによって読み取られるデータ構造のリストであると (簡単にするために) 仮定して、読み取りと評価の段階に焦点を当てましょう。

xがオブジェクトのテキスト表現である(QUOTE x)フォームを考えてみましょう。これは、 のようなシンボル リテラル、 のようなリスト リテラル、のような文字列リテラル、またはその他の種類のリテラルです。読み取り段階では、リーダーはフォームをリスト ( form1と呼びます) として読み取ります。最初の要素はシンボルで、2 番目の要素はオブジェクトx'で、テキスト表現はxです。オブジェクトx'は、式を表すリスト内に格納されている、つまり、ある意味では、コード自体の一部として格納されていると具体的に言っていることに注意してください。(QUOTE ABC)(QUOTE (A B C))(QUOTE "abc")QUOTE

今度は評価者の番です。エバリュエーターの入力は、リストであるform1です。そのため、 form1の最初の要素を見てQUOTE、それが symbol であると判断すると、評価の結果として list の 2 番目の要素を返します。これが重要なポイントです。エバリュエーターは、評価されるリストの 2 番目の要素を返します。これは、リーダーが最初の実行段階 (コンパイル前!) で読み取ったものです。 それだけです。 これには魔法はなく、非常に単純です。重要なことに、新しいオブジェクトが作成されたり、既存のオブジェクトがコピーされたりすることはありません。

したがって、「引用符付きリスト」を変更すると、コード自体が変更されます。自己変更コードは非常に紛らわしいものであり、この場合の動作は実際には定義されていません (ANSI Common Lisp では実装がコードを読み取り専用メモリに置くことを許可しているため)。

もちろん、上記は単なるメンタルモデルです。実装は、さまざまな方法でモデルを自由に実装できます。実際、私の説明のように、コンパイルをまったく行わない Common Lisp の実装を私は知りません。それでも、これが基本的な考え方です。

于 2010-10-23T14:37:11.253 に答える
6

Common Lisp で。

覚えて:

'(1 2 3 4)

上記は文字通りのリストです。定数データ

(list 1 2 3 4)

LIST は、呼び出し時に引数をリスト要素として持つ新しいリストを返す関数です。

リテラル リストの変更は避けてください。効果は標準化されていません。すべての定数データを書き込み専用メモリ領域にコンパイルする Lisp を想像してみてください。定数リストを取り、それらを関数間で共有する Lisp を想像してみてください。

(defun a () '(1 2 3)

(defun b () '(1 2 3))

Lisp コンパイラは、両方の関数で共有される 1 つのリストを作成する場合があります。

関数 a によって返されるリストを変更すると、

  • 変更されないかもしれません
  • 変更されるかもしれません
  • エラーかもしれません
  • 関数 b によって返されるリストも変更される可能性があります

実装には、好きなことをする自由があります。これにより、最適化の余地が残ります。

于 2010-10-23T10:49:17.847 に答える
6

nconc末尾を変更して最初の引数を変更する破壊的な操作です。この場合、定数リスト'(oh my)が新しいテールを取得することを意味します。

うまくいけば、これをより明確にするために。それはこのようなものです:

; Hidden variable inside exclaim
oh_my = oh → my → nil

(exclaim '(lions and tigers and bears)) =
    lions → and → tigers → and → bears → oh_my

(nconc * '(goodness)) destructively appends goodness to the last result:
    lions → and → tigers → and → bears → oh → my → goodness → nil
so now, oh_my = oh → my → goodness → nil

これ'(oh my)(list 'oh 'my)修正すると、全員が共有する定数がなくなるため、これを修正します。を呼び出すたびにexclaim、新しいリストが生成されます (このlist関数の本来の目的は、まったく新しいリストを作成することです)。

于 2010-10-23T08:34:23.017 に答える