Наткнулся на интересные грабли в F# при попытке использовать изменяемые let-привязки внутри sequence expressions, данный код компилируется и работает:

let xs = seq {
  yield 1
  let mutable x = 0
  x <- 2 // ok
  yield x
}

Однако если перенести присваивание подальше от определения let и убрать в цикл, то код перестаёт компилироваться:

let ys = seq {
  yield 1
  let mutable x = 0
  while true do
    x <- 2 // error FS0407!
    yield x
}

В ошибке компиляции говорится следующее:

error FS0407:
The mutable variable ‘x’ is used in an invalid way. Mutable variables cannot be captured by closures. Consider eliminating this use of mutation or using a heap-allocated mutable reference cell via ‘ref’ and ‘!’.

Возникает вопрос: о каком захвате в замыкание идёт речь в сообщении об ошибке? Думаю, подобное описание выглядит как-то очень неадекватно, особенно для людей, не знакомых глубоко с устройством sequence expression или итераторов C#.

А что касается причины такого поведения, то дело заключается в том, что выражение присваивание во втором примере попадает в состояние sequence expression’а, отличное от того, в котором была определена изменяемая let-привязка. То есть в другой case генерируемого для MoveNext() блока switch’а, нежели в котором изменяемая let-привязка инициализируется нулём.

Компилятор F# невидимо для пользователя разбивает sequence expression на части кода (ориентируясь по yield-выражениям и выражениям, управляющим ходом выполнения) и считает, что все обращения к именам в предущей части из следующей - это замыкания. Не знаю, возможно это действительно следует называть “замыканием”, однако границы “частей” sequence expression’ов совершенно неочевидны рядовому разработчику.

Не берусь судить как было бы сделать правильно: вовсе запретить в sequence expressions использовать mutable или наоборот разрешить мутирование из лубой части sequence-выражения, а может и оставить как сейчас. В любом случае, текст сообщения ошибке мог бы быть куда понятнее.