1

I have lately been dipping my feet into the fascinating world of functional programming, largely due to gaining experience in FP platforms like React and reading up on blogs the likes of https://blog.ploeh.dk/. As a primarily imperative programmer, this has been an interesting transition, but I am still trying to get my wet feet under me.

I am getting a little tired of using string.IsNullOrEmpty as such. So much of the time I find myself littering my code with expressions such as

_ = string.IsNullOrEmpty(str) ? "default text here" : str;

which isn't so bad as it goes, but say I wanted to chain a bunch of options past that null, e.g.

_ = string.IsNullOrEmpty(str) ? (
    util.TryGrabbingMeAnother() ??
    "default text here") : str;

Yuck. I'd much rather have something like this --

_ = monad.NonEmptyOrNull(str) ??
    util.TryGrabbingMeAnother() ??
    "default text here";

As the sample indicates, I am using a function that I am referring to as a monad to help reduce string.IsNullOrEmpty to a null-chainable operation:

public string NonEmptyOrNull(string source) =>
    string.IsNullOrEmpty(source) ? null : source;

My question is, is this proper terminology? I know Nullable<T> can be considered a monad (see Can Nullable be used as a functor in C#? and Monad in plain English? (For the OOP programmer with no FP background)). These materials are good references, but I still don't have quite enough an intuitive grasp of the subject to know if I'm not just being confusing or inconsistent here. For example, I know monads are supposed to enable function chaining like I have above, but they are also "type amplifiers" -- so my little example seems to behave like a monad for enabling chaining, but it seems like converting null/empty to just null is a reduction rather than an amplification, so I question whether this actually is a monad. So for this particular application, could someone who has a little more experience with FP tell me whether or not it is accurate to call NonEmptyOrNull a monad, and why or why not?

Bondolin
  • 2,793
  • 7
  • 34
  • 62
  • `NonEmpty` and `Option` (or `Nullable`) are both monads, so you can combine them to get the desired type. You cannot compose monads in general but you can write a function that extends a monad by the semantics of another one. This approach is referred to as monad transformers. Anyway, I'd advice you to learn functors and applicative functors first, because monads are also functors and the latter determine about 80% of their semantics. I've been writing an FP course in case you are interested. –  Jul 14 '20 at 14:28

3 Answers3

2

A monad is a triple consisting of:

  • A single-argument type constructor M
  • A function unit of type a -> M a
  • A function join of type M (M a) -> M a

which satisfies the monad laws.

A type constructor is a type-level function which takes a number of type arguments and returns a type. C# doesn't have this feature directly but when encoding monads, you need a single-argument generic type e.g. List<T>, Task<T> etc. For some generic type M you therefore need two functions which construct an instance of the generic type from a single value, an 'flattens' a nested instance of the type. For example for List<T>:

public static List<T> unit<T>(T value) { return new List<T> { value }; }
public static List<T> join<T>(List<List<T>> l) { return l.SelectMany(l => l); }

From this definition you can see that a single function cannot satisfy the definition ofa monad, so your example is not an example of a monad.

By this definition, Nullable<T> also does not have a monad instance since the nested type Nullable<Nullable<T>> cannot be constructed, so join cannot be implemented.

Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41
Lee
  • 142,018
  • 20
  • 234
  • 287
  • 1
    I didn't know that nested nullables are disallowed, but it seems to be a special (deliberate) quirk of the language rather than a fundamental property of the conceptual type. You *can* implement `SelectMany` (monadic *bind*), so the situation is unclear. Usually, if you have *bind*, you can implement `join`, or the other way around, but not here, because of the C# special case. For all practical cases, though, I'd consider `Nullable` a monad. – Mark Seemann Jul 14 '20 at 22:03
  • @MarkSeemann - You can implement `SelectMany` but not `pure` since `pure(null)` must return `null` instead of `{HasValue: true, Value: null}`. This means the left idenitty law is broken since `pure(null).SelectMany(_ => 3)` is `null` instead of `3` as required. But yes, for practical purposes it's probably ok to think of `Nullable` as having a monad instance. – Lee Jul 14 '20 at 22:16
1

This is more like a filter operation. In C#, you'd idiomatically call it Where. It may be easier to see if we make the distinction between absent and populated values more explicit, which we can do with the Maybe container:

public static Maybe<T> Where<T>(
    this Maybe<T> source,
    Func<T, bool> predicate)
{
    return source.SelectMany(x => predicate(x) ? x.ToMaybe() : Maybe.Empty<T>());
}

There's only a few containers that support filtering. The two most common ones are Maybe (AKA Option) and various collections (i.e. IEnumerable<T>).

In Haskell (which has a more powerful type system than C#) this is enabled via a class named MonadPlus, but I think that the type class Alternative actually ought to be enough to implement filtering. Alternative is described as a monoid on applicative functors. I'm not sure that that's particularly helpful, though.

With the above Where method, you could thread Maybe values through checks like IsNullOrEmpty like this:

var m = "foo".ToMaybe();
var inspected = m.Where(s => !string.IsNullOrEmpty(s));

This will let m pass through unchanged, while the following will not:

var m = "".ToMaybe();
var inspected = m.Where(s => !string.IsNullOrEmpty(s));

You could do the same with Nullable<T>, but I'll leave that as an exercise

It's also possible that you could do it with the new nullable reference types language feature of C# 8, but I haven't tried yet.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • So I suppose if I were to do what I am trying to do with proper `Maybe`s, it would involve each of the potentially empty strings being a `Maybe` and doing some nested `Match` or fallback calls. – Bondolin Jul 20 '20 at 16:06
  • @Bondolin You don't have to deal with nested Maybes. That's what `SelectMany` is for; it flattens as you go. – Mark Seemann Jul 20 '20 at 16:29
0

I believe this is usually solved in the FP paradigm a step ahead of validating null. The str value must never be null. Instead the original method must return an empty collection. This way the chaining of methods do not have to validate null. The next operation will not execute since there are no elements to operate on

There are multiple references you can find. related to this on the internet. https://www.informit.com/articles/article.aspx?p=2133373&seqNum=5 is one I could quickly grab

I learnt this from Zoran Horvat course in Pluralsight. If you do have access please check it out. The course name is " Tactical Design Patterns in .NET: Control Flow" and the module is "Null Object and Special Case Patterns"

Taking about interest in FP, Zoran Horvat also has other courses that help convert or make OO code more fuctional. I'm quite excited in responding here because lately I've been looking into FP as well. Good luck!

Vishwa
  • 195
  • 1
  • 5