Search code examples
c#functional-programmingintellisense

'Lifting' a C# function as a monoidal fails and changes signature


I'm trying to write a general monoidal pattern in C#, starting with a homogeneous function combining two non-null values, and returning either value if the other is null, or the combined value if neither is, or null. So I have this:

public static Func<TIn?, TIn?,TIn?> Drop2<TIn>(Func<TIn, TIn, TIn> f)
{
    return (lhs, rhs) =>
    {
        if (lhs == null && rhs == null)
        {
            return default;
        }

        if (lhs == null && rhs != null)
        {
            return rhs;
        }

        if (rhs == null && lhs != null)
        {
            return lhs;
        }

        return f(lhs, rhs);
    };
}

This looks fine and it even compiles, but when I try to use it, two odd things happen.

    Func<int, int, int> sum = (lhs, rhs) => lhs + rhs;

    var sumNonNull = DropNullable.Drop2(sum);

The Intellisense for sumNonNull shows as Func<int, int, int>?, not the expected Func<int?, int?, int?>, and I can't pass in null as either argument for sumNonNull (can't convert from int? to int).

Should this work? What am I missing?

Thanks in advance for any help


Solution

  • I think you want something like this:

        public static Func<TIn?, TIn?, TIn?> Drop2<TIn>(Func<TIn?, TIn?, TIn?> f)
        {
            return (lhs, rhs) =>
                  lhs is null ? rhs
                : rhs is null ? lhs
                : f(lhs, rhs);
        }
    

    note TIn? in both input argument and the result (both of them can be null). Then you can use it as follow:

    // note int? - sumInt accepts nulls (int? is a short for Nullable<int>)
    Func<int?, int?, int?> sumInt = (a, b) => a + b;
    
    Console.WriteLine(Drop2(sumInt)(null, 123));
    Console.WriteLine(Drop2(sumInt)(456, null));
    Console.WriteLine(Drop2(sumInt)(456, 123));
    Console.WriteLine(Drop2(sumInt)(null, null));
    
    // strings can be null
    Func<string, string, string> sumStr = (a, b) => a + b;
    
    Console.WriteLine(Drop2(sumStr)(null, "123"));
    Console.WriteLine(Drop2(sumStr)("456", null));
    Console.WriteLine(Drop2(sumStr)("456", "123"));
    Console.WriteLine(Drop2(sumStr)(null, null));