Exceptions (for unknown properties, more)

Discuss the future of the AutoHotkey language
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Exceptions (for unknown properties, more)

10 Sep 2018, 03:46

Split from Nothing.
Helgef wrote:For non-existent array elements, most often, an exception would be very helpful, it is very similar to call to non-existent method.
...
Especially for properties, a property get function might very well return something which is unset (perhaps by mistake, or perhaps not), it doesn't tell you anything about the existence of the property, an error does. We do not need unset for this, just an error.
What if obj.nonEx does exist, but it invokes a property function or meta-function which accesses a non-existent property? It will throw an exception, which will likely bubble up to your exception handler, which may misinterpret it as meaning obj.nonEx does not exist. The exception object needs to identify the target object and missing property name. Then there's the less likely but still real possibility that the property is removed while it is executing, and invoked recursively (maybe indirectly). To get a truly accurate answer from the exception you need to verify the type of exception, the target object, property name and the call stack at the point the exception was thrown. This is very impractical; I think script authors are more likely to skip some of that, getting an ambiguous answer, like with unset only less efficient.

The only way to get a clear answer (and easily) is to ask the object directly: theArray.has(i)? map.has(key)? node.hasProperty("textContent")?

Even without the ambiguity, I think that exceptions should not be used to represent conditions that are not errors. Using try-catch to find the answer to a question is incorrect if neither answer represents an error state. For instance, if you want to know whether a property exists, it is not an error for the property to not exist. a.nonEx can throw an exception, but only if accessing a non-existent property is always considered to be an error. In other words, either the property always exists, or the script is required to query its existence by some other mechanism before accessing it (or use some other access mechanism which does not treat non-existence as an error).

One (very much solvable) problem with adding exceptions for such things at this point is that there isn't yet an easy way to identify exceptions. That is, there isn't a proper system of exception types or any mechanism to filter based on type; or in other words, our exception system is incomplete. Completing it is not a priority for me, and if I still cared about getting v2.0 to a semi-stable (as in unchanging) state within a reasonable amount of time I probably would defer it to a later release. I might have never added exception handling at all if fincs hadn't developed it.

One of the reasons I sometimes prefer not to use exception-handling in scripts is that the syntax, while being very conventional, is very verbose. This could be counteracted with new syntax, such as the following loose ideas:
  • Some form of inline try-catch expression, possibly allowing filtering by exception type.
  • Success-or may.throw !|| errorExpression or success-conditional may.throw !&& successExpression / may.throw !? successExpression : errorExpression operators. (Or the opposite, with "exclamation" meaning "exception".) The main problem with this is the lack of exception type filtering.
Another reason is that sometimes it seems more suitable (or at least more convenient) to ignore errors within a particular sequence and handle them at the end, or just ignore them outright. Rather than making that mode the default (a poor strategy), it can be activated in a limited context with something similar to:

On Error Resume Next

When this is used in a VB sub or function and an error (exception) occurs, execution resumes at the next statement - the one following either the statement that raised the error, or the statement that called out to some other procedure which raised the error. Err.Number and Err.Description contain information about the last error. It is much like AutoHotkey's use of ErrorLevel when calling certain built-in functions, but it is not limited to built-in functions. It is more like wrapping each line (statement) in try-catch, with the catch part merely storing the error information.

Err.Number is limited to identifying only the most recent error (like ErrorLevel), but we need not be limited in this way. There could instead (or in addition) be an array of all thrown values, which also implies an error count (the length of the array).

Unlike ErrorLevel, the error information may accumulate rather than reset after every successful function call, and it may be local to the function or block. Sometimes this would be much more convenient than exception handling, especially if one has to wrap each call in try-catch (and set the error flag, increment the error count or whatever).

Unlike VB's On Error or ErrorLevel, we could hypothetically allow filtering by the type of exception or other properties of the exception. The mechanism could allow for an expression or callback to be evaluated or called when an error occurs, prior to execution resuming. An example of where this could be used is:
Helgef wrote:[...] I'd might want to set an onerror func at the start of a (probably complex) function, using a closure to clean up in case there was an error. Meaning I do not need to store a reference to the closure in the closure, which reduces the risk that I forget to break circular references.
Unlike the global OnError() callback, exceptions thrown by an interrupting thread would not reach this, just as it wouldn't reach another thread's try-catch.
nnnik wrote:Try & Catch vs If & Else. There is not much difference between the 2.
Even if we are considering only control flow, not semantics, there is a big difference between the two. If one translates

Code: Select all

If conditionalAction()
    successAction()
else
    failureAction()
; to
try {
    conditionalAction()
    successAction()
} catch
    failureAction()
