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