2017-05-21 0:13 GMT+02:00 Niall Douglas via Boost
Similarly to the value returned case, Outcome does not involve itself into whether a T value returned is valid or not in exactly the same way as it does not involve itself into whether a E value returned is valid or not.
If you are saying "I just give you a tool for storing either T or error_code or std::exception_ptr or nothing, and I do not care how you use it", I can accept that. But the semantics of `o.error()` seem to contradict that a bit: as though you were in fact trying to workaround for potential mis-usages of your library.
I very much came at the default actions as a means of saving the programmer from typing boilerplate.
The specific actions I chose have been mostly the same since day one. I think there was one which turned out to be a mistake during the past two years of using the library in my code, I changed it. Otherwise they've been tested in the real world for a quite a while now.
Given what you already said about semantics of function `error()`, I consider the documentation of this function insufficient: https://ned14.github.io/boost.outcome/structboost_1_ 1outcome_1_1v1__xxx_1_1policy_1_1monad__policy__base.html# a6d5a06127d3ab8aa317635cfef1ada6a
Good point. Logged to https://github.com/ned14/boost.outcome/issues/26
(BTW, note that there is something wrong with the links. If I click on it, I do not get any more details for `error()` but instead get "Detailed Description" of boost::outcome::v1_xxx::policy::monad_policy_base)
It worked here fine.
Anyway, the short description of function `error()` says, "Returns any errored state in the transport, throwing an exception if empty."
1. I wish you didn't use this word "transport" as a noun. It always confuses me. Do you mean "either `option` or `resutl` or `outcome`"?
Transport is a noun meaning a device which conveys something.
2. "any errored state"? -- not the specific error state previously set inside `outcome`?
If we refer to state, we mean the variant in Outcomes.
3. It does not mention your algorithm: if `has_value() == true`, returns a value-initialized error code; if `has_exception() == true`, returns `error_type((int) monad_errc::exception_present, monad_category())`
4. "Throwing exception if empty" -- what exception?
All the above is covered in the tutorial, but I agree it needs to be in the reference docs too. It will be fixed.
I trust you that all these additional guarntees cost nothing at run-time. My concerns are not really about a potential run-time overhead, but about what is a correct usage of the library and what is a buggy usage. For instance, if you changed the semantics of function `error()` to:
Requires: `has_error() == true`. Returns: the error_code stored in `*this`.
This would make the understanding of the interface simple, it would clearly indicate when the users do something wrong, you could still implement your "rescue semantics", but I when I am doing the code review for my colleagues, I have something objective to rely on: "hey, you are breaking the precondition, you are extracting the error even though it is not there". Now, with the rescue semantics, I cannot say a word in the code review because the other programmer will respond, "But I learned the detailed rescue semantics, and I figured out it is exactly what I need." <-- the code does what the programmer intended, but is difficult to maintain, because it relies on the rescue semantics.
By "rescue semantics" I mean, "id you do not have an error_code to return, just fabricate one".
I get what you're saying. The fact there are no preconditions on .error() means that calling it is always a valid thing to do, and not by definition a bug nor a code smell.'
Yes, we agree here, as to current behavior of `o.error()`
This design is intentional. *If* you learn off the "rescue semantics" as you put them, then when writing code you swap tedious boilerplate for code which relies on those rescue semantics.
Yes, I think I understand what you are saying. If `o.error()` has a "narrow contract" or IOW, if it has a precondition, many users (but not all) would be forced to manually repeat the same unpacking code: ``` if (o.has_value()) use (error_code_extended{}); else if (o.has_exception()) use error_type((int) monad_errc::exception_present, monad_category()) if ... ```
If you feel that a woolly and imprecise step too far, then you can use expected<T> instead. It's why Outcome ships with expected<T>. Each of its observers come with strict preconditions governing whether the call is valid or undefined behaviour.
I probably could, but it sounds like `outcome<>` didn't have anything else to offer except the "wooly semantics", and I do not think it is the case. It is my understanding that `outcome<>` also offers other things: - being able to store either an error code or exception_ptr - other convenience interface without "contract widening", like BOOST_OUTCOME_TRY - other performance improvements If I want to use the above advantages, but I dislike "contract widening" (or "wooly semantics"), you leave me with no option. What you could do is to offer two observer functions: ``` o.error_wide(); // with wide contract o.error_narrow(); // with wide contract ``` Don't look at the choice of names, you can make them better, but the idea is you have two functions: one for people who prefer clearly stating intentions at the expense of longer code, the other for people that prefer concise notation. This is what `std::optional` and `boost::optional` are doing, you get both: ``` *o; // with narrow contract o.value(); // with wide contract ``` Or: ``` if (o == true) // for those who like short notation if (o && *o == true) // for those who like no ambiguity ``` My proposed names: - as_error() // for wide contract - error() // for narrow contract Regards, &rzej;