It is not only more verbose, but behaves differently: an exception within successAction() will also cause failureAction() to execute, when the original intent was to execute it only when conditionalAction() fails. One might try to avoid this via exception filtering, but it might be difficult to guarantee that successAction() will not throw an exception that also matches the filter.

Another attempt at a solution might be to wrap successAction() in its own try-block, but then how do you throw the exception and ensure it won't be caught by the outer try?

So instead, one puts in the try block only the code whose exceptions should be handled by the catch block. The shortest I've come up with is:

Code: Select all

success := false
try conditionalAction(), success := true
if success
    successAction()
else
    failureAction()
In short, try-catch is not equivalent to if-else, and in some situations does not even replace it.

It is code like the above that gave me the idea for inline-try (but success-or !|| also does the job):

Code: Select all

if try(conditionalAction())  ; or:  if conditionalAction() !|| false
    successAction()
else
    failureAction()
I believe that in Python, one can do this:

Code: Select all

try:
    conditionalAction()
catch:
    failureAction()
else:
    successAction()
...which is tidier, but requires the reverse of the typical branch order.
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

10 Sep 2018, 07:18

I believe most languages use pattern matching to check if a key exists. Pattern matching has the benefit of avoiding runtime error checking (if implemented properly).

In F#: https://theburningmonk.com/2011/09/fsha ... ching-way/

Note how multiple catch or case statements are condensed into a simple (but abstract) structure.

I just googled "try catch pattern matching" but there are many more examples of advanced exception handling online.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Exceptions (for unknown properties, more)

10 Sep 2018, 09:10

AHK requires runtime matching because AHKs objects are fully dynamic.
Recommends AHK Studio
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

12 Sep 2018, 20:06

lexikos wrote:Then there's the less likely but still real possibility that the property is removed while it is executing, and invoked recursively (maybe indirectly).
There's also the possibility that the property is implemented while it is executing, so throwing an exception makes no sense. I agree.

lexikos wrote:The only way to get a clear answer (and easily) is to ask the object directly: theArray.has(i)? map.has(key)? node.hasProperty("textContent")?
I don't like this, I feel that it's more important to ask if it has a value rather than check for the existence of a key.

This link might help: https://en.wikipedia.org/wiki/Null_object_pattern

Maybe monad vs Exceptions: https://softwareengineering.stackexchan ... exceptions
So what are the advantages of the Maybe/Either approach? For one, it's a first-class citizen of the language. Let's compare a function using Either to one throwing an exception. For the exception case, your only real recourse is a try...catch statement. For the Either function, you could use existing combinators to make the flow control clearer.

Might be time to introduce some functional programming concepts into AutoHotkey...
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Exceptions (for unknown properties, more)

12 Sep 2018, 20:57

There's also the possibility that the property is implemented while it is executing,
What? The property can't begin to execute if it hasn't been implemented yet.
I don't like this, I feel that it's more important to ask if it has a value rather than check for the existence of a key.
What's the difference? theArray.has(i) asks whether the array has a value at that index. map.has(key) asks whether the map has a value associated with that key. It is not checking for the existence of a key - the array or map may have space reserved for a value with that index or key, but still indicate that it has no value.

I do not think the "null object pattern" is relevant here - did you even read the article? Note also that this topic was originally split from a discussion about null.

I do not see how the Maybe/Either pattern would be suitable for access of properties or array elements.
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

12 Sep 2018, 22:23

lexikos wrote:I do not think the "null object pattern" is relevant here - did you even read the article? Note also that this topic was originally split from a discussion about null.
Yes and the article mentions two of the Related operators you were considering, the null coalescing operator ?? and the null-conditional operator ?. as alternatives to the null object pattern. The idea behind the null object pattern is to elevate the null, unset, or nothing into the null object, so an if...then... statement or try statement can be avoided, forgoing the need to check if the object is null. In short, there would be no need to check for a possible null return (error?), because if I wanted a string, it'd return "", an object {}, a number 0, a List []. However, this makes executing successAction() easier at the cost of special handling for failureAction().

This is a design pattern that can be executed by the user as an alternative to testing for isNull so I agree this isn't too relevant.
lexikos wrote:I do not see how the Maybe/Either pattern would be suitable for access of properties or array elements.
It's an alternative syntax to try, catch and if...else... The Either pattern is sort of similar to Javascript's promises, and much cleaner because if nothing checks are encoded within the definition of Maybe/Either. If an array element is nothing/unset that can be propagated down the chain of functions until it can be handled. Importantly, the error handling changes from theArray.has(i) or "there is no key i" to "there is no value at the position i" because the calling function expects a value and not an exception. (which feels like the point of the try statements, to suppress the exceptions, and to catch them only to run a failureAction(), and never to actually check the contents of the error.)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Exceptions (for unknown properties, more)

15 Sep 2018, 11:54

Hello :wave:.

I'm all for better identification of the type of exception being thrown, however, imo, less precise information is better than no information at all. Also, mostly I do not want to handle exceptions like call to non-existent method in any other way than correcting a coding mistake. The same would be true for non-existent properties and keys.
One of the reasons I sometimes prefer not to use exception-handling in scripts is that the syntax, while being very conventional, is very verbose. This could be counteracted with new syntax, such as the following loose ideas
I like the examples. What about being able to access the exception (object) in errorExpression? Like, OutputVar in catch OutputVar. I disagree with the current syntax being very verbose, I think it is very readable. In the future one could extend catch [OutputVar] to catch [OutputVar, Filter*], future exception objects would just need to support at least the same properties as the Object returned by exception() and thrown by the program (if the current exception system remains until v2.0 that is).

Code: Select all

if try(conditionalAction())  ; or:  if conditionalAction() !|| false
    successAction()
Alternative, ifError / ifNoError. I'm not necessarily very fond of this, I guess it is classic AHK-style, cf, ifequal et al.
try-catch-else
I think that is nice.
Although I'd very much welcome a.nonEx to be an error, I think it would make more sense to add it if also the usage of uninitialised variables was considered an error. I think they are related just like call to non-existent method and call to non-existent function are related.

Cheers.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Exceptions (for unknown properties, more)

06 Jun 2020, 15:50

I changed my mind and removed ErrorLevel in v2.0-a110.

I am also considering errors for the usage of uninitialised variables.

v2.0-a110 also adds "Contine the current thread?" and Yes/No buttons to the error dialog, for most (unhandled) error conditions. (Unfortunately the typo exists within the code, not just my post.) To achieve this I reviewed all of the error calls (and consequently found opportunities for improvement). I considered it essential that OnError be able to replicate this capability, which fortunately did not require much more work.

Although this change was purely motivated by a desire to improve the user experience regarding errors, while writing an example for OnError I realized it can be used for another idea I mentioned:
I wrote:Another reason is that sometimes it seems more suitable (or at least more convenient) to ignore errors within a particular sequence and handle them at the end, or just ignore them outright. Rather than making that mode the default (a poor strategy), it can be activated in a limited context
...
Unlike ErrorLevel, the error information may accumulate rather than reset after every successful function call, and it may be local to the function or block. Sometimes this would be much more convenient than exception handling, especially if one has to wrap each call in try-catch (and set the error flag, increment the error count or whatever).
The OnError accumulator example implements the idea fairly close to how I had originally imagined. The main caveat is that OnError isn't called when there's a Try statement to jump out of. Another one is that unhandled errors raised by an interrupting thread are affected; it is tied to duration rather than scope (although maybe some stack trace trickery would help).


I plan to implement some of the ideas I outlined in Errors.md, but might defer it until a later stage.
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

06 Jun 2020, 19:21

Thanks. Upon testing I was pleasantly surprised by the ability to continue after an error.

It seems that throwing errors on uninitialized variables would conflict with #Warn - are you looking at modifying behavior of #warn or outright replacing it? Personally I have never used warn.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Exceptions (for unknown properties, more)

06 Jun 2020, 20:31

#Warn UseUnset would be removed.

I also intend to remove #Warn ClassOverwrite after making classes truly read-only. In the interim, I might just make the warning mandatory.

I do not intend to make #Warn LocalSameAsGlobal mandatory. I prefer to either assume it is a legitimate local, or make global declarations unnecessary by default (but still using them with force-local mode). I've personally found LocalSameAsGlobal to be more hindrance than help, but that might be because I write a lot of quick-and-dirty test scripts with short, non-descriptive variable names. I suppose (and I think someone already this pointed out) that if accessing an uninitialized variable causes an error, LocalSameAsGlobal is redundant, except for when you want to assign to a global but forget to declare it.
Personally I have never used warn.
I think that warnings and errors are most useful when they are automatic (on by default) but not triggered by legitimate code.

A warning is currently the same as a continuable error, except that you can't catch a warning. Perhaps they should just be continuable errors. The semantic difference doesn't matter - I've seen users ask "why do I get this error?" while posting a screenshot that shows "Warning:". :roll:

You can turn a specific warning off, because the practices that must be followed to reliably avoid them are deemed an unnecessary inconvenience by some. I think the ideal solution is to allow the script to control how each potential error condition is handled, in a way that can be localized to parts of the script (isolated from libraries written by other authors, for instance).
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

