Boost logo

Boost :

Subject: Re: [boost] [outcome] New proposed factory API as per review feedback
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-25 14:58:40


Le 25/05/2017 à 16:14, Niall Douglas via Boost a écrit :
>>> In fact, I am increasingly thinking that a consensus position could be
>>> this type factory template:
>>>
>>> // What to default construct to
>>> enum class default_to
>>> {
>>> none,
>>> T,
>>> EC,
>>> E
>>> };

none => 1

>>>
>>> // How much empty state to implement
>>> enum class emptiness
>>> {
>>> never, // imposes restrictions on T, EC, E
>>> formal, // formal empty state
>>> tolerant // empty state iff EC and E don't have nothrow move
>>> construction
>>> };
never => 1
>>>
>>> // How narrow or wide the observers will be
>>> enum class observers
>>> {
>>> narrow, // accessing not the current state = reinterpret_cast
>>> wide, // default actions already described earlier this review
>>> single_shot // you can observe state precisely once only
>>> };
narrow and wide => 1
>>>
>>> // Replacement for basic_monad
>>> template<
>>> class T, // what .value() returns, or void
>>> class EC, // what .error() returns, or void
>>> class E, // what .exception() returns, or void
>>> default_to default_to_config,
>>> emptiness empty_config,
>>> observers observers_config
>>>> class outcome_impl;
>>> // Outcome as apparently desired by reviewers
>>> template<class T> using outcome = outcome_impl<
>>> T,
>>> error_code_extended,
>>> std::exception_ptr,
>>> default_to::none, // default constructed instance =
>>> reinterpret_cast uninitialised memory
>>> emptiness::never, // Never possible to be empty, not ever
>>> observers::narrow // reinterpret_cast all observers
>>>> ;
>>> // Result as apparently desired by reviewers
>>> template<class T> using result = outcome_impl<
>>> T,
>>> error_code_extended,
>>> void, // there is still a .exception(), but it returns
>>> void and is not usable
>>> default_to::none, // default constructed instance =
>>> reinterpret_cast uninitialised memory
>>> emptiness::never, // Never possible to be empty, not ever
>>> observers::narrow // reinterpret_cast all observers
>>>> ;
>>> See what you think of the proposed API above. We supply a precanned
>>> outcome<T> and result<T> template alias, but end users can template
>>> alias any configuration they like.
>>>
>> I am geting some contradictory information here. Is the fact that
>> outcome<T> is built of policies part of the API which you commit to support
>> in subsequent releases? It was my impression that you never wanted this to
>> be exposed to the users. Now, when you use name "API" it looks like you do
>> want to expose the policies?
> Outcome was always supposed to be a *family* of Either monad varieties
> which had well defined interoperation semantics with one another. So the
> original idea was that there was a clean progression from option<T>
> right through to future<T> with a single common implementation, with a
> common DO/TRY/AWAIT mechanism, a common monadic BIND and MAP mechanism
> and so on.
I believe that some of us want these mechanism to be provided
independently of the concrete classes.
No a single class that wraps some data and provides all these mechanism.

> However I am personally uncomfortable supporting code I don't use in my
> own software, so after AFIO v1 was rejected, my personal need for such a
> wide family of Either monads, and the monadic operations, went away,
> including the promise/future specialisation. You thus got the Outcome
> presented for review here today which had been reduced to just outcome,
> result and option.
>
> During this review there are clear disagreements about what form and
> semantics a C++ Either monad should take. People aren't even sold on the
> Expected proposal. I don't get that criticism, I think the Expected
> proposal just fine, but there you go.
But we can also other Monads that don't fall on the PossiblyValued
category. E.g. we can see array, tuple, vector as monadic types. And a
lot more that I don't know yet.
>
> Outcome's CRTP policy based implementation is very flexible. All the
> code to implement all the varieties of Either monad which someone has
> asked for at least once in this review is already in Outcome. It would
> be a bit of work, but not too awful, to replace the preprocessor
> stamping out of hard coded prefabricated specialisations for outcome,
> result, option and expected with a template based compile time assembly
> of parts to provide all the varieties mentioned above. I count:
>
> 4 x 3 x 3 x 3 combinations = 108 Either monad varieties
I don't agree with the template parameters you are suggesting and I
believe that there are no so much concrete cases.
We shouldn't provide any combination some one could be ionterested in
just in order to satisfy this guy.
We should start with the concrete ones people really need.

BTW, I see only 4 x 3 x 3

>
> You can't possibly hope to test all these in a test suite. It's another
> reason why I didn't propose this design originally. But if it's what
> people want, and it's not stupidly dangerous which this is not, I can
> give the people what they want.
:)
>
> (BTW, originally Outcome *did* use a template based compile time
> assembly of parts. I replaced it with a hard coded preprocessor
> generated edition for lower compile time cost. But the old mechanism can
> be restored, and that would eliminate most of the preprocessor template
> based code generation which some have complained about. I personally
> suspect the template based system will be not particularly easier for
> casual reading)
>
> So do I want to expose the policies? Not really. But I can see no
> consensus opinion on the shape and form for Expected, let alone for
> Outcome, will emerge. So I propose Outcome provides all one hundred and
> eight varieties of Either monad, and let Boost keep its reputation for
> overwhelming complexity.
No please.
> We'll see which forms end up being used most by end users, and that
> would be great information for Vicente and WG21 to have to hand during
> standardisation.
It is simpler than that. Just propose the concrete classes that have a
sense.

We could discuss here on which emptiness, default value, contract do we
want for these classes.
IHMO we should base always our interface and decision on the experience
of the standard library (I'm not saying we should copy/past the design
that we did wrong).

We have std::varaint that doesn't provide the never-empty warranties.
This IMO was a mistake. We spent a lot of time with the specification of
this library. C++17 was too close to reopen the debate. I will hope we
will restart it for C++20 with the papers from Anthony W., Peter D. and
David S. I would prefer to have a variant that doesn't provides some
operations when the types don't satisfy some properties but that respect
the never-empty warranties. We need correction before optimization.

For the default value, each option is not better than the others and
seems arbitrary. In this case I would say that either we don't provide
it or it is provided uninitialized. Why variant<T,U> is initialized by
default to T{}? Why we will have a different result for variant<U,T>?

For the observers, I don't think we should choose, we can have both, and
it has been done this way in a lot of classes of the standard. at versus
[], * versus value, ...

If we allow variadic Errors, at the end we could get 1 or 2 concrete
classes, and the classes you are proposing will just be template aliases.

Best,
Vicente


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