Parentheses when calling gets?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Parentheses when calling gets?

16 Nov 2015, 16:20

I am noticing that even though I've defined a class property get with parameters in square brackets, AHK seems to be indifferent whether I CALL that get with its parameters enclosed in brackets or parentheses. Is this just a fluke, or a behavior I can rely on for future versions? Example appears below, and the last two lines exhibit the same behavior. Thanks in advance for your assistance.

Code: Select all

class math {
	sqr[arg] {
		get {
		MsgBox % arg**2
	}}
}
x := new math
x.sqr[3]
x.sqr(3)
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Parentheses when calling gets?

16 Nov 2015, 22:42

It's allowed for symmetry with x.y(z) := v, which is supported to facilitate converting code (or programmers ;)) from VBScript or similar. You can rely on it, but I wouldn't as a matter of style.
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Parentheses when calling gets?

17 Nov 2015, 00:43

Thanks, Lexikos! I understand. Also, just one more quick follow-up... Since you mention the conversion from other languages, I was trying to experiment with a "default method" for mirroring the functionality of the default .Item() method in the VB Collection object. Whenever I have previously built collections using associative arrays in AHK, where I also need other methods and properties in the same namespace, I end up having to either (1) contain the actual data in a sub-array or (2) use underscores to prefix all "non-collection" keys. #2 requires special iteration considerations and #1 yields long references in multi-level collections like managers.items(10).supervisors.items(57).clerks.items(143) rather than managers(10).supervisors(57).clerks(143).

I saw this text below regarding default methods in the AHK docs. I'm not quite sure how best to mix the newer object/property notation, which I pretty much exclusively use, with the older meta-function notation in a way that's most efficient/proper.

Code: Select all

The method name can also be completely omitted, as in x[](a). Scripts can utilize this by defining a default value for the __Call meta-function's first parameter, since it is not otherise supplied with a value. Note that this differs from x.(a), which is equivalent to x[""](a). If the property or method name is omitted when invoking a COM object, its "default member" is invoked.
So I've implemented that below, methinks. My new code executes fine, but I'm uncertain if I've interwoven the meta-function approach in the best way. Also, do you have thoughts about which of the three approaches is the best? Is there another good option that I'm missing?

Code: Select all

class collection {
	_items := ["placeholder1", "placeholder2", "placeholder3"]
	__Call(name="", params*) {
		if (name == "")
			return this.items(params[1])
	}
	count() {
		MsgBox, where the counting happens
	}
	sort() {
		MsgBox, where the sorting happens
	}
	items(i) {
		; some other pre-return prep stuff happens here
		return this._items[i]
	}
}
baubles := new collection
MsgBox % baubles.items(1)  ; the way I usually do it
MsgBox % baubles[](2)      ; uglier than VB's baubles(2): is this is the best way?
MsgBox % baubles.(3)       ; a little prettier, but is there a downside to this?  can a "" method be defined without __Call()?
Again, many thanks for all the hard work you do to make AHK great!
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Parentheses when calling gets?

17 Nov 2015, 03:13

You can define the "" method by assigning a method/function reference to collection[""]. You can do this when you construct the first object (__New can check if collection[""] is set) or in a static initializer expression.

However, object.() is currently prohibited in AutoHotkey v2-alpha. object[""]() and object[]() are allowed.

One alternative is to use the standard get/set syntax, and design the object to eliminate any ambiguities.

Code: Select all

baubles := new collection
MsgBox % baubles[1]
baubles.count := 42
MsgBox % baubles.count
baubles.count()  ; Still works.

class Collection extends Collection.Base {
    __New() {
        ObjRawSet(this, "_items", ["placeholder1", "placeholder2", "placeholder3"])
    }
    __Get(params*) {
        value := this._items[params*]
        ; some other pre-return prep stuff happens here
        return value
    }
    __Set(params*) {
        value := params.Pop()
        return this._items[params*] := value
    }
    class Base {
        __Call(name, params*) {
            if fn := Func("Obj" name)
                return %fn%(this._items, params*)
        }
    }
    count() {
        MsgBox, where the counting happens
    }
    sort() {
        MsgBox, where the sorting happens
    }
    _NewEnum() {
        return this._items._NewEnum()
    }
}
Notes:
  • ObjRawSet is used to bypass __Set.
  • Defining __Get/__Set in the same class as count/sort allows baubles.count or baubles["count"] to be redirected to baubles._items.count. For this to work if you subclass Collection, each subclass must redefine the meta-functions; e.g. static __Get := Collection.__Get.
  • Defining __Call in a super-class rather than in Collection allows baubles.count() to call the method, not the meta-function. Of course, you can just remove __Call if you don't want to allow all of the built-in methods to be used. In this case the base class is a bit redundant since __Call only acts if Func("Obj" name) exists.
  • baubles[x, y] will be redirected to baubles._items[x][y], so if baubles._items[x] is a Collection, it could ultimately redirect to baubles._items[x]._items[y]. However, if you assign to baubles[x, y] where baubles[x] has not previously been assigned a collection, a plain object (with no base) will be created automatically. If you don't intend to support the multi-dimensional syntax, you can replace params* with a normal parameter (and define value as a parameter in __Set).
  • baubles.base will be redirected to baubles._items.base, so won't return Collection but also won't work as a normal collection item. If you don't intend to support the multi-dimensional syntax, you can solve this by using ObjRawSet instead of the params* assignment.
  • This collection can't contain an item called "_items". One workaround for that is to create a unique object and use that as the key instead of the string "_items". In this case, I would suggest using the class itself.

Code: Select all

class Collection extends Collection.Base {
    __New() {
        ObjRawSet(this, Collection, ["placeholder1", "placeholder2", "placeholder3"])
    }
    __Get(params*) {
        value := this[Collection, params*]
        ; some other pre-return prep stuff happens here
        return value
    }
    __Set(params*) {
        value := params.Pop()
        return this[Collection, params*] := value
    }
    class Base {
        __Call(name, params*) {
            if fn := Func("Obj" name)
                return %fn%(this[Collection], params*)
        }
    }
    count() {
        MsgBox, where the counting happens
    }
    sort() {
        MsgBox, where the sorting happens
    }
    _NewEnum() {
        return this[Collection]._NewEnum()
    }
}
sirksel
Posts: 222
Joined: 12 Nov 2013, 23:48

Re: Parentheses when calling gets?

17 Nov 2015, 23:30

@Lexikos, wow, thanks so much! As always, you go above and beyond and give me things to think about I'd never considered before about the language. It was unfortunate that my simplification of example for the default method question used a numerically-keyed data array, because a purely numerically-indexed data list would rarely conflict with property/method names. However, you understood what I meant and your "count" method/property example proactively explained how to address that issue. There are a few things in your answer I don't fully understand, and I wondered if you (or others) could help me clarify whenever you have time:

1. I'm guessing that *all* instance vars must be set with ObjRawSet in __New(). Static vars still seem to work OK. When I try adding an instance var in the class style, I can tell AHK crashes (I'm guessing due to circular recursion), but I'm not quite clear on what's generating the recursion. It seems like instance vars get set using __Set behind-the-scenes, but then I'm not clear why the behavior is recursion rather than just creating it in the subarray (which I know isn't the behavior we want).

2. You said, "For this to work if you subclass Collection, each subclass must redefine the meta-functions; e.g. static __Get := Collection.__Get." Does that mean if a later "class Employees extends Collection" it needs to redefine the meta-functions? Why is that? I'm trying to follow through the logic of how these things are getting called and I'm not understanding this part for some reason. If that is the case, and I redefine the meta-functions, do all of the "this" references still refer to the right objects as they're written here without modification?

3. You said, "Defining __Call in a super-class rather than in Collection allows baubles.count() to call the method, not the meta-function." I'm still not quite understanding why this works, although I know it also has to do with the passage in the AHK Docs on order of operations of resolving keys. I'm guessing a call to count() resolves before ever calling Collection.Base.__Call? If __Call were in the same class rather than a superclass, then would __Call be called before count()? The docs say that "meta-functions define what happens when a key is requested but not found within the target object." Since count() is explicitly defined, I'm wondering why __Call would be called even if it were in the same object?

4. The superclass is named Base, but is there any magic to that name? Because "extends" is specified, it's not really using the special meaning of .base, right? I could name it Collection.Caller (or whatever) if I wanted to? I tested it with another name and it seemed to work OK.

5. You said, "if baubles._items[x] is a Collection, it could ultimately redirect to baubles._items[x]._items[y]". The example you gave already builds in that multi-parameter support, correct? In other words, I don't need to override anything in the sub-array to handle the ._items[y] part of the call, right?

6. Is there any downside to repointing the _NewEnum() reference to the data subarray? Does it perform any functions that don't get performed here because we overrode it?

7. In the special case where I have a solely-integer-indexed data just sharing the namespace with the other string-keyed properties/methods (i.e., without the sub-array construct), is there a way to override _NewEnum(), Next, etc. to make the for loop only enumerate the numeric keys? I know I can enumerate numeric keys with a simple counter loop (without the for loop), but I wanted to understand more about changing the default behavior of the enumerators.

Thanks again for all of your help!
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Parentheses when calling gets?

20 Nov 2015, 07:15

1. You probably attempted to assign the "instance variable" before initializing this[Collection]. this[Collection, params*] := value (within __Set) will recursively trigger __Set if this[Collection] hasn't been defined. If I assign this.x := 42 after calling ObjRawSet in __New, it works just fine, but as expected, the value is stored in the sub-array.

2. For this.x, the order of precedence is this.x, this.base.__Get, this.base.x, this.base.base.__Get, etc. If the meta-function is defined only in this.base.base and this.base.x exists, the meta-function won't be called. The order is documented under Meta-Functions.

I don't understand your question about 'the "this" references'. this never refers to the object in which the meta-function is defined, because meta-functions must be defined by a base object (this.base, this.base.base, etc.). this is the original object which appeared to the left of the access operator (the x in x.y or x[z]).

3. count() is not defined in the target object. It is defined in the target object's base. If you called Collection.count(), the meta-function would never be called even if it was defined directly in Collection, because in that case Collection is the target object.

4. No, base has no special meaning to the interpreter in this context. You can use whatever name you like.

5. I'm not sure that I understand your question. The condition "if baubles._items[x] is a Collection" implies that you are overriding the assignment to the sub-array. You have to put a Collection there in the first place; my example does not do that automatically.

6. It depends on what you want the enumerator to enumerate. The sub-array's enumerator will only enumerate the sub-array's values (i.e. the collection items). It will not return the sub-array itself, whereas the default enumerator would only return the sub-array (for x, y in baubles would set x to Collection and y to the sub-array).

7. Yes. As for how... just do it? For-loop describes how the enumerator object is used. Enumerator Object also describes how the Next method should behave. The rest is just about knowing what behaviour you want and applying logic. The enumerator object you design can return whatever you want it to return.

Code: Select all

class Fibonacci {
    _NewEnum() {
        return new this.E
    }
    class E {
        incr := 1, curr := 0
        Next(ByRef n, ByRef m := "") {
            n := this.curr, m := this.curr += this.incr, this.incr := n
            return true
        }
    }
}

for n, m in Fibonacci
    MsgBox % n ", " m

Esc::ExitApp

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: doodles333, mikeyww and 283 guests