请考虑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
相对应。由于Maybe
是Functor
,我可以使用Foo
将x
应用于fmap
。 C#有类似的机制吗?
答案 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>
。如果x
为null
,则会返回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;
此处result
是int?
,其中包含数字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的回答)。