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
Это работает, как я ожидал. Но если мы изменим List на Seq в функции, мы получим другое поведение:
let keys1 = seq { 1 .. 2 }
let keys2 = seq { 1 .. 3 }
let result1 = CollectionHasValidItems keys1 // true
let result2 = CollectionHasValidItems keys2 // true
Здесь с keys2 я вижу сообщение об исключении в объекте значений в отладчике, но исключение не выдается...
Почему это так? Мне нужна похожая логика в моем приложении, и я бы предпочел работать с последовательностями.
1 ответ
Это классический пример проблемы с побочными эффектами и ленивой оценкой. Seq
такие функции, как Seq.map
лениво оцениваются, это означает, что результат 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
,
Ключевой вывод заключается в том, что вы должны быть очень осторожны, сочетая побочные эффекты с ленивой оценкой. Вы не можете полагаться на эффекты, происходящие в том порядке, который вы изначально ожидали.