HighlightJS

Sunday, November 14, 2010

Maybe foreach is the new if

"Maybe . . . Maybe not. But at least it would be something real." - Nikita, in La Femme Nikita

Last weekend, I spent some time translating Nat Pryce's implementation of a Maybe type for Java to C#. You can find the motivation behind, and an introduction to, the construct in his blog post. He also suggests some better ways of avoiding nulls, BTW. But I'll stick to Maybe for now, and jump directly to some questions, observations and thoughts that arose from a discussion I had with my colleague, Victor, about the type and its usage.

First came the question of how to introduce such a construct into an already long-running project. My answer would be: piecemeal. You simply use it in new code you write, or code you refactor or otherwise modify in the normal course of work. You don't have to touch code you wouldn't have otherwise touched. So, there'll be some methods that declare they may return a nonexistent value (through the use of Maybe), and some others that'll hand you a null without bothering to set expectations. But having even a few methods that'll declare the possibility of nonexistence is better than having none, since you have that many fewer surprises to deal with.

Next, there were some questions about the how code using the type would look. So, here's an if-else structure using a null value:


@Test
public void ifThen() throws Exception {
    Maybe foo = "foo";

    if (foo != null) {
        assertThat(foo, equalTo("foo"));
    }
    else {
        fail("should not have been called");
    }
}

Here's the same structure using a Maybe:


@Test
public void ifThen() throws Exception {
    Maybe foo = definitely("foo");

    if (foo.isKnown()) for (String s : foo) {
        assertThat(s, equalTo("foo"));
    }
    else {
        fail("should not have been called");
    }
}

They look quite similar, but the Maybe example is interesting for two reasons. The first is the use of the Iterable interface (through the for loop) for accessing the 'wrapped' value. It may feel itchy to use a for loop to access what you know is a single value, but we'll address that soon. The second is the clever placement of the for statement on the same line as the if, effectively de-emphasizing the iteration aspect, and making the overall structure look no(t much) different from a regular if-else structure. Too bad, none of the automatic code formatters I've used is clever enough to see the cleverness of the formatting and retain it.

Getting back to the itchiness of the for loop for accessing the single value. After the discussion with Victor, I felt as uncomfortable with the single-element-iteration as he did. Even though it hadn't bothered me at all earlier, it now bothered me enough that I had to find an alternative. Since we were talking about my C# version of Maybe, on my way home, it struck me that one could use the .First() LINQ extension method like this:


if (foo.IsKnown())
{
    Assert.That(foo.First(), Is.EqualTo("foo"))
}

instead of:


if (foo.IsKnown()) foreach (s in foo)
{
   Assert.That(s, Is.EqualTo("foo")
}

In case of an unknown value, .First() would throw an exception. Just what you'd expect. So, that's a solution if you don't like the for loop. As an aside, the if check in the last foreach example is redundant. It's needed only if it's to be followed by an else clause. Maybe foreach is the new if.

What's so wrong with the for loop, though? I realized the reason the iteration felt awkward to me is because I knew there'll never be more than one value. Isn't an iteration really meant for multiple values? Somehow, despite the fact that Maybe implements Iterable, I expected it to hide that fact from me. I assumed the iterability is more or less a hack to be exploited by collections APIs (Guava for Java, LINQ for .net). But reflecting further, these assumptions and expectations seemed to be based on a fuzzy feeling about the form of an iterable, not on a published contract, or even some kinda logic, for that matter.

For methods that return a collection of objects, an empty collection is an acceptable - I'd even say, the only acceptable - return value (some people return a null... ouch!). Returning an empty collection as a NullObject in such cases allows one to avoid the clutter of the SentinelPattern. The iterable/collection interface allows client code to treat all return values - whether with zero, one or many elements - uniformly. So, if a collection with zero or many is fine, a collection with zero or one should be just as fine. Which is exactly what the Maybe type is - an iterable that promises to yield exactly either zero or one element. But instead of being ashamed of being an iterable, it's proud of being one with a stronger guarantee.

BTW, Maybe would definitely() be a worthy type addition to the Babbage language, next to its MIGHT DO and WHY NOT? statements.

1 comment:

mohitdargan said...

Nitish , good to see u blogging .. we want more of this !!