In F #, is it possible to pass a link to a mutable default value as a parameter?

For the Froto project (Google Protobuf in F #), I am trying to update the deserialization code using 'a ref objects to passing the values byref<'a> , for performance.

However, the code below does not work on the hydrator &element field :

 type Field = TypeA | TypeB | Etc let hydrateRepeated (hydrator:byref<'a> -> Field -> unit) (result:byref<'a list>) (field:Field) = let mutable element = Unchecked.defaultof<'a> hydrator &element field result <- element :: result 

error FS0421: the address of the variable 'element' cannot be used at this point

Is there anything I can do to make this code work without changing the signature of the hydrator parameter?

I understand very well that I can use hydrator:'a ref -> Field -> unit and make it work. However, the goal is to support deserialization in record types without having to create a bunch of ref objects on the heap every time a record is deserialized.

Please note that the following code is completely legal and has the same signature as the declaration of the hydrator function above, so I don’t understand what the problem is.

 let assign (result:byref<'a>) (x:'a) = result <- x let thisWorks() = let mutable v = Unchecked.defaultof<int> assign &v 5 printfn "%A" v 
+8
mutable f #
source share
1 answer

I will try to clarify what I said in my comments. You are right that your assign definition is excellent, and it looks like it has the signature byref<'a> -> 'a -> unit . However, if you look at the final assembly, you will find that the method compiled at the .NET presentation level:

 Void assign[a](a ByRef, a) 

(i.e. this is a method that takes two arguments and returns nothing, not the value of a function that takes one argument and returns a function that takes the next argument and returns a value of type unit - the compiler uses some additional metadata to determine how this method was actually declared).

The same applies to function definitions that are not related to byref . For example, suppose you have the following definition:

 let someFunc (x:int) (y:string) = () 

Then the compiler actually creates a method with a signature

 Void someFunc(Int32, System.String) 

The compiler is smart enough to do the right thing when you try to use a function like someFunc as a first class value - if you use it in a context where it does not apply to any arguments, the compiler will generate a subtype int -> string -> unit ( which is FSharpFunc<int, FSharpFunc<string, unit>> at the .NET view level) and everything works without problems.

However, if you try to do the same with assign , this will not work (or should not work, but there are some compiler errors that may make it seem that some variations work when they really don't work, t - you may not get a compiler error, but you may get an assembly of results that was malformed). It is not legal for instances of type .NET to use byref types as arguments of a general type, therefore FSharpFunc<int byref, FSharpFunc<int, unit>> not valid. NET The fundamental way that F # represents function values ​​just doesn't work when there are byref arguments.

So, a workaround is to create your own type using a method that takes a byref argument, and then create subtypes / instances that have the behavior you want, like manually doing what the compiler does automatically in a non-t24> case. You can do this with a named type

 type MyByrefFunc2<'a,'b> = abstract Invoke : 'a byref * 'b -> unit let assign = { new MyByrefFunc2<_,_> with member this.Invoke(result, x) = result <- x } 

or with delegation type

 type MyByrefDelegate2<'a,'b> = delegate of 'a byref * 'b -> unit let assign = MyByrefDelegate2(fun result x -> result <- x) 

Please note that when calling methods such as Invoke in a delegate or nominal type, the actual tuple is not created, so you should not worry about any additional overhead (this is a .NET method that takes two arguments and is processed as such by the compiler) . There is the cost of invoking a virtual method or delegation, but in most cases similar costs exist when using function values ​​in the first class. And in general, if you are worried about performance, you should set a goal and measure it, and not try to optimize prematurely.

+6
source share

All Articles