TextReader.TryReadLine And Idiomatic Code

Today I reread a post by Eric Lippert on High Maintenance code. It’s an interesting read in general, but some of the comments got me thinking about idioms in code. The discussion started with this bit of C#:

string line;
while ((line = reader.ReadLine()) != null)
    yield return line;

A familiar pattern in C and C++, but it just doesn’t feel right for C#. Eric cites this is a flaw “because it tries to do so much in one line.” His rewrite uses the following instead:

while (true)
{
    string line = reader.ReadLine();
    if (line == null)
        yield break;
    yield return line;
}

While certainly easier to read, it still seems like a lot of work. Commenter Sebastien Lorion suggests  this is a flaw in the API: “…we would not need such a hack or the awkward code you posted if the method was better designed.” I’m not sure I agree—ReadLine implies the line read will be returned, and if there’s nothing to read then nothing (null) should be returned. Sebastien’s “better design” doesn’t make sense idiomatically; what he really wants is TryReadLine, which would closely match his suggested signature of bool ReadLine(out string line). This strikes me as a perfect extension candidate:

public static bool TryReadLine(this TextReader reader, out string line)
{
    line = reader.ReadLine();
    return line != null;
}

With which we can write a more C#-idiomatic yet compact version of the original code:

public static IEnumerable<string> GetLines(this TextReader reader)
{
    string line;
    while (reader.TryReadLine(out line))
        yield return line;
}

Now one could argue whether or not TryReadLine() should catch the possible exceptions from ReadLine(), but that’s not the point. The point is that C# has an established pattern for methods that return a boolean indicating success in populating an out parameter, and code written as such is easier to read in a C# context.

It’s also interesting to note how the coding patterns can evolve with language features. For example, F# happens to have an Option type that aligns perfectly with this pattern: it either has Some value or is None. Paired with pattern matching, this presents a superior alternative to booleans and out parameters. So in idiomatic F# we would write TryReadLine like this instead:

namespace System.IO
    [<AutoOpen>]
    module TextReaderExtensions
        type System.IO.TextReader with
            member r.TryReadLine() =
                match r.ReadLine() with
                | null  -> None
                | line  -> Some line

Which could be used tail-recursively to fetch a sequences of lines:

            member r.Lines =
                let rec lines_core (tr : TextReader) =
                    seq {
                        match tr.TryReadLine() with
                        | None   -> yield! Seq.empty
                        | Some l -> yield l; yield! lines_core tr
                    }
                lines_core r

Eric ends his article with excellent advice: “Whenever you write a method think about the contract of that method.” As a corollary to that, whenever you write a method think about idioms that fit the method contract. An important part of maintainability is writing code that makes sense in the context of other code that has been written. Clever code that nobody can read isn’t particularly clever.

Advertisements
Posted in .NET, F#. Tags: . Comments Off on TextReader.TryReadLine And Idiomatic Code