Jump to content
Darian Miller

"Death to WITH" in your Delphi Code

Recommended Posts

I agree with this in about 95% of the cases. In fact, I've inherited an old Delphi 7 project that I'm maintaining and upgrading for a customer and it is filled with WITH statements--even nested ones! It's a mess to untangle!

 

A couple of years ago, during the "Delphi Debates" webinar series, I blogged my stance on this: Delphi Debates: With, Goto, & Label--and Exit.

 

I've added a +1 comment to your QP ticket.

Share this post


Link to post

If they added the ability to provide an alias the ambiguity would be gone. Aliases in SQL are useful and perform a similar function. Shortening repeated references can make code easier to read, the problem is the current WITH creates ambiguity. 

A code snipped from the blog post redone with the ability to provide an alias as an example:

procedure TMyForm.UpdateInventoryItem(const NewQty: Integer);
begin
  with dmStoreInventoryData as A do begin
    with A.tblUpdateItemForStore as B do begin
      B.Edit;
      B.FieldByName('Qty').AsInteger := NewQty;
      B.Post;
    end;
  end;
end;

 

  • Like 4

Share this post


Link to post
10 minutes ago, Brian Evans said:

If they added the ability to provide an alias the ambiguity would be gone

My guess is that Aliasing would bring in a new set of problems but if it's just a preprocessor type replacement, it might be OK.  

But look at your example code - what is the scope of "NewQty"?  Does it belong to A, to B, or to the Form, or to a variable or method somewhere?  If it doesn't belong to A or B, what if "NewQty" is added to A or B as a property or method later?  Boom, your with-bomb will explode into a nice bug.

 

  • Like 1

Share this post


Link to post
21 minutes ago, Darian Miller said:

I quoted your blog article in mine.

Ah! So you did!  I hadn't read down through all the links. That's a pretty comprehensive study of everyone's opinion! Good job!

Share this post


Link to post
2 hours ago, Darian Miller said:

My guess is that Aliasing would bring in a new set of problems but if it's just a preprocessor type replacement, it might be OK.  

But look at your example code - what is the scope of "NewQty"?  Does it belong to A, to B, or to the Form, or to a variable or method somewhere?  If it doesn't belong to A or B, what if "NewQty" is added to A or B as a property or method later?  Boom, your with-bomb will explode into a nice bug.

 

In this example NewQty is a parameter of the procedure.  

Share this post


Link to post
6 hours ago, Brian Evans said:

If they added the ability to provide an alias the ambiguity would be gone. Aliases in SQL are useful and perform a similar function. Shortening repeated references can make code easier to read, the problem is the current WITH creates ambiguity. 

A code snipped from the blog post redone with the ability to provide an alias as an example:

There is no need for with in your example:

 

procedure TMyForm.UpdateInventoryItem(const NewQty: Integer);
var 
 a: <the required type>;
 b: <the required type>;
begin
  a:= dmStoreInventoryData;
  b:= A.tblUpdateItemForStore;
  
  B.Edit;
  B.FieldByName('Qty').AsInteger := NewQty;
  B.Post;
end;

With will not make this code any cleaner.

  • Like 3

Share this post


Link to post

How about qualified with.:classic_biggrin:

procedure TMyForm.UpdateInventoryItem(const NewQty: Integer);

  procedure &With(A: TDataModule; B: TTable; const NewQty: Integer);
  begin
    B.Edit;
    B.FieldByName('Qty').AsInteger := NewQty;
    B.Post;

  end;

begin
  &With(dmStoreInventoryData,
    dmStoreInventoryData.tblUpdateItemForStore, NewQty);
end;

 

Share this post


Link to post

Introducing new variables isn't the same as WITH which just adjusts how the compiler handles scoping for which reference a name refers to at compile time. 

 

If introducing a new variable isn't an issue may as well use inline variables with type inference for modern Delphi at least. 

procedure TMyForm.UpdateInventoryItem(const NewQty: Integer);
begin
  var A := dmStoreInventoryData;
  var B := A.tblUpdateItemForStore;
  B.Edit;
  B.FieldByName('Qty').AsInteger := NewQty;
  B.Post;
