Boost logo

Boost :

Subject: Re: [boost] [outcome] Ternary logic -- need an example
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-22 06:27:25


2017-05-21 0:13 GMT+02:00 Niall Douglas via Boost <boost_at_[hidden]>:

> >> Similarly to the value returned case, Outcome does not involve itself
> >> into whether a T value returned is valid or not in exactly the same way
> >> as it does not involve itself into whether a E value returned is valid
> >> or not.
> >
> > If you are saying "I just give you a tool for storing either T or
> > error_code or std::exception_ptr or nothing, and I do not care how you
> use
> > it", I can accept that. But the semantics of `o.error()` seem to
> contradict
> > that a bit: as though you were in fact trying to workaround for potential
> > mis-usages of your library.
>
> I very much came at the default actions as a means of saving the
> programmer from typing boilerplate.
>
> The specific actions I chose have been mostly the same since day one. I
> think there was one which turned out to be a mistake during the past two
> years of using the library in my code, I changed it. Otherwise they've
> been tested in the real world for a quite a while now.
>
> > Given what you already said about semantics of function `error()`, I
> > consider the documentation of this function insufficient:
> > https://ned14.github.io/boost.outcome/structboost_1_
> 1outcome_1_1v1__xxx_1_1policy_1_1monad__policy__base.html#
> a6d5a06127d3ab8aa317635cfef1ada6a
>
> Good point. Logged to https://github.com/ned14/boost.outcome/issues/26
>
> > (BTW, note that there is something wrong with the links. If I click on
> it,
> > I do not get any more details for `error()` but instead get "Detailed
> > Description" of boost::outcome::v1_xxx::policy::monad_policy_base)
>
> It worked here fine.
>
> > Anyway, the short description of function `error()` says, "Returns any
> > errored state in the transport, throwing an exception if empty."
> >
> > 1. I wish you didn't use this word "transport" as a noun. It always
> > confuses me. Do you mean "either `option` or `resutl` or `outcome`"?
>
> Transport is a noun meaning a device which conveys something.
>
> > 2. "any errored state"? -- not the specific error state previously set
> > inside `outcome`?
>
> If we refer to state, we mean the variant in Outcomes.
>
> > 3. It does not mention your algorithm: if `has_value() == true`, returns
> a
> > value-initialized error code; if `has_exception() == true`, returns
> > `error_type((int) monad_errc::exception_present, monad_category())`
> >
> > 4. "Throwing exception if empty" -- what exception?
>
> All the above is covered in the tutorial, but I agree it needs to be in
> the reference docs too. It will be fixed.
>
> > I trust you that all these additional guarntees cost nothing at run-time.
> > My concerns are not really about a potential run-time overhead, but about
> > what is a correct usage of the library and what is a buggy usage. For
> > instance, if you changed the semantics of function `error()` to:
> >
> > Requires: `has_error() == true`.
> > Returns: the error_code stored in `*this`.
> >
> > This would make the understanding of the interface simple, it would
> clearly
> > indicate when the users do something wrong, you could still implement
> your
> > "rescue semantics", but I when I am doing the code review for my
> > colleagues, I have something objective to rely on: "hey, you are breaking
> > the precondition, you are extracting the error even though it is not
> > there". Now, with the rescue semantics, I cannot say a word in the code
> > review because the other programmer will respond, "But I learned the
> > detailed rescue semantics, and I figured out it is exactly what I need."
> > <-- the code does what the programmer intended, but is difficult to
> > maintain, because it relies on the rescue semantics.
> >
> > By "rescue semantics" I mean, "id you do not have an error_code to
> return,
> > just fabricate one".
>
> I get what you're saying. The fact there are no preconditions on
> .error() means that calling it is always a valid thing to do, and not by
> definition a bug nor a code smell.'
>

Yes, we agree here, as to current behavior of `o.error()`

>
> This design is intentional. *If* you learn off the "rescue semantics" as
> you put them, then when writing code you swap tedious boilerplate for
> code which relies on those rescue semantics.
>

Yes, I think I understand what you are saying. If `o.error()` has a "narrow
contract" or IOW, if it has a precondition, many users (but not all) would
be forced to manually repeat the same unpacking code:

```
if (o.has_value())
  use (error_code_extended{});
else if (o.has_exception())
  use error_type((int) monad_errc::exception_present, monad_category())
if ...

```

> If you feel that a woolly and imprecise step too far, then you can use
> expected<T> instead. It's why Outcome ships with expected<T>. Each of
> its observers come with strict preconditions governing whether the call
> is valid or undefined behaviour.
>

I probably could, but it sounds like `outcome<>` didn't have anything else
to offer except the "wooly semantics", and I do not think it is the case.
It is my understanding that `outcome<>` also offers other things:

   - being able to store either an error code or exception_ptr
   - other convenience interface without "contract widening", like
   BOOST_OUTCOME_TRY
   - other performance improvements

If I want to use the above advantages, but I dislike "contract widening"
(or "wooly semantics"), you leave me with no option.

What you could do is to offer two observer functions:

```
o.error_wide(); // with wide contract
o.error_narrow(); // with wide contract
```

Don't look at the choice of names, you can make them better, but the idea
is you have two functions: one for people who prefer clearly stating
intentions at the expense of longer code, the other for people that prefer
concise notation.

This is what `std::optional` and `boost::optional` are doing, you get both:

```
*o; // with narrow contract
o.value(); // with wide contract
```

Or:

```
if (o == true) // for those who like short notation
if (o && *o == true) // for those who like no ambiguity
```
My proposed names:

   - as_error() // for wide contract
   - error() // for narrow contract

Regards,
&rzej;


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk