Nullable可以在C#中用作函子吗?

时间:2018-01-28 15:48:57

标签: c# haskell functional-programming nullable functor

请考虑C#中的以下代码。

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

// in some other method

int? x = 0;

x = Foo(x);

最后一行将返回编译错误cannot convert from 'int?' to 'int',这是公平的。但是,例如在Haskell中,Maybe与C#中的Nullable相对应。由于MaybeFunctor,我可以使用Foox应用于fmap。 C#有类似的机制吗?

3 个答案:

答案 0 :(得分:16)

我们可以自己实现这样的功能:

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;
        }
    }

}

然后我们可以将它用于:

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

如果Foo不是x,它将调用函数null。它会将结果包装回Nullable<R>。如果xnull,则会返回Nullable<R> null

或者我们可以编写一个更等效的函数(比如Haskell中的fmap),其中我们有一个函数Fmap,它将Func<T, R>作为输入并返回Func<Nullable<T>, Nullable<R>>所以然后我们可以将它用于某个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;
            }
        };
    }

}

我们可以像以下一样使用它:

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

答案 1 :(得分:10)

函子

您不仅可以将Nullable<T>转变为仿函数,而且 C#实际上理解仿函数,使您能够编写如下内容:

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

如果您更喜欢方法调用语法,那也可以:

x = x.Select(Foo);

在这两种情况下,您都需要这样的扩展方法:

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));
}

单子

C#不仅可以理解仿函数,还可以理解monad。同时添加这些SelectMany重载:

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))));
}

这使您可以编写如下查询:

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

此处resultint?,其中包含数字42

答案 2 :(得分:4)

如果您有扩展方法:

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

你可以这样做:

// in some other method

int? x = 0;

x = x?.Foo();

?.运算符仅在Foo不为空时才会确保调用x。如果x为null,则不会调用它(而是使用返回类型的null)。

否则,编写它的规范方法自然是:

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

当然,如果愿意,你可以创建自己的Maybe基础设施(Willem Van Onsem的回答)。