Why use ErrorTypes?

In short, ErrorTypes improves the safety of your error-handling code by reducing the opportunities for human error.

Some people think the source of programming bug are programs who misbehave by not computing what they are supposed to. This is false. Bugs arise from human failure. We humans fail to understand the programs we write. We forget the edge cases. We create leaky abstractions. We don't document thoroughly.

If we want better programs, it is pointless to wait around for better humans to arrive. We can't excuse the existence of bugs with the existence human fallibility, because we will never get rid of that. Instead, we must design systems to contain and mitigate human error. Programming languages are such systems.

One source of such error is fallible functions. Some functions are naturally fallible. Consider, for example, the maximum function from Julia's Base. This function will fail on empty collections. With an edge case like this, some human is bound to forget it at some point, and produce fragile software as a result. The behaviour of maximum is a bug waiting to happen.

However, because we know there is a potential bug hiding here, we have the ability to act. We can use our programming language to force us to remember the edge case. We can, for example, encode the edge case into the type system such that any code that forgets the edge case simply won't compile.

An illustrative example

Suppose you're building an important package that includes some text processing. At some point, you need a function that gets the length of the first word (in bytes) of some text. So, you write up the following:

function first_word_bytes(s::Union{String, SubString{String}})
    findfirst(isspace, lstrip(s)) - 1
end

Easy, fast, flexible, relatively generic. You also write a couple of tests to handle the edge cases:

@test first_word_bytes("Lorem ipsum") == 5
@test first_word_bytes(" dolor sit amet") == 5 # leading whitespace
@test first_word_bytes("Rødgrød med fløde") == 9 # Unicode

All tests pass, and you push to production. But alas! Your code has a horrible bug that causes your production server to crash! See, you forgot an edge case:

julia> first_word_bytes("boo!")
ERROR: MethodError: no method matching -(::Nothing, ::Int64)

The infuriating part is that the Julia compiler is in on the plot against you: It knew that findfirst returned a Union{Int, Nothing}, not an Int as you assumed it did. It just decided to not share that information, leading you into a hidden trap.

We can do better. With this package, you can specify the return type of any function that can possibly fail. Here, we will encode it as an Option{Int}:

using ErrorTypes

function safer_findfirst(f, x)::Option{eltype(keys(x))}
    for (k, v) in pairs(x)
        f(v) && return some(k) # some: value
    end
    none # none: absence of value
end

Now, if you forget that safer_findfirst can error, and mistakenly assume that it always return an Int, your first_word_bytes will error in all cases, because almost no operation are permitted on Option objects.

Why would you NOT use ErrorTypes?

Using ErrorTypes, or packages like it, provides a small but constant friction in your code. It will increase the burden of maintenance.

You will be constantly wrapping and unwrapping return values, and you now have to annotate function's return values. Refactoring code becomes a larger job, because these large, clunky type signatures all have to be changed. You will make mistakes with your return signatures and type conversions, which will slow down deveopment. Furthermore, you (intentionally) place retrictions on the input and output types of your functions, which, depending on the function's design, can limit its uses.

So, use of this package comes down to how much developing time you are willing to pay for building more reliable software. Sometimes, it's not worth it.