Muhammad Alkarouri
Muhammad Alkarouri

Reputation: 24652

Recursive objects in F#?

This snippet of F# code

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

results in the following warning:

traynote.fs(62,41): warning FS0040: This and other recursive references to the object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning may be suppressed by using '#nowarn "40"' or '--nowarn:40'.

Is there a way in which the code can be rewritten to avoid this warning? Or is there no kosher way of having recursive objects in F#?

Upvotes: 8

Views: 1391

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243041

Your code is a perfectly fine way to construct a recursive object. The compiler emits a warning, because it cannot guarantee that the reference won't be accessed before it is initialized (which would cause a runtime error). However, if you know that EventHandler does not call the provided lambda function during the construction (it does not), then you can safely ignore the warning.

To give an example where the warning actually shows a problem, you can try the following code:

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

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

The Evil class takes a function in a constructor and calls it during the construction. As a result, the recursive reference in the lambda function tries to access e before it is set to a value (and you'll get a runtime error). However, especially when working with event handlers, this is not an issue (and you get the warnning when you're using recursive objects correctly).

If you want to get rid of the warning, you can rewrite the code using explicit ref values and using null, but then you'll be in the same danger of a runtime error, just without the warning and with uglier code:

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

Upvotes: 16

Related Questions