As an avid user of the result monad in Ocaml, I really value their use. However I just can’t really see the point in a dynamically typed language. The big win with result’s in Ocaml is that the type system ensures I handle them and tells me what errors I need to handle. Without that, I’d rather have an exception blowing up in my face.
They can be very valuable in limited cases. Most notably, in my experience, was when I had a process that needed to generate several dozen data files based on data that was frequently corrupt at the entity level – there were thousands of entities, and one entity having a character that a given emitter could not deal with should not be a showstopper, but should instead raise a flag: “I couldn’t produce XML (or JSON, or extremely-specific-terrible-format) for entity ABC today.” That allows you to treat it as something to notify the responsible party about, rather than something that an engineer has to debug and fix, while the other 99.9% of entities that have not broken themselves continue along their merry way.
The net effect is that, properly handled, you can code very much as if the errors never happened – then deal with them at the end of the logic, once. Doing similar things with exceptions always felt to me as if I were trampolining from one level of logic to another, hundreds of times, as I generated the files.
I’ve been thinking about errors via exceptions vs return values lately.
Suppose I call delete_widget(1) and there’s no such widget. If delete_widget raises an exception, it’s is telling me there’s a problem and forcing me to deal with it. But if it returns an error value, it’s telling me there’s a problem and letting me decide whether to do something about it.
This seems much better to me. Maybe what I’m trying to do is ensure the widget doesn’t exist, and I don’t care whether it was deleted or absent to start with. Or maybe it’s really important for my purposes that I’m truly deleting it. delete_widget can’t know that. It’s a decision better left to the calling code, IMO.
This is the classic “what’s an exceptional case” question. In the case you bring up, I would make the delete operation return a bool or the number things it deleted as I don’t see it as exception.
This is the kind of situation where what you actually want is substructural types, so that the type checker doesn’t even let you attempt to delete things that don’t exist.
How is the type checker going to know whether a given database record exists or not?
When you said “widget”, what I had in mind is a GUI control - a resource that your program requests exclusively for itself. In this context, you must own a Widget object before you can free it. If you store your Widgets in a collection, say, a HashMap<String, Widget>, you must first extract the Widget from the collection, and only then free it. It may be the case that the operation that extracts the Widget isn’t guaranteed to succeed. In this case, the operation’s return type is Option<Widget> (or some such), and, when you case-analyze this option, you can only delete the Widget in the branch where it actually exists.
On the other hand, if you are manipulating a shared mutable database, of course you can’t make any assumptions about the existence of a specific record. But, then, the database’s API also shouldn’t expose any operations whose precondition is the existence of a specific record.
Ah, OK. I guess widget was a confusing example name. Sorry.