Exceptions (for unknown properties, more)

Discuss the future of the AutoHotkey language
lexikos
Posts: 6129
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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] [Download] GeSHi © Codebox Plus

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] [Download] GeSHi © Codebox Plus

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] [Download] GeSHi © Codebox Plus

if try(conditionalAction())  ; or:  if conditionalAction() !|| false
successAction()
else
failureAction()


I believe that in Python, one can do this:

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

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

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: 3202
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: 272
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

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: 6129
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

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: 272
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

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: 3157
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] [Download] GeSHi © Codebox Plus

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.

Return to “AutoHotkey v2 Development”

Who is online

Users browsing this forum: No registered users and 1 guest