9

Lispの方言に関係なく、Lisp関数を含むすべてのソースコードファイル自体はリストではないようです(これに初めて「驚いた」のは、Emacsの.elファイルで作業していたときでした)。

いくつか質問がありますが、それらはすべて同じ「問題」に関連しており、おそらくいくつかのことを誤解しているだけです.

さまざまな Lisp 方言のソース コード ファイルが、次のような「まとまりのない」関数の集まりのように見える理由はありますか?

(function1 ...)
(function2 ...)
(function3 ...)

関数の「Lisp リスト」の代わりに、おそらく次のようになります。

(
  '(function1 ...)
  '(function2 ...)
  '(function3 ...)
)

この「コードはデータであり、データはコードである」ということ全体に少し驚いています。ソース コード ファイル自体がきちんとしたリストではないことがわかります...それとも!?

ソースコードファイルは「操作」するものですか?

たとえば、 .clj (Clojure) ソース ファイルの 1 つを CSS+HTML Web ページに変換したい場合、ソース コード ファイル自体が明らかにリストではないことが「問題」ではないでしょうか?

私は Lisp から始めているので、私の質問が理にかなっているかどうかわかりません。説明があれば歓迎します。

4

7 に答える 7

13

Common Lisp では、ソース ファイルにはlisp formsコメントが含まれます。Lisp フォームは、データまたは Lisp コードのいずれかです。ソース ファイルに対する一般的な操作は、関数LOADおよびCOMPILE-FILE.

LOADファイルからフォームを読み取り、1 つずつ実行します。

COMPILE-FILEははるかに複雑です。通常、フォームを読み取り、それらを他の表現 (マシンコード、バイトコード、C コードなど) にコンパイルします。コードは実行されません。

ファイルに複数のフォームが並んでいるのではなく、フォームのリストが 1 つ含まれているとしたら、何が役に立ちますか?

  • 1レベルの括弧が追加されます
  • それを使って何かをする前に、リスト全体を読む必要があります(または、別のリーダーメカニズムが必要です)
  • プログラムによってファイルの最後にフォームを追加するのは面倒です
  • ファイルの残りの部分の読者の解釈を変更する何かをファイルに追加することはできません
  • LOAD のファイルを無限に長くすることはできません

例として、コンパイラはファイル ストリームから Lisp フォームを読み取り、それらを 1 つずつコンパイルします。

あなたができるすべてのフォームが必要な場合

CL-USER 170 > (defun read-forms (file)
               (with-open-file (stream file)
                 (loop for form = (read stream nil nil)
                       while form
                       collect form)))
READ-FORMS

