Search code examples
f#interoprecordsunits-of-measurement

How can I create a measured record type without a measure in F#


I have defined a record type with a unit of measure in a common library (LibRoot) that is used by both C# (LibC) and F# (LibF) code.

I then wrote a public API in the C# library (LibC) that the F# library (LibF) consumes. But any time i attempt to pass an object to this API F# complains that it must have a unit of measure.

//LibRoot - F#
type Vec2<[Measure] 'u> = {
    X : int
    Y : int
}
//LibC - C#
public static class Funcs
{
    public void DoWork(Vec2 vec) { //no measure needed in C#
        ....
    }
}
//LibF - F#

open LibC
let myVec : Vec2<1> = { X = 123; Y = 456 }
DoWork(myVec) //FS0001

error FS0001: Type mismatch. Expecting a 'Vec2' but given a 'Vec2<1>' The tuples have differing lengths of 0 and 1

I've tried:

  • Vec2<1> : FS0001
  • Vec2<0> : Invalid Literal in type
  • Vec2<()> : Unexpected ')' in type
  • Vec2<_> : FS0001
  • Vec2<unit> : Expected unit of measure, not type
  • (Vec2) myVec : No constructors available for type 'Vec2<'u>

Does anybody know a way to construct a measured record type in an interop friendly way?


Solution

  • As mentioned in the comment, units of measure are F#-only feature that has no representation in the compiled .NET code, so when you use a unit-annotated type from C#, it will appear as a type without units.

    The compiled C# code will not contain special F# meta-data to indicate that this is a unit-annotated type and so the F# compiler referencing your C# library does not recognise it as unit-annotated type. Arguably, the F# compiler could be smarter and figure this out (because it can figure out that the Vec2 type is originally coming from F#).

    I don't think there is a safe way of converting un-measured Vec2 to measured Vec2<_> but the unsafe conversion using unbox will work at runtime:

    let v : LibRoot.Vec2<1> = { LibRoot.Vec2.X = 1; Y = 2 }
    LibCs.Funcs.DoWork(unbox v)
    

    I don't think there is a way of referring to the type Vec2 (without measure) explicitly in F# code, but unbox infers the type fine. This is not very nice, so if you need this conversion often, it's probably better to redesign your library so that C# uses a separate type without units.