digitalmars.D - 'unwrap envy' and exceptions
- jfondren (153/153) Sep 09 2021 This programming chrestomathy video:
- Dom DiSc (3/4) Sep 10 2021 Maybe a little off topic, but since when is gcd(3,5) != 1 ??
- Timon Gehr (3/8) Sep 10 2021 It's not computing the gcd of the list, just the gcd of its minimum and
- jfondren (5/13) Sep 10 2021 yep, it's a leetcode problem and this min/max requirement is
- Kagamin (4/4) Sep 10 2021 Nullable doesn't replace exceptions, it specifies a value which
- jfondren (16/24) Sep 10 2021 Asking for the maximum of an empty array is an error case (hence
- Paul Backus (6/12) Sep 10 2021 Strangely, there is no mention of this in the documentation for
- jfondren (15/28) Sep 10 2021 In a -release build, over an `int[]`, you get a RangeError from
- Paul Backus (4/15) Sep 10 2021 Either way, if a function has a precondition that the caller
- James Blachly (4/9) Sep 10 2021 I've really come to appreciate `?` operator, and don't forget there is a...
- IGotD- (6/11) Sep 10 2021 Walter mentioned that exceptions are on the way out because they
- Mike Parker (4/9) Sep 10 2021 There has been some brainstorming about it, but don't expect to
- Dukc (5/10) Sep 11 2021 Well the `@nodiscard` proposal, that is very likely to get
- H. S. Teoh (11/23) Sep 11 2021 Whatever replaces exceptions better be darned good, otherwise I will be
- jfondren (36/54) Sep 11 2021 Rust's monadic error handling is frequently ugly, but there's a
- Paulo Pinto (7/30) Sep 11 2021 Yes, one thing that gets lost is the boilerplate required to
- IGotD- (7/16) Sep 12 2021 What about the C++ approach, return values that look like
- rikki cattermole (9/29) Sep 12 2021 I recently posted a similar design here:
- Alexandru Ermicioi (3/9) Sep 12 2021 Feels like java throws statement, except the need to handle
- Paulo Pinto (7/19) Sep 12 2021 Java throws statement was based on CLU, Modula-3 and C++,
- Alexandru Ermicioi (8/13) Sep 15 2021 Sorry, for confusion, I didn't refer to throws statement in body
- Paulo Pinto (12/31) Sep 12 2021 That will probably never happen.
- IGotD- (6/18) Sep 13 2021 That still doesn't prevent D from implementing the design. D can
This programming chrestomathy video: https://www.youtube.com/watch?v=UVUjnzpQKUo has this Rust code: ```rust fn find_gcd(nums: Vec<i32>) -> i32 { num::integer::gcd(*nums.iter().max().unwrap(), *nums.iter().min().unwrap()) } ``` Here, both `max()` and `min()` return an `Option<i32>`--a sumtype over `Some(i32)` in the case that `nums` has any numbers in it, and `None` in the case that it's empty. They do this rather than throw an exception or otherwise signal the error. `find_gcd` could similary return a `None` given an empty `nums`, but here the programmer has decided that this case should be treated as an unrecoverable internal error that halts the program. Hence the two `unwrap()`s in this code: they either pull the `i32` out of a `Some(i32)` or they halt the program. Here's similar code in D, using std.typecons.Nullable and implementing our own min/max with optional results: ```d import std.typecons : Option = Nullable, some = nullable; alias unwrap = (x) => x.get; Option!int most(string op)(const int[] nums) { if (nums.length) { int n = nums[0]; foreach (m; nums[1 .. $]) { mixin("if (m " ~ op ~ " n) n = m;"); } return some(n); } else { return typeof(return).init; } } alias min = most!"<"; alias max = most!">"; int find_gcd(const int[] nums) { import std.numeric : gcd; return gcd(nums.min.unwrap, nums.max.unwrap); } unittest { import std.exception : assertThrown; import core.exception : AssertError; assert(find_gcd([3, 5, 12, 15]) == 3); assertThrown!AssertError(find_gcd([])); } ``` That `find_gcd` isn't too bad, is it? Now that we've seen it we can forget about the Rust. I'm not going to mention Rust again. Let's talk about how nice this `find_gcd` is: 1. if nums is empty, the program halts with a (normally) uncatchable error. 2. those verbose `unwrap`s clearly tell us where the program is prepared to halt with an error, and by their absence where it isn't. 3. because `Option!int` and `int` are distinct types that don't play well together, we get clear messages from the compiler if we forget to handle min/max's error case 4. because `Option!T` is a distinct type it can have its own useful methods that abstract over error handling, like `T unwrap_or(T)(Option!T opt, T alternate) { }` that returns the alternate in the None case. 5. since exceptions aren't being used, this can avoid paying the runtime costs of exceptions, can be nothrow, can be used by BetterC, can more readily be exposed in a C ABI for other languages to use, etc. The clear messages in the third case: ```d int broken_find_gcd(const int[] nums) { import std.numeric : gcd; return gcd(nums.min, nums.max); // Error: template `std.numeric.gcd` cannot deduce function from argument types `!()(Option!int, Option!int)`, candidates are: ... } ``` Conclusion: deprecate exceptions, rewrite Phobos to only use Option and Result sumtypes, and release D3! . . . Please consider this code: ```d import std.exception; int most(string op)(const int[] nums) { if (nums.length) { int n = nums[0]; foreach (m; nums[1 .. $]) { mixin("if (m " ~ op ~ " n) n = m;"); } return n; } else { throw new Exception("not an AssertError"); } } alias min = most!"<"; alias max = most!">"; int find_gcd(const int[] nums) nothrow { import std.numeric : gcd; return gcd(nums.min.assumeWontThrow, nums.max.assumeWontThrow); } unittest { import std.exception : assertThrown; import core.exception : AssertError; assert(find_gcd([3, 5, 12, 15]) == 3); assertThrown!AssertError(find_gcd([])); } ``` Or with the obvious alias: ```d int find_gcd(const int[] nums) nothrow { import std.numeric : gcd; return gcd(nums.min.unwrap, nums.max.unwrap); } ``` Things that can be said about this code: 1. if nums is empty, the program halts with a (normally) uncatchable error. 2. those verbose `unwrap`s clearly tell us where the program is prepared to halt with an error, and by their absence where it isn't. 3. because min/max otherwise throws, and because the function is nothrow, we get clear messages from the compiler if we forget to handle these error cases. 4. because D is so expressive, we can have useful abstractions over error handling like std.exception.ifThrown, where we can provide an alternate that's used in the error case. 5. since exceptions aren't leaving this function but cause the program to halt, we can (theoretically, with a Sufficiently Smart Compiler) avoid paying the runtime costs of exceptions, can be nothrow, can (theoretically) be used by BetterC, can more readily be exposed in a C ABI for other languages to use, etc. The clear messages in the third case: ```d // Error: `nothrow` function `exceptions2.broken_find_gcd` may throw int broken_find_gcd(const int[] nums) nothrow { import std.numeric : gcd; return gcd(nums.min, nums.max); // Error: function `exceptions2.most!"<".most` is not `nothrow` } ``` That's a very similar list of features. That's some very dubious handwaving about the potential performance benefits where the compiler magically replaces normal non-Error exceptions with program-halts if the only consumer of the exception is an assumeWontThrow. On the other hand, Phobos doesn't have to be rewritten. On the gripping hand, I think it's neat that status-quo D has the other benefits just from slapping a `nothrow` attribute on a function. Thoughts?
Sep 09 2021
On Friday, 10 September 2021 at 02:57:37 UTC, jfondren wrote:assert(find_gcd([3, 5, 12, 15]) == 3);Maybe a little off topic, but since when is gcd(3,5) != 1 ?? You're program seems to have some bugs...
Sep 10 2021
On 10.09.21 10:59, Dom DiSc wrote:On Friday, 10 September 2021 at 02:57:37 UTC, jfondren wrote:It's not computing the gcd of the list, just the gcd of its minimum and maximum.assert(find_gcd([3, 5, 12, 15]) == 3);Maybe a little off topic, but since when is gcd(3,5) != 1 ?? You're program seems to have some bugs...
Sep 10 2021
On Friday, 10 September 2021 at 10:17:12 UTC, Timon Gehr wrote:On 10.09.21 10:59, Dom DiSc wrote:yep, it's a leetcode problem and this min/max requirement is probably an arbitrary confounder to make for more interesting code than gcd(nums): https://leetcode.com/problems/find-greatest-common-divisor-of-array/On Friday, 10 September 2021 at 02:57:37 UTC, jfondren wrote:It's not computing the gcd of the list, just the gcd of its minimum and maximum.assert(find_gcd([3, 5, 12, 15]) == 3);Maybe a little off topic, but since when is gcd(3,5) != 1 ?? You're program seems to have some bugs...
Sep 10 2021
Nullable doesn't replace exceptions, it specifies a value which is legitimately absent, not due to an error, it's `Result` that replaces exceptions. Also languages built with this pattern in mind have syntax sugar that helps propagate those errors upwards.
Sep 10 2021
On Friday, 10 September 2021 at 15:26:18 UTC, Kagamin wrote:Nullable doesn't replace exceptions, it specifies a value which is legitimately absent, not due to an errorAsking for the maximum of an empty array is an error case (hence D's minElement/maxElement thrown an AssertError, not even a catchable exception), this is just a boring and expected one, and there aren't any other kinds of errors that need to be distinguished from it, so it's reasonable to return Option instead of Result. Everything works the same for them, Result just has more runtime and programmer overhead. Hence alsorather than "deprecate exceptions, rewrite Phobos to use Nullable".Conclusion: deprecate exceptions, rewrite Phobos to only use Option and Result sumtypes, and release D3!, it's `Result` that replaces exceptions. Also languages built with this pattern in mind have syntax sugar that helps propagate those errors upwards.Yeah, .assumeWontThrow in a nothrow function is very similar to .unwrap, but there's no analogue to ? in a function that just propagates errors. Temporarily adding nothrow still tells you where exceptions might be coming from, and I suppose a Sufficiently Smart IDE could toggle that behind your back to annotate uncaught exceptional function calls for you.
Sep 10 2021
On Friday, 10 September 2021 at 15:44:04 UTC, jfondren wrote:On Friday, 10 September 2021 at 15:26:18 UTC, Kagamin wrote:Strangely, there is no mention of this in the documentation for either `minElement` or `maxElement`. And it's also inconsistent with other similar functions in `std.algorithm.searching`; for example, `minCount` and `maxCount` throw an `Exception` on an empty range.Nullable doesn't replace exceptions, it specifies a value which is legitimately absent, not due to an errorAsking for the maximum of an empty array is an error case (hence D's minElement/maxElement thrown an AssertError, not even a catchable exception)
Sep 10 2021
On Friday, 10 September 2021 at 15:57:03 UTC, Paul Backus wrote:On Friday, 10 September 2021 at 15:44:04 UTC, jfondren wrote:In a -release build, over an `int[]`, you get a RangeError from std.range.primitives.front: ```d assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); return a[0]; ``` So the skipped assert() there and the skipped contract programming assert() in minElement/maxElement, all they're really doing is improving the error message. That might be why they're not mentioned. ... or in a -release -boundscheck=off build, you get Error: program killed by signal 11. These kind of flags also weaken the assumeWontThrow=unwrap similarity.On Friday, 10 September 2021 at 15:26:18 UTC, Kagamin wrote:Strangely, there is no mention of this in the documentation for either `minElement` or `maxElement`. And it's also inconsistent with other similar functions in `std.algorithm.searching`; for example, `minCount` and `maxCount` throw an `Exception` on an empty range.Nullable doesn't replace exceptions, it specifies a value which is legitimately absent, not due to an errorAsking for the maximum of an empty array is an error case (hence D's minElement/maxElement thrown an AssertError, not even a catchable exception)
Sep 10 2021
On Friday, 10 September 2021 at 16:10:27 UTC, jfondren wrote:In a -release build, over an `int[]`, you get a RangeError from std.range.primitives.front: ```d assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); return a[0]; ``` So the skipped assert() there and the skipped contract programming assert() in minElement/maxElement, all they're really doing is improving the error message. That might be why they're not mentioned.Either way, if a function has a precondition that the caller needs to satisfy, it should be documented. https://github.com/dlang/phobos/pull/8238
Sep 10 2021
On 9/10/21 11:44 AM, jfondren wrote:Yeah, .assumeWontThrow in a nothrow function is very similar to .unwrap, but there's no analogue to ? in a function that just propagates errors. Temporarily adding nothrow still tells you where exceptions might be coming from, and I suppose a Sufficiently Smart IDE could toggle that behind your back to annotate uncaught exceptional function calls for you.I've really come to appreciate `?` operator, and don't forget there is a complement of other library infrastructure like `map_err`, `unwrap_or_else`, etc.
Sep 10 2021
On Friday, 10 September 2021 at 15:26:18 UTC, Kagamin wrote:Nullable doesn't replace exceptions, it specifies a value which is legitimately absent, not due to an error, it's `Result` that replaces exceptions. Also languages built with this pattern in mind have syntax sugar that helps propagate those errors upwards.Walter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.
Sep 10 2021
On Friday, 10 September 2021 at 23:20:17 UTC, IGotD- wrote: ve syntax sugar that helps propagateWalter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.There has been some brainstorming about it, but don't expect to see a proposal anytime soon.
Sep 10 2021
On Friday, 10 September 2021 at 23:20:17 UTC, IGotD- wrote:Walter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.Well the ` nodiscard` proposal, that is very likely to get accepted, is sort-of one. It's basically about returning traditional error values, but forcing the user to be explicit if she really wants to summarily discard them.
Sep 11 2021
On Sat, Sep 11, 2021 at 08:13:07PM +0000, Dukc via Digitalmars-d wrote:On Friday, 10 September 2021 at 23:20:17 UTC, IGotD- wrote:Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason. I don't really buy the performance argument: if your profiler pinpoints try/catch blocks as the bottleneck, it's not hard to refactor it to use error codes instead. Anything else is premature optimization. We should not uglify code just for some idealism that exceptions are somehow "bad". T -- EMACS = Extremely Massive And Cumbersome SystemWalter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.Well the ` nodiscard` proposal, that is very likely to get accepted, is sort-of one. It's basically about returning traditional error values, but forcing the user to be explicit if she really wants to summarily discard them.
Sep 11 2021
On Sunday, 12 September 2021 at 01:50:37 UTC, H. S. Teoh wrote:On Sat, Sep 11, 2021 at 08:13:07PM +0000, Dukc via Digitalmars-d wrote:Rust's monadic error handling is frequently ugly, but there's a cultural backing to it; the feature itself doesn't demand all that ugliness. As soon as you change the culture you can have solutions like ```d // halt program on empty nums int find_gcd(const int[] nums) nothrow { import std.numeric : gcd; import mod.algorithm : minElement, maxElement; return gcd(nums.minElement.unwrap, nums.maxElement.unwrap); } ``` vs. ```d // return None on empty nums with ? syntax auto find_gcd(const int[] nums) nothrow { import std.numeric : gcd; import mod.algorithm : minElement, maxElement; return gcd(nums.minElement?, nums.maxElement?); } ``` vs. ```d // throw catchable exception on empty nums int find_gcd(const int[] nums) { import std.numeric : gcd; import mod.algorithm : minElement, maxElement; return gcd(nums.minElement, nums.maxElement); } ``` with the compiler or some `alias this` magic inserting code that checks the Option!int returns and throwing on None. i.e., if you want it you get the explicit error handling, and if you don't want it you get exceptions, and you can guard against unwanted 'helpful' automatic exceptions with `nothrow`.On Friday, 10 September 2021 at 23:20:17 UTC, IGotD- wrote:Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason.Walter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.Well the ` nodiscard` proposal, that is very likely to get accepted, is sort-of one. It's basically about returning traditional error values, but forcing the user to be explicit if she really wants to summarily discard them.
Sep 11 2021
On Sunday, 12 September 2021 at 01:50:37 UTC, H. S. Teoh wrote:On Sat, Sep 11, 2021 at 08:13:07PM +0000, Dukc via Digitalmars-d wrote:Yes, one thing that gets lost is the boilerplate required to create error types, to the point that there are crates to work around it. https://users.rust-lang.org/t/how-to-reduce-boilerplate-when-wrapping-errors/39363/6 Beware what you wish for, the grass is not always greener on the other side.On Friday, 10 September 2021 at 23:20:17 UTC, IGotD- wrote:Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason. I don't really buy the performance argument: if your profiler pinpoints try/catch blocks as the bottleneck, it's not hard to refactor it to use error codes instead. Anything else is premature optimization. We should not uglify code just for some idealism that exceptions are somehow "bad". TWalter mentioned that exceptions are on the way out because they are expensive and inhibits the optimizer which are fair points. I interpreted this that Walter wants to move away from exceptions in D. Since then I haven't seen any proposal for any new error handling for D.Well the ` nodiscard` proposal, that is very likely to get accepted, is sort-of one. It's basically about returning traditional error values, but forcing the user to be explicit if she really wants to summarily discard them.
Sep 11 2021
On Sunday, 12 September 2021 at 01:50:37 UTC, H. S. Teoh wrote:Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason. I don't really buy the performance argument: if your profiler pinpoints try/catch blocks as the bottleneck, it's not hard to refactor it to use error codes instead. Anything else is premature optimization. We should not uglify code just for some idealism that exceptions are somehow "bad". TWhat about the C++ approach, return values that look like exceptions. You probably have already seen it. http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0709r4.pdf We are allowed to "steal" from C++ if it is any good. Also it can be good for interoperability.
Sep 12 2021
On 13/09/2021 3:53 AM, IGotD- wrote:On Sunday, 12 September 2021 at 01:50:37 UTC, H. S. Teoh wrote:I recently posted a similar design here: https://forum.dlang.org/post/she82k$g31$1 digitalmars.com A big difference between the one you linked and mine is the throws keyword takes the types that can be thrown. So an empty set of throwables, is the same as none being thrown. The other difference is to recognize that it is not a return value, rather it is an out parameter instead that is hidden like this is. This allows for the purpose of playing nicely with existing code.Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason. I don't really buy the performance argument: if your profiler pinpoints try/catch blocks as the bottleneck, it's not hard to refactor it to use error codes instead. Anything else is premature optimization. We should not uglify code just for some idealism that exceptions are somehow "bad". TWhat about the C++ approach, return values that look like exceptions. You probably have already seen it. http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0709r4.pdf We are allowed to "steal" from C++ if it is any good. Also it can be good for interoperability.
Sep 12 2021
On Sunday, 12 September 2021 at 15:53:16 UTC, IGotD- wrote:What about the C++ approach, return values that look like exceptions. You probably have already seen it. http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0709r4.pdf We are allowed to "steal" from C++ if it is any good. Also it can be good for interoperability.Feels like java throws statement, except the need to handle exception in the function that calls throwing function.
Sep 12 2021
On Sunday, 12 September 2021 at 22:50:49 UTC, Alexandru Ermicioi wrote:On Sunday, 12 September 2021 at 15:53:16 UTC, IGotD- wrote:Java throws statement was based on CLU, Modula-3 and C++, actually. Herb's idea is based on how Swift does exceptions, and ironically brings exception specifications back into C++, after their removal on C++17, just written in a different form.What about the C++ approach, return values that look like exceptions. You probably have already seen it. http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0709r4.pdf We are allowed to "steal" from C++ if it is any good. Also it can be good for interoperability.Feels like java throws statement, except the need to handle exception in the function that calls throwing function.
Sep 12 2021
On Monday, 13 September 2021 at 05:56:18 UTC, Paulo Pinto wrote:Java throws statement was based on CLU, Modula-3 and C++, actually. Herb's idea is based on how Swift does exceptions, and ironically brings exception specifications back into C++, after their removal on C++17, just written in a different form.Sorry, for confusion, I didn't refer to throws statement in body of a method, but rather throws statement in method declaration, used to list checked exceptions thrown by the method. I think they also can be called an exception specification. They enforce the caller of method to handle those cases, while in C++ spec, the purpose is performance, and unification of error reporting format.
Sep 15 2021
On Sunday, 12 September 2021 at 15:53:16 UTC, IGotD- wrote:On Sunday, 12 September 2021 at 01:50:37 UTC, H. S. Teoh wrote:That will probably never happen. First, a prototype has been requested to prove the design, before going forward, so C++26 might be the next time they will actually look at it. Secondly, Bjarne has written a rebutall that the biggest issue with exceptions is that compiler vendors just don't care to optimize the implementation, because of the usual ABI discussions, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1947r0.pdf So I really don't expect this to ever be voted in into ISO C++, just like the reflection prototype and metaclasses proposals before it.Whatever replaces exceptions better be darned good, otherwise I will be very unhappy. Explicit exception handling has its place, but in other cases it just uglifies code for no good reason. I don't really buy the performance argument: if your profiler pinpoints try/catch blocks as the bottleneck, it's not hard to refactor it to use error codes instead. Anything else is premature optimization. We should not uglify code just for some idealism that exceptions are somehow "bad". TWhat about the C++ approach, return values that look like exceptions. You probably have already seen it. http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0709r4.pdf We are allowed to "steal" from C++ if it is any good. Also it can be good for interoperability.
Sep 12 2021
On Monday, 13 September 2021 at 06:05:47 UTC, Paulo Pinto wrote:That will probably never happen. First, a prototype has been requested to prove the design, before going forward, so C++26 might be the next time they will actually look at it. Secondly, Bjarne has written a rebutall that the biggest issue with exceptions is that compiler vendors just don't care to optimize the implementation, because of the usual ABI discussions, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1947r0.pdf So I really don't expect this to ever be voted in into ISO C++, just like the reflection prototype and metaclasses proposals before it.That still doesn't prevent D from implementing the design. D can actually introduce it before C++. Implementation wise it could be complicated for D. Usage wise it fits D quite well as you can still use the exception syntax with the convenient scope guards.
Sep 13 2021