Sometimes there are pieces of data which are hard to create a cohesive type around. That is, you can compose an new type from other types, but you can't really add any behavior to that new type. This is exemplified by the Tuple type in many languages: it's basically a hack to treat disparate pieces of data as one, and it doesn't offer any way to operate on its own data.
Here's an example from a relatively typical idiom of parsing an int from a string:
int parsedInt;
if(int.TryParse(s, out parsedInt))
{
Console.WriteLine("Parsed {0}",parsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
The problem here is that TryParse() really returns two values, one of which will be tested to see if the parse was successful, and the other of which is assigned to parseInt. You could test that the string can be parsed and then get the parsed value in two separate functions, but this would be inefficient because you'd essentially have to parse the string twice. I'm on my Mac so I'm not sure I'm remembering this correctly, but I think C# forces you to put the "out" there, which I think is good because it clarifies that parsedInt will be assigned. However, I think there are a number of ways in which the following (hypothetical) redefinition of TryParse (and C# syntax) feels a lot clearer: var parseSucceeded, parsedInt = int.TryParse(s);
if(parseSucceeded)
{
Console.WriteLine("Parsed {0}",parsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
First of all, the programmer has been forced to assign the success boolean to a clarifying variable. Second, the call has a clearer structure: the input are on the right side of the = and the outputs are on the left.Let's look at some other possible solutions here:
var parseTuple = int.TryParse(s);
if(parseTuple.Item1)
{
Console.WriteLine("Parsed {0}",parseTuple.Item2);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
This results in some confusing code. Item1 and Item2 are very poor names for the things they represent. IntegerParseResult parseResult = int.TryParse(s); // Didn't use var here to clarify what this code means.
if(parseResult.ParseSucceeded)
{
Console.WriteLine("Parsed {0}",parseTuple.ParsedInt);
}
else
{
Console.WriteLine("{0} was not an integer",s);
}
This is a little better, but the creation of a whole new type seems heavyweight, especially given that this new type provides no real functionality. It's tying together two pieces of data which are about to be separated and never used together again (consider, for example, that instead of printing to the console it might return).I guess the argument I'm making here is that just because a function returns to outputs doesn't mean those outputs have a meaningful relationship that would justify having a new type.