Some users on Discord (hi Lagoon!) have been pushing on the subject of error handling lately. MiniScript's error handling is simple and perhaps a bit harsh: errors are either returned as a special code (often null or an error string), or they just terminate your program and print the error. The idea is to make the error simple, reproducible, and un-ignorable. See this post by Joel Spolsky, or this one by Jason Patterson, for more on why I feel that exceptions are not a good solution.
Do, or do not. There is no try.
— Yoda
However, with MiniScript 2.0 now underway, this is the time to consider whether we can do something better.
Proposal: a new error data type.
MiniScript currently has 6 data types (number, string, list, map, function, null). Let's add one more, error, just for representing errors. An error value would be a little object that includes a message and a stack trace. And the big idea is:
A function should return an error object if something goes wrong.
And it should never return an error object when everything is fine.
As an example, let's imagine a readNumber function that reads a number from a file. It could fail in a couple ways: there might not be a file at that path, or the file might not contain anything we can parse as a number. The caller can be prepared for this, and return a default value:
num = readNumber(path)
if num isa error then num = 42
Or, maybe you want to just pass the buck on to whatever code called this code (i.e. propagate it up the chain)? You can easily do that too:
num = readNumber(path)
if num isa error then return num
What if you don't do anything with it? Well, when an error value is involved in almost any operation, it evaluates to that same error. This is true of all the basic arithmetic operators like + and -, comparison operators like ==, !=, and >, all the built-in functions like floor, sin, and upper, and the logical operators and and not (but not or; see below). So if your code does:
return readNumber(path) * 1000
and readNumber happens to return an error, well, then this code just passes that error up, exactly as received.
The or operator is the only one different: A or B, when A is an error, evaluates to B. That lets you handle exceptions in simple cases very easily:
num = readNumber(path) or 42
This says: try to read a number from path, but if you can't, just use 42.
To create a new error in your code, we'd need some new intrinsic function. I'm currently thinking err. So code might look like this:
parseInt = function(s)
if s isa error then return s
if not s then return err("parseInt: empty string")
if not "0" <= s[0] <= "9" then return err("parseInt: string does not begin with a digit")
return val(s)
end function
This checks for and returns two different error cases. Notice that we don't "throw" these errors; we just return them in place of the usual value. There is also never an implicit bail-out; when you want to return an error, you say so.
Note that there would still be some runtime exceptions which terminate the program. There are cases where we just can't continue, like if you've tried to call an undefined identifier. The new error type would add one more such case: if you try to branch based on a value that is an error, then that terminates:
num = readNumber(path)
if num then doSomething
In this case, if readNumber returned an error, this code would terminate with a runtime error (that would include info about the original error too).
You're not intended to ignore errors forever, or let them propagate all the way up to some top-level exception handler (there's no such thing here). We're aiming for a middle ground: errors are easy to catch and handle explicitly, and implicitly, they propagate only far enough to simplify common cases (like composed function calls or simple operator usage).
What do you think?
I've been thinking about error handling in MiniScript for years, of course, but only recently has this concept crystallized for me. I'd be very interested in hearing what you think. Leave your comments below, or in theFeedback channel on Discord, and let me know!