07 Jun 2020, 07:19

The only issue I run into when assigning is that only some of the operators assign. Notably:

:=
.=

The others throw exceptions, because they expect integer values. The ones that I wish assigned from an uninitialized value are : += -= and |=.
Otherwise I expect default #Warn UseUnset to be okay so long as DllCalls that emit strings don't need to be initialized to an empty string. (It's probably better to use VarSetStrCapacity anyways.)

With regards to localizing error handling, have you considered localizing them directly to classes? As a side note, I expected static __Init() to the the class' version of an auto-execute section, provided no static properties were defined.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Exceptions (for unknown properties, more)

07 Jun 2020, 08:30

I'm a bit concerned with the option to continue the thread via the button. What is the reason for allowing this? If you could put it a bit more specifically than,
lexikos wrote: this change was purely motivated by a desire to improve the user experience regarding errors
I couldn't think about any real example, but consider this code,

Code: Select all

f(p) {
	if p + 1
		msgbox 1
	else
		msgbox 2
}

f 1
f []
When writing this code, I now cannot trust that msgbox 2 will only show if p == -1. So I could write it like this,

Code: Select all

f(p) {
	if p + 1
		msgbox 1
	else if p = -1
		msgbox 2
	else
		msgbox "Please don't try to run code which have failed"
}
f 1
f []
which ofc makes no sense. This is better,

Code: Select all

f(p) {
	try x := p+1	; or if !(p is 'number') throw exception(...)
	catch e
		throw e
	if x
		msgbox 1
	else 
		msgbox 2
	
}
f 1
f []
but I wasn't entirely sure that throwing myself wouldn't present the user with an option to continue the thread. Although I found no indication that it would. In any case, in the first code we implicitly let the program verify the parameter and throw, this is very convenient. I feel that this convenience is now potentially lost.

I'm fine with the option to continue the thread using onerror, using it correctly is the responsibility of the programmer, and you gave good examples. Of course, one can argue that it is the programmer's responsibility to ensure that this exception doesn't occur, and that the option to continue the thread isn't presented to an end user. But code will contain bugs, and then I feel that the user of the script should not have this ability in the first place, so why should we need to make sure it doesn't?
I changed my mind and removed ErrorLevel in v2.0-a110.
:dance:.

iseahound wrote: I expected static __Init() to the the class' version of an auto-execute section, provided no static properties were defined.
Find static __new in the docs.

Contine
It is much easier to spell and pronounce, all English languages should consider adopting this.

Cheers.
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Exceptions (for unknown properties, more)

07 Jun 2020, 09:21

@Helgef

It allows the user themselves to debug their own code. If I was starting out in AutoHotkey, Type Mismatch would mean absolutely nothing to me. I'd even google it and get more confused.
sample
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Exceptions (for unknown properties, more)

07 Jun 2020, 22:21

Exactly, it is for the user to debug their own code. If you don't want to continue, click No. A new user is not going to use OnError, or write error handling code before (or sometimes even after) encountering errors. Sometimes continuing to the next line is adequate, or just saves time during debugging (vs. editing and restarting the script to bypass a trivial error). It can be frustrating for the script to terminate (sometimes unnecessarily) or be left running in a broken state due to the thread exiting. Maybe it is less frustrating when the user has another option - to continue the thread, consequences be damned. I have other plans for the error dialog, but this is probably the only one associated with script breaking changes (to the OnError callback syntax).

Ideally one could jump into the debugger at a click of a button on the dialog, and control execution from there; but currently there is no debugger included with the program, and no capability for a debugger to control execution (by which I mean essentially jump to a specific line).

I mostly had built-in functions in mind - specifically those that are prone to "failure" and did not throw an exception in the past. For instance, one intermittent error I've started seeing is that WinGetTitle("A") throws an exception while I'm task switching, because there is no active window (or maybe it's hidden). In those cases it was adequate for the script to continue with a blank result (but being notified of the possible mistake has its own benefits). Scripts can be designed to handle the error return, but my recommendation would be to ignore the possibility.

If you write a script for someone else, you can handle the errors. If Try is present, there is no way to continue the error; execution always jumps out of the try block. You could use OnError to disable continuation for other cases. OnError can be selective - errors in expressions leave Exception.What blank, while errors raised during execution of a built-in function use the function name. Exceptions thrown by the script are not continuable, as the mode parameter will indicate.

[Edit: In short, perhaps what I want to say is this puts the user in control by default, if only to a small degree.]

End users of an application built with AutoHotkey should not be exposed to unhandled exceptions in the first place - use OnError.

We have some control over which errors can be continued, but my initial policy was to consider only whether the C++ side will handle it correctly. As I mentioned, allowing the script more control is on my agenda.

Note that the continuation option is also a direct replacement for a similar option already provided by COM errors. The difference is basically just that COM errors exited the script instead of the thread if the user clicked No.

For throw, execution is guaranteed to jump out of the enclosing Try block or exit the thread. Only something like "On Error Resume Next", which serves to indicate where execution should be transferred when in error occurs, should change that.
If I was starting out in AutoHotkey, Type Mismatch would mean absolutely nothing to me.
Improving error dialogs with links to the help file (and help pages dedicated to explaining errors) is on my agenda, but not a priority during the alpha stage.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Exceptions (for unknown properties, more)

08 Jun 2020, 09:17

lexikos wrote:A new user is not going to use OnError, or write error handling code before (or sometimes even after) encountering errors.
End users of an application built with AutoHotkey should not be exposed to unhandled exceptions in the first place - use OnError.
There is nothing automatically bad about the error message, it provides useful information which the user can give to the author of the script, which then might get an indication about what went wrong. Now, the user will instead press Yes-Yes-Yes, possibly until something goes very wrong. They might eventually submit an error text completely detached from the original cause. The script author will then start digging way down into a rabbit hole, which should have never been reached in the first place. So much for debugging.

Ideally errors shouldn't occur, but they do. The button makes learning how to use OnError a prerequisite for sharing code. If one wants a button, one should learn how to use OnError to implement it, not the other way around.
Exactly, it is for the user to debug their own code
For instance, one intermittent error I've started seeing is that WinGetTitle("A") throws an exception while I'm task switching
Surely you will fix this in your script instead of letting it pop up intermittently, happily pressing Yes every time. This is not exactly debugging.

Personally, I don't think I ever wanted to continue the thread, except possibly when I've actually used the debugger, debugging. Then this could be very useful, since you can step forward carefully. Otherwise, I usually don't even want to continue the script when I get a warning or exception.
A warning is currently the same as a continuable error, except that you can't catch a warning. Perhaps they should just be continuable errors.
I think so, by default. (But not continuable via a button.)
iseahound wrote:If I was starting out in AutoHotkey, Type Mismatch would mean absolutely nothing to me.
The correct response then is not to continue running the code. If you do, you are not debugging, you are running faulty code having no idea what it does.

I've already accidentally pressed the Yes button several times, ofc I am clumpsy, but it probably wouldn't happen so easily if the Yes button weren't the default.

Imho, that is, cheers.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: Exceptions (for unknown properties, more)

03 Jul 2020, 23:22

You may have noticed I changed the default button. I intend to change the button names (and more about the dialog), but I am prioritizing other things.
Now, the user will instead press Yes-Yes-Yes, possibly until something goes very wrong.
I prefer not to design things under the assumption that the user is completely clueless.
The button makes learning how to use OnError a prerequisite for sharing code.
I don't see how.
If one wants a button, one should learn how to use OnError to implement it,
OnError cannot be used retroactively to add the button to the dialog after the error occurs. When I use the option to continue, it will generally be because the error is a trivial one that does not affect the overall function of the script (more common in v2 than v1), and I do not want to waste time by restarting the script. Or it might be that the script is in the middle of some process, and exiting the thread at this point would cause more problems than continuing. It's not that I won't be fixing the error (immediately or otherwise), but that I may resume (not restart) testing after doing so. If the option isn't there because I didn't think to enable it, it is useless.

Also, it is (currently) not possible to add options to the standard error dialog with OnError; one must produce their own dialog. After I do more work on the built-in error dialog, it will take even more work to reproduce any improvements in script. Why would I do that, when I can just do what I want?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Exceptions (for unknown properties, more)

04 Jul 2020, 00:16

You may have noticed I changed the default button.
I did, thank you.
I prefer not to design things under the assumption that the user is completely clueless.
I agree depending on daily mood. This topic seems less important to me today, than it did last time.
I can just do what I want
Quote dishonestly cut
Of course ;)

Cheers.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Exceptions (for unknown properties, more)

05 Jul 2020, 01:42

helgef wrote:Now, the user will instead press Yes-Yes-Yes
wrote:
04 Jul 2020, 18:54
I'm running into these errors and I don't know what to do:

Code: Select all

Error:  No valid COM object!
--->	shellObj := ComObject(9, pdisp, 1).Application

Error:  This value of type "String" has no method named "ShellExecute".
--->	shellObj.ShellExecute(prms*)

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 33 guests