1
votes

I am attempting to create a public method in a class in F#. The equivalent in C# would be:

public void MyMethod<T>(string name, Thing<T> thingToProcess)
{
    // Do stuff
}

In F#, I am trying:

member public this.MyMethod<'T>((name : System.String), (thingToProcess : Thing<'T>)) =
    (* Do similar stuff *)
    ()

This code generates the compiler error:

error FS0670: This code is not sufficiently generic. The type variable 'T could not be generalized because it would escape its scope.

If I try the following, I can compile:

member public this.MyMethod((name : System.String), (thingToProcess : Thing<_>)) =
    (* Some code *)
    ()

But, trying to call the method from C# like the following fails:

public void DoSomething<T>(Thing<T> thingToProcess)
{
    _instanceOfFSharpClass.MyMethod("A string", thingToProcess);
}

with compiler error:

The best overloaded method match for 'MyFSharpClass.MyMethod(string, Thing)' has some invalid arguments.

Suggestions? How do I create this type of method in F#? If this method cannot be created in F#, what is a reasonable workaround? I need to avoid casting Thing<T> to Thing<object> if at all possible.

Edit:

Here is more of the F# code. I'll try to stick to likely-relevant portions. EnumWithFlags is an enumeration from a C# assembly with the [FlagsAttribute]. cacheOne is populated in other methods not listed here. IInterface is defined in the C# assembly and has only one method, void ReceiveThing<T>(string name, Thing<T> thingToProcess). The function TranslateThing has signature val TranslateThing : (Guid -> Thing<'T> -> TranslatedThing<'T>). Does this help?

type TranslatedThing<'T> =
    | FirstThing of Thing<'T>
    | SecondThing of Thing<System.String>
    | ThirdThing of Thing<byte[]>
    | FourthThing of Thing<System.String>
    | IgnoreThing

[<AbstractClass>]
type public MyAbstractClass() =
    let cacheOne = new ConcurrentDictionary<EnumWithFlags, Dictionary<Guid, IInterface>>()

    member public this.MyMethod<'T>((name : System.String), (thingToProcess : Thing<'T>)) =
        cacheOne.Keys.Where(fun key -> match key with
                                       | k when (k &&& thingToProcess.EnumWithFlagsProperty) = EnumWithFlags.None -> false
                                       | _ -> true)
                     .SelectMany(fun key -> cacheOne.[key].AsEnumerable())
                     .Distinct(
                         {
                             new IEqualityComparer<KeyValuePair<Guid, IInterface>> with
                                 member x.Equals(a, b) = a.Key = b.Key
                                 member x.GetHashCode y = y.Key.GetHashCode()
                         })
                     .AsParallel()
                     .Select(new Func<KeyValuePair<Guid, IInterface>, Tuple<IInterface, TranslatedThing<_>>>(fun kvp -> new Tuple<IInterface, TranslatedThing<'T>>(kvp.Value, TranslateThing kvp.Key thingToProcess)))
                     .Where(new Func<Tuple<IInterface, TranslatedThing<'T>>, bool>(fun t -> t.Item2 <> IgnoreThing))
                     .ForAll(new Action<Tuple<IInterface, TranslatedThing<'T>>>(fun t ->
                                 match t.Item2 with
                                 | FirstThing(x) -> t.Item1.ReceiveThing(name, x)
                                 | SecondThing(x) -> t.Item1.ReceiveThing(name, x)
                                 | ThirdThing(x) -> t.Item1.ReceiveThing(name, x)
                                 | FourthThing(x) -> t.Item1.ReceiveThing(name, x)
                                 | _ -> ()))

Another edit:

After much distillation, I think I see roughly what is causing the issue. I left off the last line of MyMethod because taking it out did not solve the error. This line was:

cacheTwo.Remove(thingToProcess) |> ignore

where cacheTwo is defined earlier in the class:

let cacheTwo = new Dictionary<Thing<'T>, SpecificThingTranslator<'T>>

where the signature of SpecificThingTranslator<'T> is:

type SpecificThingTranslator<'T> =
 {First: TranslatedThing<'T>;
  Second: Lazy<TranslatedThing<'T>>;
  Third: Lazy<TranslatedThing<'T>>;
  Fourth: Lazy<TranslatedThing<'T>>;}

Eliminating the cacheTwo line did not solve the error because the function TranslateThing refers to cacheTwo, eventually. Eliminating all references to cacheTwo eliminates the error.

I can probably find a workaround to map Thing<'T> to SpecificThingTranslator<'T>. Nevertheless, have I missed something here? Have I forgotten about a .NET collection (or maybe an F#-specific one) that will allow this mapping? While the type parameter for the key and value of each pair must be the same, each KeyValuePair (or equivalent) can have a different type parameter.

1
I will, time permitting, edit the question for brevity and clarity once there is a clear solution. The compiler error is a bit mystifying to an F# newcomer, and the Google-the-error-message approach did not yield any results that seemed to apply. My hope is that another programmer encountering the error message can benefit from this question and answer. - Andrew

1 Answers

7
votes

There must be something wrong with another part of your code. The following minimal sample has exactly the same definition of MyMethod and works fine (when pasted into a new script file):

type Thing<'T> = T of 'T

type Foo() =
  member this.MyMethod<'T>(name:string, thingToProcess:Thing<'T>) =
      ()

I removed the public modifier, because that is the default for members in F# and I also removed additional parentheses, but otherwise, nothing has changed....