Wish Unlimited number of arguments when using CallSub/CallSubDelayed

LucaMs

Expert
Licensed User
I hope to explain the problem well enough by exposing a concrete example that happened to me just yesterday.

I have a routine in an Activity that receives two parameters. When I invoke it from within the Activity itself, I simply use: RoutineName(Param1, Param2).

I also called it from a service module and also using CallSubDelayed again from within the Activity.

I needed to modify that routine by adding a third parameter but CallSubDelayed3 can be used to pass two arguments only, as you know.

The only solution that came to my mind was to use a single Map parameter, but this implies some problems.

1) change a lot of code already created (in this case);
2) you must remember the number and types of arguments to add to the Map;
3) the compiler is unable to tell you that you are wrong about the arguments you're passing;
4) the readability of the code is lost a lot;
5) you no longer have the possibility to use the useful F7 directly on the name of the parameter (you will have to use variables inside the routine, immediately at the beginning, and you can press F7 on these, anyway)


I don't know if creating a CallSubDelayed4 (and CallSub4) that allows you to send an unlimited number of arguments is very complicated to implement or this can create backward compatibility problems (this probably not).

I obviously hope this will be done.
 
Last edited:

emexes

Well-Known Member
Licensed User
readability of the code
I think you're heading towards optional named variable parameters. Which are great: when you need them, you need them bad. And they do make the code a lot more readable, which is always a good thing.

The workaround you mentioned, using maps, is a bit wordy to set up, what with "helper" Subs and all, but once you've done it, you can relegate that wordiness to the background where it does not interfere with reading the code where the calls are made. I think. I've done it in other dialects, but not in B4A. But... what could possibly go wrong?
 

LucaMs

Expert
Licensed User
I think you're heading towards optional named variable parameters.
Yes, they are too useful but my example is not strictly related with them; I needed a third mandatory parameter, not an optional one.
 

emexes

Well-Known Member
Licensed User
What's your feeling about a helper Sub in a code module that takes the three (or however many) parameters, packs them into a Map, and does the CallSubDelayed2, eg:

in a helper code module:
B4X:
Sub Main_DescribePerson(FirstName As String, LastName As String, Age As Int)

    Dim VarPar as Map
    VarPar.Initialize

    VarPar.Put("FirstName", FirstName)
    VarPar.Put("LastName", LastName)
    VarPar.Put("Age", Age)

    CallSubDelayed2(Main, "DescribePerson", VarPar)

End Sub
and in the activity:
B4X:
Sub DescribePerson(VarPar As Map)

    Dim FirstName As String = VarPar.GetDefault("FirstName", "Fred")
    Dim LastName As String = VarPar.GetDefault("LastName", "Flintstone")
    Dim Age As Int = VarPar.GetDefault("Age", 30)

    Log(FirstName & " " & LastName & " is " & Age & " years old.")

End Sub
I think this ticks (or crosses?) off your points 2 thru 5, and with regards to point 1: yeah, there is a bit of setup involved, but that is a constant with no further work required, and is confined to two* subs; once done, all the calls just look like regular calls, no extra effort required.

* as in the target sub, and the helper sub(s)
 
Last edited:

emexes

Well-Known Member
Licensed User
Sorry,
1/ I know you're already twelve steps ahead of me, but... sometimes it helps to actually see stuff, so just in case :)
1b/ even if no help to you, might be of help to somebody else with the same need
2/ I should have used your example:

helper code module:
B4X:
Sub CallSubDelayed_Main_RoutineName(Param1 As String, Param2 As Int, Param3 As Float)

    Dim VarPar As Map
    VarPar.Initialize

    VarPar.Put("Param1", Param1)
    VarPar.Put("Param2", Param2)
    VarPar.Put("Param3", Param3)

    CallSubDelayed2(Main, "RoutineName", VarPar)

End Sub

'optional - map calls to previous 2-parameter version of Sub, to new 3-parameter version
Sub CallSubDelayed_Main_RoutineName_Original2Parameters(Param1 As String, Param2 As Int)

    Main_RoutineName(Param1, Param2, 0)    'or whatever default value is appropriate for Param3

End Sub
and then in the activity module:
B4X:
Sub RoutineName(VarPar As Map)

    Dim Param1 As String = VarPar.Get("Param1")
    Dim Param2 As Int = VarPar.Get("Param2")
    Dim Param3 As Float = VarPar.Get("Param3")

    ....

End Sub
and finally the actual call (hopefully we don't hit any identifier length limits):
B4X:
...
VarParHelper.CallSubDelayed_Main_RoutineName("1", 2, 3.0)
...
Again... what could possibly go wrong?!?! :)
 
Last edited:

emexes

Well-Known Member
Licensed User
Obviously it'd be better built in to B4A. In older BASIC dialects, you had to CALL a SUB, eg:
B4X:
CALL DrawPolygon(X, Y, R, NumSides, Color)
but in B4A the CALL is implied. If we imagine that the parsing and generation of the regular inline CALL was still there, then perhaps it is not impossible that an alternative version that did the delayed call via the message queue could be added, eg:
B4X:
CallSubDelayed Main.DrawPolygon(X, Y, R, NumSides, Color)
that could still do all the parameter checking just like a regular call does, no extra parsing etc required.

