Consider these two extension method signatures:

public static async Task<V> SelectMany<T, U, V>(
    this Task<T> self,
    Func<T, Task<U>> bind,
    Func<T, U, V> project) => // etc

public static Task<Validation<FAIL, C>> SelectMany<FAIL, A, B, C>(
    this Task<Validation<FAIL, A>> self,
    Func<A, Task<Validation<FAIL, B>>> bind,
    Func<A, B, C> project) => // etc

If both of these are in the same namespace then the compiler cannot always infer whether you meant Task<V> or the more specific Task<Validation<FAIL, C>> (where V is Validation<FAIL, C>).

This particular example is for extensions in the LanguageExt library, which would typically be used in a LINQ query like:

public Task<Validation<Error, int>> GetX() => // etc
public Task<Validation<Error, int>> GetY() => // etc

public Task<Validation<Error, int>> GetResult() =>
    from x from GetX()
    from y from GetY()
    select x + y;

The compiler will try to find a SelectMany implementation to bind together the rows of the LINQ query-syntax block, but, as used here, will report the choice is ambiguous.  Both extensions are in the LanguageExt namespace (though one is in LanguageExt.Core package and the other in LanguageExt.Transformers) so adding using LanguageExt brings both into scope.

One way to work around this is by using how the compiler searches for matching extension methods.

From the C# spec

The search [..] proceeds as follows:

Starting with the closest enclosing namespace declaration, continuing with each enclosing namespace declaration, and ending with the containing compilation unit, successive attempts are made to find a candidate set of extension methods:

If the given namespace or compilation unit directly contains non-generic type declarations Cᵢ with eligible extension methods Mₑ, then the set of those extension methods is the candidate set.

If namespaces imported by using namespace directives in the given namespace or compilation unit directly contain non-generic type declarations Cᵢ with eligible extension methods Mₑ, then the set of those extension methods is the candidate set.

So .. to pick one extension method you can add your own version of the extension methods within your application that just delegate to the external implementations you want to use.  These extensions have to be at the same or a higher level in the namespace hierarchy as the calling code so the compiler will select them first and (following the rules above) the two ambiguous versions in the LanguageExt namespace will never be considered.

For the original example, where I wanted to use the extension for Task<Validation<FAIL, C>> the extensions can just delegate to the generated source code in LanguageExt.Transformers:

using System;
using System.Threading.Tasks;
using LanguageExt;

namespace YourApplication;

public static class BindDisambiguationExtensions
{
    public static Task<Validation<FAIL, B>> Select<FAIL, A, B>(
        this Task<Validation<FAIL, A>>self,
        Func<A, B> f) =>
        ValidationT_AsyncSync_Extensions.Select(self, f);

    public static Task<Validation<FAIL, C>> SelectMany<FAIL, A, B, C>(
        this Task<Validation<FAIL, A>> self,
        Func<A, Task<Validation<FAIL, B>>> bind,
        Func<A, B, C> project) =>
        ValidationT_AsyncSync_Extensions.SelectMany(self, bind, project);
}

If you are using stacked monads in LanguageExt then you may encounter similar issues with different combinations of types (eg Task<Either<), in which case you can find the source-code for the extensions class name of the particular combination of stacked monads you are using and delegate to that.

Note: I have also posted this as an answer to a stackoverflow question.

Comments


Comments are closed