AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

A_ParamCount - to retrieve the number of PASSED parameters
Goto page 1, 2  Next
 
Reply to topic    AutoHotkey Community Forum Index -> Wish List
View previous topic :: View next topic  
Author Message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Fri Mar 27, 2009 5:59 am    Post subject: A_ParamCount - to retrieve the number of PASSED parameters Reply with quote

Is it possible to add a variable that would contain the number of PASSED parameters - for ease in working with optional parameters?

This would allow branching based on the number of parameters. For example, this would allow calling an external function via a dynamic call and use the default parameters of the dynamically called function.

This comes up because of the request of adding Interfaces to the Class Library. An interface would redirrect the call to the correct class. So, you could call Log_write(anyLogObject) and it would call the "write" function for the Class object (based on the Class name of the object).

Ex.
Code:
;ClassName = "ThisLog"
ThisLogObject := ThisLog_new(args...)

;ClassName = "ThatLog"
ThatLogObject := ThatLog_new(args...)


;note, these calls look IDENTICAL to the function without an A_ParamCount value

;Log_Write will dynamically call ThisLog_write (since "ThisLogObject" is an instance of Class ThisLog), and use the default parameters as specified by ThisLog_write for any optional parameters not passed.

;two parameter passed
Log_write(ThisLogObject, "SomeText")

useFormat := 0
;three parameters passed
Log_write(ThisLogObject, "SomeText", useFormat)

TimeStampFormat := "MM/DD/YYYY HH:MM:SS"
;four parameters passed
Log_write(ThisLogObject, "SomeText", useFormat, TimeStampFormat)


;note, these calls look IDENTICAL to the function without an A_ParamCount value

;Log_Write will dynamically call ThatLog_write (since "ThatLogObject" is an instance of Class ThatLog), and use the default parameters as specified by ThatLog_write for any optional parameters not passed.

;two parameters passed
Log_write(ThatLogObject, "SomeText")

useFormat := 0
;three parameters passed
Log_write(ThatLogObject, "SomeText", useFormat)

TimeStampFormat := "MM/DD/YYYY HH:MM:SS"
;four parameters passed
Log_write(ThatLogObject, "SomeText", useFormat, TimeStampFormat)

return


;note, this would be the function setup, if A_ParamCount existed
Log_write(LogObject, Text, useFormat = 0, TimeStampFormat = "MM/DD/YYYY HH:MM:SS")
{
    ;the values for useFormat and TimeStampFormat don't matter, since they will only be passed if an actual value was inputted as an argument.

    if !ClassName := Class_implementsInterface(LogObject, "Log")
        return

    if (A_ParamCount = 2)
        return %ClassName%_write(LogObject, Text)
    else if (A_ParamCount = 3)
        return %ClassName%_write(LogObject, Text, useFormat)
    else
        return %ClassName%_write(LogObject, Text, useFormat, TimeStampFormat)
}


;ThisLog implements the Log interface - created by User1

;inside Class ThisLog
ThisLog_write(ThisLogObject, Text, useFormat = 1, TimeStampFormat = "MM/DD/YYYY HH:MM:SS")
{
    ;default format is 1

    ;code to write the given text to the log using the specified format
}


;ThatLog implements the Log interface - created by User2

