F# and Output Parameters

One perpetual source of annoyance in C# is output parameters. Normally, output parameters are used when multiple values need to be returned from a method. Within the BCL, it is common to have methods of the form TryXXXX, which have a boolean return value to indicate success or failure and an output parameter to hold the resulting value, if any. So you have Int32.TryParse, Dictionary<,>.TryGetValue, etc.

Why are out params annoying? Firstly because you're forced to declare a variable to hold the parameter value in the advance of the method call, even if you don't actually care about the value. Here's an example of calling Int32.TryParse in C#:

int value;  
if (int.TryParse(maybeInt, out value))  
    Console.WriteLine("It's the number {0}.", value);
    Console.WriteLine("It's not a number.");

It seems silly to complain about needing to declare variables in advance, but then, programmers are flakey people who like to obsess over the minor details of how a code block looks. Apparently it was enough of a problem that inline declaration of out params is slated for inclusion in the next version of C#.

The real problem with out params, in my opinion, is simply that they are a kludge. In every case except for interoping with legacy code, out params are an attempt to compensate for the fact that C# and VB.NET methods don't allow any natural way to return multiple values. That's because those languages lack usable tuples and pattern matching.

If we were to reimplement Int32.TryParse in F#, we could simply have it return a tuple, which we could then destructure into two separate values:

let success, value = Int32.TryParse maybeInt  

But Int32.TryParse is already implemented as a method with an out param. So one way to call it in F# is to follow the same pattern we used in C# using a mutable value:

let mutable value = 0;  
if Int32.TryParse(maybeInt, &value) then  
    printfn "It's the number %d." value
    printfn "It's not a number."

Rather than using a keyword, F# represents reference and out params with the byref<'T> type, which MSDN says is a managed pointer.

Another way to call a method with out params is by using a reference cell, represented by the ref<'T> type. A reference cell is simply a mutable value that is stored on the heap, and thus can survive the destruction of the scope in which it was created. You create a reference with the ref function:

let valueRef = ref 0;  
if Int32.TryParse(maybeInt, valueRef) then  
    printfn "It's the number %d." !valueRef
    printfn "It's not an integer."

Interestingly, reference cells can be created inline, which is nice for cases where we don't care about the value:

if Int32.TryParse(maybeInt, ref 0) then  
    printfn "It's an integer, but I don't care which."
    printfn "It's not an integer."

But not caring about the value is something of an edge case, so in most cases we're stuck doing it the C# way.

Or are we? I said before that we don't want to reimplement Int32.TryParse to have it return a tuple, but it turns out that we don't have to. In F# you have the option of consuming methods with out params as if they return a tuple! So the destructuring snippet from above will work automatically, which also means that we can use a match expression in place of an if-else:

match Int32.TryParse maybeInt with  
| true, value -> printfn "It's the number %d." value
| _ -> printfn "It's not a number."

Of course, in F# we're used to consuming values that might or might not exist using the option<'T> type rather than this silly tuple. So one thing we might do is write a function to adapt the tuple to an option, since these TryXXXX methods are so common in .NET:

let opt tryResult =  
    match tryResult with
    | true, value -> Some value
    | _ -> None

Now we can rewrite our parsing code as follows:

match Int32.TryParse maybeInt |> opt with  
| Some value -> printfn "It's the number %d." value
| None -> printfn "It's not a number."

Another way we could clean things up is by using a multi-case active pattern:

let (|Success|Failure|) tryResult =  
    match tryResult with
    | true, value -> Success value
    | _ -> Failure

This allows us to write it even more elegantly:

match Int32.TryParse maybeInt with  
| Success value -> printfn "It's the number %d." value
| Failure -> printfn "It's not a number."

Now you may be thinking: "OK, so what's the big deal? All he did was define some functions. I could have done the same thing in C#." But you couldn't have done most of these things, since C# doesn't provide the automatic out param to tuple conversion, and thus writing a generic method to handle TryXXXX results is impossible. The fact that F# provides such a nice interface for dealing with an edge case points toward it being a well-designed, well-thought-out language.

Luke Sandell

Read more posts by this author.

comments powered by Disqus