The only downgrade I can see in implementing it like that is that the Sub name (DrawPolygon) is now not a String, and thus you could not dynamically change the target Sub to be called. Not sure how many people use that ability. I never have, but presumably it is useful for calling library routines or dynamic methods or something.

Perhaps it is the runtime nature of the target routine name that makes it impossible to check the parameters at compile time.
 

Erel

Administrator
Staff member
Licensed User
Tip:
B4X:
Sub Main_DescribePerson(FirstName As String, LastName As String, Age As Int)
    Dim VarPar as Map
    VarPar.Initialize
    VarPar.Put("FirstName", FirstName)
    VarPar.Put("LastName", LastName)
    VarPar.Put("Age", Age)
    CallSubDelayed2(Main, "DescribePerson", VarPar)
End Sub
Better:
B4X:
Sub Main_DescribePerson(FirstName As String, LastName As String, Age As Int)
    CallSubDelayed2(Main, "DescribePerson", CreateMap("FirstName": FirstName, "LastName": LastName, "Age": Age))
End Sub
 

LucaMs

Expert
Licensed User
Tip :) Erel, you could assign a priority level to our "wishes" (posting it in the thread). Maybe between 0 and 100.
 

emexes

Well-Known Member
Licensed User
I think this is as close as we're going to get on this wish for the time being:
B4X:
Sub Main_DescribePerson(FirstName As String, LastName As String, Age As Int)
    CallSubDelayed2(Main, "DescribePerson", CreateMap("FirstName": FirstName, "LastName": LastName, "Age": Age))
End Sub
which gives you:
- "unlimited" parameters
- parameter type checking
- as-you-type parameter help/prompting
- readable calls

at the expense of having to write:
- a single-line connecting Sub
- matching unpack lines in the called sub
 

MarkusR

Well-Known Member
Licensed User
usually u have to use a struct/type/class (with properties & functionality) that hold your data.
easy to access and process and easy to act as sub input/output because it is a single argument.

in your case
B4X:
Sub Process_Globals
   
    Type Person(FirstName As String, LastName As String, Age As Int)
   
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   
    Dim p As Person
    p.FirstName = "Markus"
    p.Age = 47
   
    Any(p)
    'CallSubDelayed2(Me,"Any",p)
   
End Sub

Sub Any(p As Person)
    Log(p.FirstName)
End Sub
 
Last edited:

LucaMs

Expert
Licensed User
in your case
"your"? Not mine.

I would use classes (or custom types) in that case, but I mean to talk about generic routines, in which, for example, you have to pass a value to be processed, a processing mode and a boolean that changes the operations inside the routine (and really what I had to add was precisely this last parameter, the first two mentioned here are invented at this time).

In short, independent parameters, not fields of "an entity".
 

emexes

Well-Known Member
Licensed User
"your"? Not mine.
Guilty. My case, not LucaMs's.

Types are another way of passing more than 2 parameters, but the example given demonstrated that the parameter checking of the use-a-type way is not as good as the checking of the use-a-helper-sub way, because presumably the compiler would not complain about the missing LastName parameter.
 

MarkusR

Well-Known Member
Licensed User
in your case
ups, i wrote about this example from emexes.

the compiler would not complain about the missing LastName parameter.
not missing, just empty.
giving a argument as "" or empty string is also valid.
a class modul is better than just a type. it depends on what you need.

independent parameters
so more a list of anything?
i remember in vb6 it was possible to write arguments comma-separated but it was just a object array which have different syntax in b4x.
B4X:
Test a,b,c,f,e
Test Array(a,b,c,d)
 

emexes

Well-Known Member
Licensed User
not missing, just empty.
giving a argument as "" or empty string is also valid.
True. Nice recovery! :)

But for those of us whose memory ain't what it used to be, it is useful for the compiler to point out missed parameters just like it does for normal Sub calls.

B4X:
Test a,b,c,f,e
Test Array(a,b,c,d)
That's pretty nifty. So if you're willing to forego compile-time parameter checking, then multi-parameter CallSubs can be done with one parameter which is an Array of Objects. I was so charmed by CreateMap() that it blinded me to seeing that an inline Array() could also do the job. I just checked in B4J and this works:
B4X:
Sub Test(P() As Object)
    Log(P.Length)
    For I = 1 to P.Length
        Log("Parameter " & I & " = " & P(I - 1))
    Next
End Sub

CallSub2(Me, "Test", Array("Now", "Is", "The", "Time"))
CallSub2(Me, "Test", Array("Fred", "Flintstone", 47, True))
CallSub2(Me, "Test", Array("Pi", 3.14159, "e", 2.718, "c", 299792458, "g", 9.80665))
I would probably still go through a helper Sub to do the parameter checking = cheap insurance against shooting new holes in my foot ;-)
B4X:
Sub Main_Test(FirstName As String, LastName As String, Age As Int, Married As Boolean)
    CallSub2(Main, "Test", Array(FirstName, LastName, Age, Married))
End Sub
 

JohnC

Well-Known Member
Licensed User
How does the VB6/VB.NET compiler support optional parameters, and can that ability be implemented in B4X?
 
Top