31

私の Lisp に関する知識はごくわずかであることを率直に認めます。しかし、私はその言語に非常に興味があり、近い将来、それを真剣に学び始める予定です. これらの問題に対する私の理解には間違いなく欠陥があるため、明らかに間違っていることを言う場合は、反対票を投じるのではなく、コメントして修正してください。

真のホモイコニックで自己修正可能な言語

Homoiconicity (コードはデータと同じ表現を持つ) と無制限の自己変更 (新しいコードを発行したり、関数ポインターを変更したりするだけでなく、実行中のコードのあらゆる側面を変更できることを意味する無制限の意味) の両方をサポートするプログラミング言語の例を探しています/代議員。)

私がこれまでに見つけた、この基準に適合する例は 3 つしかありません。

  1. マシンコード。すべてが数字であるホモイコニック。ポインターが含まれているという点で無制限に変更可能であり、そのアドレスがコードまたはデータを保持しているかどうかに関係なく、任意のメモリ アドレスを操作するために使用できます。
  2. マルボルジ。マシンコードと同じ理由。すべての命令は、実行後に自身を変更します
  3. DNA。プログラミング言語ではありませんが、それでも興味深いものです。マシンコードと同じ意味での自己変更ではありません。実際の命令とデータが変更された場所。ただし、それは自己複製であり、以前の状態に応じて変異/進化する可能性があります (放射線などの副作用が時々発生します)。とにかく、これは自己修正の間接的な方法です。簡単に言えば、DNA は自己改変することができますが、それは関連する突然変異と共にその全体を複製することによって行われます。DNA の物理的な文字列は「不変」です。

Lisp がこのリストにない理由

Lisp はそのリストに含まれていません。なぜなら、Lisp はほぼホモイコニックであり、制限付きの自己変更しかサポートしていないように思われるからです。次のようなことができます

(+ 1 2 3)

と同じことを行います

