Date: Sun, 13 Sep 2020 17:11:43 +0200
Interuptus vulgaris :)
"... (Note, however, that it is not entirely true, because *sometimes* before
returning an error you want to capture some context, add some explanation
and so on..., which means it is not *just* return)...."
*It is not just return ... Hmm, Can I interest you in P2192 :)*
On Sun, 13 Sep 2020 at 10:48, Dmitry Dmitry via Std-Proposals <
std-proposals_at_[hidden]> wrote:
>
> But there was one strong outlier, and it looks like this:
>> std::optional<std::string> oldFindUsersCity(bool non_default) {
>> std::optional<UserId> uid = UserId{};
>> if (non_default) {
>> uid = GetUserId();
>> if (!uid) return nullopt;
>> }
>> std::optional<Location> uloc = uid->GetLocation();
>> if (!uloc) return nullopt;
>> return uloc->GetCityName();
>> }
>> Those if/return pairs make the code really ugly really fast, so people
>> made macros:
>> std::optional<string> FindUsersCity(bool non_default) {
>> UserId uid;
>> if (non_default) ASSIGN_OR_RETURN(uid, GetUserId());
>> ASSIGN_OR_RETURN(Location uloc, uid.GetLocation());
>> return uloc.GetCityName();
>> }
>>
>
> While reading this, I realised that it reminds me very much of another
> area of day-to-day programming: error handling via Result/Expected/Outcome,
> which we do a lot. (Note, however, that it is not entirely true, because
> *sometimes* before returning an error you want to capture some context,
> add some explanation and so on..., which means it is not *just* return).
>
> Nevertheless, it seems, a pattern emerges. In all the cases,
>
> 1. there is a type
> 2. that defines some invalid state (in case of iterator the state is
> "being equal to end()", in case of Result the state is "representing an
> error", in case of optional it is "nullopt", and so on...)
> 3. attempts of using which in the invalid state are supposed to
> "return" (from the scope where it is used). We can take a step back and say
> that instead of just "return" we, *perhaps*, can allow any other
> control statement(?).
>
> Without going into implementation details, what do you think about the
> conceptual idea of being able to describe this pattern somewhere somehow?
> If conceptually it is not very awful, then what do you think about
> implementing the idea by creating another flavour of return statements that
> can return not only from its own scope, but also from parent scope? If you
> wish, this new flavour can be viewed as a counterpart of couritine control
> flow statement (co_yield), which expects control-flow to continue execution
> after co_yield (and therefore does not "entirely" leave the scope, while
> the new flavour will leave *parent* scope as well).
>
> The code would look like:
> // new implementation of Result's GetOk that can return from *outer* scope:
> class Result {
> ...
> T GetOkOrReturn() {
> if(Ok)
> return OkValue;
> return_from_parent_scope; // <-- new flavour of return
> }
> ...
>
> // usage
> void DoSomething() {
> Result<int, std::string> uidOrErr = GetUid();
> const int uid = uidOrErr.GetOkOrReturn(); // <-- returns from
> *DoSomething* in case of error.
> }
>
> A more elaborate example will need to mention non-void return types as
> well, but I think it gives an idea.
>
> It seems that it solves all mentioned problems:
>
> - iterators being equal to end,
> - using optional without boilerplate,
> - error-handling via Result<> without boilerplate, which has been a
> constant source of pain and complaints,
> - it *also* eliminates one more reason to exist for macros.
>
>
> --
> Dmitry
> *Sent from gmail*
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
"... (Note, however, that it is not entirely true, because *sometimes* before
returning an error you want to capture some context, add some explanation
and so on..., which means it is not *just* return)...."
*It is not just return ... Hmm, Can I interest you in P2192 :)*
On Sun, 13 Sep 2020 at 10:48, Dmitry Dmitry via Std-Proposals <
std-proposals_at_[hidden]> wrote:
>
> But there was one strong outlier, and it looks like this:
>> std::optional<std::string> oldFindUsersCity(bool non_default) {
>> std::optional<UserId> uid = UserId{};
>> if (non_default) {
>> uid = GetUserId();
>> if (!uid) return nullopt;
>> }
>> std::optional<Location> uloc = uid->GetLocation();
>> if (!uloc) return nullopt;
>> return uloc->GetCityName();
>> }
>> Those if/return pairs make the code really ugly really fast, so people
>> made macros:
>> std::optional<string> FindUsersCity(bool non_default) {
>> UserId uid;
>> if (non_default) ASSIGN_OR_RETURN(uid, GetUserId());
>> ASSIGN_OR_RETURN(Location uloc, uid.GetLocation());
>> return uloc.GetCityName();
>> }
>>
>
> While reading this, I realised that it reminds me very much of another
> area of day-to-day programming: error handling via Result/Expected/Outcome,
> which we do a lot. (Note, however, that it is not entirely true, because
> *sometimes* before returning an error you want to capture some context,
> add some explanation and so on..., which means it is not *just* return).
>
> Nevertheless, it seems, a pattern emerges. In all the cases,
>
> 1. there is a type
> 2. that defines some invalid state (in case of iterator the state is
> "being equal to end()", in case of Result the state is "representing an
> error", in case of optional it is "nullopt", and so on...)
> 3. attempts of using which in the invalid state are supposed to
> "return" (from the scope where it is used). We can take a step back and say
> that instead of just "return" we, *perhaps*, can allow any other
> control statement(?).
>
> Without going into implementation details, what do you think about the
> conceptual idea of being able to describe this pattern somewhere somehow?
> If conceptually it is not very awful, then what do you think about
> implementing the idea by creating another flavour of return statements that
> can return not only from its own scope, but also from parent scope? If you
> wish, this new flavour can be viewed as a counterpart of couritine control
> flow statement (co_yield), which expects control-flow to continue execution
> after co_yield (and therefore does not "entirely" leave the scope, while
> the new flavour will leave *parent* scope as well).
>
> The code would look like:
> // new implementation of Result's GetOk that can return from *outer* scope:
> class Result {
> ...
> T GetOkOrReturn() {
> if(Ok)
> return OkValue;
> return_from_parent_scope; // <-- new flavour of return
> }
> ...
>
> // usage
> void DoSomething() {
> Result<int, std::string> uidOrErr = GetUid();
> const int uid = uidOrErr.GetOkOrReturn(); // <-- returns from
> *DoSomething* in case of error.
> }
>
> A more elaborate example will need to mention non-void return types as
> well, but I think it gives an idea.
>
> It seems that it solves all mentioned problems:
>
> - iterators being equal to end,
> - using optional without boilerplate,
> - error-handling via Result<> without boilerplate, which has been a
> constant source of pain and complaints,
> - it *also* eliminates one more reason to exist for macros.
>
>
> --
> Dmitry
> *Sent from gmail*
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2020-09-13 10:12:01