Boost logo

Boost :

Subject: Re: [boost] [outcome] On the design and documentation
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-25 05:28:33


Le 24/05/2017 à 21:44, Thomas Heller via Boost a écrit :
> Hi Niall (and also probably Vicente)
I believe that this should be discussed probably in the std-proposal ML.
Anyway as we are here
> I was following the discussion about expected/outcome/result very closely and
> I can absolutely see the usefulness of such a library (I explicitly discarded
> option here).
>
> I am not sure if this post should count as a review, mainly because I disagree
> with the fundamental design decisions (that includes expected as defined in
> D0323R2) and therefor would cast my vote as in "not ready yet", I hope I can
> convey my points below.
Wow, first notice I have of this disagreement. It is better later than
never.
>
> First of all, I don't agree with the strong (conceptual) relationship between
> optional (be it boost:: or std::experimental) in such a way that expected is a
> generalization of it. From what I understand, the purpose of expected/outcome/
> result is to be used as a mechanism to return the result of a function. As
> such it should also expose the semantics of it. Fortunately, we already have a
> (asynchronous) return object standardized (std::future). And this is my basic
> disagreement here: Why not model expected in the lines of future? With the
> main points being:
> - expected being movable only
> - expected<T,E>::value() should always return by value and invalidate this.
> - (I would really prefer the color .get though ;))
> So the question to Vicente/Niall is: what is the motivation to make it
> "optional-ish"? Do we have use cases which make this design undesirable?
expected is a generalization of optional as it is synchronous and could
return more information about why the the value is not there.
expected could be seen as the ready storage of a future.
future::get block until the future is ready and then returns by reference :)

I want to ask you, what would be the liabilities of an expected that is
copyable?
We don't have a problem returning be reference, why would we like to
return by value?
Why do you prefer get? what do you get with get? How will you name the
function that give you access to the value of a PossiblyValued type?
>
> By having these constraints, expected of course needs to have an uninitialized
> state. As such we'd have the three observers: valid(): true when has_value()
> || has_error(), false otherwise (for example default constructed,
> invalidated), has_value() and has_error().
Sorry but, not. We don't need such state. This is something future
needs, but not expected.
I we decided to default construct to an uninitialized state, I wouldn't
support to show to the user this state via any observable function, but
via UB (as for chrono::duration).
>
> Second, I think Niall raised a valid about outcome being a framework for
> interoperability (completely orthogonal to the first point). However, I totally
> miss this from the proposed library, most pressing are non intrusive
> mechanisms. For that purpose I postulate, that a mechanism to transform
> between different unexpected results, that is: various error codes etc.
> However, for that to work, one would of course need a properly defined concept,
> for example, as Vicente suggested "EitherValue", and a mechanism to coerce one
> error type into another, maybe through ADL, or traits specialization or
> whatever.
There will be such a proposal as a generalization of Nullable based on
what I named PossiblyValued..
https://github.com/viboes/std-make/blob/master/doc/proposal/nullable/D0196R3.md

> With that in place, one could simply define the different EitherValue types,
> there is no need that everything needs to be in the form of "basic_XXX". For
> the library under review, this would be perfectly sufficient:
> template <typename T, typename E>
> class expected;
> template <typename T>
> using result = expected<T, extended_error_info>;
> template <typename T>
> using outcome = expected<T, variant<extended_error_info, exception_ptr>>;
We will need to have a specialization of expected<T, varaint<E...>> as
we have an index for variant.
The revision 2 of the expected proposal talks of a expected<T, E1, ..., En>.
>
> That is, given that we have either a value or unexpected, we can convert
> expected<T, E1> to expected<U, E2> if T is convertible to U and E1 "coercable"
> (with whichever mechanism) to E2.
I have added this conversion constructor recently to the expected
proposal as the result of my understanding of the need of Outcome and I
hope we will discuss it in Toronto.
>
> If we then have a generic mechanism to get from a (possibly user defined "E")
> to an exception, I completely miss the point of the outcome template.
And why not to throw E?
>
> Furthermore, I believe that .value/.error should really have a narrow
> contract, that is that it is UB to call those functions if the respective
> types are not held (easy to catch this in a debug build...). Why? Probably
> just a micro optimization, but consider the canonical usage:
> auto r = some_function_returning_expected(...);
> if (r.has_value())
> // Do something with the value
> else
> // An error occurred, PANIC
> In both branches, we know exactly what's in there ... so why check again when
> getting the state out? I don't get the "reinterpret_cast" argument. I am one
> of the persons that believe that UB is a necessary evil for some
> optimizations...
We agree here.
>
> All optimizations can then easily be put as implementation details and the
> generic expected<T, E> will probably suffice for most use cases, for everything
> else, we can implement special types which conform to our concepts and
> implement the error conversion mechanisms. This will most likely also work
> with different APIs/ABIs.
The main problem is that we don't have here the generic interface and it
is for this reason we are discussing on the details of a concrete class.
The original expected proposal has fmap, bind, catch_error functions. We
have removed them form expected since the last revision, but we need now
to have a generic interface for those functions.

For me expected should have the minimum, everything else should be
associated to a specific concept, as Nullable, PossiblyValued,
MonadError, SumType.

https://github.com/viboes/std-make/blob/master/doc/proposal/monads/Monads.md
https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/nullable
https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/monad
https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/sum_type

Vicente


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