2012-09-12 3 views
22

なぜこのコンセプトでlazy-seqでconsが機能するのですか?conjはありませんか?clojure consとlazy-seqとのconj

これは動作します:

(defn compound-interest [p i] 
    (cons p (lazy-seq (compound-interest (* p (+ 1 i)) i)))) 

はこれません(それはスタックオーバーフロー[1]例外を与える):

(defn compound-interest2 [p i] 
    (conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p)) 

[1]屋ああ! stackoverflowにスタックオーバーフローを含む質問をする。

+0

ここに来る他の人のために:私の答えは非常に詳細です(おそらくあまりにも詳細です)。混乱する。ここでの主なポイントを繰り返してみましょう: 'conj'のセマンティクスは、コレクション型に基づいてその振る舞いを変える必要があります。そのためには、コレクションオブジェクトに対して(多態的な)メソッド呼び出しを使用する必要があります。 'LazySeq'は、その内部値を実現する必要がある内部値に委譲することによってそのメソッド呼び出しを処理します。対照的に、consのセマンティクスではコレクションのメソッドを呼び出す必要はありません。 'Cons'オブジェクトの1つのフィールドに格納するだけです。 –

+0

答えの最後の3つの段落が特に役立ちます。一部の人は最初にそれを読んでみたいかもしれません。 – Mars

答えて

38

(conj collection item)は、itemからcollectionを加算する。そのためには、collectionを実現する必要があります。 (私は以下の理由を説明します)。したがって、再帰呼び出しは、遅延されるのではなく、ただちに行われます。

(cons item collection)は、itemで始まり、次にすべてがcollectionで始まるシーケンスを作成します。意義深く、それはcollectionを実現する必要はありません。したがって、再帰呼び出しは、(lazy-seqを使用しているため)誰かが結果のシーケンスの末尾を取得するまで延期されます。

私はこれが内部でどのように機能するかを説明します:

consは実際に怠惰なシーケンスがで作られているものであるclojure.lang.Consオブジェクトを返します。 conjは、それが渡されたコレクションと同じタイプのコレクションを返します(リスト、ベクターなど)。 conjは、コレクション自体に対して多形Javaメソッド呼び出しを使用してこれを行います。 (line 524 of clojure/src/jvm/clojure/lang/RT.javaを参照してください)

オブジェクトのJavaメソッド呼び出しがlazy-seqによって返されるとどうなりますか? (ConsLazySeqオブジェクトが一緒になってレイジーシーケンスを形成する方法は、以下でより明確になります)。line 98 of clojure/src/jvm/clojure/lang/LazySeq.javaを見てください。これは、seqというメソッドを呼び出していることに注意してください。これはLazySeqの値を実現するものです(詳細はline 55にジャンプしてください)。

conjは、あなたが渡したコレクションの種類を正確に知る必要があると言えるでしょうが、consはそうではありません。 cons "collection"引数がISeqであることが必要です。

Clojureのオブジェクトは、他のLispsの「コンスセル」とは異なります。ほとんどのLispsでは、「cons」は他の任意のオブジェクトへの2つのポインタを保持するオブジェクトです。したがって、コンスセルを使用してツリーを構築することができます。 Clojure Consは任意のObjectを頭に、ISeqをテールとして取ります。 Cons自体はISeqを実装しているので、Consオブジェクトからシーケンスを構築できますが、ベクトルやリストなどを指すこともできます(Clojureの「リスト」は特殊タイプ(PersistentList)で、Consオブジェクトから構築されたではありません。)clojure.lang.LazySeqはを実装していますので、Consのテール( "Lis"の "cdr")として使用できます。LazySeqは、が何らかの種類のISeqを評価するコードへの参照を保持しますが、実際にコードが評価されるまでは実際には評価されず、コードを評価した後に返されたISeqをキャッシュして代行します。

...これはすべて理にかなっていますか?あなたはレイジーシーケンスがどのように機能するか考えていますか?基本的には、LazySeqで始まります。 LazySeqが実現されると、Consと評価され、LazySeqを指します。そのことが実現すると、あなたはそのアイデアを得ます。だからのチェーンがあり、それぞれがConsを保持(および委任)しています。

Clojureの "conses"と "lists"の違いについて、 "リスト"(PersistentListオブジェクト)にはキャッシュされた "長さ"フィールドが含まれているため、O(1)時間でcountに応答できます。ほとんどのLispsでは "リスト"が変更可能であるため、これは他のLispsでは機能しません。しかし、Clojureではそれらは不変なので、長さのキャッシュは機能します。 Clojureの

Consオブジェクトがは、キャッシュされた長さを持っていない - 彼らがした場合、彼らは怠惰な(とでも無限の)配列を実装するために使用することができる方法? Conscountを取得しようとすると、ちょうどその末尾にcountが呼び出され、結果が1だけ増えます。

+0

アイテムを実現することを意味しますか? –

+0

@Ivan:いいえ - 「cons」と「conj」の引数の順序は反対です。 –

+0

私は彼が '(conj p(lazy-seq(compound-interest2(* p(+ 1 i))i))'を書いたと思って申し訳ありません。 –