[outcome] Ternary logic -- need an example

Niall Douglas wrote:
Finally, if you did have some special E value to mean empty, you would have to write special checks for it in Outcome in order to give it the stronger abort type semantics it has - if you don't have it being given alternative semantics, then there is no point in having an empty state. If you are writing special checks, then you might as well just have a formal empty state in the first place.
This argument can be generalised into the argument in favour of ternary logics over binary logics. Sure, 90% of the time binary logics are sufficient, but binary is a subset of ternary. And *you don't have to use* the "other" state in a ternary logic just because it's there. Just don't use it, so long as its implementation signals its unintentional usage with a very loud bang, it's a safe design.
(There is an argument that Outcome's "very loud bang" isn't loud enough. I'd be pleased to hear feedback on that)
Niall, you mention "ternary logic" here, and I guess it has to do with 'module' `tribool` exposed by Outcome library. But I do not believe I have seen in the documentation (I mean the tutorial section) how I am supposed to use it, and how it improves my programs. Or I might have just missed it. Could you give me an example? Regards, &rzej;

This argument can be generalised into the argument in favour of ternary logics over binary logics. Sure, 90% of the time binary logics are sufficient, but binary is a subset of ternary. And *you don't have to use* the "other" state in a ternary logic just because it's there. Just don't use it, so long as its implementation signals its unintentional usage with a very loud bang, it's a safe design.
(There is an argument that Outcome's "very loud bang" isn't loud enough. I'd be pleased to hear feedback on that)
Niall, you mention "ternary logic" here, and I guess it has to do with 'module' `tribool` exposed by Outcome library. But I do not believe I have seen in the documentation (I mean the tutorial section) how I am supposed to use it, and how it improves my programs. Or I might have just missed it. Could you give me an example?
It doesn't have much to do with tribool. outcome<T> and result<T> deliberately take a three state based design with an explicit choice of ternary instead of binary state. The original motivation was actually for the basic_monad based, since removed, non-allocating future-promise implementation where we needed a third state to indicate "pending", but it turned out to be very useful in itself for simplifying usage, eliminating writing boilerplate, and acting as an out-of-band signalling mechanism. The debate over ternary vs binary is long standing since the very beginnings of computer science. This past C++ Now talk by the review manager does a far better job of explaining than I could: https://www.youtube.com/watch?v=gLJrOTFw6J0 Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-15 17:57 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
This argument can be generalised into the argument in favour of ternary logics over binary logics. Sure, 90% of the time binary logics are sufficient, but binary is a subset of ternary. And *you don't have to use* the "other" state in a ternary logic just because it's there. Just don't use it, so long as its implementation signals its unintentional usage with a very loud bang, it's a safe design.
(There is an argument that Outcome's "very loud bang" isn't loud enough. I'd be pleased to hear feedback on that)
Niall, you mention "ternary logic" here, and I guess it has to do with 'module' `tribool` exposed by Outcome library. But I do not believe I have seen in the documentation (I mean the tutorial section) how I am supposed to use it, and how it improves my programs. Or I might have just missed it. Could you give me an example?
It doesn't have much to do with tribool. outcome<T> and result<T> deliberately take a three state based design with an explicit choice of ternary instead of binary state. The original motivation was actually for the basic_monad based, since removed, non-allocating future-promise implementation where we needed a third state to indicate "pending", but it turned out to be very useful in itself for simplifying usage, eliminating writing boilerplate, and acting as an out-of-band signalling mechanism.
Ok, so by "ternary" you mean "eihter value, or error, or just nothing". But still, in the documentation we can see the "module" `boost_lite::xxx::tribool`: https://ned14.github.io/boost.outcome/group__tribool.html Does this document only an implementation detail? If so, maybe you should dropit from documentation, as it suggests that tribool is part of Outcome's interface. Or maybe it is part of Outcome's interface? Also, what do you understand by "module" here? Regards, &rzej;

It doesn't have much to do with tribool. outcome<T> and result<T> deliberately take a three state based design with an explicit choice of ternary instead of binary state. The original motivation was actually for the basic_monad based, since removed, non-allocating future-promise implementation where we needed a third state to indicate "pending", but it turned out to be very useful in itself for simplifying usage, eliminating writing boilerplate, and acting as an out-of-band signalling mechanism.
Ok, so by "ternary" you mean "eihter value, or error, or just nothing".
It's more than that. Originally before I wrote any code, I drew up the design on a whiteboard with the exact semantics for all possible permutations of all possible operations based on the three state logic. It was there that I decided that the valued state corresponds to ternary value TRUE, the errored state corresponds to FALSE, and the empty state to OTHER. Now that choice is quite controversial. Here on boost-dev we bikeshedded that choice for some weeks if I remember correctly. Many felt that empty should be FALSE, and errored should be OTHER. But I'm sticking to my guns on that, I still think my assignment is the right choice. The OTHER state gets treated as truly other, and it's why it gets the strongest abort semantics of any of the states. It is considered to be the most abnormal of the three possible states by the default actions by observer functions. I can already tell that some people will want me to make the assignment of the default actions customisable by policy class such that empty is not treated so seriously. The good news is that that is very easy to do for any end user (simply write a new policy class and go), but I have chosen to not document it in the docs. If reviewers feel that documenting how to make arbitrary custom editions of basic_monad (soon to become basic_outcome) is important and to be encouraged, I can do that.
But still, in the documentation we can see the "module" `boost_lite::xxx::tribool`: https://ned14.github.io/boost.outcome/group__tribool.html Does this document only an implementation detail? If so, maybe you should dropit from documentation, as it suggests that tribool is part of Outcome's interface. Or maybe it is part of Outcome's interface?
It is part of Outcome's interface, and hence why boost_lite::tribool is documented (I drag it in specially from the boost-lite docs). You'll find on every basic_monad there is: constexpr operator boost_lite::tribool::tribool () const noexcept; This lets you write: outcome<T> v; if(v == tribool::unknown) ...
Also, what do you understand by "module" here?
The choice of the word "module" is imposed by doxygen. For some very odd reason, it places documentation groups under a "Modules" heading. If anyone knows how to tell it to do otherwise, please let me know. Note that I depart for a business trip tomorrow and Wednesday. Expect no replies until Thursday. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 16/05/2017 09:42, Niall Douglas via Boost wrote:
I can already tell that some people will want me to make the assignment of the default actions customisable by policy class such that empty is not treated so seriously. The good news is that that is very easy to do for any end user (simply write a new policy class and go), but I have chosen to not document it in the docs. If reviewers feel that documenting how to make arbitrary custom editions of basic_monad (soon to become basic_outcome) is important and to be encouraged, I can do that.
Presumably people who want an empty state to not be treated as error-like could simply use boost::optional<X> for T. Although technically I suppose that's a different kind of empty state than the one you're talking about, but I suspect it's what people are really wanting when they say that. I don't know if it would be worthwhile to try to optimise storage for that case.

Gavin Lambert wrote:
I don't know if it would be worthwhile to try to optimise storage for that case. [result<optional<T>>]
An interesting point, because this is an argument in favor of returning by value from result<>::value() - I can synthesize an optional<T> on demand instead of storing one. (Return by reference mandates having an optional<> object stored.) Also applies to other possible combinations such as expected<expected<T, E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle be flattened into a single variant under the hood.

I don't know if it would be worthwhile to try to optimise storage for that case. [result<optional<T>>]
An interesting point, because this is an argument in favor of returning by value from result<>::value() - I can synthesize an optional<T> on demand instead of storing one. (Return by reference mandates having an optional<> object stored.)
I did consider returning optionals which might be empty instead of throwing an exception. Syntactically, the user must then do the check for state after the .value() if they want to avoid a throw, plus you now get ugly outcome.value().value() everywhere. I felt that undesirable.
Also applies to other possible combinations such as expected<expected<T, E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle be flattened into a single variant under the hood.
Earlier Expected proposals had all sorts of useful semantics for nested expected. They have been removed from the most recent proposal. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
I did consider returning optionals which might be empty instead of throwing an exception.
That's not what I was saying. I was saying that for result<optional<T>>, if you return by reference, you have to store an optional<T> object inside your result. But if you return by value, you don't have to store an optional and you can reuse your existing tag instead of adding the extra bool flag. That is, use variant<T, none, error_code> instead of variant<optional<T>, error_code>.
Also applies to other possible combinations such as expected<expected<T, E1>, E2>, ...
Similarly, expected<expected<T, E1>, E2> can use a single variant<T, E1, E2> for storage, if it can return by value. Return by reference requires it to have an actual expected<T, E1> object to which to bind the returned reference.

Le 18/05/2017 à 16:50, Peter Dimov via Boost a écrit :
Niall Douglas wrote:
I did consider returning optionals which might be empty instead of throwing an exception.
That's not what I was saying. I was saying that for result<optional<T>>, if you return by reference, you have to store an optional<T> object inside your result. But if you return by value, you don't have to store an optional and you can reuse your existing tag instead of adding the extra bool flag. That is, use variant<T, none, error_code> instead of variant<optional<T>, error_code>.
Also applies to other possible combinations such as expected<expected<T, > E1>, E2>, ...
Similarly, expected<expected<T, E1>, E2> can use a single variant<T, E1, E2> for storage, if it can return by value. Return by reference requires it to have an actual expected<T, E1> object to which to bind the returned reference. Do you believe that these function should return by value in the general case or just for the nested expected?
Best, Vicente

Le 18/05/2017 à 16:41, Niall Douglas via Boost a écrit :
Also applies to other possible combinations such as expected<expected<T, E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle be flattened into a single variant under the hood.
Earlier Expected proposals had all sorts of useful semantics for nested expected. They have been removed from the most recent proposal.
Niall, are you talking of my proposals? If yes, could you recall me what was there and was removed? Best, Vicente

2017-05-15 23:42 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
It doesn't have much to do with tribool. outcome<T> and result<T> deliberately take a three state based design with an explicit choice of ternary instead of binary state. The original motivation was actually for the basic_monad based, since removed, non-allocating future-promise implementation where we needed a third state to indicate "pending", but it turned out to be very useful in itself for simplifying usage, eliminating writing boilerplate, and acting as an out-of-band signalling mechanism.
Ok, so by "ternary" you mean "eihter value, or error, or just nothing".
It's more than that. Originally before I wrote any code, I drew up the design on a whiteboard with the exact semantics for all possible permutations of all possible operations based on the three state logic. It was there that I decided that the valued state corresponds to ternary value TRUE, the errored state corresponds to FALSE, and the empty state to OTHER.
Now that choice is quite controversial. Here on boost-dev we bikeshedded that choice for some weeks if I remember correctly. Many felt that empty should be FALSE, and errored should be OTHER. But I'm sticking to my guns on that, I still think my assignment is the right choice.
The OTHER state gets treated as truly other, and it's why it gets the strongest abort semantics of any of the states. It is considered to be the most abnormal of the three possible states by the default actions by observer functions.
The above explanation, treated in isolation, makes sense to me: empty is the most abnormal state. But in the other thread, you say: But the point being made (badly) in the example above was that you can
use the empty state to indicate lack of find, and any errored state for errors during find etc. So, let me rewrite the above now it's morning and I am not babbling incoherently:
outcome<Foo> found(empty); for(auto &i : container) { auto v = something(i); // returns a result<Foo> if(!v.empty()) // if not empty { found = std::move(v); // auto upconverts preserving error or Foo break; } } if(found.has_error()) { die(found.error()); } if(found) { Do something with found.value() ... }
This seams to be implying something opposite: that empty can can be treated as less abnormal than error. It is my impression that the system of types in Boost.Outcome confuses two meanings of "empty" option<T> -- either T or "empty" -- in this case "empty" looks like "just another state of T", as in boost::optional result<T> -- either T or an error or "empty" -- in this case it means "abnormally empty" Am I right? If so, maybe you need two separate states "simply no T" and "abnormal situation"? Regards, &rzej;

This seams to be implying something opposite: that empty can can be treated as less abnormal than error.
You're forgetting locality. Locally to the find loop, the valued or errored state returned by something() is the normal situation and you don't care which it returns. Locally to the find loop, failing to find what we are looking for is the abnormal situation. The programmer looking at the find loop won't be thinking in terms of errors or success of something(), but rather that the flow of execution in the find loop is correct or incorrect. I really should emphasise that this stuff, when used in practice, is nothing like as complicated as this thread of discussion is making it seem. Any programmer, even an undergrad, will look at that find loop and know exactly what it means and how it works intuitively.
It is my impression that the system of types in Boost.Outcome confuses two meanings of "empty"
option<T> -- either T or "empty" -- in this case "empty" looks like "just another state of T", as in boost::optional result<T> -- either T or an error or "empty" -- in this case it means "abnormally empty"
Am I right? If so, maybe you need two separate states "simply no T" and "abnormal situation"?
Empty is *defaulted* to the most abnormal state by Outcome's default semantics. So if you call .error() on a valued Outcome, you get back a default constructed error type, no exception thrown. Same goes for .exception(). But if you call .error() or .exception() on an empty Outcome, you *always* get an exception thrown. Therefore, if you write your Outcome using code without state checks before observation i.e. you call .error() without checking .has_error() beforehand, you get a stronger, more abortive default action than if the Outcome were errored, valued or excepted. You the programmer may wish to avoid the default actions, in which case you check state before access. You can then manually specify any other action you prefer. The "more abnormal" solely refers to my design choice of default semantics only for the empty state. Reviewers may think those choices misguided or plain wrong, and might suggest better default semantics. For example, some might feel that any time one tries to observe state where the state is different, you should always throw an exception. That would be a very conservative design choice and I'd disagree with it. But I would understand the rationale. I would also add that it is trivial to wrap an Outcome with replacement observer functions which change the default actions, or to customise the policy class to have different defaults. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-18 17:06 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
This seams to be implying something opposite: that empty can can be treated as less abnormal than error.
You're forgetting locality. Locally to the find loop, the valued or errored state returned by something() is the normal situation and you don't care which it returns. Locally to the find loop, failing to find what we are looking for is the abnormal situation.
The programmer looking at the find loop won't be thinking in terms of errors or success of something(), but rather that the flow of execution in the find loop is correct or incorrect.
I really should emphasise that this stuff, when used in practice, is nothing like as complicated as this thread of discussion is making it seem. Any programmer, even an undergrad, will look at that find loop and know exactly what it means and how it works intuitively.
Ok, I misunderstood your example (maybe because I am not an undergrad :), sorry. Indeed, it treate an empty state as the most abnormal.
It is my impression that the system of types in Boost.Outcome confuses two meanings of "empty"
option<T> -- either T or "empty" -- in this case "empty" looks like "just another state of T", as in boost::optional result<T> -- either T or an error or "empty" -- in this case it means "abnormally empty"
Am I right? If so, maybe you need two separate states "simply no T" and "abnormal situation"?
Now, my above observation is invalid.
Empty is *defaulted* to the most abnormal state by Outcome's default semantics. So if you call .error() on a valued Outcome, you get back a default constructed error type, no exception thrown. Same goes for .exception().
But if you call .error() or .exception() on an empty Outcome, you *always* get an exception thrown. Therefore, if you write your Outcome using code without state checks before observation i.e. you call .error() without checking .has_error() beforehand, you get a stronger, more abortive default action than if the Outcome were errored, valued or excepted.
You the programmer may wish to avoid the default actions, in which case you check state before access. You can then manually specify any other action you prefer.
The "more abnormal" solely refers to my design choice of default semantics only for the empty state. Reviewers may think those choices misguided or plain wrong, and might suggest better default semantics. For example, some might feel that any time one tries to observe state where the state is different, you should always throw an exception.
That would be a very conservative design choice and I'd disagree with it. But I would understand the rationale. I would also add that it is trivial to wrap an Outcome with replacement observer functions which change the default actions, or to customise the policy class to have different defaults.
My personal preference on this is that if you call `o.error()` before you have confirmed you actually have an error, you are doing something wrong. I would classify such situation as undefined behavior. But your choice fits into the scope of undefined behaviour: if program can do anything, it might as well return a default-constructed error_code. On the other hand, you do loose something when you chose something else than undefined behavior: the potential for such user bugs to be detected by static analyzers. But still, my initial concern remains somewhat un-addressed. I understand that .empty() is treated as the most ubnormal state. I accept your choice of throwing an exception upon a call to .exception(), I understand why someone might want to type: ``` if (o.has_value()) {} else if (o.has_error()) {} else if (o.has exception()) {} else {} ``` But I do not understand why in my program I would want to write: ``` if (o == tribool::unknown) {} ``` How is that better ina any way from `o.is_empty()`? Regards, &rzej;

My personal preference on this is that if you call `o.error()` before you have confirmed you actually have an error, you are doing something wrong. I would classify such situation as undefined behavior. But your choice fits into the scope of undefined behaviour: if program can do anything, it might as well return a default-constructed error_code.
Ah but remember that outcome<T> and result<T> both guarantee that E will be a type meeting the std::error_code contract. Therefore calling o.error() on a valued outcome returning a default constructed (null) error code is exactly correct: we return there is no error. No undefined behaviour needed, and you can write your code using Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
On the other hand, you do loose something when you chose something else than undefined behavior: the potential for such user bugs to be detected by static analyzers.
No static analyser that I am aware of can diagnose when someone is causing UB in std::optional<T>. Writing one which didn't spew false positives would be hard, even if the entire program were visible. After all, maybe the user is _intentionally_ doing the reinterpret_cast by effectively treating the common state as a union? Remember, expected<std::error_code, std::error_code> is legal, though Outcome's Expected doesn't allow it.
But I do not understand why in my program I would want to write:
``` if (o == tribool::unknown) {} ```
How is that better ina any way from `o.is_empty()`?
I don't want to talk about this much as it's outside the scope of the current review, but in functional programming using Outcome the tribool can help make the logic much clearer. A common pattern I've used personally is for true and false to select which branch of lambdas to call next, and empty means to terminate processing immediately with no further functions called. I should emphasise that those extensions are disabled in the presented library, and are out of scope for this review. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-19 0:49 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
My personal preference on this is that if you call `o.error()` before you have confirmed you actually have an error, you are doing something wrong. I would classify such situation as undefined behavior. But your choice fits into the scope of undefined behaviour: if program can do anything, it might as well return a default-constructed error_code.
Ah but remember that outcome<T> and result<T> both guarantee that E will be a type meeting the std::error_code contract.
Ok, I remember, both error_code and exception_ptr are default-constructible. It is not immediately clear to me that a default-constructed error_code represents a no-error condition. In the blog post by Chris Kohlhoff you refer to, he uses the following enum for representing http error conditions: ``` enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 }; ``` Which implies that numeric value 200 means no-error and a value initialized `http_error` is meaningless. Maybe this does not affect the value of a default-constructed `std::error_code`, but surely it adds to the confusion.
Therefore calling o.error() on a valued outcome returning a default constructed (null) error code is exactly correct: we return there is no error.
Only under some definition of "correct". By correct, you probably mean "not invoking UB" and "being compliant with your specification", but it is not intuitive at all that a function should return this or that when invoked in a context that is most likely a programmer's bug.
No undefined behaviour needed, and you can write your code using Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
Modulo this situation with `http_error::ok == 200`. But with this you are also saying, the library provides two ways for checking if you have an error: o.has_error(); // option 1 o.error() == std::error_code{}; // option 2 And by describing clear semantics for option 2, you are saying, it is equally fine to use option 2 for checking if we have an error. This encourages the usage of option 2, but I would not want my colleague programmers to start using this syntax, because I then cannot tell proper usages from inadvertent omissions. And reading the value of `error()` without having confirmed that some error occurred is almost surely a bug, even if you can assign a well defined semantics in `boost::outcome` for it.
On the other hand, you do loose something when you chose something else than undefined behavior: the potential for such user bugs to be detected by static analyzers.
No static analyser that I am aware of can diagnose when someone is causing UB in std::optional<T>. Writing one which didn't spew false positives would be hard, even if the entire program were visible. After all, maybe the user is _intentionally_ doing the reinterpret_cast by effectively treating the common state as a union?
Challenge accepted.
Remember, expected<std::error_code, std::error_code> is legal, though Outcome's Expected doesn't allow it.
But I do not understand why in my program I would want to write:
``` if (o == tribool::unknown) {} ```
How is that better ina any way from `o.is_empty()`?
I don't want to talk about this much as it's outside the scope of the current review, but in functional programming using Outcome the tribool can help make the logic much clearer. A common pattern I've used personally is for true and false to select which branch of lambdas to call next, and empty means to terminate processing immediately with no further functions called.
I should emphasise that those extensions are disabled in the presented library, and are out of scope for this review.
I am fine with this scope disabled. However, `tribool` is still in scope, so I consider it valid to ask about its usefulness (especially given that all contexts in which it would prove useful is disabled). Maybe `tribool` should be also removed from the scope? Regards, &rzej;

Ok, I remember, both error_code and exception_ptr are default-constructible. It is not immediately clear to me that a default-constructed error_code represents a no-error condition.
A null error code is widely held to mean "no error". Ditto with exception_ptr. In any ASIO completion handler, you'll see: void asio_handler(const error_code &ec, ...) { if(ec) handle_error(); ... }
In the blog post by Chris Kohlhoff you refer to, he uses the following enum for representing http error conditions:
``` enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 }; ``` Which implies that numeric value 200 means no-error and a value initialized `http_error` is meaningless. Maybe this does not affect the value of a default-constructed `std::error_code`, but surely it adds to the confusion.
I have absolutely no idea why Chris chose "http_error" for that enum. It should have been "http_status" because those are the HTTP status codes as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes. I am also highly unsure why you'd choose the error_code infrastructure for these. It's not like almost all of the HTTP status codes (e.g. 306 Switch Proxy) can be any error condition except resource_temporarily_unavailable. Vinnie, what does Beast do?
Therefore calling o.error() on a valued outcome returning a default constructed (null) error code is exactly correct: we return there is no error.
Only under some definition of "correct". By correct, you probably mean "not invoking UB" and "being compliant with your specification", but it is not intuitive at all that a function should return this or that when invoked in a context that is most likely a programmer's bug.
No, really a null error code meaning "no error here" is the widely held interpretation. The reason I cannot categorically says it means no error here is because the C++ standard guarantees that a null error code has value 0 and the **system** category. And that's a defect, I think they meant value 0 and the *generic* category, because if they had then the standard would categorically guarantee a null error code means no error here. As it currently stands, the standard accidentally has made a null error code mean "system dependent behaviour" which if it didn't mean "no error here" would fundamentally wreck the Networking TS and the Filesystem TS. Yay.
No undefined behaviour needed, and you can write your code using Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
Modulo this situation with `http_error::ok == 200`. But with this you are also saying, the library provides two ways for checking if you have an error:
o.has_error(); // option 1
This returns whether the outcome contains an error_code. Not whether there is an error. A program might return an outcome containing a null error_code. It probably is a bug, but Outcome can't reasonably enforce how people misuse error_code, not least because of the C++ standard defect above.
o.error() == std::error_code{}; // option 2
Actually "!o.error()" but I think you meant to write that anyway. This would be preferred over option 1. If the function returned an empty outcome, this would throw, but that is probably a good thing.
And by describing clear semantics for option 2, you are saying, it is equally fine to use option 2 for checking if we have an error. This encourages the usage of option 2, but I would not want my colleague programmers to start using this syntax, because I then cannot tell proper usages from inadvertent omissions. And reading the value of `error()` without having confirmed that some error occurred is almost surely a bug, even if you can assign a well defined semantics in `boost::outcome` for it.
Option 1 is misleading. Option 2 is correct, and means typing less boilerplate e.g. if(o.error()) ... instead of if(o.has_error() && o.error()) ...
I am fine with this scope disabled. However, `tribool` is still in scope, so I consider it valid to ask about its usefulness (especially given that all contexts in which it would prove useful is disabled). Maybe `tribool` should be also removed from the scope?
I believe you've convinced me to move tribool to under the advanced operations macro. Thanks. Logged to https://github.com/ned14/boost.outcome/issues/22 Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On Fri, May 19, 2017 at 6:42 AM, Niall Douglas via Boost <boost@lists.boost.org> wrote:
... I am also highly unsure why you'd choose the error_code infrastructure for these.
I agree, error_code for http status makes no sense at all for all the reasons listed.
... Vinnie, what does Beast do?
This is the extent of it: https://github.com/vinniefalco/Beast/blob/76f1084ecb010596d080ea96b9e47d15da... Thanks

2017-05-19 15:42 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
Ok, I remember, both error_code and exception_ptr are default-constructible. It is not immediately clear to me that a default-constructed error_code represents a no-error condition.
A null error code is widely held to mean "no error". Ditto with exception_ptr. In any ASIO completion handler, you'll see:
void asio_handler(const error_code &ec, ...) { if(ec) handle_error(); ... }
In the blog post by Chris Kohlhoff you refer to, he uses the following enum for representing http error conditions:
``` enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 }; ``` Which implies that numeric value 200 means no-error and a value initialized `http_error` is meaningless. Maybe this does not affect the value of a default-constructed `std::error_code`, but surely it adds to the confusion.
I have absolutely no idea why Chris chose "http_error" for that enum. It should have been "http_status" because those are the HTTP status codes as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
I am also highly unsure why you'd choose the error_code infrastructure for these. It's not like almost all of the HTTP status codes (e.g. 306 Switch Proxy) can be any error condition except resource_temporarily_unavailable.
Vinnie, what does Beast do?
Therefore calling o.error() on a valued outcome returning a default constructed (null) error code is exactly correct: we return there is no error.
Only under some definition of "correct". By correct, you probably mean "not invoking UB" and "being compliant with your specification", but it is not intuitive at all that a function should return this or that when invoked in a context that is most likely a programmer's bug.
No, really a null error code meaning "no error here" is the widely held interpretation. The reason I cannot categorically says it means no error here is because the C++ standard guarantees that a null error code has value 0 and the **system** category. And that's a defect, I think they meant value 0 and the *generic* category, because if they had then the standard would categorically guarantee a null error code means no error here.
As it currently stands, the standard accidentally has made a null error code mean "system dependent behaviour" which if it didn't mean "no error here" would fundamentally wreck the Networking TS and the Filesystem TS. Yay.
No undefined behaviour needed, and you can write your code using Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
Modulo this situation with `http_error::ok == 200`. But with this you are also saying, the library provides two ways for checking if you have an error:
o.has_error(); // option 1
This returns whether the outcome contains an error_code. Not whether there is an error.
Oh..
A program might return an outcome containing a null error_code. It probably is a bug,
Interesting. I need to think about it. But my first response would be. If a user cheats the system in this way, you should not try to rescue the situation.
but Outcome can't reasonably enforce how people misuse error_code, not least because of the C++ standard defect above.
o.error() == std::error_code{}; // option 2
Actually "!o.error()" but I think you meant to write that anyway.
This would be preferred over option 1. If the function returned an empty outcome, this would throw, but that is probably a good thing.
If I take your library, and also apply the "Sea of noexcept" approach, this means that if I call `if(o.error())` to check if I have an error, I might be triggering `std::terminate()`. Scary, but maybe this is ok, it you treat empty outcome as a really abnormal state.

If I take your library, and also apply the "Sea of noexcept" approach, this means that if I call `if(o.error())` to check if I have an error, I might be triggering `std::terminate()`. Scary, but maybe this is ok, it you treat empty outcome as a really abnormal state.
As with all software libraries, you have to assume that the end user has read the documentation and has some understanding of the standard library. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-19 16:03 GMT+02:00 Andrzej Krzemienski <akrzemi1@gmail.com>:
2017-05-19 15:42 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org> :
Ok, I remember, both error_code and exception_ptr are default-constructible. It is not immediately clear to me that a default-constructed error_code represents a no-error condition.
A null error code is widely held to mean "no error". Ditto with exception_ptr. In any ASIO completion handler, you'll see:
void asio_handler(const error_code &ec, ...) { if(ec) handle_error(); ... }
In the blog post by Chris Kohlhoff you refer to, he uses the following enum for representing http error conditions:
``` enum class http_error { continue_request = 100, switching_protocols = 101, ok = 200, ... gateway_timeout = 504, version_not_supported = 505 }; ``` Which implies that numeric value 200 means no-error and a value initialized `http_error` is meaningless. Maybe this does not affect the value of a default-constructed `std::error_code`, but surely it adds to the confusion.
I have absolutely no idea why Chris chose "http_error" for that enum. It should have been "http_status" because those are the HTTP status codes as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
I am also highly unsure why you'd choose the error_code infrastructure for these. It's not like almost all of the HTTP status codes (e.g. 306 Switch Proxy) can be any error condition except resource_temporarily_unavailable.
Vinnie, what does Beast do?
Therefore calling o.error() on a valued outcome returning a default constructed (null) error code is exactly correct: we return there is no error.
Only under some definition of "correct". By correct, you probably mean "not invoking UB" and "being compliant with your specification", but it is not intuitive at all that a function should return this or that when invoked in a context that is most likely a programmer's bug.
No, really a null error code meaning "no error here" is the widely held interpretation. The reason I cannot categorically says it means no error here is because the C++ standard guarantees that a null error code has value 0 and the **system** category. And that's a defect, I think they meant value 0 and the *generic* category, because if they had then the standard would categorically guarantee a null error code means no error here.
As it currently stands, the standard accidentally has made a null error code mean "system dependent behaviour" which if it didn't mean "no error here" would fundamentally wreck the Networking TS and the Filesystem TS. Yay.
No undefined behaviour needed, and you can write your code using Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
Modulo this situation with `http_error::ok == 200`. But with this you are also saying, the library provides two ways for checking if you have an error:
o.has_error(); // option 1
This returns whether the outcome contains an error_code. Not whether there is an error.
Oh..
A program might return an outcome containing a null error_code. It probably is a bug,
Interesting. I need to think about it. But my first response would be. If a user cheats the system in this way, you should not try to rescue the situation.
I can think of two ways of looking at the situation with a value-initialized error code: 1. Class outocme<T> has an invariant: putting it into state where it stores a value-initialized error code is a bug, close to undefined behavior, which means, you can assert() inside and users and algorithms can expect it never happens. 2. Class outcome<T> can be in yet another state: "strange". So we have the following states: valued, exceptional, errored, empty, and strange. And the user when inspecting the state has to take into account all the five possible states. I guess the same applies to a null exception_ptr. My preference would be #1, so that working with `outcome` is managable. Regards, &rzej;

A program might return an outcome containing a null error_code. It probably is a bug,
Interesting. I need to think about it. But my first response would be. If a user cheats the system in this way, you should not try to rescue the situation.
I can think of two ways of looking at the situation with a value-initialized error code:
1. Class outocme<T> has an invariant: putting it into state where it stores a value-initialized error code is a bug, close to undefined behavior, which means, you can assert() inside and users and algorithms can expect it never happens.
I am afraid I don't understand. A value initialised error code is surely a normal error code? Why would I need to assert stuff?
2. Class outcome<T> can be in yet another state: "strange". So we have the following states: valued, exceptional, errored, empty, and strange. And the user when inspecting the state has to take into account all the five possible states.
Every additional possible state generates more branches for the compiler's optimiser to deal with. I would therefore reject any additional possible states. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-20 0:03 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
A program might return an outcome containing a null error_code. It probably is a bug,
Interesting. I need to think about it. But my first response would be. If a user cheats the system in this way, you should not try to rescue the situation.
I can think of two ways of looking at the situation with a value-initialized error code:
1. Class outocme<T> has an invariant: putting it into state where it stores a value-initialized error code is a bug, close to undefined behavior, which means, you can assert() inside and users and algorithms can expect it never happens.
I am afraid I don't understand. A value initialised error code is surely a normal error code? Why would I need to assert stuff?
2. Class outcome<T> can be in yet another state: "strange". So we have the following states: valued, exceptional, errored, empty, and strange. And the user when inspecting the state has to take into account all the five possible states.
Every additional possible state generates more branches for the compiler's optimiser to deal with. I would therefore reject any additional possible states.
I am not trying to propose any new logic here. I am trying to assign some mental model to what `outcome<T>` currently does. Consider: some funciton `f` is supposed to compute an `int`. But the computation may fail, in this case I want the function to return the reason why it failed. Also, it is possible that I will get some "internal error". For this reason funcio `f` returns `result<T>`: ``` result<T> r = f(); ``` Now, `r` can represent the following states: ``` if (r.has_value()) // value successfully computed {} else if (r.empty()) // internal error {} else if (r.error()) // reason why f failed {} else if (r.has_error() && !r.error()) // what is that??? {} ``` The last case is neither a value, nor a reason for failure, nor an "internal error". How should a caller interpret it? Two possible answers: 1. There is a fourth state with no intuitive or sane interpretation. 2. Assume this never happens. If it happens it is a bug in calle site that needs to be fixed. Regards, &rzej;

I am not trying to propose any new logic here. I am trying to assign some mental model to what `outcome<T>` currently does. Consider: some funciton `f` is supposed to compute an `int`. But the computation may fail, in this case I want the function to return the reason why it failed. Also, it is possible that I will get some "internal error". For this reason funcio `f` returns `result<T>`:
``` result<T> r = f(); ``` Now, `r` can represent the following states:
``` if (r.has_value()) // value successfully computed {}
No, the correct interpretation here is that the result returned has a value, not that the value was successfully computed.
else if (r.empty()) // internal error {} else if (r.error()) // reason why f failed {} else if (r.has_error() && !r.error()) // what is that??? {}
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.
The last case is neither a value, nor a reason for failure, nor an "internal error". How should a caller interpret it? Two possible answers:
1. There is a fourth state with no intuitive or sane interpretation. 2. Assume this never happens. If it happens it is a bug in calle site that needs to be fixed.
You've got to remember all these potential state flow paths never occur in any real world code base. You'll even see the compiler eliding in the assembler output any handling of an empty state being returned if it can see that that is not possible. Same goes for if you return only ever a value. The result<T> effectively "disappears" in the assembler generated, and it is as if you returned a T directly. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-20 20:01 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
I am not trying to propose any new logic here. I am trying to assign some mental model to what `outcome<T>` currently does. Consider: some funciton `f` is supposed to compute an `int`. But the computation may fail, in this case I want the function to return the reason why it failed. Also, it is possible that I will get some "internal error". For this reason funcio `f` returns `result<T>`:
``` result<T> r = f(); ``` Now, `r` can represent the following states:
``` if (r.has_value()) // value successfully computed {}
No, the correct interpretation here is that the result returned has a value, not that the value was successfully computed.
else if (r.empty()) // internal error {} else if (r.error()) // reason why f failed {} else if (r.has_error() && !r.error()) // what is that??? {}
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. 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_1p... (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) 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`"? 2. "any errored state"? -- not the specific error state previously set inside `outcome`? 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?
The last case is neither a value, nor a reason for failure, nor an "internal error". How should a caller interpret it? Two possible answers:
1. There is a fourth state with no intuitive or sane interpretation. 2. Assume this never happens. If it happens it is a bug in calle site that needs to be fixed.
You've got to remember all these potential state flow paths never occur in any real world code base. You'll even see the compiler eliding in the assembler output any handling of an empty state being returned if it can see that that is not possible.
Same goes for if you return only ever a value. The result<T> effectively "disappears" in the assembler generated, and it is as if you returned a T directly.
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". Regards, &rzej;

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_1p...
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. 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. 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. In the end it's up to each end user and their use case. If an end user does a lot of code correctness auditing and peer review, depending on that team's style they may prefer the boilerplate to always be spelled out for easier auditing. Or they may prefer tighter, denser code so more logic can be reviewed per screenful at once. It really does depend on the team, the code base, and what you're looking for. As I've mentioned in other threads, Outcome really is my only library where I give people - including myself - a choice in what style or use pattern or convention to use. Because every team, code base and company is different. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Anyway, the short description of function `error()` says, "Returns any errored state in the transport, throwing an exception if empty."
Incidentally, "errored" and "valued" don't make sense to me in this context. It's not an errored result or a valued result, nor an errored outcome or a valued outcome, it's simply an error result or a value result, an error outcome or a value outcome. (The result or the outcome can still be highly valued by the user, of course.) (Exceptional outcome looks correct.)

2017-05-21 0:13 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
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;

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 ...
```
Correct. The default actions aim to do a useful action on use, thus allowing the programmer to skip manual checks. They can of course still manually check if they wish to override the default action.
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.
I would say that is exactly the case.
It is my understanding that `outcome<>` also offers other things:
- being able to store either an error code or exception_ptr
Yes outcome<T> can store all four states: empty, T, EC or E.
- other convenience interface without "contract widening", like BOOST_OUTCOME_TRY
BOOST_OUTCOME_TRY works perfectly with expected<T, E> too. And all basic_monad flavours.
- other performance improvements
There are no performance improvements. These are the actual implementations of the public "classes" provided by Outcome: template<class T, class E = std::error_code> using expected = basic_monad<policies::expected_policy<T, E>>; template<class T> using outcome = basic_monad<policies::monad_policy<T, error_code_extended, std::exception_ptr>>; template<class T> using result = basic_monad<policies::monad_policy<T, error_code_extended, void>>; template<class T> using option = basic_monad<policies::monad_policy<T, void, void>>; In other words, all four are the exact same implementation, identical in every way. They just have different "personality". This is why it's result<T> and not result<T, E = error_code_extended>. The design premise is that someone wanting a result<T> not using error_code_extended simply template aliased their own custom result<T> type. I originally expected that AFIO v2 would create its own custom but extended result<T> called io_result<T> reusing the above framework. But it turned out to be overkill for what AFIO needed, so AFIO v2 actually typedefs result<T> directly into io_result<T> and it works well.
If I want to use the above advantages, but I dislike "contract widening" (or "wooly semantics"), you leave me with no option.
You can use expected<T, E> if you want narrow contracts. Or you can roll your own custom implementations! The policy class infrastructure is very straightforward and very easily adapted into any mix you like. Storage is also policy driven, so if you feel like using a std::variant or malloced memory or mutex locked state, that's fine too.
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
It would fit much better with the design of Outcome if these were new typedefs of basic_monad. How about these for the narrow contract editions of outcome<T>, result<T> and option<T>: - outcome_u<T> - result_u<T> - option_u<T> Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-22 22:52 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
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 ...
```
Correct. The default actions aim to do a useful action on use, thus allowing the programmer to skip manual checks. They can of course still manually check if they wish to override the default action.
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.
I would say that is exactly the case.
It is my understanding that `outcome<>` also offers other things:
- being able to store either an error code or exception_ptr
Yes outcome<T> can store all four states: empty, T, EC or E.
- other convenience interface without "contract widening", like BOOST_OUTCOME_TRY
BOOST_OUTCOME_TRY works perfectly with expected<T, E> too. And all basic_monad flavours.
- other performance improvements
There are no performance improvements. These are the actual implementations of the public "classes" provided by Outcome:
template<class T, class E = std::error_code> using expected = basic_monad<policies::expected_policy<T, E>>;
template<class T> using outcome = basic_monad<policies::monad_policy<T, error_code_extended, std::exception_ptr>>;
template<class T> using result = basic_monad<policies::monad_policy<T, error_code_extended, void>>;
template<class T> using option = basic_monad<policies::monad_policy<T, void, void>>;
In other words, all four are the exact same implementation, identical in every way. They just have different "personality".
This is why it's result<T> and not result<T, E = error_code_extended>. The design premise is that someone wanting a result<T> not using error_code_extended simply template aliased their own custom result<T> type.
I originally expected that AFIO v2 would create its own custom but extended result<T> called io_result<T> reusing the above framework. But it turned out to be overkill for what AFIO needed, so AFIO v2 actually typedefs result<T> directly into io_result<T> and it works well.
If I want to use the above advantages, but I dislike "contract widening" (or "wooly semantics"), you leave me with no option.
You can use expected<T, E> if you want narrow contracts.
Or you can roll your own custom implementations! The policy class infrastructure is very straightforward and very easily adapted into any mix you like.
Storage is also policy driven, so if you feel like using a std::variant or malloced memory or mutex locked state, that's fine too.
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
It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
Ok, so are you saying that `basic_monad` (by now probably something like `outcome_base`) is part of this library's public API? But the documentation leaves me with little information as to how I can use it. Unless I missed it, I recommend that you provide a guide in the docs how one can compose one's own type, and a mention that it would blend nicely with other outcome-like objects, e.g., that BOOST_OUTCOME_TRY will still work for a custom outcome. Also, the reference seems to be missing some information. If I go to `basic_monad`: https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1ba... The first thing that interests me: this is a template parametrized by `implementation_policy`. What constraints does a type `implementation_policy` need to satisfy to be a valid policy and meet this library's requirements? For sure, it cannot be just any type, like `int`. What I am missing here is the concept (not in the sense of concepts Lite, but the description of requirements as in here: http://www.sgi.com/tech/stl/ForwardIterator.html). Regards, &rzej;

It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
Ok, so are you saying that `basic_monad` (by now probably something like `outcome_base`) is part of this library's public API?
I'm saying that it *might* be, if reviewers think it should be so.
But the documentation leaves me with little information as to how I can use it. Unless I missed it, I recommend that you provide a guide in the docs how one can compose one's own type, and a mention that it would blend nicely with other outcome-like objects, e.g., that BOOST_OUTCOME_TRY will still work for a custom outcome.
I did have documentation on how to do that, but one of the rounds of Reddit review eliminated it because it assumed too much of the average end user. Their argument was that 95% of end users don't want to know that stuff, and keeping such documentation was a major turn off for potential end users. There was also a valid argument by them that if you are in the 5% who do want to know, you'd end up studying the (well commented) source code anyway, and anything in the public docs is likely redundant.
Also, the reference seems to be missing some information. If I go to `basic_monad`: https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1ba...
The first thing that interests me: this is a template parametrized by `implementation_policy`. What constraints does a type `implementation_policy` need to satisfy to be a valid policy and meet this library's requirements? For sure, it cannot be just any type, like `int`.
Here is the policy class for expected<T, E> personality: https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1.... Would you say it fairly self explanatory for those knowledgeable of CRTP? The storage policy class can be found at https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1..... It essentially wraps a "value_storage" which is the variant storage. That is a standalone implementation found at https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1..... Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-24 16:38 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
Ok, so are you saying that `basic_monad` (by now probably something like `outcome_base`) is part of this library's public API?
I'm saying that it *might* be, if reviewers think it should be so.
But the documentation leaves me with little information as to how I can use it. Unless I missed it, I recommend that you provide a guide in the docs how one can compose one's own type, and a mention that it would blend nicely with other outcome-like objects, e.g., that BOOST_OUTCOME_TRY will still work for a custom outcome.
I did have documentation on how to do that, but one of the rounds of Reddit review eliminated it because it assumed too much of the average end user. Their argument was that 95% of end users don't want to know that stuff, and keeping such documentation was a major turn off for potential end users.
There was also a valid argument by them that if you are in the 5% who do want to know, you'd end up studying the (well commented) source code anyway, and anything in the public docs is likely redundant.
Also, the reference seems to be missing some information. If I go to `basic_monad`: https://ned14.github.io/boost.outcome/classboost_1_1outcome_ 1_1v1__xxx_1_1basic__monad.html
The first thing that interests me: this is a template parametrized by `implementation_policy`. What constraints does a type `implementation_policy` need to satisfy to be a valid policy and meet this library's requirements? For sure, it cannot be just any type, like `int`.
Here is the policy class for expected<T, E> personality: https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/detail/expected_policy.ipp#L407
Would you say it fairly self explanatory for those knowledgeable of CRTP?
The storage policy class can be found at https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/monad.hpp#L553. It essentially wraps a "value_storage" which is the variant storage. That is a standalone implementation found at https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/value_storage.hpp.
I am not entirely satisfied with this reply. I need this documented not because I do not know how to do it (well, this also), but because this way you commit to supporting this form of customization in subsequent releases. Sure, I can peek into the code, but I do not know if it is something you intend to keep in the new releases, or if it is just an implementation detail, or even a bug, which you will "fix" in the next release. I am also not sure I belong to this 5%. I complained about the wide contracts, and your response was I should write my own customization. I read this as saying it is relatively easy to customize. But now I am hearing I need to look into the source files and copy that. But this is like giving me a number of concrete models of a concept and expecting of me to synthesize the concept. I cannot do it correctly, because there is a number of ways to do that. I may arrive at one that happens to work in this release, but will stop in the next. On the other hand, I understand your position. First invest in the parts that bring value to 95% of users, and only then proceed to the remaining problems. Regards, &rzej;

2017-05-24 16:38 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
Ok, so are you saying that `basic_monad` (by now probably something like `outcome_base`) is part of this library's public API? I'm saying that it *might* be, if reviewers think it should be so.
But the documentation leaves me with little information as to how I can use it. Unless I missed it, I recommend that you provide a guide in the docs how one can compose one's own type, and a mention that it would blend nicely with other outcome-like objects, e.g., that BOOST_OUTCOME_TRY will still work for a custom outcome. I did have documentation on how to do that, but one of the rounds of Reddit review eliminated it because it assumed too much of the average end user. Their argument was that 95% of end users don't want to know that stuff, and keeping such documentation was a major turn off for potential end users.
There was also a valid argument by them that if you are in the 5% who do want to know, you'd end up studying the (well commented) source code anyway, and anything in the public docs is likely redundant.
Also, the reference seems to be missing some information. If I go to `basic_monad`: https://ned14.github.io/boost.outcome/classboost_1_1outcome_ 1_1v1__xxx_1_1basic__monad.html The first thing that interests me: this is a template parametrized by `implementation_policy`. What constraints does a type `implementation_policy` need to satisfy to be a valid policy and meet this library's requirements? For sure, it cannot be just any type, like `int`. Here is the policy class for expected<T, E> personality: https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/detail/expected_policy.ipp#L407
Would you say it fairly self explanatory for those knowledgeable of CRTP?
The storage policy class can be found at https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/monad.hpp#L553. It essentially wraps a "value_storage" which is the variant storage. That is a standalone implementation found at https://github.com/ned14/boost.outcome/blob/master/ include/boost/outcome/v1.0/value_storage.hpp.
I am not entirely satisfied with this reply. I need this documented not because I do not know how to do it (well, this also), but because this way you commit to supporting this form of customization in subsequent releases. Sure, I can peek into the code, but I do not know if it is something you intend to keep in the new releases, or if it is just an implementation detail, or even a bug, which you will "fix" in the next release. +1
I am also not sure I belong to this 5%. I complained about the wide contracts, and your response was I should write my own customization. I read this as saying it is relatively easy to customize. But now I am hearing I need to look into the source files and copy that. But this is like giving me a number of concrete models of a concept and expecting of me to synthesize the concept. I cannot do it correctly, because there is a number of ways to do that. I may arrive at one that happens to work in this release, but will stop in the next. If customizing is simple, it should be simple to explain it on an Advanced Extension section. If it is complex, maybe we don't want it. On the other hand, I understand your position. First invest in the parts that bring value to 95% of users, and only then proceed to the remaining problems. This mean that base_outcome is not part of the interface and only when
Le 24/05/2017 à 17:03, Andrzej Krzemienski via Boost a écrit : the customization is public we can look at it. BR, Vicente

I am not entirely satisfied with this reply. I need this documented not because I do not know how to do it (well, this also), but because this way you commit to supporting this form of customization in subsequent releases.
Ah but I haven't, nor was I intending to. Earlier rounds of feedback from Reddit convinced me that customising basic_monad is a very niche enterprise. Very, very few users of Outcome will want to do that. Furthermore, a big aim of this review was to find out what other precooked varieties of basic_monad would be preferable so we can expand the family of precooked implementations. That would further reduce the need for anyone to dive into writing policy classes for basic_monad.
I am also not sure I belong to this 5%. I complained about the wide contracts, and your response was I should write my own customization.
That was *not* my response. My response was to go use expected<T, E> if you want narrow contracts. Now, the way it looks like consensus opinion is going is that most reviewers want a really long template with lots of template parameters twiddling all sorts of knobs like what default construction should do, wide vs narrow observer functions, empty state to be formal, nearly-never or never and so on. End users then template alias the Outcome long template into a local edition configured as they want with whatever local name they want. If that's the consensus opinion, that's a fair bit of work to implement and I'm fairly sure it will affect compile times, but I am open to it. The single implementation-many personality design I chose is very amenable to such knobs for twiddling. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-24 22:04 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
I am not entirely satisfied with this reply. I need this documented not because I do not know how to do it (well, this also), but because this way you commit to supporting this form of customization in subsequent releases.
Ah but I haven't, nor was I intending to.
Ok.
Earlier rounds of feedback from Reddit convinced me that customising basic_monad is a very niche enterprise. Very, very few users of Outcome will want to do that.
I agree with this assumption. But if this is the case, I think you should not expose in the reference section of the documentation that outcome<> has a base class. As per your explanation, it is just an implementation detail. I am referring to this page: https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1ou...
Furthermore, a big aim of this review was to find out what other precooked varieties of basic_monad would be preferable so we can expand the family of precooked implementations. That would further reduce the need for anyone to dive into writing policy classes for basic_monad.
I am also not sure I belong to this 5%. I complained about the wide contracts, and your response was I should write my own customization.
That was *not* my response.
My response was to go use expected<T, E> if you want narrow contracts.
Yes, you did suggest ti use expected instead. Then you also wrote:
- as_error() // for wide contract
- error() // for narrow contract
It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
But I mistakenly took it as saying "do it yourself". You were proposing more of precooked "transports". Sorry. Regarding your suggestion to use `expected<>`, I could use `expected<T, error_code_extended>` and this would get me close to `result<T>`, but I get no counterpart for `outcome<T>`. Or are you really proposing to add `outcome_u<T>` to collection?
Now, the way it looks like consensus opinion is going is that most reviewers want a really long template with lots of template parameters twiddling all sorts of knobs like what default construction should do, wide vs narrow observer functions, empty state to be formal, nearly-never or never and so on. End users then template alias the Outcome long template into a local edition configured as they want with whatever local name they want.
If that's the consensus opinion, that's a fair bit of work to implement and I'm fairly sure it will affect compile times, but I am open to it. The single implementation-many personality design I chose is very amenable to such knobs for twiddling.
Niall
-- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost

Earlier rounds of feedback from Reddit convinced me that customising basic_monad is a very niche enterprise. Very, very few users of Outcome will want to do that.
I agree with this assumption. But if this is the case, I think you should not expose in the reference section of the documentation that outcome<> has a base class. As per your explanation, it is just an implementation detail. I am referring to this page: https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1ou...
I didn't want to be accused of misrepresenting the reference documentation. Actually, I am surprised nobody has yet accused me of doing so as the doxygen reference docs refer to classes which don't exist as classes e.g. outcome, result, option.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
But I mistakenly took it as saying "do it yourself". You were proposing more of precooked "transports". Sorry.
Yes I was. 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 }; // 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 }; // 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 }; // 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
;
Regarding your suggestion to use `expected<>`, I could use `expected<T, error_code_extended>` and this would get me close to `result<T>`, but I get no counterpart for `outcome<T>`. Or are you really proposing to add `outcome_u<T>` to collection?
Yes I was. 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.
If that's the consensus opinion, that's a fair bit of work to implement and I'm fairly sure it will affect compile times, but I am open to it. The single implementation-many personality design I chose is very amenable to such knobs for twiddling.
I may be able to keep compile times low using extern template and supplying a static library to be linked against. But I believe the above will punish header only usage badly. I'll do my best. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-24 23:11 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
Earlier rounds of feedback from Reddit convinced me that customising basic_monad is a very niche enterprise. Very, very few users of Outcome will want to do that.
I agree with this assumption. But if this is the case, I think you should not expose in the reference section of the documentation that outcome<> has a base class. As per your explanation, it is just an implementation detail. I am referring to this page: https://ned14.github.io/boost.outcome/classboost_1_1outcome_ 1_1v1__xxx_1_1outcome.html
I didn't want to be accused of misrepresenting the reference documentation.
What does it mean to "misrepresent the reference documentation"?
Actually, I am surprised nobody has yet accused me of doing so as the doxygen reference docs refer to classes which don't exist as classes e.g. outcome, result, option.
The purpose of the reference documentation -- as I see it -- is to tell apart things you consider the interface for the users form the implementation details. The fact that `outcome<T>` is not a class template does not make the understanding of the inerface confusing, therefore I do not expect anobody to complain. It is irrelevant whether it is actually a class of an alias. But the base classes: why users should know abut them from the docs?
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
But I mistakenly took it as saying "do it yourself". You were proposing more of precooked "transports". Sorry.
Yes I was.
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 };
// 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 };
// 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 };
// 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
;
Regarding your suggestion to use `expected<>`, I could use `expected<T, error_code_extended>` and this would get me close to `result<T>`, but I get no counterpart for `outcome<T>`. Or are you really proposing to add `outcome_u<T>` to collection?
Yes I was.
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?
If that's the consensus opinion, that's a fair bit of work to implement and I'm fairly sure it will affect compile times, but I am open to it. The single implementation-many personality design I chose is very amenable to such knobs for twiddling.
I may be able to keep compile times low using extern template and supplying a static library to be linked against. But I believe the above will punish header only usage badly. I'll do my best.
Niall
-- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost

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 };
// 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 };
// 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 };
// 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. 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. 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 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. 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. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

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

Le 20/05/2017 à 23:28, Andrzej Krzemienski via Boost a écrit :
2017-05-20 20:01 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
I am not trying to propose any new logic here. I am trying to assign some mental model to what `outcome<T>` currently does. Consider: some funciton `f` is supposed to compute an `int`. But the computation may fail, in this case I want the function to return the reason why it failed. Also, it is possible that I will get some "internal error". For this reason funcio `f` returns `result<T>`:
``` result<T> r = f(); ``` Now, `r` can represent the following states:
``` if (r.has_value()) // value successfully computed {} No, the correct interpretation here is that the result returned has a value, not that the value was successfully computed.
else if (r.empty()) // internal error {} else if (r.error()) // reason why f failed {} else if (r.has_error() && !r.error()) // what is that??? {} 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.
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_1p...
(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)
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`"?
2. "any errored state"? -- not the specific error state previously set inside `outcome`?
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?
The last case is neither a value, nor a reason for failure, nor an "internal error". How should a caller interpret it? Two possible answers:
1. There is a fourth state with no intuitive or sane interpretation. 2. Assume this never happens. If it happens it is a bug in calle site that needs to be fixed. You've got to remember all these potential state flow paths never occur in any real world code base. You'll even see the compiler eliding in the assembler output any handling of an empty state being returned if it can see that that is not possible.
Same goes for if you return only ever a value. The result<T> effectively "disappears" in the assembler generated, and it is as if you returned a T directly.
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".
+1 for all Vicente

Le 19/05/2017 à 09:01, Andrzej Krzemienski via Boost a écrit :
2017-05-19 0:49 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
No undefined behaviour needed, and you can write your code using
Outcome with the hard assumption that o.error() will always return an accurate view of the current state. No need to check .has_error(), or anything like it.
Modulo this situation with `http_error::ok == 200`. But with this you are also saying, the library provides two ways for checking if you have an error:
o.has_error(); // option 1 o.error() == std::error_code{}; // option 2 In the current standard expected proposal, we have an open point about a has_error(exp, err) that return true if exp has no value and the errror is the provided as parameter. This has the advantage to not throwing. And by describing clear semantics for option 2, you are saying, it is equally fine to use option 2 for checking if we have an error. This encourages the usage of option 2, but I would not want my colleague programmers to start using this syntax, because I then cannot tell proper usages from inadvertent omissions. And reading the value of `error()` without having confirmed that some error occurred is almost surely a bug, even if you can assign a well defined semantics in `boost::outcome` for it.
Remember, expected<std::error_code, std::error_code> is legal, though Outcome's Expected doesn't allow it. Why?
Best, Vicente

On 05/19/2017 03:42 PM, Niall Douglas via Boost wrote:
I have absolutely no idea why Chris chose "http_error" for that enum. It should have been "http_status" because those are the HTTP status codes as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
Agreed.
I am also highly unsure why you'd choose the error_code infrastructure for these. It's not like almost all of the HTTP status codes (e.g. 306 Switch Proxy) can be any error condition except resource_temporarily_unavailable.
When I perform an asynchronous HTTP operation that returns an error_code then I prefer that the error_code contains a unified response, i.e. an HTTP status code, a system error code (e.g. network problem), or an internal library error code. We can use the same solution as C++11 (std::system_category versus std::generic_category) 1. Define a native HTTP category that contains the raw HTTP status integer value. 2. Define a generic HTTP category that contains enums for the HTTP status codes. 3. Implement the equivalent() function on the native HTTP category that maps the native value to the enum. 4. Mark the generic HTTP enum as an error condition. That enables you to propagate the native value, so there is no loss of information in case the HTTP status contains a hitherto unknown code, and to compare it in a more user-friendly value using the enum. You can find a tutorial here: http://breese.github.io/2017/05/12/customizing-error-codes.html

On Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost <boost@lists.boost.org> wrote:
When I perform an asynchronous HTTP operation that returns an error_code then I prefer that the error_code contains a unified response, i.e. an HTTP status code, a system error code (e.g. network problem), or an internal library error code.
A nice thing about HTTP (if there is any such thing) is that the HTTP "message" can be modeled as a self contained object. The low-level result of the asynchronous operation (for example boost::asio::error::connection_reset) reflects metadata about the HTTP message and should not be part of that container.

On Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost <boost@lists.boost.org> wrote:
When I perform an asynchronous HTTP operation that returns an error_code then I prefer that the error_code contains a unified response, i.e. an HTTP status code, a system error code (e.g. network problem), or an internal library error code.
A nice thing about HTTP (if there is any such thing) is that the HTTP "message" can be modeled as a self contained object. The low-level result of the asynchronous operation (for example boost::asio::error::connection_reset) reflects metadata about the HTTP message and should not be part of that container. However, the status code is part of the message and therefore should be part of the container. I do not think it should be treated as metadata, nor represented using error_code.

Vinnie Falco wrote:
However, the [HTTP] status code is part of the message and therefore should be part of the container. I do not think it should be treated as metadata, nor represented using error_code.
Consider this high-level function result<vector<char>> get( string const& url ); used like this auto r = get( "https://example.com/data.zip" ); This function can fail in many different ways, starting from the URL parser (invalid URL), passing through the possibility of an invalid/unrecognized URL scheme, a host that cannot be resolved, a TCP address that can't be connected to, and finally, a 404 HTTP error code. All these things should come back as std::error_code. There's no HTTP message here on this layer to contain the status.

On Sat, May 20, 2017 at 7:28 AM, Peter Dimov via Boost <boost@lists.boost.org> wrote:
All these things should come back as std::error_code. There's no HTTP message here on this layer to contain the status.
For this interface, I agree, but Bjorne made a blanket statement:
When I perform an asynchronous HTTP operation that returns an error_code then I prefer that the error_code contains a unified response,
Obviously I don't agree with that; Beast has asynchronous HTTP operations which return error_code, and they do not communicate the response-status in that error_code. Nor should they, for the reasons stated earlier.

On Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost <boost@lists.boost.org> wrote:
When I perform an asynchronous HTTP operation that returns an error_code then I prefer that the error_code contains a unified response, i.e. an HTTP status code, a system error code (e.g. network problem), or an internal library error code.
I should have taken a bit more time before sending my reply - apologies. I am bursting with things to say about this subject as you can imagine so I will elaborate. Consider the following: f is a function r is an unspecified result of performing some calculation on m m is an HTTP response message r = f(m) If the calculation of r depends on the HTTP status, your proposal to shove the status into an error_code would require: r = f(m, ec) Where ec is an error_code. This introduces a problem, because now ec can contain things that are not HTTP status codes, such as boost::asio::error::connection_reset. f might not want or need to handle metadata about the TCP/IP connection or other errors that are not status codes. Now consider this: g is a function m' is an HTTP response message m' = g(m) This example uses a functional style where a message m is transformed by a function g into a new message. If we put the HTTP status in an error_code not part of m or m', the signature would look like this: pair<m', error_code> g(m, error_code); This signature imposes unnecessary burden on callers. It is also confusing. Does the error_code in the return value indicate that g can fail? I hope readers are convinced that HTTP requests and responses should be modeled as self-contained objects with all necessary fields prescribed by rfc7230, and that the HTTP status should be neither separate nor stored in an error_code.

On 05/20/2017 04:17 PM, Vinnie Falco via Boost wrote:
I hope readers are convinced that HTTP requests and responses should be modeled as self-contained objects with all necessary fields prescribed by rfc7230, and that the HTTP status should be neither separate nor stored in an error_code.
Not really, but that is the subject for a review of a different library.
participants (7)
-
Andrzej Krzemienski
-
Bjorn Reese
-
Gavin Lambert
-
Niall Douglas
-
Peter Dimov
-
Vicente J. Botet Escriba
-
Vinnie Falco