Le 22/05/2017 à 22:33, Niall Douglas via Boost a écrit :
|* |is expected
the sum type of T, E and empty? and exception_ptr? expected 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 .
Okay, understood, even if I don't agree, at least for result<T> and option<T>. For outcome<T> I have my doubts, I don't know if the never-empty warranties can be implemented in an efficient way.
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).
Okay, I don't see yet if you could ensure non-empty warranties for outcome.
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.
I appreciate your popular words "masses", "ivory tower" and "weird design". You know well how and when to use them.
* 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.
Maybe you can point me to some blog, mail exchange, ...
Am I wrong on this?
Possibly. What is clear to you is is not forcedly to other.
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.
I don't know what you are talking of, but I will really be interested in an explanation of the mechanism. There is always something new on the STL if the author reaches to have consensus. Evidently the standard couldn't include what each one could consider as a best solution. We need a relative majority and not too much disagreement.
* what is the sizeof expected
? 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.
Where E is stored? What happens if sizeof(E)>sizeof(T)? I don't know yet what 24 and 8 stands for. Are 8 bytes use for the boolean?
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
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
will interact with std::experimental::expected or std::optional<T>, or something else that behaves like a monad error? Did you mean boost::outcome::experimental::expected ?
No evidently.
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.
So if the interaction is 3pp libraries is by defining conversion operators, why do we need Outcome. We could define already those operators without making the design more complex.
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
in Outcome, so each has a totally standalone and unrelated implementation, and each provides custom conversion mechanics into the others. I would do it as std::optional, std::expected are done now. If there is a common implementation, this is an implementation detail. Note that at the end all these types are sum types and as far as we have a never-empty variant implementation we could share a lot of code between these concrete classes. I haven't a never-empty warranty xxx::variant at hand, but once we have it the optional and expected implementation are almost evident. Conversion between monadic types is concern I have not study deeply, but in any case I will not use an intrusive approach as yours. I may be wrong, but each one should live with its incoherencies (coherency).
For example I'm proposing a ProductType concept to the standard. Each product type could define when it converts from any ProductType satisfying certain properties. This will simplify and extend the current tuple-like interfaces with less code and more software. I believe we could define a PossiblyValued type of classes and define conversion from them in a non intrusive way. Note that I used PossiblyValued and not MonadError as the required interfaces wil not be the same. A PossiblyValued value type that is close to Nullable, but where the not-a-value is considered an error and can have multiple instances. optional and expected and any the smart pointer I know will be PossiblyValued types. Let me know if you consider this design on the ivory tower and a weird design.
That would be the traditional way of implementing this. I chose not to follow that design in this particular case.
I've no problem as far as the common implementation is hidden and doesn't guide the concrete interface. I believe that generic programming must be built on top od concepts, not inside a intrusive class that know how to do everything.
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.
No problem, take your time.
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.
It is weird, that you say that it is a great proposal, but you found something that must be added, modified or removed. I would like to understand these differences, because surely the expected design must be improved when I would take into account *valid* use cases that I have not considered yet. Best, Vicente