For the Froto project (Google Protobuf in F#), I am trying to update the deserialization code from using 'a ref
objects to passing values byref<'a>
, for performance.
However, the code below fails on the hydrator &element field
line:
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 get this code to work without changing the signature of the hydrator
parameter?
I'm very aware that I could use hydrator:'a ref -> Field -> unit
and get things to work. However, the goal is to support deserializing into record
types without needing to create a bunch of ref
objects on the heap every time a record is deserialize.
Note that the following code is perfectly legal and has the same signature as the hydrator
function declaration, above, so I'm unclear on 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
1 Celsius is equal to 33.8 Fahrenheit.
Answer: 1° Celsius is equivalent to 33.8° Fahrenheit. Let us use the formulae of conversion between Celsius and Fahrenheit scales. Explanation: The formula to convert Celsius to Fahrenheit is given by °F = °C × (9/5) + 32.
Answer: 50° Celsius is equal to 122° Fahrenheit.
The formula for converting Fahrenheit to Celsius is C = 5/9(F-32). Fahrenheit and Celsius are the same at -40°. At ordinary temperatures, Fahrenheit is a larger number than Celsius. For example, body temperature is 98.6 °F or 37 °C.
I'll try to clarify what I was saying in my comments. You're right that your definition of assign
is perfectly fine, and it appears to have the signature byref<'a> -> 'a -> unit
. However, if you look at the resulting assembly, you'll find that the way it's compiled at the .NET representation level is:
Void assign[a](a ByRef, a)
(that is, it's a method that takes two arguments and doesn't return anything, not a function value 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 the method was actually declared).
The same is true of function definitions that don't involve byref
. For instance, assume you've got the following definition:
let someFunc (x:int) (y:string) = ()
Then the compiler actually creates a method with the 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 isn't applied to any arguments, the compiler will generate a subtype of int -> string -> unit
(which is FSharpFunc<int, FSharpFunc<string, unit>>
at the .NET representation level), and everything works seamlessly.
However, if you try to do the same thing with assign
, it won't work (or shouldn't work, but there are several compiler bugs that may make it seem like certain variations work when really they don't - you might not get a compiler error but you may get an output assembly that is malformed instead) - it's not legal for .NET type instantiations to use byref
types as generic type arguments, so FSharpFunc<int byref, FSharpFunc<int, unit>>
is not a valid .NET type. The fundamental way that F# represents function values just doesn't work when there are byref
arguments.
So the workaround is to create your own type with a method taking a byref
argument and then create subtypes/instances that have the behavior you want, sort of like doing manually what the compiler does automatically in the non-byref
case. You could 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 a delegate type
type MyByrefDelegate2<'a,'b> = delegate of 'a byref * 'b -> unit
let assign = MyByrefDelegate2(fun result x -> result <- x)
Note that when calling methods like Invoke
on the delegate or nominal type, no actual tuple is created, so you shouldn't be concerned about any extra overhead there (it's a .NET method that takes two arguments and is treated as such by the compiler). There is the cost of a virtual method call or delegate call, but in most cases similar costs exist when using function values in a first class way too. And in general, if you're worried about performance then you should set a target and measure against it rather than trying to optimize prematurely.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With