;inside Class ThatLog
ThatLog_write(ThisLogObject, Text, useFormat = 2, TimeStampFormat = "DD/MM/YYYY HH:MM:SS)
{
    ;default format is 2
    ;the default timestamp format has the day, then the month (e.g. spanish speaking countries)

    ;code to write the given text to the log using the specified format
}



Knowing how many parameters were ACTUALLY PASSED is required to allow propper interfaces. I'm going to release the interface design tomorrow using a "reserved parameter" ("ClassLibrary_ReservedValue") for optional parameters. This works off the assumption that if the parameter equals the reserved value, then that parameter was ommited. However, in doing so, the code is less readable then the above form.

Note, the below code is the functional equivalent to the above code, using the present capabilities - changes in red.


Code:
;ClassName = "ThisLog"
ThisLogObject := ThisLog_new(args...)

;ClassName = "ThatLog"
ThatLogObject := ThatLog_new(args...)


;note, these calls look IDENTICAL to the function without an A_ParamCount value

;Log_Write will dynamically call ThisLog_write (since "ThisLogObject" is an instance of Class ThisLog), and use the default parameters as specified by ThisLog_write for any optional parameters not passed.

;two parameter passed
Log_write(ThisLogObject, "SomeText")

useFormat := 0
;three parameters passed
Log_write(ThisLogObject, "SomeText", useFormat)

TimeStampFormat := "MM/DD/YYYY HH:MM:SS"
;four parameters passed
Log_write(ThisLogObject, "SomeText", useFormat, TimeStampFormat)


;note, these calls look IDENTICAL to the function without an A_ParamCount value

;Log_Write will dynamically call ThatLog_write (since "ThatLogObject" is an instance of Class ThatLog), and use the default parameters as specified by ThatLog_write for any optional parameters not passed.

;two parameters passed
Log_write(ThatLogObject, "SomeText")

useFormat := 0
;three parameters passed
Log_write(ThatLogObject, "SomeText", useFormat)

TimeStampFormat := "MM/DD/YYYY HH:MM:SS"
;four parameters passed
Log_write(ThatLogObject, "SomeText", useFormat, TimeStampFormat)

return


;note, this would be the function setup, if A_ParamCount existed
Log_write(LogObject, Text, useFormat = "ClassLibrary_ReservedValue", TimeStampFormat = "ClassLibrary_ReservedValue")
{
    ;optional parameters make use of the "reserved value" ("ClassLibrary_ReservedValue"). 
    ;If the parameter is equal to the "reserved value", then it is safe to assume the passed parameter was a default value, and not actually passed.

    ;I hope each of the "reserved valuue" strings don't take up memory - hopefully there is a single copied shared by them all - to save memory.

    if !ClassName := Class_implementsInterface(LogObject, "Log")
        return

    if (usedFormat == "ClassLibrary_ReservedValue")
        return %ClassName%_write(LogObject, Text)
    else if (TimeStampFormat == "ClassLibrary_ReservedValue")
        return %ClassName%_write(LogObject, Text, useFormat)
    else
        return %ClassName%_write(LogObject, Text, useFormat, TimeStampFormat)
}


;ThisLog implements the Log interface - created by User1

;inside Class ThisLog
ThisLog_write(ThisLogObject, Text, useFormat = 1, TimeStampFormat = "MM/DD/YYYY HH:MM:SS")
{
    ;default format is 1

    ;code to write the given text to the log using the specified format
}


;ThatLog implements the Log interface - created by User2

;inside Class ThatLog
ThatLog_write(ThisLogObject, Text, useFormat = 2, TimeStampFormat = "DD/MM/YYYY HH:MM:SS)
{
    ;default format is 2
    ;the default timestamp format has the day, then the month (e.g. spanish speaking countries)

    ;code to write the given text to the log using the specified format
}



Some remarks:
  • Interfaces are still possible because the above method works.

  • The readability only impacts the interface object - not the end user or the developer of the implementing class. In other works, it's not the end of the world. However, depending on how many optional parameters a function has and how long the variable names are, more functions than required may run off the screen (due to limits in the number of characters visable at a time). Additionally, if you were to type the "reserved value" (I'd copy and paste, personally), there is an increased chance of typos, which this addition would otherwise prevent.



Final thoughts:
Not only would adding an A_ParamCount value make the above code more readable, it would allow more generic overloading of functions.


Ex.

The Vector_add function has two forms.

  1. Result := Vector_add(VectorObject, object)
  2. Result := Vector_add(VectorObject, index, object)


Currently, this is done by making the last parameter an optional value (with a "dummy value" as the default). Since the last parameter is a Class object (a number), I used the text "Append to end" as the dummy value. It was originally the empty string, but I realized this would have an increased chance of bugs if a typo occured and an empty string was passed when a value was intended. However, I couldn't freely change the default value without producing some complications. If an A_ParamCount value existed, it could have been used to test for the two cases - thus preventing this problem.


Closing statement:
Although this is not an urgent matter, I do believe the topic should be open for discussion. As AHK becomes larger, increasing the functionality of the language, in my opinion, is key. The limitations of yesterday only come to light today because the sun is located in a different position. In other words, I bring this to light now, because I believe there is a use for it in this new period of OOP in AHK.
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
Solar



Joined: 03 May 2009
Posts: 345
Location: OH, USA

PostPosted: Fri May 29, 2009 4:19 pm    Post subject: Reply with quote

I will second this. Some of my functions would get a nice performance increase with this built in variable.
Back to top
View user's profile Send private message
majkinetor



Joined: 24 May 2006
Posts: 4511
Location: Belgrade

PostPosted: Sat Jul 04, 2009 9:15 am    Post subject: Reply with quote

I toally suport this.
Currently you can't detect such thing, generally speaking.
_________________
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4710
Location: Boulder, CO

PostPosted: Sat Jul 04, 2009 4:17 pm    Post subject: Reply with quote

Can you distinguish between omitted parameters and ones, which happen to be their defaults? I guess, it is not easy, so the parameter count variable would be useful.
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Sat Jul 04, 2009 9:47 pm    Post subject: Reply with quote

Laszlo wrote:
Can you distinguish between omitted parameters and ones, which happen to be their defaults?

From my testing, no (for non-ByRef parameters). For ByValue (non-ByRef) parameters, a copy of the variables contents are copied. This is "exactly" what is done when a default value is used (in the case of omitting the parameter). I haven't checked the source code, so "exactly" means "as reflected in my testing."

Code:
a := "1"
Function(a)
Function(a, 2)
Function(1, 2)

Function(arg1, arg2 = 2)
{
    ;these addresses remain the same for all calls
    MsgBox, % (&arg1) " " (&arg2)
}



Now, ByRef, optional parameters you CAN check. Because ByRef parameters pass an address, instead of copying the variable's contents, you can test whether the address for the variable is the "local version" or not. If the parameter is passed, the address is that of the passed value. If omitted, it is the address of the local variable.

Functions -> Optional Parameters wrote:
In v1.0.46.13+, ByRef parameters also support default values; for example: Func(ByRef p1 = ""). Whenever the caller omits such a parameter, the function creates a local variable to contain the default value; in other words, the function behaves as though the keyword "ByRef" is absent.


However, to use this method, you need to "cache" the address and compare it. Since the address for the parameters don't change between calls (from my testing, at least), you can use this to compare the address in the "cache" with the passed address. If they are identical, the local copy is used (i.e. the parameter was omitted). Otherwise, the parameter was passed.

The caching requires calling the function, ommitting the optional arguments. This would be the "first run". By using a static variable, you can check if the run is the "first run". If it is, you cache the value and return (don't actually run the function, in full). Otherwise, first use the cache to determine if the ByRef parameter was passed.

Code:
;first run (builds the address "cache")
;mandatory parameters can be anything,
;    since the function is returned immediately after building the "cache"
Function(1)
Function2(1)

arg2 := 2
arg3 := 3
arg4 := 4

;passed one parameter
Function(1)

;passed two parameters
Function(1, arg2)

;passed three parameters
Function(1, arg2, arg3)


;passed one parameter
Function2(1)

;passed two parameters
Function2(1, arg2)

;passed three parameters
Function2(1, arg2, arg3)

;passed four parameters
Function2(1, arg2, arg3, arg4)

Function(arg1, ByRef arg2 = 2, ByRef arg3 = 3)
{
    ;alreadyDone tracks the "first run",
    ;paramCount is the total number of parameters (including optional ones)
    static alreadyDone, ParamCount = 3

    if (!alreadyDone)
    {
        ;"first run"
        alreadyDone := true

        ;set-up the "cache" for the addresses of the ByRef variables
        A_ParamCount(A_ThisFunc, ParamCount, arg2, arg3)

        ;don't actually run the function - just build the "cache"
        return
    }
    else
    {
        ;not the "first run" - see which parameters, if any, were omitted
        ;specify the negative of ParamCount to
        A_ParamCount := A_ParamCount(A_ThisFunc, -ParamCount, arg2, arg3)
    }

    MsgBox, % "A_ParamCount = " A_ParamCount
}

Function2(arg1, ByRef arg2 = 2, ByRef arg3 = 3, ByRef arg4 = 4)
{
    ;alreadyDone tracks the "first run",
    ;paramCount is the total number of parameters (including optional ones)
    static alreadyDone, ParamCount = 4

    if (!alreadyDone)
    {
        ;"first run"
        alreadyDone := true

        ;set-up the "cache" for the addresses of the ByRef variables
        A_ParamCount(A_ThisFunc, ParamCount, arg2, arg3, arg4)

        ;don't actually run the function - just build the "cache"
        return
    }
    else
    {
        ;not the "first run" - see which parameters, if any, were omitted
        ;specify the negative of ParamCount to
        A_ParamCount := A_ParamCount(A_ThisFunc, -ParamCount, arg2, arg3, arg4)
    }

    MsgBox, % "A_ParamCount = " A_ParamCount
}

/*
used to cache the addresses of optional ByRef parameters to detect if they were passed or omitted
FunctionName is the function's name (A_ThisFunc)
ParamCount is the total number of parameters (including optional ones)
arg# is the 1st, 2nd, 3rd, ... ByRef argument

if ParamCount is negative, then (-ParamCount) is the
    actual number of parameters (including optional ones),
    and the ACTUAL number of parameters passed will be returned


Note: using this method works only if all optional parameters are ByRef
Other means are required to detect non-ByRef parameters
*/
A_ParamCount(FunctionName, ParamCount = "", ByRef arg1 = "", ByRef arg2 = "", ByRef arg3 = "", ByRef arg4 = "", ByRef arg5 = "", ByRef arg6 = "", ByRef arg7 = "", ByRef arg8 = "", ByRef arg9 = "")
{
    static

    if (ParamCount < 0)
    {
        ;return the actual parameter count

        ParamCount *= -1

        ;for each parameter
        loop, % ParamCount - (IsFunc(FunctionName) - 1)
        {
            ;if the address equals the cached address, then it was omitted
            if (&arg%A_Index% = @$%FunctionName%#%A_Index%)
            {
                ;IsFunc(FunctionName) - 1 is the number of mandatory parameters
                ;A_Index - 1 is to refer to the parameter before this one
                ;   (because it was omitted)
                return IsFunc(FunctionName) - 1 + A_Index - 1
            }
        }

        ;all arguments were passed
        return ParamCount
    }

    ;for each parameter
    loop, % ParamCount - (IsFunc(FunctionName) - 1)
    {
        ;store the variable's address
        @$%FunctionName%#%A_Index% := &arg%A_Index%
    }
}



This work around works for any function that takes ONLY ByRef arguments for the optional parameters. If a way to detect an omitted non-ByRef parameter versus one passed with the default value becomes known, it can be added to the above function, and we can emulate an A_ParamCount value.

Please don't start using the above workaround yet, as I'm going to start some more testing. For example, it might be possible to detect more cases than it does, for example, functions that have some ByRef and some ByValue parameters for optional parameters. Also, if a value isn't the default value, we know it was passed. So, I'm hoping that I can expand the above function to all cases except ByValue parameters whose passed argument is the default value.

I'll post the updates here.
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Mon Aug 03, 2009 7:49 pm    Post subject: Reply with quote

Since %0% holds the number of args passed to a script initially, it might make sense for %0% to also hold the number of args handed to a function, while inside that function.
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Mon Aug 03, 2009 7:53 pm    Post subject: Reply with quote

No, that wouldn't work, because what if you want to access the command-line arguments in a function? - you would want to know how many arguments there were, so you would check %0%.
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Mon Aug 03, 2009 9:05 pm    Post subject: Reply with quote

You can't access the %0% variable, within a function... I've tried it - it is an empty variable.

Code:
MsgBox "NumOfArgs %0% "

Foo( "Test" )

Foo( Test, Test1=0, Test2=1)
{
   MsgBox, "Foo Args: %0%"
}
return

2nd MsgBox lists nothing for %0%, 1st one displays the number of args.
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Mon Aug 03, 2009 9:15 pm    Post subject: Reply with quote

Huh, weird. Is that suppose to happen??

Also, would it cause confusion to overload the %0% variable? I love the idea, provided the 0 isn't suppose to wok in functions, but will it confuse those new to AHK? those familiar with AHK?
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Mon Aug 03, 2009 9:54 pm    Post subject: Reply with quote

I dunno Smile If it's supposed to happen...

Trying: "Global 0" -- Doesn't help either hehe.
Back to top
View user's profile Send private message
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Tue Aug 04, 2009 3:06 am    Post subject: Reply with quote

Ok, I got an itch in regards to A_paramCount
I dunno if you'll find this useful or not, and it has a couple of caveats:
1) You wont use any variables that are -MAXINT :: -0x8000000000000000 AND
2) You dont set optional variables to meaningful values with assigned defaults, OR only needed for 1 optional variable in a function.
,,) i.e. you don't do: Foo( Test1, Test2=3, Test3=7), instead you use defaults to indicate unused variables, and normally set them to 0 or -1 or the like.
,,) i.e.:
Code:
DateRelatedFn( aWord, timeStamp=-1 )
{
   Args := CheckAndSetArgs( -1, "MM/DD/YY", 0, timeStamp )
}
Code:
Foo( "Test", 1, 2 )


Foo( Test, Test1=-255, Test2=-255, Test3=-255)
{
   Args := CheckAndSetArgs(-255, 0, 1, Test1, Test2, Test3)

   MsgBox, "Args: %Test1%, %Test2%, %Test3%, #Args %Args%"
   MsgBox, "Arg3: %Test3%"
}
return

When you create a function that can have optional arguments, assign those arguments
Quote:
1) A value that is not -MAXINT:: -0x8000000000000000
..) It CAN be +MAXINT:: 0x7FFFFFFFFFFFFFFF
..) It CAN be a string.
2) A value that the ARGS will NEVER have.
3) The SAME value for all arguments.

USAGE: CheckAndSetArgs( default, newVal, nullVal, [Var*] )
Quote:
default: the value your optional variables are set to.

newVal: 1) If newVal == default, then your optional variables will not change value.
newVal: 2) If newVal <> default, then your optional variables will be set to that value.

nullVal: IF newVal is 0 AND nullVal is 1, then your optional variables will be UNSET i.e Var1 := ""

Code:
CheckAndSetArgs(default, newVal, nullVal, byRef Var1=-0x8000000000000000, byRef Var2=-0x8000000000000000, byRef Var3=-0x8000000000000000)
{
   maxArgs = 3
   numArgs = 0
   while ( A_index <= maxArgs )
   {
      if( Var%A_index% <> -0x8000000000000000 )
      {
         if( Var%A_index% <> default )
         {
            numArgs += 1
            continue
         }
         else
         if( default <> newVal )
         {
            if( newVal == 0 && nullVal == 1 )
            {
               Var%A_index% := ""
            }
            else
            {
               Var%A_index% := newVal
            }
            continue
         }
         break
      }
      break
   }
   return numArgs
}

