1. *** New version of B4J is available ***
    B4J v7.8
    Dismiss Notice

Wish Unlimited number of arguments when using CallSub/CallSubDelayed

Discussion in 'Bugs & wishlist' started by LucaMs, Jul 2, 2019.

  1. LucaMs

    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: Jul 2, 2019
  2. emexes

    emexes Well-Known Member Licensed User

    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?
     
  3. LucaMs

    LucaMs Expert Licensed User

    Yes, they are too useful but my example is not strictly related with them; I needed a third mandatory parameter, not an optional one.
     
  4. emexes

    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:
    Code:
    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:
    Code:
    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: Jul 2, 2019
  5. emexes

    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:
    Code:
    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:
    Code:
    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):
    Code:
    ...
    VarParHelper.CallSubDelayed_Main_RoutineName(
    "1"23.0)
    ...
    Again... what could possibly go wrong?!?! :)
     
    Last edited: Jul 2, 2019
  6. emexes

    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:
    Code:
    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:
    Code:
    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.
     
  7. Erel

    Erel Administrator Staff Member Licensed User

    Tip:
    Code:
    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:
    Code:
    Sub Main_DescribePerson(FirstName As String, LastName As String, Age As Int)
        CallSubDelayed2(Main, 
    "DescribePerson", CreateMap("FirstName": FirstName, "LastName": LastName, "Age": Age))
    End Sub
     
    Johan Hormaza and jimmyF like this.
  8. emexes

    emexes Well-Known Member Licensed User

    spewin!!! where was that hidden?!?! off to look now...
     
  9. LucaMs

    LucaMs Expert Licensed User

    Yes, I just used CreateMap (but the list on my first post... ;))
     
  10. LucaMs

    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.
     
  11. Erel

    Erel Administrator Staff Member Licensed User

  12. LucaMs

    LucaMs Expert Licensed User

  13. emexes

    emexes Well-Known Member Licensed User

    that made me lol ... at least we're all working in the same general direction, and still smiling :)
     
  14. emexes

    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:
    Code:
    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
     
    Erel likes this.
  15. MarkusR

    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
    Code:
    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: Jul 2, 2019
  16. LucaMs

    LucaMs Expert Licensed User

    "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".
     
  17. emexes

    emexes Well-Known Member Licensed User

    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.
     
  18. MarkusR

    MarkusR Well-Known Member Licensed User

    ups, i wrote about this example from emexes.

    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.

    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.
    Code:
    Test a,b,c,f,e
    Test 
    Array(a,b,c,d)
     
  19. emexes

    emexes Well-Known Member Licensed User

    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.

    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:
    Code:
    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"47True))
    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 ;-)
    Code:
    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
     
  20. JohnC

    JohnC Well-Known Member Licensed User

    How does the VB6/VB.NET compiler support optional parameters, and can that ability be implemented in B4X?
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice