2017-08-11 14 views
4

次のコードを想像してみて:F# - Seq.mapが例外を伝播しないのはなぜですか?

let d = dict [1, "one"; 2, "two" ] 

let CollectionHasValidItems keys = 
    try 
     let values = keys |> List.map (fun k -> d.Item k) 
     true 
    with 
     | :? KeyNotFoundException -> false 

を今私たちはそれをテストしてみましょう:私が期待するよう

let keys1 = [ 1 ; 2 ] 
let keys2 = [ 1 ; 2; 3 ] 

let result1 = CollectionHasValidItems keys1 // true 
let result2 = CollectionHasValidItems keys2 // false 

これは動作します。しかし、我々は関数内で配列にリストを変更した場合、我々は異なる動作を取得:keys2とここ

let keys1 = seq { 1 .. 2 } 
let keys2 = seq { 1 .. 3 } 

let result1 = CollectionHasValidItems keys1 // true 
let result2 = CollectionHasValidItems keys2 // true 

私は、デバッガでオブジェクト内の例外メッセージを見ることができますが、例外がスローされません...

なぜこのようなのですか?私は私のアプリでいくつかの同様のロジックが必要とシーケンスで動作することを好むだろう。

+3

シーケンスの[遅延評価](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/lazy-computations)が原因です。 'let values = keys |> Seq.map(fun k - > d.Item k)|> Seq.toList'を試してみましょう。 – Funk

答えて

6

これは、副作用と遅延評価の問題の古典的な例です。 Seq.mapのようなSeq関数は遅延評価されます。つまり、Seq.mapの結果は、返されるシーケンスが列挙されるまで計算されません。あなたの例では、valuesで何もしないのでこれは決して起こりません。

あなたはlistのように、具体的なコレクションを生成することによって、シーケンスの評価を強制する場合は、あなたがあなたの例外を取得し、機能がfalseを返します。

let CollectionHasValidItems keys = 
    try 
     let values = keys |> Seq.map (fun k -> d.Item k) |> Seq.toList 
     true 
    with 
     | :? System.Collections.Generic.KeyNotFoundException -> false 

あなたが気づいてきたように、代わりにList.mapを使用してSeq.mapの場合は、呼び出されたときに熱心に評価され、新しいコンクリートlistが返されるため、問題が解決されます。

重要なのは、副作用を遅延評価と組み合わせることに本当に注意する必要があるということです。最初に期待している順序でエフェクトを使用することはできません。

+1

そうですね、F#だけではなく、一般的な怠惰な評価についてです(私は母国語のC#で試してみました)。私はちょっと読んだが、今は意味がある。ありがとうございました! – psfinaki

+0

@psfi​​nakiうん、これをC#に 'IEnumerable'/LINQで直接翻訳することができます。まったく同じ動作になります。 – TheInnerLight

+0

この失敗の根本的な理由は、インテントを明示的にエンコードするのではなく暗黙的な副作用(ここでは 'd.Item'が例外をスローするという知識)に依存するということです。 –

関連する問題