end;

 

  • Confused 1

Share this post


Link to post
1 hour ago, Brian Evans said:

If introducing a new variable isn't an issue may as well use inline variables with type inference for modern Delphi at least. 

This is good in that it eliminates scope confusion and is actually 2 lines shorter than using the nested with because there's no need for "end" statements. And, you could combine the first two var lines into one.

 

Now, if refactoring and the debugger would just work well enough with inline vars that this won't cause frustration down the road if you ever need to change or debug it, then this is the perfect answer!

Share this post


Link to post
Posted (edited)

I've been working in Delphi 7 projects for the past year. Some are being migrated to 11.3+ and some are staying with Delphi 7. I always refactor with statements away by hand, but even that can be tricky. For example, I learned after refactoring several "with dataset do ... while not Eof do ; Next;" constructs, and overlooking one call to Next, that TForm has a public Next method, related to MDI children of a MDI parent. :classic_blink:

 

https://docwiki.embarcadero.com/Libraries/Alexandria/en/Vcl.Forms.TForm.Next

 

Hello infinite loop! Now, go away. :classic_wink:

Edited by JonRobertson
  • Like 1

Share this post


Link to post
On 8/27/2024 at 5:02 PM, JonRobertson said:

I've been working in Delphi 7 projects for the past year. Some are being migrated to 11.3+ and some are staying with Delphi 7. I always refactor with statements away by hand, but even that can be tricky. For example, I learned after refactoring several "with dataset do ... while not Eof do ; Next;" constructs, and overlooking one call to Next, that TForm has a public Next method, related to MDI children of a MDI parent. :classic_blink:

 

https://docwiki.embarcadero.com/Libraries/Alexandria/en/Vcl.Forms.TForm.Next

 

Hello infinite loop! Now, go away. :classic_wink:

This is why long long ago made feature request of compiler/AST assisted with-removed refactoring tool. It never happened.

Made new one in the new Bug-tracker: https://embt.atlassian.net/servicedesk/customer/portal/1/RSS-1666 
Please comment the bug report and give some pressure...

 

-Tee-

 

 

  • Like 1

Share this post


Link to post

Do you really need A?

 

As you can have

for var i:=1 to 10 do ...

why not

with var B := dmStoreInventoryData.tblUpdateItemForStore do begin
  B.AppendRecord([1,'foo','bar',3.1415]);
end;

and "B" would only be visible inside the "with" block, not outside of it.

 

Currently you have to "fake" it by

begin var B := dmStoreInventoryData.tblUpdateItemForStore
  B.Edit;
  B.FieldByName('Qty').AsInteger := NewQty;
  B.Post;
end;

 

Share this post


Link to post
33 minutes ago, Frickler said:

Currently you have to "fake" it by

Which is actually six chars less than the with version.

 

I can see no benefit in such with-V2 syntax. Local begin-end blocks without with, for or while have been part of the language since the beginning. With the advent of inline variables they may get some more use cases now. I would not call that fake in the first place.

Share this post


Link to post
3 hours ago, Frickler said:

why not


with var B := dmStoreInventoryData.tblUpdateItemForStore do begin
  B.AppendRecord([1,'foo','bar',3.1415]);
end;

and "B" would only be visible inside the "with" block, not outside of it.

What benefit do you perceive from this? In what way is this superior to less verbose begin..end block?

Edited by Brandon Staggs

Share this post


Link to post
2 hours ago, Uwe Raabe said:

Which is actually six chars less than the with version.

Eight chars, if you include spaces.

 

2 hours ago, Uwe Raabe said:

I would not call that fake in the first place.

Agreed.

Share this post


Link to post
On 9/4/2024 at 8:29 PM, Brandon Staggs said:

What benefit do you perceive from this? In what way is this superior to less verbose begin..end block?

Syntactic sugar. It does nothing more than that begin-end, but makes it clear (to me) that "B" is defined only for the purpose of abbreviating that expression. 

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×