4

Code example: http://www.tryfsharp.org/create/dutts/Generics.fsx

I have some mapping code in my F# which takes a C# object and wraps it in a discriminated union.

module MyModule =
    type MappedThings =
        | DoThings of External.Things.DoThings

    type MappedStuff =
        | DoStuff of External.Stuff.DoStuff

As I always use the same name in my discriminated union as the external object I would like to try to make my mapping code generic for scalability. This is what I've tried so far:

let toDomain<'T> external : 'T =
    let found = FSharpType.GetUnionCases(typeof<'T>) |> Seq.where (fun t -> t.Name = external.GetType().Name) |> Seq.head
    FSharpValue.MakeUnion(found, [| box external |]) :?> 'T  

I am trying to use it like this:

let testThings = toDomain<MyModule.MappedThings> doExternalThings
let testStuff = toDomain<MyModule.MappedStuff> doExternalStuff 

This works fine for the first call, but if I try to use it for MyModule.MappedStuff type it complains with

This expression was expected to have type DoThings but here has type DoStuff

I've tried using statically resolved type parameters, ^T, but the typeof<^T> complains.

I was thinking I could get this to work if I could somehow pass the "Type" (if that's the correct term, e.g. MyModule.Mapped) as a parameter but I don't know how to get that programmatically.

Can anyone help?

Dutts
  • 5,781
  • 3
  • 39
  • 61
  • I think you need the counter-intuitively named keyword "inline" in front of toDomain to prevent the compiler from drawing too many conclusions about your function and pinning it down to 1 type. – BitTickler Feb 16 '15 at 09:27
  • Maybe along with some type constraints. – BitTickler Feb 16 '15 at 09:36
  • I have updated my question with some more info of the error. I get the same error if I use inline. Could this be because I'm playing around in am F# script and F# Interactive? I haven't tried putting this into a project yet? – Dutts Feb 16 '15 at 09:45
  • Cannot repro. Your function is indeed generic (signature `val toDomain : external:obj -> 'T`) and when I substitute your undeclared values `doExternalThings` and `doExternalStuff` with actual instances of `External.DoThings` and `External.DoStuff`, it works as expected. – kaefer Feb 16 '15 at 10:22
  • Here's what I'm seeing @kaefer http://www.tryfsharp.org/create/dutts/Generics.fsx – Dutts Feb 16 '15 at 11:05
  • Just realised the difference between my production code and the sanitised version here, my input external objects are in separate namespaces.... so External.Things.DoThings and External.Stuff.DoStuff if that's relevant? I've updated the code example in the question. – Dutts Feb 16 '15 at 11:10

1 Answers1

4

I think that the additional boxing that you introduce for the 2nd parameter of Makeunion throws the type inference off the track and in fact, the function isn't generic anymore. Either annotate the argument or remove the box.

let toDomain<'T> external : 'T =
    let found = FSharpType.GetUnionCases(typeof<'T>) |> Array.find (fun t -> t.Name = external.GetType().Name) 
    FSharpValue.MakeUnion(found, [| external |]) :?> 'T 
kaefer
  • 5,491
  • 1
  • 15
  • 20
  • 1
    That's excellent, thank you so much! The boxing was a hangover from before I tried to make it generic. – Dutts Feb 16 '15 at 13:59