digitalmars.D.learn - Idiomatic way to express errors without resorting to exceptions
- Adnan (24/24) Feb 29 2020 I have a struct that has to arrays. Each of those must have the
- Sebastiaan Koppe (5/10) Feb 29 2020 I would argue it is one of its strengths.
- Adnan (4/11) Feb 29 2020 Doesn't that pretty much defeat the entire purpose of
- Paul Backus (13/26) Feb 29 2020 I suppose it depends on what you think that purpose is.
- Sebastiaan Koppe (14/27) Feb 29 2020 Like I said, I don't use optionals when I care about errors. That
- Arine (23/36) Mar 07 2020 I feel as though that's it's greatest weakness. It makes the
- Sebastiaan Koppe (5/23) Mar 07 2020 What can I say? If you don't like the semantics, don't use it. I
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (18/21) Mar 09 2020 Except when it is useful, and shouldn't be handled explicitly. I
- Basile B. (28/52) Feb 29 2020 There's no idiomatic way since D lang is based on exceptions...
I have a struct that has to arrays. Each of those must have the same sizes. So while constructing the array, if you pass two arrays of different sizes the constructor must return nothing. In Rust I could easily use Option<T>. D has no answer to Optional types as far as I am concerned. Is throwing exceptions the only idiomatic way? --- What I already considered: * Using Nullable!T: Okay but Nullable!T has all the overloads for regular T which makes the API even more unpredictable. In Rust you don't just add a Some(44) and 34; No overloads for Some<T> and i32 are allowed (purposefully). * Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`. * Value-based error handling like Go and C: well, that works but the error checking is opt-in. There's no such thing as [[nodiscard]] in D too so the user of the API might as well forget to check for error value. * if specialization: Clever workaround but sometimes the struct may fail for complex reasons, not only for a single condition.
Feb 29 2020
On Saturday, 29 February 2020 at 12:50:59 UTC, Adnan wrote:* Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`.I would argue it is one of its strengths. Regardless, I don't think option/nullable/maybe is a good type for errors. Rather you should use something like SumType!(T, Error), or any other 'either' type.
Feb 29 2020
On Saturday, 29 February 2020 at 13:03:21 UTC, Sebastiaan Koppe wrote:On Saturday, 29 February 2020 at 12:50:59 UTC, Adnan wrote:Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?* Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`.I would argue it is one of its strengths.
Feb 29 2020
On Saturday, 29 February 2020 at 13:40:11 UTC, Adnan wrote:On Saturday, 29 February 2020 at 13:03:21 UTC, Sebastiaan Koppe wrote:I suppose it depends on what you think that purpose is. Optional!T will not allow you to do anything that requires a T unless the T is actually present. In some cases, it will allow you to *attempt* such operations, and return an empty Optional at run time if they are not allowed; in others, the attempt will result in a type error at compile time. This is a compromise between strictness and convenience, but there is no compromise to correctness. Unlike, say, a null pointer, Optional!T will not allow you to cause a run-time error by forgetting a check. (Of course, you can explicitly unwrap the Optional without checking, but that's not something you're likely to do by accident.)On Saturday, 29 February 2020 at 12:50:59 UTC, Adnan wrote:Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?* Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`.I would argue it is one of its strengths.
Feb 29 2020
On Saturday, 29 February 2020 at 13:40:11 UTC, Adnan wrote:On Saturday, 29 February 2020 at 13:03:21 UTC, Sebastiaan Koppe wrote:Like I said, I don't use optionals when I care about errors. That is not what they are designed for. If I want to type-guard potential errors I will use SumType!(T, Error). It forces you to handle both cases, either at compile time (with the match template), or at runtime (with the tryMatch template). The power of optionals, however, lies in the following: ``` list.first.doSomething(); ``` Here doSomething will only be called when the list contains at least one item. Here I don't care about cases where the list is empty, I just want to doSomething if it isn't.On Saturday, 29 February 2020 at 12:50:59 UTC, Adnan wrote:Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?* Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`.I would argue it is one of its strengths.
Feb 29 2020
On Saturday, 29 February 2020 at 15:23:02 UTC, Sebastiaan Koppe wrote:Like I said, I don't use optionals when I care about errors. That is not what they are designed for. If I want to type-guard potential errors I will use SumType!(T, Error). It forces you to handle both cases, either at compile time (with the match template), or at runtime (with the tryMatch template). The power of optionals, however, lies in the following: ``` list.first.doSomething(); ``` Here doSomething will only be called when the list contains at least one item. Here I don't care about cases where the list is empty, I just want to doSomething if it isn't.I feel as though that's it's greatest weakness. It makes the check whether there is or isn't a value hidden. The case when there isn't a value should be handled explicitly, not implicitly. Propogating a None value isn't useful and is computationally demanding as each subsequent call will need to do a check to see if it has a value as it results in an optional type (for binary operators). So something like `a + b * c` is now much more expensive than it appears. This is exactly the kind of abuse of operator overloading that the feature is shunned for. Anyways not sure what you mean here with the code below. If "first" here returns an optional type, you can't call "doSomething" like that without first checking if the value exists. Optional just doesn't allow it and Nullable will throw an exception. ``` list.first.doSomething(); // error ``` Which makes it odd that it forwards operator overloading to the underlying type, which is basically just syntactic sugar for calling a method on the object. Seems like a conflicting design decision to me.
Mar 07 2020
On Saturday, 7 March 2020 at 15:44:38 UTC, Arine wrote:I feel as though that's it's greatest weakness. It makes the check whether there is or isn't a value hidden. The case when there isn't a value should be handled explicitly, not implicitly. Propogating a None value isn't useful and is computationally demanding as each subsequent call will need to do a check to see if it has a value as it results in an optional type (for binary operators). So something like `a + b * c` is now much more expensive than it appears. This is exactly the kind of abuse of operator overloading that the feature is shunned for.What can I say? If you don't like the semantics, don't use it. I have found value in it.Anyways not sure what you mean here with the code below. If "first" here returns an optional type, you can't call "doSomething" like that without first checking if the value exists. Optional just doesn't allow it and Nullable will throw an exception. ``` list.first.doSomething(); // error ```You are right. Nowadays you need a `.oc` call in between. Also, doSomething cannot be a free-standing function (sadly).
Mar 07 2020
On Saturday, 7 March 2020 at 15:44:38 UTC, Arine wrote:The case when there isn't a value should be handled explicitly, not implicitly. Propogating a None value isn't usefulExcept when it is useful, and shouldn't be handled explicitly. I have code in D, C and C++ that looks like this: ReturnValue result = someInitialValue; auto foo = getFoo(); if (!foo) return result; auto bar = foo.fun(); if (!bar) return result; return bar.gun(); return getFoo()?.fun().gun() ?? someInitialValue; And with implicit handling in Optional!T, it looks like this: return getFoo().oc.fun().gun().or(someInitialValue); Clearly the latter two are more readable, and I'm not gonna care that it's a little slower in the 99% of cases where speed is not important. -- Simen
Mar 09 2020
On Saturday, 29 February 2020 at 12:50:59 UTC, Adnan wrote:I have a struct that has to arrays. Each of those must have the same sizes. So while constructing the array, if you pass two arrays of different sizes the constructor must return nothing. In Rust I could easily use Option<T>. D has no answer to Optional types as far as I am concerned. Is throwing exceptions the only idiomatic way? --- What I already considered: * Using Nullable!T: Okay but Nullable!T has all the overloads for regular T which makes the API even more unpredictable. In Rust you don't just add a Some(44) and 34; No overloads for Some<T> and i32 are allowed (purposefully). * Option!T from the optional package: Has even worse problem IMO. Not only it allows None + int but also it returns a `[]`. This API is not to my liking. You could say well Haskell has fmap for Optional etc, and I am aware of that, so does Rust with map etc. But I am talking about basic things: like `+`. * Value-based error handling like Go and C: well, that works but the error checking is opt-in. There's no such thing as [[nodiscard]] in D too so the user of the API might as well forget to check for error value. * if specialization: Clever workaround but sometimes the struct may fail for complex reasons, not only for a single condition.There's no idiomatic way since D lang is based on exceptions... However I'd use one of those system: 1. return error, write result in ref parameter. alias CheckedValueProto(RT, P...) = bool function(ref RT, P params); 2. the same using a special struct and no more ref param. So more like Nullable/Optional but with a dedicated generic type that contain a single opover used to indicate if there's been an error or not. struct CheckedValue(T) { bool noError; T t; B opCast(B : bool)() inout pure nothrow safe { return noError; } } and you make your functions to return CheckedValues... CheckedValue!int strToInt(string input); .... if (const CheckedValue!int = strToInt("a") {} else {} Although - both still require self-discpline or a specialized linter to detect unchecked calls ; - the whole standard library is incompatible ; I have a personal preference for 2. even if it causes problems when T is of same size as a pointer. Now the question is also what's the more costly ? try/catch or this non atomic return ?
Feb 29 2020