Can Nullable be used as a functor in C#?

|▌冷眼眸甩不掉的悲伤 提交于 2020-08-22 08:32:33

问题


Consider the following code in C#.

public int Foo(int a)
{
    // ...
}

// in some other method

int? x = 0;

x = Foo(x);

The last line will return a compilation error cannot convert from 'int?' to 'int' which is fair enough. However, for example in Haskell there is Maybe which is a counterpart to Nullable in C#. Since Maybe is a Functor I would be able to apply Foo to x using fmap. Does C# have a similar mechanism?


回答1:


We can implement such functionality ourselves:

public static class FuncUtils {

    public static Nullable<R> Fmap<T, R>(this Nullable<T> x, Func<T, R> f)
        where T : struct
        where R : struct {
        if(x != null) {
            return f(x.Value);
        } else {
            return null;
        }
    }

}

Then we can use it with:

int? x = 0;
x = x.Fmap(Foo);

It will thus call the function Foo if x is not null. It will wrap the result back in a Nullable<R>. In case x is null, it will return a Nullable<R> with null.

Or we can write a more equivalent function (like fmap in Haskell) where we have a function Fmap that takes as input a Func<T, R> and returns a Func<Nullable<T>, Nullable<R>> so that we can then use it for a certain x:

public static class FuncUtils {

    public static Func<Nullable<T>, Nullable<R>> Fmap<T, R>(Func<T, R> f)
        where T : struct
        where R : struct {
        return delegate (Nullable<T> x) {
            if(x != null) {
                return f(x.Value);
            } else {
                return null;
            }
        };
    }

}

We can then use it like:

var fmapf = FuncUtils.Fmap<int, int>(Foo);
fmapf(null);  // -> null
fmapf(12);    // -> Foo(12) as int?



回答2:


Functor

Not only can you turn Nullable<T> into a functor, but C# actually understands functors, enabling you to write something like this:

x = from x1 in x
    select Foo(x1);

If you prefer method call syntax, that's also possible:

x = x.Select(Foo);

In both cases, you need an extension method like this:

public static TResult? Select<T, TResult>(
    this T? source,
    Func<T, TResult> selector) where T : struct where TResult : struct
{
    if (!source.HasValue)
        return null;

    return new TResult?(selector(source.Value));
}

Monad

Not only does C# understand functors, but it understands monads as well. Add these SelectMany overloads as well:

public static TResult? SelectMany<T, TResult>(
    this T? source,
    Func<T, TResult?> selector)
    where T : struct
    where TResult : struct
{
    if (!source.HasValue)
        return null;

    return selector(source.Value);
}

public static TResult? SelectMany<T, U, TResult>(
    this T? source,
    Func<T, U?> k,
    Func<T, U, TResult> s)
    where T : struct
    where TResult : struct
    where U : struct
{
    return source
        .SelectMany(x => k(x)
            .SelectMany(y => new TResult?(s(x, y))));
}

This enables you to write queries like this:

var result = from x in (int?)6
             from y in (int?)7
             select x * y;

Here, result is an int? containing the number 42.




回答3:


If you have an extension method:

public int Foo(this int a)
{
    // ...
}

you can do:

// in some other method

int? x = 0;

x = x?.Foo();

The ?. operator will ensure Foo is called only if x is not null. If x is null, it is not called (a null of the return type is used instead).


Otherwise, the canonical way to write it is naturally:

x = x.HasValue ? Foo(x.Value) : (int?)null;

Of course you can create your own Maybe infrastructure if you will (Willem Van Onsem's answer).



来源:https://stackoverflow.com/questions/48488178/can-nullable-be-used-as-a-functor-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!