Thursday, September 25, 2008

Anonymous methods as an alternative to 'out' parameters

Out parameters are a useful language feature for multiple results, particularly when the language in question doesn't have tuples as a first-class feature. Even though Java omitted pass-by-reference (meaning both var and out in Delphi parlance), C# did not follow its lead and includes both parameter-passing semantics. And so it follows that the .NET method System.Int32.TryParse(String,Int32&) is possible in C#, but not in Java.

The API works well enough when you want to put the extra return value(s) into local variables or visible fields. What about times when you want to put the return value into a property, though? Since you can't (generally) take a reference to a property, you have to manually create a local variable, pass the local variable by reference, and then, in a separate statement, assign the variable to the property.

Anonymous methods can provide an "out" here, if you'll pardon the pun. By passing a setter instead of a mutable reference, you can escape from this constraint. Here's are two overloaded ReadLine functions, one using the out mechanism, the other using the setter pattern:

function ReadLine(const Prompt: string; out Dest: string): Boolean; overload;
var
  line: string;
begin
  Write(Prompt, '> ');
  Flush(Output);
  Readln(line);
  Result := line <> '';
  if Result then
    Dest := line;
end;

function ReadLine(const Prompt: string; const Setter: TProc<string>): Boolean; overload;
var
  line: string;
begin
  Write(Prompt, '> ');
  Flush(Output);
  Readln(line);
  Result := line <> '';
  if Result then
    Setter(line);
end;

As can be seen, the code that uses setters versus out parameters is little different. (It would be more different in a language that requires definite assignment, like C#, as otherwise we would have to set Dest to some value in the case of no entry.)

The real difference is in the usage point of this idiom. (And don't forget, this is an idiom; it might look odd the first time you see it, but when you understand anonymous methods, or even just the idiom, it's a trivial pattern and easy to understand.) Here's the definition of a class with a couple of properties, and a couple of functions. The first uses the out idiom, and the second uses the setter idiom:

type
  TFrob = class
  private
    FFoo: string;
    FBar: string;
  public
    property Foo: string read FFoo write FFoo;
    property Bar: string read FBar write FBar;
  end;

// Using 'out' idiom - requires separate assignment statement
function WorkWithFrob: Boolean;
var
  frob: TFrob;
  temp: string;
begin
  frob := TFrob.Create;
  try
    if not ReadLine('Give me Foo', temp) then
      Exit(False);
    frob.Foo := temp;
    if not ReadLine('Give me Bar', temp) then
      Exit(False);
    frob.Bar := temp;
    Writeln(Format('Your foo is %s and bar is %s', [frob.Foo, frob.Bar]));
    Result := True;
  finally
    frob.Free;
  end;
end;

// Using 'setter' idiom - can be done inline
function WorkWithFrob2: Boolean;
var
  frob: TFrob;
begin
  frob := TFrob.Create;
  try
    if not ReadLine('Give me Foo', procedure(x: string) begin frob.Foo := x; end)
        or not ReadLine('Give me Bar', procedure(x: string) begin frob.Bar := x; end) then
      Exit(False);
    Writeln(Format('Your foo is %s and bar is %s', [frob.Foo, frob.Bar]));
    Result := True;
  finally
    frob.Free;
  end;
end;

This idiom isn't useful very often, but when the right situation comes up, it's really quite nice to have, especially when you need persistent references to properties - e.g. when you need to store the reference in some structure somewhere, and update it with different values over time.

Viewed more generally, anonymous methods / method references can act as kinds of pipes that join up producers and consumers of data, with getters (TFunc<TResult>) for consumer-pull and setters (TProc<TArg>) for producer-push.

9 comments:

Raymond Wilson said...

Does this work the same was as if the properties in the class had been given setter methods? ie: Given such a setter method, can you pass the setter method itself instead of a chunk of anonymous method code?

Barry Kelly said...

@Raymond: The idiom presented here is agnostic as to the implementation details of the property. In other words, it doesn't matter if the property directly references a field or uses getter and setter methods, or some mix of both.

Now, if the setter method is visible (e.g. you are writing code belonging to the class itself), and it is compatible with TProc<TArg> where TArg is the property type, then you could pass the setter method directly, yes.

But from the outside, you can't tell at the use-point whether the property is using a direct field access or a setter, and indeed you wouldn't want to know - it would be a violation of encapsulation. So, for code on the outside, you're best off writing the little anonymous method thunk.

Junior said...

A nice syntax improvement would be:


if not ReadLine('Give me Foo', |x: string| begin frob.Foo := x end)
or not ReadLine('Give me Bar', |x: string| begin frob.Bar := x end) then
Exit(False);

m. Th. said...

To complete Junior:

Or perhaps to allow the begin/end pair to be optional? As in if {cond} {statement}, for {cycle} {statement} in the same manner why should be compusory that the routine body to be between begin/end? IOW, if the routine has only one statement then begin/end can be omitted. So, we getting close to lambdas, isn't?

Just my 2c.

Barry Kelly said...

Junior, m. Th: A better syntax will have to wait until the compiler is better configured for type inference.

m. Th. said...

@Barry: Aha! Thanks Barry. Way to go, son, way to go! Of course, isn't necessary to remind you that having type inference is a much bigger deal than what we discuss here, and _just_IMHO_ type inference musn't be avoided because 'opens the possibility for nasty programming errors' as, perhaps, a Pascal purist might say.

Just my2c,

m. Th.

David S said...

FINALLY! A solution to having to create different blocks of "getData" and "setData" methods! This is common when using, say, ini or regini for data storage.

procedure getData(obj : TMyObj);
begin
obj.var1 := ReadStr('xx','var1','');
obj.var2 := ReadInt('xx', 'var2',0);
end;

procedure setData(obj : TMyObj);
begin
WriteStr('xx','var1',obj.var1);
WriteInt('xx','var2',obj.var2);
end;

If var1 and var2 are set as properties, you can't make both signatures the same, eg.,

ReadStr('xx','var1',obj.var1)
and
WriteStr('xx','var1',obj.var1)

But using closures the way you're illustrating, the whole approach can be made much more elegant and far simpler. Let the compiler do this work rather than the code writer!

-David

Fran├žois said...

A nice one Barry!
I just wish you would show the way by providing a (new) standard formatting to write anons instead of compacted 1 liners.
Keeping somehow the standard routine layout helps a lot if you need step by step debugging (helps also in CPU view). Anonymous methods must not become associated with unreadability... Just my 2c.

maybe writing like:
if not ReadLine('Give me Foo',
procedure(x: string)
begin
frob.Foo := x;
end) or

Fran├žois said...

Unfortunately, I see the indentation is gone...