camlspotterの答えはすでに十分です。ここでさらにいくつかのポイントを追加したいと思います。
まず、 の問題についてはwrite a function that receives a finite list and returns an infinite, circular version of it、コード/実装レベルで実行できますが、実際に関数を使用すると、スタックオーバーフローの問題が発生し、決して返されません。
あなたがやろうとしていたことの簡単なバージョンは次のようなものです:
let rec circle1 xs = List.rev_append (List.rev xs) (circle1 xs)
val circle: 'a list -> 'a list = <fun>
コンパイルでき、理論的には正しいです。[1;2;3]で、生成するはずです[1;2;3;1;2;3;1;2;3;1;2;3;...]。
ただし、その実行は無限であり、最終的にはスタックオーバーフローになるため、もちろん失敗します。
では、なぜlet rec circle2 = 1::2::3::circle2機能するのでしょうか。
実行するとどうなるか見てみましょう。
まず、circle2は値で、リストです。OCaml がこの情報を取得すると、リストのメモリ表現を使用して circle2 の静的アドレスを作成できます。
つまり、int 1 のノードと int 2 のノードのアドレスと int 3 のノードのアドレスと circle2 のアドレス1::2::3::circle2です。Node (1, Node (2, Node (3, circle2)))でも、circle2 のアドレスはもう知っていますよね?OCaml は circle2 のアドレスをそこに置くだけです。
すべてが機能します。
また、この例を通して、このように定義された無限の円で囲まれたリストは、実際には限られたメモリを消費しないという事実も知ることができます。すべてのメモリを消費する実際の無限リストを生成するのではなく、円が終了すると、リストの先頭に「戻る」だけです。
の例に戻りましょうcircle1。Circle1 は関数です。はい、アドレスがありますが、それは必要ありません。必要なのは、関数 application のアドレスですcircle1 xs。これは circle2 とは異なり、アドレスを取得するために何かを計算する必要があることを意味する関数アプリケーションです。そう、
OCaml は を実行しList.rev xs、次にアドレスを取得しようとし、circle1 xsそれから繰り返し、繰り返します。
では、なぜ私たちは時々 を得るのError: This kind of expression is not allowed as right-hand side of 'let rec'ですか?
http://caml.inria.fr/pub/docs/manual-ocaml/extn.html#s%3aletrecvaluesから
let rec バインディング構造は、再帰関数の定義に加えて、次のような非関数値の再帰定義の特定のクラスもサポートします。
let rec name1 = 1 :: name2 and name2 = 2 :: name1 を expr 内で循環リスト 1::2::1::2::… にバインドし、name2 を循環リスト 2::1:: にバインドします。 2::1::…非公式には、受け入れられる定義のクラスは、定義された名前が関数本体内またはデータ コンストラクターへの引数としてのみ発生する定義で構成されます。
let recバインディングを定義するために使用する場合は、 let rec name. これnameは、関数本体またはデータ コンストラクターのいずれかでのみ使用できます。
前の 2 つの例でcircle1は、 は関数本体 ( let rec circle1 = fun xs -> ...)circle2内にあり、データ コンストラクター内にあります。
を行うlet rec circle = circleと、円が 2 つの許可されたケースにないため、エラーが発生します。let rec x = let y = x in y繰り返しますが、 x はコンストラクターまたは関数にないため、どちらも行いません。
ここにも明確な説明があります:
https://realworldocaml.org/v1/en/html/imperative-programming-1.html
セクションLimitations of let rec