问题
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