可能性のある複製:
Ocaml/F#の関数がデフォルトで再帰的でないのはなぜですか?
OCamlはlet
を使用して新しい関数を定義するか、let rec
を使用して再帰的な関数を定義します。なぜこれらの両方が必要なのですか?すべてにlet
を使用できないのでしょうか?
たとえば、OCamlで(実際にはOCamlインタープリターで)非再帰的な後続関数と再帰的な階乗を定義するには、次のように記述します。
let succ n = n + 1;;
let rec fact n =
if n = 0 then 1 else n * fact (n-1);;
Haskell(GHCI)では、
let succ n = n + 1
let fact n =
if n == 0 then 1 else n * fact (n-1)
OCamlがlet
とlet rec
を区別するのはなぜですか?パフォーマンスの問題ですか、それとももっと微妙な問題ですか?
まあ、1つだけではなく両方を使用できるようにすると、プログラマーはスコープをより厳密に制御できます。 _let x = e1 in e2
_の場合、バインディングは_e2
_の環境にのみ存在しますが、_let rec x = e1 in e2
_の場合、バインディングは_e1
_と_e2
_の両方の環境に存在します。
(編集:パフォーマンスの問題ではないではないことを強調したいので、まったく違いはありません。)
この非再帰的バインディングが役立つ2つの状況を次に示します。
古いバインディングを使用する改良により、既存の定義をシャドウイングします。 let f x = (let x = sanitize x in ...)
のようなもの。sanitize
は、入力にいくつかの望ましいプロパティがあることを保証する関数です(たとえば、正規化されていない可能性のあるベクトルのノルムを取るなど)。これは、場合によっては非常に役立ちます。
マクロプログラミングなどのメタプログラミング。任意の式foo
に対して、_let x = foo in x * x
_にデガーするマクロSQUARE(foo)
を定義したいとします。出力でのコードの重複を避けるために、このバインディングが必要です(SQUARE(factorial n)
で_factorial n
_を2回計算したくない)。これは、let
バインディングが再帰的でない場合にのみ衛生的です。それ以外の場合、let x = 2 in SQUARE(x)
を記述して正しい結果を得ることができませんでした。
したがって、再帰的バインディングと非再帰的バインディングの両方を使用できるようにすることが非常に重要であると私は主張します。さて、let-bindingのdefault動作は慣例の問題です。 _let x = ...
_は再帰的であると言うことができ、非再帰的バインダーを取得するには_let nonrec x = ...
_を使用する必要があります。どちらか一方のデフォルトを選択することは、どちらのプログラミングスタイルを採用するかという問題であり、どちらを選択するかには十分な理由があります。 Haskellは、この非再帰モードを使用できないことに悩まされており、OCamlには型レベルでまったく同じ欠陥があります。_type foo = ...
_は再帰的であり、使用可能な非再帰的オプションはありません-参照 このブログ投稿 。
¹:Google Code Searchが利用可能になったとき、Haskellコードでパターン_let x' = sanitize x in ...
_を検索するために使用しました。これは、非再帰的バインディングが利用できない場合の通常の回避策ですが、後で誤って_x'
_ではなくx
を書き込むリスクがあるため、安全性は低くなります。両方を利用可能にしたい場合もあります。なので、別の名前を選ぶのは自発的です。最初のx
には、_unsanitized_x
_のように長い変数名を使用することをお勧めします。とにかく、文字通り_x'
_(他の変数名はありません)を検索すると、_x1
_は多くの結果を返しました。 Erlang(および変数のシャドウイングを困難にしようとするすべての言語:Coffeescriptなど)には、この種のさらに悪い問題があります。
とは言っても、Haskellバインディングをデフォルトで(非再帰的ではなく)再帰的に選択することは、デフォルトによる遅延評価と一貫性があり、再帰的な値を構築することを非常に簡単にするため、確かに理にかなっています。言語には、どの再帰的定義が意味をなすかについてより多くの制限があります。