(eval '(+ 1 2 3))

最初のバージョンでは(+ 1 2 3)は生のコードですが、2 番目のバージョンではデータです。このステートメントが真実であると仮定することにより、Lisp はホミニックでさえないと主張することができます。コードは、リスト/ツリー/S 式の両方であるという意味で、データと同じ表現をしています。しかし、これらのリスト/ツリー/S 式のどれがコードで、どれがデータであるかを明示的にマークしなければならないという事実は、結局のところ Lisp はホミニックではないと言っているように思えます。表現は非常に似ていますが、コードまたはデータを扱っているかどうかを実際に言わなければならない細部が異なります。これは決して悪いことではありません (実際、それ以外のことはすべて狂気です) が、Lisp とマシン コードの違いを浮き彫りにしています。マシン コードでは、どの数値が命令で、どの数値がポインターで、どの数値がデータであるかを明示的にマークする必要はありません。

これは、無制限の自己変更に対するさらに強力なケースです。もちろん、コードを表すリストを取得して操作することもできます。たとえば、

'(+ 1 2 3)

'(+ 1 4 3)

そして、あなたはそれを実行しますeval。しかし、これを行うと、コードをコンパイルして実行するだけです。既存のコードを変更するのではなく、新しいコードを発行して実行するだけです。C# は式ツリーを使用してまったく同じことを行うことができますが、形式があまり便利ではありません (これ、独自の AST である Lisp とは対照的に、C# コードがその AST とは異なる表現を持つために発生します)。実際にソース ファイル全体を取得し、実行中にそのソース ファイル全体を変更して、ソース ファイルに加えられた変更がプログラムの動作にリアルタイムで影響を与えることはできますか?

これを行う何らかの方法がない限り、Lisp はホモニックでも自己修正でもありません。(定義に関する議論を先延ばしにするために、Lisp はホモイコニックでもなければ、マシン コードと同じ程度の自己修正でもありません。 )

Lisp Homoiconic/Unrestrictly 自己変更可能にする方法

Lisp をマシンコードのようにホモイコニック/自己修正可能にする 3 つの方法が考えられます。

  1. 非フォンノイマン アーキテクチャ。誰かが、プログラムの最下位レベルの表現が直接実行できる AST である驚くべき仮想マシンを発明できたら (それ以上のコンパイルは必要ありません)。そのようなマシンでは、AST は実行可能な命令とデータの両方を表します。残念ながら、AST は依然としてコードまたはデータのいずれかでなければならないため、問題は解決されていません。eval 関数が存在しても、これは変わりません。機械語では、コードとデータの間を好きなだけ行ったり来たりできます。一方、eval と Lisp では、リストをデータからコードに「評価」して実行すると、そのリストを再びデータとして取得する方法はありません。実際、そのリストは永久になくなり、その値に置き換えられました。たまたまポインターである重要なものが欠けている可能性があります。
  2. ラベルを一覧表示します。すべてのリストにも一意のラベルが必要である場合、特定のラベルを持つリストに対して関数を実行することにより、間接的な自己変更を行うことができます。継続と組み合わせることで、最終的にマシンコードと同じ意味で自己修正コードが可能になります。ラベルは、マシン コードのメモリ アドレスと同等です。例として、AST の最上位ノードに「main」というラベルが付いている Lisp プログラムを考えてみましょう。次に、main 内で、ラベル、整数、アトムを受け取る関数を実行し、アトムを、関数に指定されたインデックスに一致するラベルを使用してリストにコピーできます。次に、メインで現在の継続を呼び出します。ほら、自己修正コード。
  3. Lisp マクロ。私は Lisp マクロを理解するのに時間をかけたことがありません。実際、それらは私が考えていることを正確に実行する可能性があります。

ポイント 1. と 2. を組み合わせると、完全に自己変更的な Lisp が生成されます。説明されている魔法の Lisp マシンを作成できるという条件で。2. 単独で自己変更 Lisp を作成できますが、フォン ノイマン アーキテクチャでの実装は非常に非効率的です。

質問

  1. マシンコード、DNA、malbolge 以外に、完全自己改変が可能でホモイコニックな言語はありますか?
  2. (上記のテキストで tl;dr を実行した場合は、わざわざ回答しないでください) . Lispは本当にホモイコニック+自己修正ですか? もしあなたがそうおっしゃるなら、私の議論のどこで私が道に迷ったのかを正確に引用していただけますか?

付録

制限のない自己修正を伴うがホモニシティを持たない言語

  1. 組み立て。コードは数字ではなく単語を使用するため、同義性は失われますが、メモリに対する完全な制御を保持し、無制限の自己変更を可能にするポインタがまだあります。
  2. 生のポインターを使用するすべての言語。例: C/C++/Objective C。アセンブリと同じ引数
  3. 仮想ポインターを含む JIT 言語。たとえば、安全でないコンテキストで実行されている C#/.net などです。Assembly と同じ引数。

何らかの形で関連/興味深い可能性のあるその他の概念と言語: Lisp、Ruby、Snobol、Forth とコンパイル時のメタプログラミング、Smalltalk とそのリフレクション、すべてが関数であるというプロパティを持つ型指定されていないラムダ計算 (これは、ラムダ計算を直接実行するマシンを発明できれば、ラムダ計算はホモイコニックであり、フォン ノイマンのマシン コードは、そのマシンで実行するとそうではありません.[そして、ゲーデルの定理は実行可能になるでしょう.ハハ、恐ろしい考え :P])

4

4 に答える 4

22

最初のバージョン(+ 1 2 3)では生のコードですが、2番目のバージョンではデータです。この声明の真実を仮定することによって、Lispはホモコニックでさえないと主張することができます。コードは、両方がリスト/ツリー/ S式であるという意味で、データと同じ表現を持っています。しかし、これらのリスト/ツリー/ S式のどれがコードであり、どれが私にとってのデータであるかを明示的にマークする必要があるという事実は、Lispが結局のところホモコニックではないことを示しているようです。

本当じゃない。最初のバージョンでは、(+ 1 2 3)データであるリストがインタープリターに送られ、実行されます。つまり、コードとして解釈されますS式を特定のコンテキストのコードまたはデータとしてマークする必要があるという事実は、Lispを非同像性にするものではありません。

同像性のポイントは、すべてのデータがプログラムであるということではなく、すべてのプログラムがデータであるということです。したがって、2つの間にはまだ違いがあります。Lispでは、(1 2 3)は有効なリストですが、整数は関数ではないため、有効なプログラムではありません。

[他の優れた同像性プログラミング言語であるPrologを見ると、同じ現象が見られます。データ構造を構築することはできfoo(X, 1, bar)ますが、の定義fooがないと実行できません。また、変数を述語やファクトの名前にすることはできないためX.、有効なプログラムになることはありません。]

Lispはかなりの程度自己修正的です。たとえば、関数の定義を変更する方法は次のとおりです。

[1]> (defun foo (x) (+ x 1))
FOO
[2]> (defun bar (x) (+ x 2))
BAR
[3]> (setf (symbol-function 'foo) #'bar)
#<FUNCTION BAR (X) (DECLARE (SYSTEM::IN-DEFUN BAR)) (BLOCK BAR (+ X 2))>
[4]> (foo 3)
5

説明:で、関数をadd-1関数として[1]定義しました。fooで、add-2関数として[2]定義しました。barで、add-2関数に[3]リセットします。foo[4]、が正常に変更されたことがわかりますfoo

于 2011-12-13T14:19:26.970 に答える
13

Lisp はプログラミング言語のファミリーです。このファミリのメンバーは、機能と実装手法が大きく異なります。これには 2 つの異なる解釈があります。

  • Lisp は、いくつかの重複する機能セットを共有する言語ファミリーです。これには、最初の Lisp から Maclisp、Scheme、Logo、Common Lisp、Clojure、そして何百もの異なる言語とその実装まで、すべてが含まれます。

  • Lisp には、名前にほとんど 'Lisp' が含まれる主要な言語ブランチもあります: Lisp、MacLisp、Common Lisp、Emacs Lisp、...

言語の改善 (より「機能的」にするか、よりオブジェクト指向にするか、またはその両方) と実装技術の改善のために、多くの研究が時間の経過とともに投資されてきました。

たとえば、Common Lisp はコンパイル、さまざまな最適化などをサポートしており、開発者が柔軟性と機能のバランスが必要な大規模なプロジェクトで使用できるようになっています。コンパイルされた関数はマシン コードであり、リスト、シンボル、文字列、数値で構成されるデータ構造ではなくなります。

Common Lisp では、実装で静的コードを作成できます。同時に、制御された実行時の変更 (たとえば、実行時コンパイラの使用、コードの読み込み、コードの評価、コードの置換など) のための場所が残されます。

OTOH インタープリターを使用した Lisp 実装があり、さらにインタープリターが Lisp データ構造を使用してソースを表す可能性がある場合、たとえばリスト構造を変更することにより、実行中のプログラムを変更できます。それを可能にする Lisp の方言の実装があります。典型的なことの 1 つは、そのようなシステムによって実行時に計算できるマクロの使用です。他のいくつかの Lisp には、いわゆる Fexpr があり、これは同様のメカニズムです (ただし、通常は効果的にコンパイルできません)。

ソース情報の多くが配信ツールによって削除されている Common Lisp ベースのアプリケーション (Lisp で書かれた CAD システムなど) では、これは不可能です。実行時の柔軟性の多くが取り除かれたマシン コードの実行可能ファイルが 1 つある場合があります。

ホモイコニシティもあまり明確に定義された概念ではありません。Lisp の場合、ソース コードはデータのシリアル化形式である s 式の外部表現を使用するため、ソース コードをデータに変換できると言っているほうが好きです。しかし、すべての s 式が有効な Lisp プログラムであるとは限りません。また、Lisp データとしてのソース コードの内部表現は、決して「象徴的」ではありません。Lisp は外部 s 式としてソース コードを持ち、ソース コードを読み取った後、Lisp データとして内部表現を持ちます。READ はそれを読み取り、PRINT はそれを出力し、EVAL はそれを評価します。

Lispには、プログラムを実行しているLispとプログラムへのアクセスを提供するための他のアプローチもあります。

  • CLOS (Common Lisp Object System) のメタオブジェクト プロトコルがその例です。

  • 3Lisp は無限のインタプリタの塔を提供します。プログラムを実行する Lisp インタープリターがあります。この Lisp インタープリターは、別の Lisp インタープリターによって実行され、別の Lisp インタープリターで実行されています。

于 2011-12-13T15:03:45.977 に答える
3

私はここでファンボーイとして出くわします.前にも言ったことがありますが 、Lispマクロについて学びたい場合は、 Paul GrahamのOn Lispを読んでください. 他の方法では実行不可能な変更を可能にするという点で、それらは大きな問題です。さらに、ここで、言語ファミリーである Lisp、特定の Lisp、および特定の Lisp 実装を区別することが重要だと思います。

あなたの議論で私が取り上げる主な問題は、「なぜLispがこのリストにないのか」の後の最初の段落にあり、LispのREPLに関係していると思います。exp (+ 1 2 3) をインタープリターに入力すると、実際にはリスト (+ 1 2 3) で関数 EVAL を呼び出しています。あなたが説明する「生のコード」は、実際には「データ」であり、他のLispコードに供給されます。これは、特定のコンテキスト内の単なるデータです。

于 2012-02-24T18:43:13.787 に答える
3

マクロは、それがあなたが求めているものであれば、引用を避けることができます:

> (defmacro foo (x) (cdr x))
> (foo (+ - 5 2))
3

(+ - 5 2)コードですか、それともデータですか。書き込み時にはデータのように見えます。マクロ展開時間の後、コードのように見えます。また、 の定義fooが別の場所にある場合、(誤って) 簡単に関数と見なすことができます。その場合(+ - 5 2)、コードのように振る舞うデータのように振る舞うコードと見なされます。

于 2011-12-15T06:48:39.487 に答える