NOTE(1): This can easily be changed to have more Var#'s just add more, and change maxArgs to how many Var#'s there are.

NOTE(2): This can easily be changed so that each Var# has its own "newVal" and "nullVal" setting, so that you could pass meaningful values to multiple optional variables.
Back to top
View user's profile Send private message
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Tue Aug 04, 2009 6:07 pm    Post subject: Reply with quote

Crash&Burn wrote:
Trying: "Global 0" -- Doesn't help either hehe.
It works for me.
Code:
MsgBox "NumOfArgs %0% "

Foo( "Test" )

Foo( Test, Test1=0, Test2=1)
{
   global 0
   MsgBox, "Foo Args: %0%"
}
return
Back to top
View user's profile Send private message Visit poster's website
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Tue Aug 04, 2009 6:19 pm    Post subject: Reply with quote

@jaco it may of been my AHK version, I accidentally restored an older zip file when I reinstalled (Windows) last month. I only noticed yesterday after trying to use "while()" which I seen a code-sample in the Top40 Features list.
Back to top
View user's profile Send private message
Crash&Burn



Joined: 02 Aug 2009
Posts: 210

PostPosted: Wed Jun 16, 2010 6:51 pm    Post subject: Something a little cleaner Reply with quote

Dunno if this will be of any use, but I came up with this the other day as a solution for checking if a minimum number of args were passed to a function.

