Nullable 可以用作 C# 中的函子吗?
Can Nullable be used as a functor in C#?
考虑以下 C# 代码。
public int Foo(int a)
{
// ...
}
// in some other method
int? x = 0;
x = Foo(x);
最后一行将 return 编译错误 cannot convert from 'int?' to 'int'
这很公平。但是,例如在 Haskell 中有 Maybe
,它对应于 C# 中的 Nullable
。由于 Maybe
是 Functor
,因此我可以使用 fmap
将 Foo
应用于 x
。 C#有没有类似的机制?
我们可以自己实现这样的功能:
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);
如果x
不是null
,它将因此调用函数Foo
。它会将结果包装回 Nullable<R>
。如果 x
是 null
,它将 return 一个 Nullable<R>
和 null
。
或者我们可以编写一个更等效的函数(如 Haskell 中的 fmap
),其中我们有一个函数 Fmap
,它以 Func<T, R>
和 returns a 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?
如果你有扩展方法:
public int Foo(this int a)
{
// ...
}
你可以做到:
// in some other method
int? x = 0;
x = x?.Foo();
?.
运算符将确保仅当 x
不为空时才调用 Foo
。如果 x
为 null,则不调用它(而是使用 return 类型的 null)。
否则,规范的写法自然是:
x = x.HasValue ? Foo(x.Value) : (int?)null;
当然,如果愿意,您可以创建自己的 Maybe
基础设施(Willem Van Onsem 的回答)。
函子
您不仅可以将 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
是一个包含数字 42
的 int?
。
考虑以下 C# 代码。
public int Foo(int a)
{
// ...
}
// in some other method
int? x = 0;
x = Foo(x);
最后一行将 return 编译错误 cannot convert from 'int?' to 'int'
这很公平。但是,例如在 Haskell 中有 Maybe
,它对应于 C# 中的 Nullable
。由于 Maybe
是 Functor
,因此我可以使用 fmap
将 Foo
应用于 x
。 C#有没有类似的机制?
我们可以自己实现这样的功能:
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);
如果x
不是null
,它将因此调用函数Foo
。它会将结果包装回 Nullable<R>
。如果 x
是 null
,它将 return 一个 Nullable<R>
和 null
。
或者我们可以编写一个更等效的函数(如 Haskell 中的 fmap
),其中我们有一个函数 Fmap
,它以 Func<T, R>
和 returns a 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?
如果你有扩展方法:
public int Foo(this int a)
{
// ...
}
你可以做到:
// in some other method
int? x = 0;
x = x?.Foo();
?.
运算符将确保仅当 x
不为空时才调用 Foo
。如果 x
为 null,则不调用它(而是使用 return 类型的 null)。
否则,规范的写法自然是:
x = x.HasValue ? Foo(x.Value) : (int?)null;
当然,如果愿意,您可以创建自己的 Maybe
基础设施(Willem Van Onsem 的回答)。
函子
您不仅可以将 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
是一个包含数字 42
的 int?
。