Вы упомянули, что локальные изменяемые значения не могут быть захвачены замыканием, поэтому вам нужно использовать ref
вместо этого. Причина этого заключается в том, что изменяемые значения, захваченные в замыкании, должны быть распределены в куче (поскольку замыкание выделяется в куче).
F # заставляет вас написать это явно (используя ref
). В C # вы можете «захватить изменяемую переменную», но компилятор преобразует ее в поле в выделенном куче объекте за сценой, так что он все равно будет в куче.
Сводка: Если вы хотите использовать замыкания, переменные должны быть размещены в куче.
Теперь, что касается вашего кода - ваша реализация использует ref
, который создает небольшой объект для каждой изменяемой переменной, которую вы используете. Альтернативой может быть создание одного объекта с несколькими изменяемыми полями. Используя записи, вы можете написать:
type ReadClosure = {
mutable c : char
mutable k : SomeType } // whatever type you use here
let rec read file includepath =
let state = { c = ' '; k = NONE }
// ...
let readc() =
state.c <- stream.Read() |> char
// etc...
Это может быть немного более эффективно, потому что вы выделяете один объект вместо нескольких объектов, но я не ожидаю, что разница будет заметна.
Есть и одна запутанная вещь в вашем коде - значение stream
будет удалено после возврата функции read
, поэтому вызов stream.Read
может быть недопустимым (если вы вызываете readc
после read
завершается).
let rec read file includepath =
let c = ref ' '
use stream = File.OpenText file
let readc() =
c := stream.Read() |> char
readc
let f = read a1 a2
f() // This would fail!
Я не совсем уверен, как вы на самом деле используете readc
, но об этой проблеме может подумать. Кроме того, если вы объявляете его только как вспомогательное закрытие, вы, вероятно, можете переписать код без закрытия (или написать его явно, используя tail-recursion, который переводится в императивный цикл с изменяемыми переменными), чтобы избежать каких-либо выделений.