简体   繁体   中英

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?

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.

Tomas' answer is correct. I'll share my workaround for posterity.

My measure units were

 [<Measure>] type Absolute
 [<Measure>] type Relative 

I introduced 'concrete' record types that were convertible to Vec2<> for interop. For example

type Offset2 = { 
   X : int
   Y : int 
}
   with 
       static member ofVec (vec : Vec2<Relative>) : Offset2 = { X = vec.X; Y = vec.Y }
       static member toVec (off: Offset2) : Vec2<Relative> = { X = off.X; Y = off.Y }

I convert them into Vec2's to perform math and but use the 'concrete' types for public APIs. It is a little annoying to have the extra types but it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM