Get an error when calling a method that does not exist?

Get help with using AutoHotkey and its commands and hotkeys
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Get an error when calling a method that does not exist?

04 Mar 2015, 08:08

Hi,

I am trying to figure out how to make AutoHotkey report an error if I am calling a non-existing method on an object.

Here is an example:

Code: [Select all] [Download] (Script.ahk)GeSHi © Codebox Plus

#Warn All ; Included to show that this flag does not help.

class Test
{
}

a:= new Test()
a.Hello() ; Does nothing and does not report an error.
Hello() ; Reports an error "Call to nonexistent function".

I would really like a.Hello() to report an error too, something like "Call to nonexistent method".

Is there a way to do that? Is there a special flag similar to the #Warn flag?
Coco-guest

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 08:41

Code: [Select all] [Expand] [Download] GeSHi © Codebox Plus

Coco-guest

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 09:14

If you're planning to use it again for other classes, you can use the following function:

Code: [Select all] [Download] GeSHi © Codebox Plus

Assert_IsMethod(obj, method, lvl:=-1)
{
if !ObjHaskey(obj, method) || !IsFunc(obj[method])
throw Exception("Call to nonexistent method", lvl, method)
return true
}


Usage:

Code: [Select all] [Expand] [Download] GeSHi © Codebox Plus

lexikos
Posts: 5415
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 20:22

This is simpler and also works for static methods:

Code: [Select all] [Expand] [Download] GeSHi © Codebox Plus

Btw, this is the default behaviour in v2.

Coco's method won't work if methods are defined in this.base.base or this.base.base.base, etc.
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 23:10

Thanks a lot both of you!

Your solution, Lexikos, is one of those simple and beautiful solutions that just make me go "well, why didn't I think of that?". :-) I guess that solution shows the hallmark of a master - coming up with those simple but ingenious solutions. What a joy! And good to hear by the way that that behavior is/will be standard in version 2.

I also want to thank you, Coco, for taking the time to make the solution you came up with. Even though Lexikos' solution was simpler and more complete, I really appreciate your helpfulness. Thank you! :-)
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 13:12

Hm, Lexikos' solution seems to kill the builtin Object() methods of classes inheriting from ThrowsNonexistentMethod. This includes the method _NewEnum(), which means that you cannot iterate over the object any more with a for loop:

for key, value in object


This 'for' statement triggers the __Call() method, giving the error:

"Non-existent method, specifically _NewEnum"

How can I make __Call() not override the default methods, or at least pass any calls to the default methods along to the actual default methods?

Or is there a version of __Call() that is only called if a method with a given name does not exist at all in the given object?
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 13:37

I think I might have solved it with this:

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus


I am using the fact that all builtin methods can also be called as Obj<methodname>, e.g. Obj_NewEnum for the _NewEnum method etc.

EDIT: Corrected the code. Now it seems to work.
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 14:13

Only problem now is that if I call an existing method with too few arguments, the call does nothing and doesn't report an error:

Code: [Select all] [Download] (Script.ahk)GeSHi © Codebox Plus

class Test extends ThrowsNonexistentMethod {
ExistentMethod(x) {
MsgBox Hello %x%.
}
}

Test.ExistentMethod() ; Does nothing and does not report an error.
Test.ExistentMethod(1) ; Shows a message box with the text "Hello 1."

Is there any way to catch this situation as well?


EDIT: I thought the problem was there for too many arguments as well, but I was wrong about that.
Coco-guest

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 15:23

Code: [Select all] [Expand] [Download] GeSHi © Codebox Plus

jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 15:55

Oh wow, cool, it works! :dance: Thanks Coco! :bravo:

And now I also understand better how the __Call method works. I didn't realize it was also called when a method was called with too many or too few parameters.

Thanks again! :thumbup:
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:19

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus

But this won't catch stuff like cx.fun() (just accessing the key of the object vs using the inheritance algorithm to locate the key in one of bases of object).
lexikos
Posts: 5415
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:33

I forgot about built-in methods. You don't need to call ObjMethod(); it's more efficient to not call them:

Code: [Select all] [Download] GeSHi © Codebox Plus

class ThrowsNonexistentMethod {
__Call(method:="") {
;if !(fn := Func("Obj" method)) || !fn.IsBuiltIn ; Incorrect result for AddRef/Release/ect.
if method not in Insert,Remove,MinIndex,MaxIndex,SetCapacity
,GetCapacity,GetAddress,_NewEnum,HasKey,Clone
throw Exception("Non-existent method", -1, method)
}
}
Alternatively, you can define the built-in methods in the class (in which case you don't need the if not in line).

Code: [Select all] [Expand] [Download] GeSHi © Codebox Plus

This also makes Coco's validation code work for those methods.

jhertel wrote:I didn't realize it was also called when a method was called with too many or too few parameters.
It's not. Like __Get and __Set, it's called if the key isn't defined in this. The methods are defined in this.base. The reason Coco added a second __Call rather than using the first one is that the __Call in the super-class is not called for methods defined in Test, even if the methods themselves can't be called. If you subclass Test, any methods you define in the subclass will not be validated unless you redefine __Call, like so:

Code: [Select all] [Download] GeSHi © Codebox Plus

class Test2 extends Test {
static __Call := Test.__Call
;...
}

[trismarck beat me to it, but I'm leaving my explanation:]
Also note that because __Call is only called for keys which don't exist in the target object, it isn't called for static methods. That is, it is called for t.ExistentMethod() but not Test.ExistentMethod(), because ObjHasKey(t, "ExistentMethod") is false while ObjHasKey(Test, "ExistentMethod") is true.


Btw, if you pass more parameters than needed, it will normally not prevent the method from being called. Only passing too few parameters is considered an error when dynamically calling a function or method. In v2, generally an exception is thrown if a required parameter is missing a value.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:49

trismarck wrote:o_fun := this[aFnName] ; will call meta-functions, so avoid that?
Unless required by functionality, a well-designed __Get should not tamper with or override the result when the caller tries to access a key which happens to be a method(declared in the class) or a Property. IMO, devs should always allow meta-functions to finish/cast normally, it makes classes/objects easier to extend/subclass, maintain, debug(subjective), etc.
lexikos
Posts: 5415
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 17:10

Since the addition of properties, I think the appropriate uses of __Get are less normal classes and more proxy objects, wrappers and such.

this[aFnName] will also call a property if aFnName is a property name.
jhertel
Posts: 16
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 04:45

Wow, you guys are so helpful! Thank you all!

I did notice just before going to bed last night that what I thought worked in my last comment above did not work in subclasses, which is what I want it to do.

I can see that all this and all your comments requires of me to take some time to sit down with a nice cup of coffee and try to really wrap my head around how objects and classes work in AutoHotkey. Especially the intricate details of when and in what order the meta functions are called. I think I tried at first to partly use my intuition and experience from other languages I know (Python probably being the closest) without completely understanding how it works in AutoHotkey. I did try to read the docs of course, but I failed to understand all the details. Now I will try again, combined with you very helpful comments above, to see if I can get a clear understanding.

Thank you again for all your help!
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 05:26

Coco wrote:Unless required by functionality, a well-designed __Get should not tamper with or override the result when the caller tries to access a key which happens to be a method(declared in the class) or a Property.
Coco, I agree with that (~well-designed meta-functions / classes should work in every case, even if meta-functions are called in the scenario above). Actually, this could be the point of why my example with base[aFnName] is broken.
Coco wrote:IMO, devs should always allow meta-functions to finish/cast normally, it makes classes/objects easier to extend/subclass, maintain, debug(subjective), etc.
To clarify, by "finish/cast normally" I understand that meta-functions should never explicitly return, unless its explicitly needed.

Lexikos wrote:this[aFnName] will also call a property if aFnName is a property name.
Thanks. So in the example I posted, if there was custom behaviour defined for the key aFnName somewhere upstream in the class hierarchy (i.e. a behaviour that forbids to _call_ (vs get) a Property), then base[aFnName] would just skip that behaviour (undesired). So that example is actually crap. Another note is that this[aFnName] really does __Get, not __Call. Yet another note is that even doing this[aFnName]() (__Call) will _still_ invoke 'get' of the Property; fortunately, call invocation of getter of Property can be forbidden i.e. inside of a meta-function of the class that (the class) defines the Property.

So it looks like the best practice is this:
  • for the existence of the method, at the top of the class hierarchy, define a class that reports non-existence
  • for the number of parameters, every class should act on its own and report errors about too few/too many params / uncallable Properties _only_ for keys of that class object (vs for base classes). Checking can be implemented inside of meta-functions - i.e. inside of __Call.

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus

It also looks like Functors can't be used as meta-functions in the example above (i.e. as a replacement of __Call if there were multiple keys that we would like to handle and each key would require a different behaviour). Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
lexikos
Posts: 5415
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 06:46

trismarck wrote:Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
Huh? The target of invocation is this, which is passed as an additional parameter to the functor in place of the method name. If you meant whichever class defined the method, you know that at load time because you know where you wrote the method. Likewise, for functors you just need to include some information in the functor to identify which class it belongs to.

Another option is to derive your object not from the class, but from another object which merely defines meta-functions. The meta-functions can then implement inheritance in whatever way you want.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

08 Mar 2015, 10:30

Lexikos wrote:
trismarck wrote:Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
Huh? The target of invocation is this, which is passed as an additional parameter to the functor in place of the method name. If you meant whichever class defined the method, you know that at load time because you know where you wrote the method.
Yes, I didn't actually think when I wrote this. What I've meant was this: lets suppose we're invoking x.fun3() and there is a key "fun3" at the end of the inheritance tree of x, in the third base. In the first or second base, there is a functor instance defined as the __Call meta-function. When that functor instance is invoked, the original invocation can't detect the existence of the method in the rest of inheritance tree of 'x' anymore, because that invocation has just ended (by 'has just ended' I actually mean that the invocation won't traverse automatically through the rest of the inheritance tree of x) (actually, I'd have to check if the original invocation really 'ends' at that point - probably not - the original invocation call is I guess still on the callstack during the whole time the new invocation was spawned). What I've meant was sort of described in this thread and just mentioned it again here, w/o providing the link (should have done that really). But lets leave that aside.
Lexikos wrote:Likewise, for functors you just need to include some information in the functor to identify which class it belongs to.
Didn't realize that it's possible to pass 'parameters' to the functor handler by just putting the parameters inside of the functor. Thanks.

Ok, so to implement checking of too few/too many params with functors: lets suppose we have the following classes:

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus


We include the following:

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus

Lets suppose we want to have parameter checking in class dx. We modify the class like this:

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus

And now the script should throw an exception for fun3() for too few / too many / uncallable value for fun3(). The functor could have as well be attached to cx. Complete example:

Code: [Select all] [Expand] [Download] (Script.ahk)GeSHi © Codebox Plus

The above is untested. Perhaps it could be used for debugging purposes.
__CallstackDepthFix is specified manually because I didn't know how to fix it. The callstack depth changes whenever the functor handler 'continues' the previous invocation and because the base of target is temporarily changed for that resumed invocation, the functor handler can't scan the original inheritance tree of the target to determine the proper callstack depth fix (or at least it looks like it).

//edit: the example requires >=1.1.20 (because of this) or at least >=1.1.19 because of .Call.

Return to “Ask For Help”

Who is online

Users browsing this forum: Ulfric Stormcloak and 33 guests