5 ms·
What is the advantage of that over exceptions? And what if IO.read fails? Who closes the file handle?
by bjourne 3y ago
What is the advantage of that over exceptions? And what if IO.read fails? Who closes the file handle?
- ahoka 3y agoUnless you are writing oldschool Java, then exceptions are not typechecked.
- el_oni 3y agoThe file handle is the PID of the process that opened the file, it monitors the process that asked for the file to be opened. and if that process goes down the file will be closed. You can also add an else in the with so it looks like with {:ok, fh} <- File.open(filename), {:ok, contents} <- IO.read(fh) do contents else {:error, reason} -> File.close(fh) {:error, reason} # this will return after the file has been closed end or maybe you would prefer to open the file, and pass that into the with and either way close the file. I just used IO as an example because they return nice {:ok, x} or {:error, reason} tuples, but this works with any pattern.
- dahfizz 3y ago> The benefits over an exception is as the caller i can pattern match on the result of this. so i could have this is in a function in a case Your example looks just like exceptions to me, just with different keywords try: return with_example(fname) except RecoverableError: recover(fname) except Exception as e: raise e
- el_oni 3y agoIn the examples yes, because it's just a simple binary ok or error case but if you returned a list you can pattern match on an empty list, a single element list or a list that's longer. which you wouldn't do with exceptions in another language. that's the nice part about `with` that you can stop once you stop matching the pattern and return whatever you currently have, which in the list example may be a perfectly valid thing to return. with [value] <- Module.some_list_function(arg), # can return an empty list too [head | tail] = list <- Module.another_func(value), # can return a single element list longer_list <- Module.takes_multi_element_list(list) do longer_list end There are loads of other examples for uses of this, or you can just write in an additional clause for your function that handles the other case and pipe it down the line. It's about what makes sense for your domain. I may not be explaining this well so apologies for any confusion
- bedobi 3y agoException based error handling is so bad and unsafe that adopting functional error handling with Either, Try etc as implemented by functional addon libraries for many languages, while not yet common, in time it will become the new default even in OO languages. (just like it's been the default in functional languages for decades) Functional error handling types are much simpler, safer and more powerful. Simpler because they don't rely on dedicated syntax- they're just regular objects no different to any other object. Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more. (no risk of ignoring errors and no risk of catching a higher level of error than desired, ubiquitous bugs in exception based error handling) Powerful because they support map, flatmap, applicative etc, making it easy to eg chain multiple computations together in desired ways, which is unwieldy and bug prone when using exceptions. > What is wrong about dedicated syntax It adds complexity to the language! It could be that, when learning Java, Kotlin and any other language, we learn that methods return what they say they do... and that's that. No weird dedicated syntax and magic, special treatment for returning anything other than the happy path, and the HUGE complexity that comes with it, eg the dedicated syntax itself and how it behaves, differences between checked and unchecked exceptions, hierarchies of exceptions etc etc. > Exceptions are easier But that's the point, they're not. Exceptions based error handling is unnecessary, hugely complex, doesn't compose at all, obfuscates or straight up hides what can go wrong with any given call, so leads to countless trivially preventable bugs... I could go on. And after decades of use, there's still no consensus about what exceptions should be or how they should be used. Exceptions are a failed experiment and I have no doubt that in ten years, Java, Kotlin and many other languages will acknowledge as much and move away from it the same way Joda Time outcompeted and replaced the horrible Java date and time library.
- jaen 3y agoOption and Result types, as implemented today in mainstream languages (ie. mostly anemically), are not the answer to exceptions being a mess. Exceptions have a lot of additional functionality in larger ecosystems such as: - Backtraces ie. showing the exact path of the error from its source to whereever it was handled, in a zero-cost way. This is by far the most important aspect of exceptions, as it enables automatically analysing and aggregating them in large systems, to eg. attribute blame from changes in error metrics to individual commits. - Nested exceptions ie. converting from one error system to another without losing information. Extensible with arbitrary metadata. - An open and extensible error type hierarchy. Again, necessary in large scale systems to differentiate between eg. the cause (caller fault, callee fault aka HTTP 400/500 divide), retryable or permanently fatal, loggable etc. exceptions while also maintaining API/ABI backward/forward compatibility. (for some of these, eg. Rust has crates for a Result-y equivalent, but a community consensus does not exist, yet...) General-purpose exceptions simply are complicated, and any system trying to "re-invent" them will eventually run into the same problems. Over-simplifying error handling just results in less maintainable, debuggable and reliable systems.
- el_oni 3y agoThe file handle is the PID of the process that opened the file, it monitors the process that asked for the file to be opened. and if that process goes down the file will be closed. You can also add an else in the with so it looks like with {:ok, fh} <- File.open(filename), {:ok, contents} <- IO.read(fh) do contents else {:error, reason} -> File.close(fh) {:error, reason} # this will return after the file has been closed end or maybe you would prefer to open the file, and pass that into the with and either way close the file. I just used IO as an example because they return nice {:ok, x} or {:error, reason} tuples, but this works with any pattern. The benefits over an exception is as the caller i can pattern match on the result of this. so i could have this is in a function in a case result = case with_example(filename) do {:ok, result} -> result {:error, :some_reason} -> # this is recoverable, do something else #log the issue recover!(filename) # bangs in functions indicate they can fail and raise an error {:error, :another_reason} -> # this is unrecoverable # log the issue raise "unrecoverable error" _ -> # any other case we don't know about raise "unexpected issue" or i might not care and want it to crash if it doesn't match {:ok, parsed} = with_example(filename) # will raise a match error if {:error, reason is returned
- deleted 3y ago[deleted]
- sodapopcan 3y agoExceptions in Elixir are reserved for situations that are truly exceptional. If IO might fail as a business concern, then we use File.read and pattern match on the return type where we can explicitly handle the error case. Otherwise, if we know a file will always be there and IO is failing for reasons out of our control, that is truly exceptional so we can use File.read! which will throw an exception on failure. In Elixir we generally don't handle this, we just crash and a supervisor brings the process back up.