Code:
CountArgs( var1="", var2="", var3="", var4="", var5="" )
{
   Loop, 5
      var .= SubStr(var%A_Index%,1,1)
   return StrLen(var)
}

Code:
#a::
{
   numArgs := CountArgs(Foo, Bar, FooBar)
   MsgBox, numArgs: %numArgs%

   Foo := "Foo", Bar := "Bar"
   numArgs := CountArgs(Foo, Bar, FooBar)
   MsgBox, numArgs: %numArgs%

   Foo := "", Bar := "", FooBar := "FooBar"
   numArgs := CountArgs(Foo, Bar, FooBar)
   MsgBox, numArgs: %numArgs%
return
}
Back to top
View user's profile Send private message
jaco0646



Joined: 07 Oct 2006
Posts: 3113
Location: MN, USA

PostPosted: Thu Jun 17, 2010 12:53 am    Post subject: Reply with quote

It still only reports the number of non-blank parameters. For the purpose of overloading a function, passing a blank parameter is not the same as passing no parameter.
Code:
MsgBox,% CountArgs()            ;0 args passed
MsgBox,% CountArgs(foo,bar,baz) ;3 args passed

CountArgs( var1="", var2="", var3="", var4="", var5="" )
{
   Loop, 5
      var .= SubStr(var%A_Index%,1,1)
   return StrLen(var)
}
Also, the assumption that every optional parameter's default value will be blank is rarely valid.
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Wish List All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group