CL-USER 171 > (read-forms (capi:prompt-for-file "source file"))
((DEFPARAMETER *UNITS-TO-SHOW* 4.1)
 (DEFPARAMETER *TEXT-WIDTH-IN-PICAS* 28.0)
 (DEFPARAMETER *DEVICE-PIXELS-PER-INCH* 300)
 (DEFPARAMETER *PIXELS-PER-UNIT* (* (/ (/ *TEXT-WIDTH-IN-PICAS* 6)
                                       (* *UNITS-TO-SHOW* 2))
                                    *DEVICE-PIXELS-PER-INCH*))
...

すべてを括弧で囲みたい場合は、次を使用しますPROGN

 (progn
   'form-1
   (defun function-defintion-form () )
   42)

PROGNサブフォームの「トップレベル性」も保持します。

補足: Lisp では、これに代わるものが何十年にもわたって研究されてきました。最も顕著な例は、Xerox の現在は機能していない Interlisp-D です。Interlisp-D は、Xerox PARC によって Smalltalk と並行して開発されました。Interlisp-D はもともと構造エディタを使用して Lisp データを編集し、ソース コードは Lisp データとして編集されていました。開発環境はこの考えに基づいていました。しかし、長期的には「テキストとしてのソース」が勝利しました。それでも、現在の多くの Lisp 環境でその一部をエミュレートできます。たとえば、多くの Lisp システムでは、現在の実行メモリの「イメージ」を書き込むことができます。このイメージには、すべてのデータとすべてのコード (コンパイルされたコードも) が含まれます。したがって、このデータ/コードで作業し、時々画像を保存できます。

于 2012-05-09T19:34:30.087 に答える
10

ソース コード ファイルは、リストを保存するのに便利な場所です。Lisp コード (一般に) は、各入力自体がリストであるread-eval-print-loop (REPL) で実行されることを意図しています。したがって、ソース コード ファイルを実行すると、その中の各リストが 1 つずつ REPL に読み込まれていると考えることができます。アイデアは、「コードはデータ」パラダイムを補完する完全にインタラクティブな環境を持っているということです。

確かに、ファイルを 1 つのメガリストとして扱うことができますが、その場合、ファイルが明確に定義された構造を持っていることを暗示しており、常にそうであるとは限りません。本当に 1 つの巨大なリストを含むファイルを作成したい場合は、それを妨げるものは何もありません。Lisp リーダーを使用して、(データの?) 1 つの大きなリストとしてそれを読み取り、必要に応じて (おそらくある種の eval を使用して?) 処理することができます。たとえば、Leiningen の project.clj ファイルを見てみましょう。通常、これらは 1 つの大きな defproject リストにすぎません。

于 2012-05-09T16:54:14.963 に答える
6

徹底するために、すべてのソース ファイルはテキストであり、Lisp データ構造ではありません。コードを評価またはコンパイルするには、Lisp は最初READにファイルを作成する必要があります。これは、テキストを Lisp データ構造に変換することを意味します。READ最初の 2 文字がとを表す頭字語 REPL を思い出してくださいEVALREADコードの文字列表現を受け取り、コードを表すデータ構造を返します。EVAL返されたデータ構造を受け取り、データ構造をコードとして解釈 (またはコンパイルして実行) します。したがって、中間ステップが含まれていることを覚えておくことが重要です。

良い質問は、複数の s-expression が に渡されREAD、それらがリストにない場合はどうなるかということです。

コードを見ると、通常、複数のバージョンの が見つかりますREAD。clojureread-stringは最初の s 式のみを読み取って返し、残りは無視します。しかし、clojure のload-fileで使用されるリーダーは、文字列全体を取得し、"効果的に" (実装は異なる場合があります) 暗黙的do(またはprogn一般的な Lisp) をすべてのフォームにラップし、それを に渡しevalます。この動作は、フォームが順番に読み取られ、評価され、印刷される REPL での動作とは対照的です。

ただし、どちらの場合も、この「舞台裏」の動作は、簡潔にするためのトレードオフです。s 式のテキストのファイルをロードするときに、それらすべてを評価して、せいぜい最後の s 式の値を返すと想定できます。

于 2012-05-09T18:47:08.717 に答える
6

Lisp では、ソース コードの定義方法に応じて、2 つのレベルのソース コードが存在するか、ソース コードがまったく存在しません。

2 つのレベルが存在するのは、Lisp インタープリター/コンパイラーによって (通常は) 2 つの別個の概念ステップが実行されるためです。

最初のステップ:「読む」

このステップでは、ソース コードは一連の文字であり、たとえばファイルから取得されます。ここでは、括弧、引用符で囲まれた文字列、数字、記号、引用符、さらには準引用構文の一部が処理され、Lisp データ構造に変換されます。このレベルの構文規則は、括弧、数字、パイプ、引用符、セミコロン、シャープ記号、カンマ、アットマークなどに関するものです。

第二段階:「コンパイル」/「解釈」

このステップでは、入力は Lisp データ構造であり、出力はマシン コード、バイト コード、または場合によってはインタプリタによって直接実行されるソースのいずれかです。このレベルでは、構文は特殊な形式の意味に関するものです。たとえば、、(if ...)など(labels ...)です(symbol-macrolet ...)。構造は Lisp コードで統一されています (リストとアトムだけです) が、セマンティックはそうではありません (ifフォームは関数呼び出しのように見えますが、そうではありません)。

したがって、このビューでは、あなたの答えに対する質問はイエスとノーです。手順 1 ではいいえ、手順 2 でははい。ファイルだけを検討する場合、答えはいいえです... ファイルにはリストではなく文字が含まれます。これらの文字は、リーダーによってリストに変換できます。

Lispには構文がありません

それでは、実際には2 つの異なる構文レベルがあるのに、Lisp には構文がないと誰かが言うのはなぜでしょうか? その理由は、これらのレベルの両方がプログラマーの制御下にあるためです。

レベル 1 はリーダー マクロを定義してカスタマイズでき、レベル 2 はマクロを定義してカスタマイズできます。したがって、Lisp には決まった構文がないため、ソース ファイルは "lispy" な外観で始まり、Python コードとまったく同じ外観で終わる可能性があります。

ソース ファイルには、(特定の時点から) 何でも含めることができます。これは、最初のフォームが、後続の文字の意味を変更するいくつかの新しい読み取り規則を定義する可能性があるためです。

通常、Lisp プログラマーは読解レベルで狂ったことをしないので、ほとんどの Lisp ソース コード ファイルは Lisp フォームのシーケンスのように見え、"lispy" のままです。

しかし、これは厳しい制約ではありません...たとえば、Lisp 構文が Python にモーフィングすることについて冗談を言っているわけではありません:誰かがまさにそれをやった.

于 2012-05-10T06:50:29.257 に答える
3

(Lispの) 初めに、インタラクティブな REPLがありました: read、次にevaluate、次に結果を出力して再度尋ねる、ループ。プロンプトでテキストを入力します。ランタイム システムはそれを「読み取り」、テキストを「コード」の内部表現に変換してから、それを評価 (「実行」など)ます。

> (setq s "(setq a 2)")
"(setq a 2)"
> (type-of s)          ; s is just a bunch of text characters
(SIMPLE-BASE-STRING 10)
> (setq r (read (make-string-input-stream s)))
(SETQ A 2)
> (type-of r)          ; the result of reading is Lisp data - a CONS cell
CONS                   ;     - - - - - - - - -    ~~~~~~~~~
> (type-of 'a)         ; A is just a symol
SYMBOL
> (type-of a)          ; ERROR: A has no value    
*** - EVAL: variable A has no value

> (eval r)             ; now what? The data got treated as code.
2                      ;               ~~~~                ~~~~
> a                    ; 'A' has got its value
2
> (setf (caddr r) 4)   ; alter the Lisp data object! that is 
4                      ;  the value of a symbol 'r'
> (eval r)             ; execute the altered data, as 
4                      ;  new version of code
> a
4

ご覧のとおり、"s-expressions" や AST などは抽象化であり、Lisp では具体的で単純な基本的な Lisp データ オブジェクトによって表されます。

ソースファイルは不思議なものではなく、 REPLで何度も何度も定義を入力する必要がなくなるだけです。ソースファイルの内容がどのように読み取られるかは、具体的な実装まで、完全に任意です。Python、Haskell、または C に似た構文ファイルを読み取る実装も簡単に作成できます。

もちろん、Common Lisp 標準は、準拠した実装がその Common Lisp ソース ファイルをどのように読み取るべきかを定義しています。ただし、システムでは、読み取りに有効な追加の形式をいくつか定義することもできます。少なくとも、それらすべてを Lisp のリストのような構文で表現する必要があるという制約があり、1 つの巨大なリストとして表現する必要はありません。ソーステキストをどのように扱うかは自由です。

于 2012-05-14T09:15:54.227 に答える
2

あなたが提案するバリエーション — 引用されたリストのリストを持つこと — はおそらく、(IMHO) Lisp の唯一の最も紛らわしいことを反映しています ☺ — 引用!

本質的なアイデアは次のようになります。

コンパイラ (またはインタプリタ) は、入力 (REPL またはソース ファイル) を通過します。次に、各リストは「フォーム」として評価されます。ほとんどのフォーム (リスト) は、 のようなタイプになりますdefun。フォームを評価するdefunと、シンボル テーブルが変更されます (これは別の議論のトピックです) 。これは、フォーム内のシンボル名に基づいてアクションをdef実行します。fun(は、実質的に に評価される(defun foo (bar) (print bar))のエントリをシンボル テーブルに含める必要があることを定義します。)foo(lamba (bar) (print bar))

これらのリストは、すぐに評価されるようにするため、引用されていません。'(…)orで引用することは、コンパイラ/REPL が何かをすぐに評価するの(quote …)防ぐためのものです。

コンパイラの出力 (どちらであるかによって異なります) は、通常、定義したすべての関数を含むある種のバイナリまたはバイトコードになります。または、おそらく、ある種の「メイン関数」によって最終的に参照されるものだけです。

次のようなものを提供した場合:

 (
         '(defun foo (bar) (print bar))
 )

コンパイラは、引用符で囲まれたdefun特別な形式 (またはマクロ) である外側のリストの最初の要素を評価しようとしますが、何もする必要はありません。

それにもかかわらず、あなたが言うように、それを使用してではなくLispソースファイルを読み込むなどのことを行うことができます:HTMLの「コピー」または類似のものを生成します。read eval

funcallandを掘り下げたらdefmacro、これらすべての引用符がどこに属しているかを理解すると (さらに良いことに、backquote-comma quote-unquote パラダイム)、おそらく慣れるまでにしばらく時間がかかるでしょう…</p>

于 2012-05-09T17:16:10.883 に答える
0

Lisp では、ネストされたリストとして表現される抽象構文ツリーに直接プログラムします。Lisp は独自のリスト データ構造で表現されるため、これらのリストはプログラムで変更できるため、結果としてマクロは除外されます。最上位のリストが暗示されていると思います。そのため、少なくともClojureでは、(...で開始および終了するプログラムが表示されません)

于 2012-05-09T17:04:03.143 に答える