"Best" is rather vague and largely dependent on your goals and needs. I can only compare different approaches. At the end of the day, however, you will have to decide whether any of them fits your particular use-case, and if so, which one does it
best.
Conditionals
Code: Select all
arrLen := (A_AhkVersion < "2") ? arr.Length() : arr.Length
+ Simple.
- Overhead of redundant conditionals.
- Code duplication. (Additional overhead if abstracted away with a function wrapper.)
Function Reference
Code: Select all
global LenFunc := (A_AhkVersion < "2") ? "ObjLength" : Array.Prototype.GetOwnPropDesc("Length").Get
arrLen2 := %LenFunc%(arr)
+ Simple.
+ More performant in v2. (Same overhead in v1 as "redundant conditionals".)
- Global namespace pollution.
- Special case handling for force-local functions.
- Danger of accidental overwrites.
Define Length() on Array in v2
Code: Select all
if (A_AhkVersion >= "2")
Array.Prototype.DefineMethod("Length", Array.Prototype.GetOwnPropDesc("Length").Get)
+ Simple.
+ No source code changes required. (Array.Length() is now v1/v2 compatible.)
+ Similar performance in v1 and v2. (Less overhead than v1's "redundant conditionals".)
- Standard Array interface contamination.
- Possibly brittle. (End-user decides to clear or replace the prototype.)
User-defined function object
Code: Select all
#Warn
class FuncObjBase
{
__Call(methodName, Args*)
{
switch
{
case methodName = "": return this.Call(Args*)
case IsObject(methodName): return this.Call(methodName, Args*)
}
}
}
/*
Every function object template has to inherit from 'FuncObjBase'
and 'Init()' has to be copied over by the end-user verbatim and
'className' set to that of the function object. Define a 'Call()'
*instance* method with the appropriate number of arguments and
provide a v1/v2 compatible implementation.
*/
class ObjLength extends FuncObjBase
{
Init()
{
; It is not possible to obtain a reference to the class at the
; time the static initializers are run, because of which the
; class name has to be manually hard-coded in by the end-user.
static className := "ObjLength"
static ClassObj := %className%
static invokeSelf := (A_AhkVersion < "2")
? ClassObj.Init()
: ClassObj.Prototype.Init()
if (A_AhkVersion < "2")
{
; In v1, '%Obj%()' walks the object's base prototype chain and
; invokes the first defined meta-'__Call()'. No special
; handling is required apart from erasing the 'Init()' method.
ClassObj.Delete("Init")
}
else
{
; In v2, '%Obj%()' always invokes a 'Call()' method and never a
; meta-'__Call()'. In order to be able to invoke '%ClassObj%()'
; properly, it is required that a *static*, class method be
; defined. Create one based on the currently implemented *instance*
; method, then clean up the interface by erasing the 'Init()' and
; 'Call()' instance methods.
ClassObj.DefineMethod("Call", ClassObj.Prototype.GetMethod("Call"))
ClassObj.Prototype.DeleteMethod("Call")
ClassObj.Prototype.DeleteMethod("Init")
}
}
Call(Arr)
{
; Substitute with a v1/v2 compatible implementation of your choice.
static fn := (A_AhkVersion < "2")
? Func("ObjLength")
: Array.Prototype.GetOwnPropDesc("Length").Get
return %fn%(Arr)
}
}
MsgBox % %ObjLength%(Arr) ; v1
MsgBox %ObjLength%(Arr) ; v2
+ Some degree of protection against accidental overwrites (#Warn).
+ No source code changes required. (Use %ObjLength%(Arr) universally.)
- Complex.
- Sub-par v2 performance, abysmal v1 performance.
- BoundFunc interface nonconformity.
- Global namespace pollution.
- Special case handling for force-local functions.
- Brittle.