I think there are some misunderstanding here, so let me summarise why I think a Result type is actually a way of ensuring the unhappy path is handled
In your opinion a “try now, catch now” approach is more desirable because it may prevent an application becoming unstable by handling errors at the immediate opportunity such as Timeout errors etc
Therefore in order to make sure this happens, your preference is to always use and recommend others use Exceptions.
However there are many situations where a “try now, catch later” approach makes more sense which for clarity is not the same as a “try now, ignore forever” approach with silencing errors
There have been cases for example with database migrations where I need to process what it can and then tell me what it couldn’t migrate at the end. It’s a better experience if I see the logs at the end, so because python is “try now, catch now” by default I have to code in the logic monitor what failed and let me know
To be clear these migration errors are just recoverable errors such as it couldn’t find an database entry rather than something like corrupted data
These migrations are isolated per database entry so when a specific entry fails to be migrated we handle it by halting any further logic that may be unstable
But we do not prevent any further of otherwise uneffected
As we said before we do not have a Time Machine, and unfortunately it’s for this kind of reason many libraries feel some results from their API do not need to be handled straight away and instead would fit a “try now catch later “ approach
I’m not saying it this is right or wrong, all I’m saying is it exists. We can’t undo it nor prevent libraries from continuing to do it
What we can do is create a framework around it to make it a more productive experience
For example you may think this code looks correct
try:
api = requests.get()
message = api.json.message
except APIError:
# do stuff
But unfortunately you would be wrong, firstly this API returns a result object which represented a unhappy path since it subscribes to try now, catch later
The HTPP body has a field “message” containing an error message but so does the API response when it’s successful so this doesn’t fail when it should
The only way to handle this error is to understand the specific API for this bespoke Result type either by understanding what it means to be in an error and analysing those fields or using any APIs to convert this to be an exception
This means developers need to learn how to make sure they are ensuring every library they use are having their unhappy paths handled immediately when it doesn’t use exceptions
Futures and Result types don’t have this problem, the API hands you a contain object
What your interested in is contained within it
try:
api = requests.get()
message = api.value.json.message
except APIError:
# do stuff
If we do this to grab the value of its result then just like with futures if there is an error then it should throw an Exception indicating we’ve tried to access an unhandled error
This protects us from the code above, this semantics allow these APIs to keep their try now, catch later mentality by ensuring you catch the error the moment you attempt to consume the result of a failed operation
Just like with futures you can just reach in to grab the exception
api = requests.get()
if api.error:
message =“error loading”
This error would be specific to the needs of the particular API, and can be handled the usual way with try catch, or by reading the error property like with futures
Now this isn’t a guarantee that a bloody minded person wouldn’t intentionally ignore the unhappy path and find ways to silence these exceptions
But that’s the same as now with futures and exceptions and hard to fix without becoming unpleasant and having to annotate your code with all the exceptions it could ever throw
What this unlocks is a proper framework around try now, catch later for sync code and not just async code
It will still force you to consider if you are letting errors slip through,via providing a consistent interface between libraries that are already providing their own result types
Preventing times you forgot to use their API correctly are providing your application with invalid data
And it unlocks paradigms such as the null operator where the developer doesn’t want to handle the error for accessing nested keys immediately but only when they attempt to consume it
name = object?.name
print(name.value) //“crash!”
In some ways you can think of it like an exception generator. The compute has happened but the exception throwing hasn’t yet