36

この質問はすでに見ましたが、私が疑問に思っていることを説明していません。

Common Lisp から Clojure に初めて来たとき、シンボルとキーワードを別の型として扱う理由に戸惑いましたが、後でそれがわかり、今では素晴らしいアイデアだと思います。今、シンボルと変数が別のオブジェクトである理由を突き止めようとしています。

私が知る限り、Common Lisp の実装は一般に、1) 名前の文字列、2) 関数呼び出し位置で評価されたときのシンボルの値へのポインター、3) 次のときのその値へのポインターを持つ構造体を使用して「シンボル」を表します。呼び出し位置の外で評価され、4) プロパティ リストなど。

Lisp-1/Lisp-2 の区別を無視すると、CL では「シンボル」オブジェクトがその値を直接指すという事実が残ります。つまり、CL は、Clojure が「シンボル」と「var」と呼ぶものを 1 つのオブジェクトに結合します。

Clojure でシンボルを評価するには、まず対応する var を検索し、次にvar を逆参照する必要があります。Clojure がこのように機能するのはなぜですか? そのような設計からどんな利益が得られるでしょうか? var には特定の特別なプロパティ (private、const、dynamic など) があることは理解していますが、これらのプロパティを単にシンボル自体に適用することはできませんか?

4

8 に答える 8

54

他の質問はシンボルの多くの真の側面に触れていますが、別の角度から説明してみます.

シンボルは名前です

ほとんどのプログラミング言語とは異なり、Clojure はモノとモノの名前を区別します。ほとんどの言語では、 のようなことvar x = 1を言うと、「x は 1 です」または「x の値は 1 です」と言うのは正しく完全です。しかし、Clojure では(def x 1)、Var (値を保持するエンティティ) を作成シンボルにx. 「x の値が 1 である」ということは、Clojure のすべてを物語っているとは言えません。より正確な (面倒ですが) ステートメントは、「シンボル x で指定された var の値は 1 です」です。

シンボル自体は単なる名前ですが、変数は値を運ぶエンティティであり、それ自体には名前がありません。前の例を拡張して(def y x)、新しい var を作成していない、既存の var に 2 番目の名前を付けただけだとします。2 つのシンボルxyは、値が 1 の同じ var の名前です。

例えるなら、私の名前は「ルーク」ですが、それは私という人間と同じではありません。それはただの言葉です。ある時点で名前を変更することは不可能ではありませんし、私の名前を共有する人は他にもたくさんいます。しかし、私の友人の輪の文脈では(私の名前空間で)、「ルーク」という言葉は私を意味します. そして、ファンタジーの Clojure ランドでは、私はあなたの価値を運ぶ var になることができます。

しかし、なぜ?

では、ほとんどの言語のように 2 つを混同するのではなく、変数とは別の名前というこの特別な概念がなぜ必要なのでしょうか?

1 つには、すべてのシンボルが vars にバインドされているわけではありません。関数の引数や let バインディングなどのローカル コンテキストでは、コード内のシンボルによって参照される値は実際には var ではありません。コンパイラ。

ただし、最も重要なことは、これが Clojure の「コードはデータである」という哲学の一部であることです。コード行は(def x 1)単なる式ではなく、データ、具体的には値defx、およびで構成されるリストでもあります1。これは、特にコードをデータとして操作するマクロにとって非常に重要です。

しかし、リストの場合 (def x 1)、リスト内の値は何ですか? 特に、それらの値の型は何ですか? 明らか1に数字です。しかし、defとはxどうでしょうか。それらをデータとして操作しているとき、それらの型は何ですか? もちろん、答えは記号です。

これが、Clojure でシンボルが別個のエンティティである主な理由です。マクロなどの一部のコンテキストでは、名前を取得して操作し、ランタイムまたはコンパイラによって付与された特定の意味やバインディングから切り離したい場合があります。名前は何らかのものでなければならず、そのようなものはシンボルです。

于 2012-07-27T02:34:32.250 に答える
11

この質問についてよく考えてみると、シンボルと変数を区別する理由、または Omri がよく言ったように、「シンボルをその基になる値にマッピングするために 2 レベルの間接化」を使用する理由がいくつか考えられます。最後に最高のものを保存します...

1: 「変数」と「変数を参照できる識別子」の概念を分離することで、Clojure は概念を少しすっきりさせます。CL では、リーダーがを参照 すると、現在のスコープでローカルにバインドされている場合でもa、トップレベルのバインドへのポインターを運ぶシンボル オブジェクトが返されます。(この場合、エバリュエーターはそれらのトップレベルのバインディングを利用しません。) Clojure では、シンボルは単なる識別子であり、それ以上のものではありません。 a

これは、シンボルが Clojure の Java クラスも参照できるという一部の投稿者が作成したポイントに関連しています。シンボルにバインディングが含まれている場合、シンボルが Java クラスを参照するコンテキストではそれらのバインディングを無視できますが、概念的には面倒です。

2: 場合によっては、シンボルをマップ キーなどとして使用したい場合があります。シンボルが (CL のように) 可変オブジェクトである場合、Clojure の不変データ構造にはうまく適合しません。

3: (おそらくまれですが) シンボルがマップ キーなどとして使用され、API によって返されることさえある場合、Clojure のシンボルの等価セマンティクスは CL のシンボルよりも直感的です。(@ amalloyの回答を参照してください。)

partial4: Clojure は関数型プログラミングを重視しているため、多くの作業は、comp、などの高階関数を使用して行わjuxtれます。これらを使用していない場合でも、関数を独自の関数などの引数として使用できます。

my-funcこれで、高階関数に渡すと、 「my-func」と呼ばれる変数への参照が保持されなくなります今ある価値をそのまま捉えているだけです。後で再定義my-funcすると、変更は の値を使用して定義された他のエンティティに「伝播」しませんmy-func

このような状況でも、 を使用すると、派生関数が呼び出されるたび#'my-funcに の現在の値を参照するように明示的に要求できますmy-func。(おそらく、小さなパフォーマンス ヒットを犠牲にして)。

CL や Scheme では、この種の間接化が必要な場合、cons、vector、または struct に関数オブジェクトを格納し、呼び出されるたびにそこから取得することを想像できます。実際、コードのさまざまな部分で共有できる「可変参照」オブジェクトが必要なときはいつでも、コンスまたはその他の可変構造を使用することができました。しかし、Clojure では、lists/vectors/etc. すべて不変であるため、「変更可能なもの」を明示的に参照する方法が必要です

于 2012-07-27T01:58:06.700 に答える
6

あなたの投稿から次の質問を推測しました (私がベースから外れているかどうか教えてください):
シンボルをその基になる値にマッピングするための間接的なレベルが 2 つあるのはなぜですか?

最初にこの質問に答えようとしたとき、しばらくして考えられる理由が 2 つあります。その場での「再定義」と、関連する動的スコープの概念です。ただし、次のことから、これらのどちらもこの二重の間接性を持つ理由ではないことがわかりました。

=> (identical? (def a 0) (def a 10))
=> true

=> (declare ^:dynamic bar)
=> (binding [bar "bar1"]
     (identical? (var bar)
                 (binding [bar "bar2"]
                   (var bar))))
=> true

私にとって、これは、「再定義」も動的スコープも、名前空間で修飾されたシンボルとそれが指す var との間の関係を変更しないことを示しています。

この時点で、新しい質問をします。
名前空間で修飾されたシンボルは、それが参照する var と常に同義ですか?

この質問に対する答えが「はい」の場合、別のレベルの間接化が必要な理由がまったくわかりません。

答えが「いいえ」の場合、同じプログラムの 1 回の実行中に、名前空間で修飾されたシンボルが異なる変数を指すのはどのような状況かを知りたいです。

要約すると、素晴らしい質問だと思います:P

于 2012-07-26T06:30:16.950 に答える
6
(ns a)

(defn foo [] 'foo)
(prn (foo))


(ns b)

(defn foo [] 'foo))
(prn (foo))

シンボルfooは両方のコンテキストでまったく同じシンボル (つまり、(= 'foo (a/foo) (b/foo))真) ですが、2 つのコンテキストでは異なる値 (この場合は 2 つの関数の 1 つへのポインター) を運ぶ必要があります。

于 2012-07-26T04:04:04.377 に答える
6

主な利点は、さまざまなインスタンスで役立つ追加の抽象化レイヤーであることです。

具体的な例として、シンボルは、それらが参照する var の作成前に問題なく存在できます。

(def my-code `(foo 1 2))     ;; create a list containing symbol user/foo
=> #'user/my-code

my-code                      ;; confirm my-code contains the symbol user/foo
=> (user/foo 1 2)

(eval my-code)               ;; fails because user/foo not bound to a var
=> CompilerException java.lang.RuntimeException: No such var: user/foo...

(def foo +)                  ;; define user/foo
=> #'user/foo

(eval my-code)               ;; now it works!
=> 3

メタプログラミングの利点は明らかです。インスタンス化する前にコードを構築して操作し、完全に入力された名前空間で実行できます。

于 2012-07-26T07:06:23.533 に答える
1

Clojure は私の最初の (そして唯一の) Lisp なので、この答えは推測にすぎません。そうは言っても、clojure Web サイトからの次の議論は適切なようです (強調は私のものです)。

Clojure は、変化する値への永続的な参照を時折維持する必要性を認識し、制御された方法でそれを行うための 4 つの異なるメカニズム (Var、Ref、Agent、Atom) を提供する実用的な言語です。Vars は、スレッドごとに (新しいストレージの場所に) 動的に再バインドできる変更可能なストレージの場所を参照するメカニズムを提供します。すべての Var は、ルート バインディングを持つことができます (必須ではありません)。これは、スレッドごとのバインディングを持たないすべてのスレッドによって共有されるバインディングです。したがって、Var の値は、スレッドごとのバインディングの値、または値を要求するスレッドでバインドされていない場合は、ルート バインディングの値 (存在する場合) です。

そのため、シンボルを Vars に間接的に指定すると、スレッド セーフな動的な再バインドが可能になります (これは他の方法で実行できるかもしれませんが、私にはわかりません)。これは、ID と状態を厳密かつ広範囲に区別して堅牢な並行性を実現するという clojure のコア哲学の一部であると考えています。

この機能が真のメリットをもたらすのは、スレッド固有の動的バインディングを必要としないように問題を再考する場合と比較して、たとえあったとしてもめったにないと思いますが、必要な場合は利用できます。

于 2012-07-26T14:50:09.200 に答える
1

Common Lisp またはその他の Lisp については、 httpDifferences with other Lisps ://clojure.org/lispsから確認してください。

于 2012-07-26T04:03:50.550 に答える