3

I get the value restriction error on let makeElem in the following code:

let elemCreator (doc: XmlDocument) = 
    fun name (value: obj) ->
        let elem = doc.CreateElement(name)
        match value with
        | :? seq<#XmlNode> as childs -> 
            childs |> Seq.iter (fun c -> elem.AppendChild(c) |> ignore)
            elem
        | _ -> elem.Value <- value.ToString(); elem

let doc = new XmlDocument()
let makeElem = elemCreator doc

Why I get the value restriction error if anonymous function returned from elemCreator hasn't any generic parameters?

The compiler states that the infered type of makeElem is (string -> 'a -> XmlNode). But why it infers second parameter as 'a if I've declared it as obj?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Dmitrii Lobanov
  • 4,897
  • 1
  • 33
  • 50

3 Answers3

1

I believe that this may be the "expected" behavior (although unfortunate in this case), as a result of the compiler's generalization and condensation processes. Consider Tomas's example:

let foo (s:string) (a:obj) = a

If you were to define

let bar a = foo "test" a

then the compiler will infer the type bar : 'a -> obj because it generalizes the type of the first argument. In your case, you have the equivalent of

let bar = foo "test"

so bar is a value rather than a syntactic function. The compiler does essentially the same inference procedure, except now the value restriction applies. This is unfortunate in your case, since it means that you have to explicitly annotate makeElem with a type annotation (or make it a syntactic function).

kvb
  • 54,864
  • 2
  • 91
  • 133
0

This looks like an unexpected behavior to me. It can be demonstrated using a simpler function:

let foo (s:string) (a:obj) = a
let bar = foo "bar"             // Value restriction

One possible explanation might be that the F# compiler allows you to call a function taking parameter of some type with an argument of any subtype. So, you can call foo "hi" (new A()) without explicitly casting A to obj (which used to be required some time ago).

This implicit casting could mean that the compiler actually interprets bar as something like this:

let bar a = foo "bar" (a :> obj)

...and so it thinks that the argument is generic. Anyway, this is just a speculation, so you could try sending this as a bug report to fsbugs at microsoft dot com.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
0

(The following is based solely on observation.)

If you have a function obj -> 'a, calls to that function are not used to infer/solve the type of its argument. An illustration:

let writeLine (arg: obj) = System.Console.WriteLine(arg)

writeLine is obj -> unit

let square x = 
  writeLine x
  x * x

In the above function x is inferred as int because of (*). If a type could be constrained by obj then this function would not work (x would be inferred as obj prior to the use of (*), which would cause an error along the lines of: type obj does not support operator (*)).

I think this behavior is a Good Thing. There's no need to restrict a type as obj because every type is already implicitly convertible to obj. This allows your program to be more generic and provides better interoperability with the .NET BCL.

In short, obj has no bearing on type inference (yay!).

Daniel
  • 47,404
  • 11
  • 101
  • 179