April 26, 2019 in Development4 minutes
As promised in the last post in this series, I will give a rationale for why language-ext belongs in your toolkit.
When LINQ first hit the scene, it was a big deal. C# developers to this point were largely limited to imperative code when walking over enumerable sequences. Think about how you might average a list of integers without LINQ:
With the LINQ enumerable extensions, we can skip the mutation and loop:
To be fair, it’s not that there’s no mutation at all; under the hood, at some point, machine code is represented in terms of imperative instructions and mutable memory. However, you as a developer should have the opportunity to step back and speak in terms of abstractions. Rather than spelling out to the computer how it should do every little thing, modern languages give you the opportunity to express ideas.
Ideas can be shared quickly and conversationally. They can be diagrammed easily and don’t require a CS degree to understand. An idea can outlive the lines of code that make it happen.
You, the programmer, are living in a world where you no longer need to micro-manage your code.
Okay, so the skeptics among you might be thinking:
That’s great and all. I see what you did with that array, but not everything’s an enumerable object.
I introduced the Option
in the first post in the earlier post. At the time, I sold it as the solution to the problem of nullability. This by itself is actually a weak case to be made for language-ext, with C# 8 bringing nullable reference types to the masses.
However, that’s not its only benefit. The language-ext types, including Option, can help you get the same kind of declarative code for other types too.
First, let’s consider a scenario where we’re trying to split a key=value
string. The key we care about is “name”, and in the event we have a valid (non-empty) value for the name we return it; if not, we return “no name”.
Here’s how I might write this in C#:
The important parts here are:
=
Because there are multiple ways we could get into a “no name” condition, it makes sense to have that be the fallthrough case. One possible improvement we could make to my illustration would be to get around the null/empty check with a safe navigation operator:
Personally, I’m okay with this code. However, with Options, we could maybe offer a solution that is both clever and readable (two things that don’t normally go hand-in-hand in programming):
The major downside is that we had to have our input wrapped in an Option<string>
beforehand, but this is relatively simple if we’ve imported the LanguageExt.Prelude
namespace:
Besides that, you might be thinking that the Option-based version looks tedious. It’s more dense, but I would argue that the workflow is clearer when piped this way. I see this as a point that will probably be hotly contested, but before you give up on it, there are some extractions that I think will make things a bit clearer:
At the expense of having to write more code, the getName
function now reads as a chain of terse and self-explanatory steps. The logic that backs those extracted functions have the added benefit of being reusable for other situations, in addition to being named in a way that is self-documenting.
The full source for my example can be found here.