Home > Software design >  Why can the compiler sometimes not implicitly infer the return type that contains the generic type?
Why can the compiler sometimes not implicitly infer the return type that contains the generic type?

Time:01-13

Below is some code in regards to functional progamming (the question itself doesn't relate to functional programming):

public delegate Exceptional<T> Try<T>();

public static Exceptional<T> Run<T>(this Try<T> @try)
{
   try { return @try(); }
   catch (Exception ex) { return ex; }
}

public struct Exceptional<T>
{
      private Exception? Ex { get; }
      private T? Value { get; }
      
      private bool IsSuccess { get; }
      private bool IsException => !IsSuccess;

      internal Exceptional(Exception ex)
      {
         IsSuccess = false;
         Ex = ex ?? throw new ArgumentNullException(nameof(ex));
         Value = default;
      }

      internal Exceptional(T value)
      {
         IsSuccess = true;
         Value = value ?? throw new ArgumentNullException(nameof(value));
         Ex = default;
      }

      public static implicit operator Exceptional<T>(Exception ex) => new (ex);
      public static implicit operator Exceptional<T>(T t) => new (t);

      public TR Match<TR>(Func<Exception, TR> Exception, Func<T, TR> Success)
         => this.IsException ? Exception(Ex!) : Success(Value!);

}

public static class TryExt
{
    public static Try<R> Map<T, R>(this Try<T> @try, Func<T, R> f)
    {
        return () => @try.Run().Match<Exceptional<R>>(ex => ex, t => f(t));
    }

    public static Try<R> Bind<T, R>(this Try<T> @try, Func<T, Try<R>> f)
    {
        return () => @try.Run().Match<Exceptional<R>>(ex => ex, t => f(t).Run());  // doesn't need Match<Exceptional<R>>, can be Match(ex => ex, t => f(t).Run())
    }
}

enter image description here

As you can see inside the second method Bind, Exceptional<R> in Match<Exceptional>(...) is not highlighted, which means I don't need to specify Exceptional<R>return type for the Match method.

However, in the first method Map,Exceptional<R> is needed for the Match method, so why can the compiler detect the return type for Bind, but not Map, when Bind and Map method signatures look quite similar?, What is the reason the compiler can infer one but not the other?

CodePudding user response:

Since Match is defined as:

TR Match<TR>(Func<Exception, TR> Exception, Func<T, TR> Success)

Then here:

public static Try<R> Map<T, R>(this Try<T> @try, Func<T, R> f)
{
    return () => @try.Run().Match(ex => ex, t => f(t));
}

first argument to Match accepts exception and returns exception. Second argument accepts T and returns R. R is arbitrary type and so compiler cannot find common type TR to which both Exception and R can be converted. Yes there is implicit conversion from any type to Exceptional<T> but compiler cannot search all available types for implicit conversions.

In second case

public static Try<R> Bind<T, R>(this Try<T> @try, Func<T, Try<R>> f)
{
    return () => @try.Run().Match(ex => ex, t => f(t).Run())
}

first argument is the same, but second now returns Exceptional<R>. Now there is common type TR - that is Exceptional<R>, because Exception can be implicitly converted to that type. So compiler infers TR in Match to be Exceptional<R> and the rest compiles. The difference is that now compiler can find implicit conversion because one of the types (Exception) can be converted to the other (Exceptional<R>). In first case there were R and Exception types which cannot be converted to each other without additional information (that they both can be converted to another type not mentioned anywhere - to Exceptional<R>).

Note that, as Eric Lippert describes here - type information during inference flows from inside to outside only. That means first Match types should be inferred and then inference continues to outside (if necessary). As such it does not use the fact that return type of Map is Try<T> which is basically Func<Exceptional<R>>, and so the return type of Match should be Exceptional<R>.

  • Related