4

F# の値は不変であると読みました。しかし、値の定義を再定義するという概念にも出くわしました。これは、以前のものに影を落としています。これは変更可能な値とどう違うのですか? これは単なる理論上の構成要素としてではなく、変更可能な値をいつ使用し、代わりにいつ式を再定義するかについてアドバイスがあればお願いします。または、後者が慣用的な f# ではないことを誰かが指摘できる場合。

再定義の基本的な例:

let a = 1;;
a;; //1
let a = 2;;
a;; //2

更新 1:

以下の回答に加えて、最上位レベルの Fsharp インタラクティブでの再定義は、異なる終端でのみ許可されます。以下は fsi でも同様にエラーになります:

let a = 1
let a = 2;;

Error: Duplicate definition of value 'a'

一方、let バインディングでは再定義が可能です。

更新 2: 実質的な違い、クロージャーは変更可能な変数では機能しません:

let f =
   let mutable a = 1
   let g () = a //error
   0  
f;;

更新 3:

参照を使用して副作用をモデル化できますが、たとえば:

let f =
   let  a = ref 1
   let g = a
   a:=2
   let x = !g  + !a
   printfn "x: %i" x //4

f;;

再定義と変更可能なキーワードの使用の実際的な違いは、クロージャーでの使用方法の違いに加えて、まったくわかりません。たとえば、次のようになります。

let f  =
   let a = 1
   let g  = a
   let a = 2
   let x = g + a
   printfn "x: %i" x //3

f;;

let f =
   let mutable a = 1
   let g = a
   a <-2
   let x = g  + a
   printfn "x: %i" x //3
 f;;

別の考え: スレッドの操作方法がわかりませんが、(a) 別のスレッドが let バインディング内の可変変数の値を変更できるか、(b) 別のスレッドが a 内の値の名前を再バインド/再定義できるか束縛させます。私は確かにここに何かが欠けています。

更新 4: 最後のケースの違いは、ネストされたスコープからミューテーションが引き続き発生するのに対し、ネストされたスコープでの再定義/再バインドは、外部スコープからの定義を「シャドウ」することです。

let f =
   let mutable a = 1
   let g = a
   if true then
      a <-2   
   let x = g  + a
   printfn "x: %i" x //3

f;;

let f =
   let a = 1
   let g = a
   if true then
      let a = 2  
      printfn "a: %i" a   
   let x = g  + a
   printfn "x: %i" x //2
f;;
4

4 に答える 4

5

特にF#には詳しくありませんが、「理論上」の部分はお答えできます。

オブジェクトの変更は、グローバルに可視である (または少なくともそうなる可能性がある)副作用。同じオブジェクトへの参照を含む他のコードは、変更を監視します。オブジェクトの値に依存するプログラム内の任意の場所で確立されたプロパティは、変更される可能性があります。たとえば、並べ替え位置に影響を与える方法でそのリストで参照されているオブジェクトを変更すると、リストが並べ替えられたという事実が false になる可能性があります。これは、非常に目立たない非ローカルな影響です。並べ替えられたリストを処理するコードとミューテーションを行うコードは、完全に別のライブラリにあり (直接の依存関係がない)、呼び出しの長いチェーンを介してのみ接続されている可能性があります。 (そのうちのいくつかは、他のコードによって設定されたクロージャーである可能性があります)。ミューテーションをかなり広く使用している場合、2 つの場所の間に直接の呼び出しチェーン リンクさえ存在しない可能性があります。この変更可能なオブジェクトは、その変更コードに渡されて、これまでにプログラムによって実行された特定の操作シーケンスに依存する可能性があります。

一方、ローカル変数をある不変値から別の値に再バインドすることは、技術的には「副作用」と見なされる可能性がありますが (言語の正確なセマンティクスによって異なります)、かなりローカライズされています。nameにのみ影響し、 before や after のvalueには影響しないため、オブジェクトがどこから来たのか、この後どこに行くのかは問題ではありません。意味が変わるだけ名前にアクセスする他のコードのビット。これによって影響を受けるコードを精査する必要がある場所は、名前の範囲に限定されます。これは、メソッド/関数/その他の内部に保持するのが非常に簡単な種類の副作用であるため、外部の観点から見た場合、関数は依然として副作用のない (純粋な; 参照透過性) です - 実際にキャプチャするクロージャーはありません。値ではなく名前 この種のローカルの再バインドが外部から見える副作用になることはあり得ないと私は信じています。

于 2013-12-03T02:08:52.133 に答える
4

「与えられた答えのいくつかに同意するかどうかはわかりません.

以下は、FSI と実際のアセンブリの両方で完全にコンパイルおよび実行されます。

let TestShadowing() =
   let a = 1
   let a = 2
   a

しかし、起こっているのは突然変異ではなくシャドーイングであることを理解することが重要です。つまり、「a」の値は再割り当てされていません。別の 'a' が独自の不変値で宣言されています。なぜ区別が重要なのですか?内側のブロックで 'a' が影になっているとどうなるかを考えてみましょう:

let TestShadowing2() =
   let a = 1
   printfn "a: %i" a
   if true then
      let a = 2
      printfn "a: %i" a
   printfn "a: %i" a

> TestShadowing2();;
a: 1
a: 2
a: 1

この場合、2 番目の 'a' は最初の 1 つをシャドウするだけで、2 番目の 'a' はスコープ内にあります。範囲外になると、最初の「a」が再び存在します。

これを認識しないと、微妙なバグにつながる可能性があります!

Guy Coder のコメントに照らして明確化:

上記の動作は、再定義がいくつかの let バインディング内 (つまり、私の例の TestShadowing() 関数内) にある場合に発生します。これは、実際には最も一般的なシナリオです。しかし、ガイが言うように、最上位で再定義すると、たとえば次のようになります。

module Demo =

   let a = 1
   let a = 2

実際にコンパイラ エラーが発生します。

于 2013-12-03T08:47:11.087 に答える
2

この種の再定義は、fsi でのみ機能します。コンパイラはここでエラーを生成しますが、ときどき次のようなことを行うことができます

let f h = match h with h::t -> h

h引数から定義をシャドウする new を作成すると、最初の要素が返されます。

再定義が機能する唯一の理由は、次のように fsi で間違いを犯す可能性があるためです。

let one = 2;;
let one = 1;; //and fix the mistake

コンパイル済みの F# コードでは、これは不可能です。

于 2013-12-02T09:28:46.847 に答える
2

あなたの質問の要点、つまり、再バインドと突然変異の違いについて、より直接的な答えを追加させてください。この関数の違いを確認できます。

let f () =
   let a = 1
   let g () = a
   let a = 2
   g () + a

これは 3 を返します。これは、aingが の前者のバインディングを参照しているのaに対し、後者は別個のものであるためです。上記のプログラムは完全に同等です

let f () =
   let a = 1
   let g () = a
   let b = 2
   g () + b

aここで、2番目とそれへのすべての参照の名前を一貫して変更しましたb

于 2013-12-03T14:25:04.367 に答える