Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In F#, is it possible to pass a reference to a mutable, defaulted value as a parameter?

Tags:

mutable

f#

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
like image 366
James Hugard Avatar asked Mar 07 '16 16:03

James Hugard


People also ask

What temp is C in F?

1 Celsius is equal to 33.8 Fahrenheit.

What is 1 C equal to in 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.

What temperature in Fahrenheit is 50 C?

Answer: 50° Celsius is equal to 122° Fahrenheit.

How do you calculate F to C?

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.


1 Answers

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.

like image 125
kvb Avatar answered Nov 08 '22 22:11

kvb