« Wellington .NET user group: C# 4 and CLR 4.0 | Main | Small businesses and "fire at will" »
February 22, 2009
Scala for C# programmers, part 4: multiple return values
Everybody hates out parameters. We all know that code like this just isn't nice:
Widget widget;
if (widgetDictionary.TryGetValue(widgetKey, out widget))
{
widget.ReticulateSplines();
}
And once you start composing higher-level functions into the mix, it gets positively nasty. Suppose I want to make a HTTP request. Well, that's going to produce two outputs in itself, the success code and the response data. But now suppose I want to time it. Writing the timing code isn't a problem, but now I have three outputs, and to paraphrase Was Not Was, I feel worse than Jamie Zawinski.
You can get around this in specific situations by creating custom types like DictionaryLookupResult or TimedHttpRequestResult, but eventually the terrible allure wears off, and you just want it to work.
Enter tuples. A tuple is just a small number of values -- a single value, a pair of values, a triplet of values, that sort of thing. You can tell that tuples were named by mathematicians because the name only makes sense when you look at the cases you hardly ever use (quadruple, quintuple, sextuple, etc.). If C# had tuples, our timed HTTP request might look a bit better:
public Tuple<bool, Stream, TimeSpan> Time(Func<Tuple<bool, Stream>> action)
{
StartTimer();
var result = action();
TimeSpan howLong = StopTimer();
return new Tuple<bool, Stream, TimeSpan>(result.First, result.Second, howLong);
}
var result = Time(() => MakeRequest(uri));
var succeeded = result.First;
var response = result.Second;
var howLong = result.Third.
Console.WriteLine("it took " + howLong);
What's that you say? Not better?
The reason this keeps getting verbose on us is that C# doesn’t provide any syntatical support for tuples. To C#, a Tuple<> is just another generic type. To us, the readers, a Tuple<> is just another generic type with particularly unhelpful member names.
Really, what we're really trying to articulate by returning a Tuple<> is, "this method has several outputs." So what do we want to do with those outputs? We want to access them, for example to stash them in variables, without having to construct and pick apart the tuple one value at a time. That means the language has to provide some kind of syntax-level support for tuples, instead of treating them like every other class the compiler doesn’t know about.
Many functional languages have exactly this kind of syntactical support, and Scala is no exception. Here’s how the above pseudo-C# looks in Scala:
def time(action: => (Boolean, Stream)): (Boolean, Stream, TimeSpan) = {
startTimer()
val (succeeded, response) = action
(succeeded, response, stopTimer());
}
val (succeeded, response, timeTaken) = time(makeRequest);
Console.WriteLine("it took " + timeTaken);
Notice the multiple variables on the left hand side of the time call? Notice the (Boolean, Stream, TimeSpan) return type of the time method? That return value is effectively a tuple type, but instead of having to capture the returned tuple in a Tuple<> variable and extract its various bits by hand, we are getting the Scala compiler to (in the time function) compose the tuple implicitly for us, without us having to write the constructor call, and (in the calling code) unpick the tuple into meaningful, named variables for us without us having to explicitly copy the values one by one and by name.
(By the way, a proper implementation of the time() method wouldn’t be restricted to (Boolean, Stream) results: we’d be looking to write a method that could time anything. I’ve skipped that because I don’t know how to do it it would distract from the point at hand.)
How would this play with the dictionary example?
val (found, widget) = widgetDictionary.GetValue(key)
if (found) {
widget.ReticulateSplines()
}
We don’t actually save any lines of code, what with having to now capture the “found” value into a variable and test it separately; and it’s not as if the original C# version was horribly unreadable anyway. So maybe this is a matter of taste, but I find this a lot easier to read and to write: all the outputs are on the left of the equals sign where they belong, instead of being spread between the assignment result and the parameter list, and we don’t have that odd Widget declaration at the top.
Encouragingly enough, .NET 4.0 will include a Tuple class, I believe precisely in order to support functional and other languages that use the multiple return value idiom. Unfortunately, C# 4.0 still isn’t getting syntactical support for them, so we’re stuck with out parameters or special result types for now.
February 22, 2009 in Software | Permalink
TrackBack
TrackBack URL for this entry:
https://www.typepad.com/services/trackback/6a00d8341c5c9b53ef01127902db7728a4
Listed below are links to weblogs that reference Scala for C# programmers, part 4: multiple return values:
Comments
I shall comment since these posts were looking lonely and I was thinking about tuples.
The trouble with multiple return values, surely, is that they bugger up the main value of return values which is that they can be used in functional composition e.g.
hamster(penguin(gerbil,lemming(wombat)),goat(stoat))+badger(cheesecake(duck))
Something I am surprised no-one has tried is some sort of visible demarcation between in, out and shakeitallabout parameters e.g.
fred(in1,in2|siab1,siab2,siab3|out2)
presumably if there are only in then you omit the markers e.g.
square(2), sum(3,4)
incr(|i|)
httpthingy(inparam||statuscode,returnedlumpoftext)
Beyond this, what is a tuple but an anonymous struct? That way, I suspect, lies madness. Certainly we have someone at work who is fond of pairs (and possibly even pairs of pairs) in C++-land which do not seem to me to aid cosmic happiness - cue a joke about pair programming here.
As far as your example goes, the thing-plus-status-code use case is something that needs thinking about but probably as a special case. In COM land we have the HRESULT and retval, with the #import generating both
HRESULT raw_blah(thing *)
and
thing *blah() throwing an exception
but of course that only works for 'error' rather than 'success but not OK' return values.
Perhaps we need some sort of concept of an unexception (errors return exceptions, non OK non errors return unexceptions).
try
{
blah()
}
catch(e *)
{
}
clasp (ue *)
{
}
or some other suitable synonym for catch. Or something.
Posted by: Harvey Pengwyn at Feb 23, 2009 11:30:36 AM
Oops... for return exception and return unexception read throw exception and throw unexception (or maybe one gently lobs them, lob would be a good keyword).
Posted by: Harvey Pengwyn at Feb 23, 2009 11:32:29 AM
Also, for antonym read synonym. That will teach me to proofread. Clearly time for bed.
Posted by: Harvey Pengwyn at Feb 23, 2009 11:37:11 AM
Interesting point about functional composition but sometimes that's valuable and sometimes it's not. In the case of the HTTP request example, the chances are you're not going to just hand the result to another function -- you're going to take different code paths depending on the result. Having the option for multiple return values doesn't *obligate* you to return multiple values from every function, and indeed most functions will still return a single value and therefore remain composable.
In fact, pattern matching (perhaps not coincidentally also common in functional languages) might address this anyway: a function might pattern-match on (false, _) to no-op or display an exception, and on (true, stm) to process the contents of the stream. Though in this case you would be getting tight but anonymous coupling between the functions and the madness clause would probably have to be invoked.
I like the description of tuples as anonymous structs, and I agree that such things lead to madness if you need to, uh, name them -- that was the point of my C# example (I imagine your C++ pairing colleague's code looks much the same) -- or if you couple on them as in the previous paragraph. But with syntactical support you are simply mapping n return values to n variables instead of mapping 1 return value to 1 return variable. Is that so mad?
The approach you describe for the dictionary example is kinda the .NET default -- if you write dict[key] then it will return the value if there is one or throw an exception if there isn't. This is a viable strategy, but as you observe it doesn't handle what COM called the S_FALSE scenario. The idea of signalling a failure with an unexception is intriguing, but doesn't help with the more general multiple return values case, e.g. where you want to compose a time with a result.
As for what one does with an unexception, I think it should be something gentler than lob... perhaps punt...
Posted by: Ivan at Feb 25, 2009 8:55:31 PM
Great posts! Nice work, and very useful. Always nice to get a blogger-style perspective on a new lang, as opposed to programming-language-book perspective.
I think you have a typo in the line:
def time(action: => (Boolean, Stream): (Boolean, Stream, TimeSpan)
. . . should be another right-paren in there, no?
Posted by: Brandon Harvey at Aug 22, 2009 3:05:16 AM
Yep, you're right, there should be another right-parenthesis before the second colon. Fixed -- thanks!
Posted by: Ivan at Aug 22, 2009 9:10:21 AM
try
{
getMoney(ATM, $100);
}
punt
{
message("kindly fuzz off - no cash!");
}
Great Posts! Grats!
Posted by: Mihail at Feb 11, 2012 3:41:58 AM