AutoHotkey Community

It is currently May 27th, 2012, 3:28 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: March 27th, 2009, 6:59 am 
Offline

Joined: November 4th, 2008, 9:23 am
Posts: 1045
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: May 29th, 2009, 5:19 pm 
Offline

Joined: May 3rd, 2009, 7:16 pm
Posts: 345
Location: OH, USA
I will second this. Some of my functions would get a nice performance increase with this built in variable.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 4th, 2009, 10:15 am 
Offline

Joined: May 24th, 2006, 2:49 pm
Posts: 4511
Location: Belgrade
I toally suport this.
Currently you can't detect such thing, generally speaking.

_________________
Image


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 4th, 2009, 5:17 pm 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 4th, 2009, 10:47 pm 
Offline

Joined: November 4th, 2008, 9:23 am
Posts: 1045
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.

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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 3rd, 2009, 8:49 pm 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 3rd, 2009, 8:53 pm 
Offline

Joined: November 4th, 2008, 9:23 am
Posts: 1045
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 3rd, 2009, 10:05 pm 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 3rd, 2009, 10:15 pm 
Offline

Joined: November 4th, 2008, 9:23 am
Posts: 1045
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 3rd, 2009, 10:54 pm 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
I dunno :-) If it's supposed to happen...

Trying: "Global 0" -- Doesn't help either hehe.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 4th, 2009, 4:06 am 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 4th, 2009, 7:07 pm 
Offline

Joined: October 7th, 2006, 4:50 pm
Posts: 3157
Location: MN, USA
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


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 4th, 2009, 7:19 pm 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
@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.


Report this post
Top
 Profile  
Reply with quote  
PostPosted: June 16th, 2010, 7:51 pm 
Offline

Joined: August 2nd, 2009, 6:40 am
Posts: 215
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
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: June 17th, 2010, 1:53 am 
Offline

Joined: October 7th, 2006, 4:50 pm
Posts: 3157
Location: MN, USA
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.


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next

All times are UTC [ DST ]


Who is online

Users browsing this forum: No registered users and 4 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group