Boost logo

Boost :

Subject: Re: [boost] Boost.Outcome review - First questions
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-05-22 20:33:15


>>>>> |* |is expected<T,E> the sum type of T, E and empty? and
>>>>> exception_ptr?
>>>> expected<T, E> may only have the state of T or E. The valueless by
>>>> exception state has been eliminated recently in develop branch.
>>> Is this the case also for outcome/result/option?
>> Yes.
> Great, so result<T> and option<T> wouldn't have no anymore a tri-logic,
> and outcome<T> would have a tri-logic as it can be T | E |
> exception_ptr, but never empty.

Formal empty state remains for outcome<T>, result<T>, option<T>. As I've
hopefully shown by now in other discussion threads, the formal empty
state is very useful, saves a lot of boilerplate. For those not wanting
the empty state, there is expected<T, E>.

> I'm not sure, but I believe you maybe will need to double the storage.
> Have you implemented this already?

I explained before the exact implementation, and where valueless due to
exception can still arise. It will be documented soon (this change was
implemented during this review).

>> Consider it a populist extension over LEWG Expected. I know you don't
>> like it, and I to be honest think it best used sparingly, but it'll be
>> very popular with many.
> Popular is not a criteria I will run after.

I am, however, a populist, and that is evident throughout the design of
Outcome and especially AFIO v2. I give the people want they want so long
as it is not obviously dangerous nor stupid.

I appreciate that is not a common design philosophy here, and especially
not on WG21. But I think C++ could do with more feeding the masses
rather than so much ivory tower design and forcing weird design, build
and distribution systems onto end users.

>>>>> * raw types
>>>>>
>>>>> It is not clear what is the difference between xxx and raw_xxx
>>>> Logged to https://github.com/ned14/boost.outcome/issues/29
>>> Thanks for creating the issue, but I'm interested in knowing it now
>>> during the review. Otherwise I couldn't accept even conditionally the
>>> library.
>> When configured with void, value_type, error_type and exception_type are
>> all set to an unusable but usefully named types suitable for compiler
>> warnings and errors. This makes writing metaprogramming much easier as
>> you don't need to deal specially with void and its weird semantics, plus
>> any attempt to use the type causes a very descriptive compiler error.
>>
>> raw_value_type, raw_error_type and raw_exception_type are the true,
>> original type configured. You are correct that this is a deviation from
>> LEWG Expected which would cause code written for LEWG Expected to fail
>> to compile with Outcome's Expected. I think that safe.
> If the raw_ are the reason d'être to avoid the void specialization, and
> this avers to be a useful technique, I believe it merits a full
> implementation section explaining how this improve the compiler
> performances, DRY, et all.

I had thought it a very common technique, so common that it not worth
explaining here.

Am I wrong on this?

> I'm not against something I don't understand, in principle, just want to
> understand why you did the way you did and if it is a good technique
> adopt it on my proposals.

I suspect it would not be accepted into the STL. Disabling overloads via
setting their type to something impossible to match is not something
I've ever seen in any STL.

>>>>> * what is the sizeof expected <T,E>?
>>>> It says already at the top of the page. It's max(24, sizeof(R)+8) on 64
>>>> bit CPUs.
>>> An now that you will ensure the never empty warranties?
>> It's the same sizeof.
> What are the magic number 24 and 8? I'm sure I can undertand it better
> if I read the implementation, but I don't I want to read the
> documentation and understand it.

Is the max of either 24 bytes OR sizeof(T) + 8

So on x64 the bookkeeping for a basic_monad (any implementation) is 8
bytes above the largest possible type stored in the variant storage
which is the minimum possible. The presented library has a small space
optimisation which can pack storage into one byte in some circumstances,
but I'm soon to remove it entirely. It suffers the same problem as
vector<bool>, so .value() and .error() suddenly return by value instead
of by reference etc.

>> I will document the semantics described above in the tutorial and
>> reference API docs. They are a good balance of never empty warranties
>> with space consumption and runtime overhead. I believe these guarantees
>> are similar to those in your Expected proposal? As in, you don't
>> implement the double buffer solution either.
> No, I don't need to. There is not need if E concerned function not
> throw, which I could expect as E is an error.

Cool, then Outcome's Expected is now conforming to your proposal in this
area.

>>> This is what I don't like of your library. In order to have a common
>>> implementation you sacrifice the concrete interfaces.
>> The common implementation is the heart and soul of Outcome, and is
>> precisely its "value add" over most other Either monad or Expected
>> implementations. A common implementation allows seamless interoperation
>> between implementations. It lets you write code using expected<T, E>
>> with outcome<T>, result<T> and option<T> all using the exact same
>> framework. End users can extend the family with any arbitrary custom
>> error transport of their choosing for some bespoke use case.
>
> Your solution is intrusive IMHO.

Yes, that is fair.

> How boost::experimental::expected<T,E>
> will interact with std::experimental::expected<T,E> or std::optional<T>,
> or something else that behaves like a monad error?

Did you mean boost::outcome::experimental::expected<T, E>?

The answer is that we currently don't. The seamless interop applies to
within Outcome's island of types only. However it is very
straightforward to write converting constructors to construct the std::*
editions into Outcome editions. Getting them back out is on the end
user, though I could be persuaded to write an explicit conversion
operator for the std::** editions.

This sort of relationship is exactly the one you previously said is how
you would have implemented each of outcome<T>, result<T>, option<T> and
expected<T, E> in Outcome, so each has a totally standalone and
unrelated implementation, and each provides custom conversion mechanics
into the others.

That would be the traditional way of implementing this. I chose not to
follow that design in this particular case.

>>> Niall, please, don't wait until the review is finished to tell us how
>>> the issues will be fixed. You could do it on each issue and come back in
>>> this ML.
>> I try my best to do so. But a lot of time, how the issues will be fixed
>> is very obvious: I copy and paste documentation from one location to
>> another for example, or I repair an incorrect constructor so it is
>> correct and so on. It's not rocket science to deduce how most issues
>> will be fixed.
>>
> It could be obvious for you, but we need to review those possible
> changes before accepting the library.

I will try to do better. I was particularly lacking in time to do this
review until yesterday because I had my final maths coursework to
complete and submit. That went in yesterday. I should have a lot more
free time not at 1am in the morning from now on. That should make for
better quality replies here, I am not at my best so late at night.

> Niall, I'm not against not for you library. We're just reviewing it now.
> For me the goal is to improve it, and if we can at the same time improve
> the std expected proposal this will be very valuable to the C++ community.

I would be very glad if this process helps the Expected proposal.
Expected is a great proposal, it needs into the C++ standard.

Niall

-- 
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

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