Introducing nearby exceptions

While working on this post, I discovered that Swift uses already very similar approach of handling the errors.

The implementation is cheapest I could think of, I am aware of the problems with backend, but enough with excuses — it works, so I will be able to play with it and test if the model is useful or not.

To keep things short let’s start with C# approach which is used in Skila as well:

Errors as exceptions

You access an array with index out of its boundary. You divide by zero. In cases like those you throw an exception to indicate the error in computation.

Errors as values

Your common sense, practice, intuition, tells you that for some computations throwing an exception would be too harsh, so instead you return magic value instead to indicate “not a value” value. Probably the best example is String.indexOf — for such purpose Skila has type Option<T> and special value null (it is not null you know from C# — it is like Scala none or null in Swift or Kotlin).

Ok, that’s it.

Wait, we missed the magic ingredient which makes exceptions fun to work with! Consider accessing the dictionary — to provide similar API as with arrays, when the key is not found the exception is thrown, it happens in C# and Skila as well. But it would be useful to be able to try to fetch the value — in C# you have to add another function to the class (TryGetValue in this case). In Skila you convert one mode (error as exception) to the other one (error as value):

let dict = {String:Int}();
let val ?Int = dict["hi"] $;  

Let’s focus on the second line — when we access the dictionary (it is empty) the exception is thrown from the indexer getter. That exception in Skila is special — it is nearby exception (because it is thrown in nearby, or next if you like, function we just called). Then there is dollar — $ — character which works as an exception trap. Only nearby exceptions can be trapped (the others go through) — and if this happens, they are converted to null value.

You don’t have to write duplicated code as in C#, you simply convert errors as you like — go from null to exception, or from exception to null. Both conversions are expressions and they are pretty accurate:

def thrower() String => throw Exception();

let dict = {String:Int}();
let val ?Int = dict[thrower()] $;  

Can you tell what will happen? The program will crash — that is because we trapped exception coming from dictionary indexer, not a thrower.

Why not trap all exceptions? Because I would like to distinguish between three outcomes in total — we looked and we found the key, we looked and we didn’t find the key (i.e. we are sure the key is not there), we crashed while looking (because, say, our comparison function is buggy), so we cannot tell if the key exists or not.

Maybe it is an overkill but after a day of playing with exception trap I felt I miss one more feature — consider reading an ini file and then adding key and value pair to properties dictionary:

properties!add(key,value);

When the key already exists we will get exception with a message and stack trace. However we won’t get plenty of information about the context of the problem, like the filename or line number. Sure, we can debug, but we might add those information right away:

properties!add(key,value) && "File ${filename}, line ${line}.";

The mechanism is old as exceptions — chaining. You can chain an exception or a string (Skila will create basic Exception for you). Since the new exception is created it is equivalent of explicit throw, i.e. for the outer function such chained exception will be a nearby exception.

And to just describe all the tools required for the job — there has to be a way to close the distance of the exception, otherwise assert and fail would be useless:

def [key K] V
set:
  •••
  fail("Key not found.");
end

Your code calls dictionary indexer, dictionary indexer calls fail, fail calls assert, assert throws an exception. So for sure such far placed throw will not be considered as “nearby” from your code perspective. Unless we tamper with the exception distance counter:

def assert(•••) Void
  •••
  throw Exception(•••) &&>>;
end

def fail(•••) Void
  assert(•••) &&>>;
end

This cryptic code &&>> tells compiler to pass an exception further. Effectively when you write assert or fail the exception which is thrown thinks it originated from where those functions were called.

Advertisement
Tagged , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: