Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Closures and Open Arrays


This question is answered. Helpful answers available: 0. Correct answers available: 1.


Permlink Replies: 4 - Last Post: Jul 2, 2014 10:24 AM Last Post By: Peter Below
Scott van der L...

Posts: 67
Registered: 5/11/02
Closures and Open Arrays  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 26, 2014 7:45 PM
Can anyone tell me why an open array cannot be captured by a closure (anonymous method)?

very simply I wanted to do something like:

procedure TMyObj,MyProc(values: array of string);
begin
InternalCall(procedure (var Arg1: string)
var
i: Integer;
begin
for i := 0 to Length(values) -1 do
Arg1 := Arg1 + ';' + values[i];
end);
end;

but apparently open array values can't be captured.

To me this seems like a ridiculous restriction. if you can capture other types of parameters that aren't marked as var or out, and if you can pass local arrays what is the difference in scope with an open array parameter? By definition it is immutable so it should be treated like a pre-defined local variable but instead it is treated the same as a parameter passed as a reference!
Peter Below

Posts: 1,227
Registered: 12/16/99
Re: Closures and Open Arrays
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 27, 2014 9:45 AM   in response to: Scott van der L... in response to: Scott van der L...
Scott van der Linden wrote:

Can anyone tell me why an open array cannot be captured by a closure
(anonymous method)?

very simply I wanted to do something like:

procedure TMyObj,MyProc(values: array of string);
begin
InternalCall(procedure (var Arg1: string)
var
i: Integer;
begin
for i := 0 to Length(values) -1 do
Arg1 := Arg1 + ';' + values[i];
end);
end;

but apparently open array values can't be captured.

To me this seems like a ridiculous restriction. if you can capture
other types of parameters that aren't marked as var or out, and if
you can pass local arrays what is the difference in scope with an
open array parameter?

It occupies two parameter slots, even though that is not evident from
the syntax. The first slot has the address of the first element of the
array, the second the highest valid index (length of the passed array
-1).

For this specific purpose TStringhelper.Combine would do, by the way.

--
Peter Below (TeamB)

Joseph Mitzen

Posts: 392
Registered: 6/9/02
Re: Closures and Open Arrays
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 28, 2014 4:48 PM   in response to: Peter Below in response to: Peter Below
Peter Below wrote:

It occupies two parameter slots, even though that is not evident from
the syntax.

