On 05/25/2017 07:28 AM, Vicente J. Botet Escriba wrote:
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
Sure, whatever you think is best :)
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.
Still not sure if everything I said really makes sense, I just felt this line of thought might be interesting to the general discussion.
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
::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.
Right, I can see this argument now, let me try to rephrase it a little
bit (correct me if I got something wrong please):
We want to be able to have a mechanism to return (or store) a value
which might not be there, and we need to know why it is not there. The
class that's currently available which almost does what we want is
optional, in fact, it is already used in such situations, so what we
miss is the possible error. So here we are, and then we naturally end up
with something like variant
expected could be seen as the ready storage of a future. future::get block until the future is ready and then returns by reference :)
Except not quite ;)
excepted indeed sounds like the perfect fit for the value store in the
share state. The only problem here, is that it really requires some kind
of empty (or uninitialized) since the value is only computed some time
in the future (this is motivation #1 for the proposed default
constructed semantics), having either a default constructed T or E
doesn't make sense in that scenario.
So it is more like a variant
I want to ask you, what would be the liabilities of an expected that is copyable?
That's a good question. When initially thinking about it, I was very deep into the semantics of asynchronous return objects, which use a common shared state which presents an entirely different problem. So I was always thinking in terms of: Does a copy share the result, like shared_future, or does it have to be unique? I guess this doesn't apply to expected and keeping the value semantics of the underlying types makes most sense. I guess there is nothing wrong with expected being copyable if T and E are copyable.
We don't have a problem returning be reference, why would we like to return by value?
Mainly to represent the fact that we have a return value from a function. There is always one return, if you want to alias it, bind it to a named variable.
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?
It's really just a different color. The preference to get is coming from my mental model of mine with expected being more like a asynchronous return object (future calls it get as well). There are other types (with similar purpose) using the name get for their accessor: shared_ptr, unique_ptr, tuple and variant.
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.
This state is the logical conclusion coming from the destructive value function. As a result, one could just as well reuse it for default construction. If there is consensus that value returns by reference, I would go for result not being default constructible and have never-empty guarantees.
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).
That's fine for me as well...
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...
That's a good starting point!
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
class expected; template <typename T> using result = expected ; template <typename T> using outcome = expected >; We will need to have a specialization of expected > as we have an index for variant. The revision 2 of the expected proposal talks of a expected . That is, given that we have either a value or unexpected, we can convert expected
to expected 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.
Cool!
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?
throw E; is certainly a nice default, but it really should be customisable. For example, for std::exception_ptr, you probably want to throw the underlying exception? What if you have exceptions disabled, do you want to give users the chance to implement whatever they want?
All optimizations can then easily be put as implementation details and the generic expected
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.
Those should be the next step once we have the underlying concepts ready. I was made aware of a very interesting experiment recently ... mainly to use the mechanisms defined in the coroutine TS for optional. Ignoring that co_await sounds awkward when dealing with PossiblyValued objects, it almost gives you everything you need to handle those objects with the nice syntax people usually refer to as "monadic".
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/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
I guess we are not fundamentally disagreeing. I would really like to see "monad" to vanish again from our vocabulary though, while it is certainly a nice underlying theory, C++ just misses too much to ever really define Monads properly without making fun itself (Type Categories?, Concepts are not really the same...).
Vicente
-- Thomas Heller Friedrich-Alexander-Universität Erlangen-Nürnberg Department Informatik - Lehrstuhl Rechnerarchitektur Martensstr. 3 91058 Erlangen Tel.: 09131/85-27018 Fax: 09131/85-27912 Email: thomas.heller@fau.de