2

At the moment I'm converting a project written in VBA to Delphi and have stumbled upon a problem with converting some Subs with Optional arguments. Say, there is a Sub declaration (just an example, actual Subs have up to 10 optional parameters):

Sub SetMark
    (x0 As Double, y0 As Double, 
     Optional TextOffset As Integer =5,
     Optional TextBefore As String = "",
     Optional Text As String = "",
     Optional TextAfter As String = "mm",
     Optional Color As String = "FFFFFF",
     Optional ArrowPresent As Boolean = True)

That Sub subsequently can be called like this:

    Call SetMark (15, 100,,,"135")
    Call SetMark (100, 100, 8,, "My text here..", "")
    'a lot of calls here

The Optional arguments are very flexible here, you can omit any of them, and you can assign a value to any of them as well. Unlike in Delphi.

Procedure SetMark
    (x0: real; y0: real, 
            TextOffset: Integer =5;
     TextBefore: ShortString = '';
     Text: ShortString = '';
     TextAfter: ShortString = 'mm';
     Color: ShortString = 'FFFFFF';
     ArrowPresent: Boolean = True);

It seems you cannot just make a copy of VBA call:

SetMark (15, 100,,,'135');// error here 

So, the question is: is there any way to convert that Subs to Delphi procedures keeping the same flexibility in parameters? My first idea was to use default parameters, but it doesn't work. As for now it seems in Delphi I will have to pass all the parameters in the list with their values directly but that means a lot of work for reviewing and proper porting of VBA calls.

Any ideas?

2
  • 2
    By the way, you really should not be using ShortString. That has been deprecated for many many years. Use the native string type, string. Commented Sep 26, 2016 at 10:36
  • 1
    Also, tangentially, real is a legacy type that only really exists for compatibility (it is an alias of double). Better to declare a double in the Delphi code, just as the VB code does. Commented Sep 26, 2016 at 20:45

2 Answers 2

12

Is there any way to convert the VBA subroutines to Delphi procedures, still keeping the same flexibility in parameters?

There is no way to achieve that – that flexibility to omit parameters, other than at the end of the list, simply does not exist.

For methods of automation objects, you can use named parameters, as described here: Named/optional parameters in Delphi? However, I very much recommend that you don't implement your classes as automation objects just to get that functionality.

Whenever you switch between languages, you will find differences that are inconvenient. That is inevitable. The best approach is to try to find the best way to solve the problem in the new language, rather than trying to force idioms from the old language into the new language.

In this case you might want to use overloaded functions or parameter objects as ways to alleviate this inconvenience.

Sign up to request clarification or add additional context in comments.

10 Comments

Yes. Overloads would be my preferred solution.
Coulod you please specify how to implement overloading that could tell between calls of specified parameters Text and TextAfter, for example.
@J... A constructor is special. When you invoke it, a new object is instantiated and then initialised. On the other hand, a record constructor just initialises. It does not instantiate. I'm used to seeing code like, obj := TMyClass.Create and knowing that I need to pair it with a call to obj.Free. That's not the case with records. I find that using rec := TMyRec.Create causes confusion. Is obj a value type or a reference type? By reserving the use of constructors to reference types, I can be confident that I know what I am dealing with.
@J... Another problem is that parameterless constructors are not allowed on records. If you are a FreeAndNiler then you can get caught out by accidentally passing your record to FreeAndNil. Actually that's really a reason to stop using FreeAndNil isn't it? Finally, I don't like that I can call constructors on instances. I can just write rec.Create(666);. I'd rather not allow that. My convention is a static class method named New. So I would initialise a record like this: rec := TMyRec.New. By following that convention I find it's easier to be sure I know what I am dealing with.
@J... Now in this instance the asker is using Delphi 7 and can use neither constructors nor static class functions. Naked function is the only choice for him.
|
2

Just to expand on the idea of refactoring to use a parameter object, you could declare a record like:

 TSetMarkParams = record
   x0 : double; 
   y0 : double; 
   TextOffset : integer;
   TextBefore : string;
   Text : string;
   TextAfter : string;
   Color : string;
   ArrowPresent : boolean;
   constructor Create(Ax0, Ay0 : double);
 end;

And implement the constructor to populate default values as :

 constructor TSetMarkParams.Create(Ax0, Ay0 : double);
 begin
   x0 := Ax0;
   y0 := Ay0;
   TextOffset := 5;
   TextBefore := '';
   Text := '';
   TextAfter := 'mm';
   Color := 'FFFFFF';
   AllowPresent := true;
 end;

Your procedure would then have signature :

 procedure SetMark(ASetMarkParams : TSetMarkParams);

Which you could then, using your example of SetMark (15, 100,,,'135'); call as :

 var 
   LSetMarkParams : TSetMarkParams
 begin
   LSetMarkParams := TSetMarkParams.Create(15, 100);
   LSetMarkParams.Text := '135';
   SetMark(LSetMarkParams);
 end;

As collateral benefit, the above is much more readable as it saves you from going blind trying to count commas when returning to debug a troublesome method call.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.