The syntax is all that matters. To the developer it's one slot, so to be consistent the API designer needs to treat it as such to follow the "principle of least confusion". If you need to understand the internal implementation then that's called a "leaky abstraction". Closures have other poor design issues, such as not being able to use the captured variable in a for loop due to "optimization". Of course, nested "functions" not being usable (how are they really functions if you can't do everything you can do with a regular function?) is another problem.

The first slot has the address of the first element of the
array, the second the highest valid index (length of the passed array
-1).

I had to think a lot about this and I believe the problem is that an array isn't an object in Delphi. It seems if they passed a list rather than an array this problem would go away.
Scott van der L...

Posts: 67
Registered: 5/11/02
Re: Closures and Open Arrays  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 1, 2014 6:55 PM   in response to: Joseph Mitzen in response to: Joseph Mitzen
Joseph Mitzen wrote:
Peter Below wrote:

It occupies two parameter slots, even though that is not evident from
the syntax.

The syntax is all that matters. To the developer it's one slot, so to be consistent the API designer needs to treat it as such to follow the "principle of least confusion". If you need to understand the internal implementation then that's called a "leaky abstraction". Closures have other poor design issues, such as not being able to use the captured variable in a for loop due to "optimization". Of course, nested "functions" not being usable (how are they really functions if you can't do everything you can do with a regular function?) is another problem.

The first slot has the address of the first element of the
array, the second the highest valid index (length of the passed array
-1).

I had to think a lot about this and I believe the problem is that an array isn't an object in Delphi. It seems if they passed a list rather than an array this problem would go away.

Thanks Peter & Joseph,

The purpose for using the anonymous method was to abstract the container type from a method. The container could be a TList, TDictionary, open array, or even an iterator. The anonymous function passed to the method would do the combining, and the result would be used within the method. I guess the other way to do it would be to do the calculation up front and pass the result into the method without using an anonymous method at all. The only benefit to doing it this way is if there is only a possibility this data is needed you have saved the processing time by deferring it to when it is required.

In regards to Joseph's comment about using an object. Yes, the whole reason for the open array was because it is really easy to construct the list within the call, all you need to write is two square brackets and then list everything you want within those brackets. It would be really nice if there were similar shortcuts you could use for lists - thing is you would need ARC to ensure there were no memory leaks. Once ARC is in place (or we descend from TInterfacedObject) the procedure call could be as simple as:

MyCall(Arg1, Arg2, TList<string>.Create(['A', 'B', 'C']));

a little more wordy than

MyCall(Arg1, Arg2, ['A', 'B', 'C']);

but still usable as an analog.

Perhaps as a suggestion for a future version of Delphi a new syntax could be used. Passing an open array as the type of a generic implies the create, and the type is determined by example.

e.g.
MyCall(Arg1, Arg2, TList<['A', 'B', 'C']>);

The simplest type that applies to everything in the open array of the above example is Char, so the equivalent longhand for the TList constructor would be:

with TList<Char>.Create do
for value in TCharDynArray.Create(['A', 'B', 'C']) do // Assumes TCharDynArray has been created in the same pattern as TStringDynArray
Add(value);

since the the open array generic constructor is by definition defined at compile time the compiler should be able to identify the type of each element of the open array either because it is a variable of a given type, or by the same method it uses for constants currently. Then all that would be required is some simple rules for combining disparate types (which already exist for most situations):
* floating types expand to the largest required type of float,
* Integers become floats when combined with floats
* Chars become strings when combined with strings
* Mixing across simple types e.g. string and integer could make them all into variants
* Structure types must be name compatible or a compile error is raised

I will place this suggestion into http://delphi.uservoice.com
Peter Below

Posts: 1,227
Registered: 12/16/99
Re: Closures and Open Arrays [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 2, 2014 10:24 AM   in response to: Scott van der L... in response to: Scott van der L...
Scott van der Linden wrote:

The purpose for using the anonymous method was to abstract the
container type from a method. The container could be a TList,
TDictionary, open array, or even an iterator. The anonymous function
passed to the method would do the combining, and the result would be
used within the method. I guess the other way to do it would be to
do the calculation up front and pass the result into the method
without using an anonymous method at all. The only benefit to doing
it this way is if there is only a possibilit y this data is needed
you have saved the processing time by deferring it to when it is
required.

In regards to Joseph's comment about using an object. Yes, the whole
reason for the open array was because it is really easy to construct
the list within the call, all you need to write is two square
brackets and then list everything you want within those brackets. It
would be really nice if there were similar shortcuts you could use
for lists - thing is you would need ARC to ensure there were no
memory leaks. Once ARC is in place (or we descend from
TInterfacedObject) the procedure call could be as si mple as:

MyCall(Arg1, Arg2, TList<string>.Create(['A', 'B', 'C']));

Perhaps you can use something like this:

type
TArrayBuilder<T> = class
public
class function Construct(Values: array of T): TArray<T>;
end;
TStringProc = reference to procedure (var S: String);
TFoo = class
private
FProduct: string;
procedure InternalCall(aProc: TStringProc);
public
procedure MyProc(values: TArray<string>);
property Product: string read FProduct;
end;

class function TArrayBuilder<T>.Construct(Values: array of T):
TArray<T>;
var
I: integer;
begin
SetLength(Result, Length(Values));
for I := Low(Values) to High(Values) do
Result[I] := Values[I];
end;

procedure TFoo.InternalCall(aProc: TStringProc);
begin
FProduct := '';
aProc(FProduct);
end;

procedure TFoo.MyProc(values: TArray<string>);
begin
InternalCall(procedure (var Arg1: string)
var
i: Integer;
begin
for i := 0 to Length(values) -1 do
Arg1 := Arg1 + ';' + values[i];
end);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
LFoo : TFoo;
begin
LFoo := TFoo.Create;
try
LFoo.MyProc(TArrayBuilder<string>.Construct(['A','B','C']));
Display(LFoo.Product);
// shows ';A;B;C'
finally
LFoo.Free;
end;
end;

--
Peter Below (TeamB)

Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02