www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Idiomatic way to express errors without resorting to exceptions

reply Adnan <relay.public.adnan outlook.com> writes:
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
next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
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
parent reply Adnan <relay.public.adnan outlook.com> writes:
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:
 * 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.
Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?
Feb 29 2020
next sibling parent Paul Backus <snarwin gmail.com> writes:
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:
 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.
Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?
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.)
Feb 29 2020
prev sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
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:
 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.
Doesn't that pretty much defeat the entire purpose of type-guarding a potentially error/absent value?
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.
Feb 29 2020
parent reply Arine <arine123445128843 gmail.com> writes:
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
next sibling parent Sebastiaan Koppe <mail skoppe.eu> writes:
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
prev sibling parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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 useful
Except 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
prev sibling parent Basile B. <b2.temp gmx.com> writes:
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