Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Nullable be used as a functor in C#?

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?

like image 442
Bobby Avatar asked Jan 28 '18 15:01

Bobby


People also ask

Is maybe a functor?

Summary. Together with a functor called Either, Maybe is one of the workhorses of statically typed functional programming.

Is Haskell NULL safe?

But it isn't null-safe. Hence, it is well suited to demonstrate the problem of the null pointer error. Haskell is the most famous one in the category of pure functional languages. It doesn't support null .


3 Answers

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?
like image 156
Willem Van Onsem Avatar answered Oct 01 '22 14:10

Willem Van Onsem


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.

like image 42
Mark Seemann Avatar answered Sep 29 '22 14:09

Mark Seemann


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).

like image 34
Jeppe Stig Nielsen Avatar answered Sep 28 '22 14:09

Jeppe Stig Nielsen