Рекурсивные объекты в F #? - PullRequest
7 голосов
/ 26 декабря 2011

Этот фрагмент кода F #

    let rec reformat = new EventHandler(fun _ _ ->
        b.TextChanged.RemoveHandler reformat
        b |> ScrollParser.rewrite_contents_of_rtb
        b.TextChanged.AddHandler reformat
        )
    b.TextChanged.AddHandler reformat

выдает следующее предупреждение:

traynote.fs (62,41): предупреждение FS0040: Эта и другие рекурсивные ссылки на определяемый объект (ы) будут проверены на предмет правильности инициализации во время выполнения посредством использования отложенной ссылки. Это потому, что вы определяете один или несколько рекурсивных объектов, а не рекурсивные функции. Это предупреждение может быть подавлено с помощью «#nowarn« 40 »или« --nowarn: 40 ».

Есть ли способ переписать код, чтобы избежать этого предупреждения? Или нет кошерного способа иметь рекурсивные объекты в F #?

1 Ответ

15 голосов
/ 26 декабря 2011

Ваш код - прекрасный способ создания рекурсивного объекта. Компилятор выдает предупреждение, потому что он не может гарантировать, что ссылка не будет доступна до ее инициализации (что может вызвать ошибку времени выполнения). Однако, если вы знаете, что EventHandler не вызывает предоставленную лямбда-функцию во время построения (это не так), вы можете спокойно проигнорировать предупреждение.

Чтобы привести пример, в котором предупреждение фактически указывает на проблему, вы можете попробовать следующий код:

type Evil(f) =
  let n = f() 
  member x.N = n + 1

let rec e = Evil(fun () -> 
  printfn "%d" (e:Evil).N; 1)

Класс Evil берет функцию в конструкторе и вызывает ее во время построения . В результате рекурсивная ссылка в лямбда-функции пытается получить доступ к e до того, как ей будет присвоено значение (и вы получите ошибку времени выполнения). Однако, особенно при работе с обработчиками событий, это не проблема (и вы получаете предупреждение при правильном использовании рекурсивных объектов).

Если вы хотите избавиться от предупреждения, вы можете переписать код, используя явные значения ref и null, но тогда вы будете в той же опасности ошибки времени выполнения, просто без предупреждения и с более уродливым кодом:

let foo (evt:IEvent<_, _>) = 
  let eh = ref null
  eh := new EventHandler(fun _ _ -> 
    evt.RemoveHandler(!eh) )
  evt.AddHandler(!eh)
...