質問
それは機能F#と存(およびその他の言語)によるデフォルトの再帰?
つまり、なぜ言語のデザイナーを決めたので良いアイデアを明示的にまだタイプ rec
宣言のように:
let rec foo ... = ...
んでい機能を再帰的能力によりデフォルト?理由の明示的な rec
建?
解決
元MLのフランスとイギリスの子孫は、さまざまな選択肢を作り、彼らの選択は、近代的な変異体を数十年を通じて受け継がれてきました。だから、これは単なる遺産であるが、それはこれらの言語のイディオムに影響を与えています。
関数(OCamlのを含む)の言語のフランス語Camlの家族の中で、デフォルトでは再帰的ではありません。あなたは新しい定義の本体内部の前の定義を参照することができますので、この選択は、これらの言語でlet
を使用して定義関数(および変数)を代わるが容易になります。 F#がOCamlのからこの構文を継承します。
例えば、OCamlのシーケンスのシャノンエントロピーを計算するとき、関数p
を取っ
let shannon fold p =
let p x = p x *. log(p x) /. log 2.0 in
let p t x = t +. p x in
-. fold p 0.0
高次p
関数の引数shannon
は、本体の最初の行に別のp
と本体の2行目で、別p
によって取って代わられている方法に注意してください。
逆に、言語のMLファミリーの英国SMLブランチは、他の選択肢を取って、SMLのfun
結合機能は、デフォルトでは再帰的です。ほとんどの関数定義は、その関数名の前のバインディングへのアクセスを必要としない場合には、これは単純なコードになります。しかし、代わら機能は、スコープを汚染し、誤っ機能の間違った「バージョン」を起動することが可能となり、異なる名前(f1
、f2
など)を使用するように作られています。そして今、暗黙的に再帰fun
結合機能と非再帰的val
結合機能との間に矛盾があります。
Haskellは純粋であること、それらを制限することによって定義間の依存関係を推測することが可能となります。これは、おもちゃのサンプルは単純に見えるのですが、別の場所に墓コストがかかります。
ガネーシャとエディによって与えられた答えは赤ニシンであることに注意してください。型変数は、一般得るとき、それが影響するための機能のグループが巨大なlet rec ... and ...
の内側に配置することができない理由を彼らは説明しました。これはrec
はSMLではなく、OCamlではデフォルトであることとは何の関係もありません。
他のヒント
一つの重要な理由を明確にし、その利用 rec
はいHindley-ウ型推論、その根底にある全てのstaticly型プログラミング言語でも変更と拡張さまざまに).
また定義 let f x = x
, おいしいタイプ 'a -> 'a
とすることに異なる 'a
種類の異なる。も同様に、書き込んだ場合 let g x = (x + 1) + ...
, すい x
処として int
ののの g
.
こHindley-ウ推論お得にこの特徴がより明確な generalisation ます。一部のポイントが処理プログラムは、タイプシステム停止するものとして"okの種類のこれらの定義する節点する場合には、このような人が利用して、フリータイプの変数をタイプする 出来立て インスタンスを生成し、このような支障その他の用途この定義で設定します。"
ここで示されているデータに良いこgeneralisationが確認後、互いに再帰的に設定機能を備えています。先んgeneraliseも、状況の種類が実際に衝突させる.によってはそれ以降のんgeneraliseが少なすぎるので、定義すること複数のタイプinstantiations.
なので、これらのタイプチェッカーのニーズについて知るセットの定義が相互に再帰的に何ができるのか一つの可能性はない依存関係を解析すべての定義を適用範囲および並べ替えてください、最小できます。ウんなことが言語のようなF#(および存およびSML)の自由な副作用は、こう可能性があるからであ並べ替えの副作用です。なので、そのような、ユーザが明示的にインスピレーションを受けて定義が相互に再帰的に、このようにする拡張子がgeneralisationきます。
二つの重要な理由は、これは良いアイデアですがあります:
あなたは再帰的な定義を有効にした場合、まず、あなたは同じ名前の値の前の結合を参照することはできません。これは、多くの場合、既存のモジュールを拡張するような何かをやっている便利なイディオムです。
第二に、再帰的な値、および相互再帰的な値の特にセットは、およそその後、理由がはるかに困難です順番に進むの定義は、すでに定義されているものの上に、それぞれの新しい定義の建物です。明示的に再帰的なとしてマークされた定義を除き、新しい定義が唯一の以前の定義を参照することができ、保証を持っているために、このようなコードを読むとき、それはいいです。
一部の推測:
let
が用いられるだけではなく結合する機能も普通の値です。最もお受けしていますすることが認められていないた列に変換します。特定の形態の再帰値が許可され(例えば機能のぐさの表現等)、 その中から、明示的な書式を示します。- この最適化が容易に非再帰的機能
- 閉鎖を作成したときに再帰関数をエントリをポイントの機能自体の機能を再帰的に呼もい)の上に、再帰的閉鎖以上に複雑非再帰を抑止するためには、必要か何をやってもうまくいかないのも良いでしを作成できるので簡単な非再帰的に閉鎖する必要がな再帰
- できる関数を定義するのは、事前に定義された機能の値と同じ名前;がんの悪い習
- 追加。いることを確認することができます。例えばだいすき再帰が誤って使って名前の関数と同名のストアドファンクとしての機能そのものが文句を言(いない場合はnameが定義される前に)
- の
let
の構築にlet
の構築にLispおよびスキーム;非列に変換します。が別途発生しますletrec
構築スキームのための再帰しましょう
を考えると、この:
let f x = ... and g y = ...;;
の比較:
let f a = f (g a)
これによります:
let rec f a = f (g a)
前者の再定義はf
にf
を適用した結果と、以前に定義されたg
を適用するa
。後者の再定義を使用すると、MLバリアントで欲しいものは通常されていない、永遠にf
するg
を適用するループにa
ます。
それはそれは言語設計者のスタイルのことだ、と述べました。ウソツキは結婚のはじまります。
それの大部分は、それがプログラマに自分のローカルスコープの複雑さをより細かく制御を与えることです。 let
、let*
とlet rec
のスペクトルは、消費電力とコストの両方の増加レベルを提供します。 let*
とlet rec
のでどちらか一方を使用すると、より高価であり、本質的には簡単なlet
のネストされたバージョンです。この格付けは、あなたが当面の作業のために必要とせのどのレベルを選択することができるよう、あなたのプログラムの最適化をmicromanageすることができます。あなたは再帰または前のバインディングを参照する機能を必要としない場合は、パフォーマンスのビットを保存するために、単純なLETに頼ることができます。
これは、Schemeで段階的等価述語に似ています。 (すなわちeq?
、eqv?
及びequal?
)