[outcome] expected, etc. why are these assignable
It just occurred to me that for a similar component I created some time ago for my own purposes, I (intentionally?) support of the assignment operator. Copy construction was permitted however. So the only way to use the component is: checked_result<int> = function(...); something void function(checked_result & cr, ....) will be the same as void function(const checked_result & cr, ...) because checked_result only contains const functions. That is, once constructed, it's immutable. Personally I never use an interface which passes a mutable type to a function. To me it violates the mathematical idea of what a function is. Doesn't this simplify things? Doesn't this eliminate the whole questions about about empty state, etc. Doesn't this better model the "mondad" concept we're trying to leverage on. And why the hell are we modifying and an object designed to model a result in the first place? It doesn't make logical sense in terms of we expect a "result" to be. I plunk down my money (a function) and ask for a pepperoni pizza. Either they say they don't have pepperoni and give me my money back or give me a pizza. I don't think of modifying this result - it's an artifact of history. And wouldn't making these things immutable, eliminate a large part of the whole controversy? Just a thought. I think sometimes something is hard because we haven't thought enough about what we're really trying to do - what problem we're trying to solve. Robert Ramey
Robert Ramey wrote:
And wouldn't making these things immutable, eliminate a large part of the whole controversy?
An immutable result/outcome would have no default constructor (because it's default-constructed only in cases you need to put the value into it later) which would eliminate the motivation for an empty state as something into which to default-construct... but that's only a small part of the whole controversy. Niall's argument for empty state does not rest entirely on default construction. He argues that it makes Outcome a four-state variant instead of a tri-state one and having the fourth state is useful in certain scenarios when the function that returns it has a legitimate need to report four states. That is, he wants to be able to express this: enum class no_difference { _ } expected<std::path, no_difference, std::error_code, std::exception_ptr> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... } (or something along these lines, I may not have gotten the example entirely correct, but that's the spirit of it.) There are different legitimate ways to draw the line; you could say three states are it, for more use expected<>, or you could say, four states cover the majority of the legitimate cases, so let's have that.
2017-06-02 17:42 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Robert Ramey wrote:
And wouldn't making these things immutable, eliminate a large part of the
whole controversy?
An immutable result/outcome would have no default constructor (because it's default-constructed only in cases you need to put the value into it later) which would eliminate the motivation for an empty state as something into which to default-construct... but that's only a small part of the whole controversy.
Niall's argument for empty state does not rest entirely on default construction. He argues that it makes Outcome a four-state variant instead of a tri-state one and having the fourth state is useful in certain scenarios when the function that returns it has a legitimate need to report four states.
That is, he wants to be able to express this:
enum class no_difference { _ }
expected<std::path, no_difference, std::error_code, std::exception_ptr> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... }
(or something along these lines, I may not have gotten the example entirely correct, but that's the spirit of it.)
There are different legitimate ways to draw the line; you could say three states are it, for more use expected<>,
This is a valid point. If you need this tricky fourth state once in 1000 use cases, you can afford to use std::variant instead and leave without the convenience of `outcome`. Regards, &rzej;
Andrzej Krzemienski wrote:
This is a valid point. If you need this tricky fourth state once in 1000 use cases, you can afford to use std::variant instead and leave without the convenience of `outcome`.
Or you could use expected<T, E...>, assuming we decide this is a direction worth pursuing. Proof of concept here: https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect...
Or you could use expected<T, E...>, assuming we decide this is a direction worth pursuing. Proof of concept here:
https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect...
Now with some preliminary documentation: https://github.com/pdimov/variant2/blob/develop/doc/expected.md
On 6/2/17 8:42 AM, Peter Dimov via Boost wrote:
Robert Ramey wrote:
And wouldn't making these things immutable, eliminate a large part of the whole controversy?
An immutable result/outcome would have no default constructor (because it's default-constructed only in cases you need to put the value into it later) which would eliminate the motivation for an empty state as something into which to default-construct... but that's only a small part of the whole controversy.
Right - so it would only eliminate a small part of the whole controversy.
Niall's argument for empty state does not rest entirely on default construction. He argues that it makes Outcome a four-state variant instead of a tri-state one and having the fourth state is useful in certain scenarios when the function that returns it has a legitimate need to report four states.
That is, he wants to be able to express this:
enum class no_difference { _ }
expected<std::path, no_difference, std::error_code, std::exception_ptr> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... }
(or something along these lines, I may not have gotten the example entirely correct, but that's the spirit of it.)
OK - I guess then that "expected" wouldn't be consistent with my expectations for a function result - which for me is the main (or only) use case. In my world, I would expect template<class T, class E> struct expected {...}; expected<std::path, no_difference> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... } then use it like this std::path p1 = "asdfasdfsdf"; std::path p2 = "34234324"; const expected<std::path, no_difference> result = return_first_difference(p1,p2); if(result) // convertable to bool? // convert result to error_code const std::error_code e(result, error_catagory); throw ? std::system_error(e) const std::path path = result; I don't see why expected has to have more than two states. One for the legitimate result and one for anything else. The "anything else" doesn't have to be dealt with inside of "expected". Basically the E parameter is an "Escape" clause which is the user's responsibility to define.
There are different legitimate ways to draw the line; you could say three states are it, for more use expected<>, or you could say, four states cover the majority of the legitimate cases, so let's have that.
Hmmm - maybe states and parameters are getting confused. An "Escape" parameter can contain an arbitrary number of states in one parameter. Robert Ramey
Forgive me chiming in late, but what is the semantic difference between: enum class no_difference { _ } expected<std::path, no_difference, std::error_code, std::exception_ptr> and this: using path_difference = std::variant<std::path, no_difference>; using error = std::variant<std::error_code, std::exception_ptr>; expected<path_difference, error> return_first_diff(...); Doesn't the latter form satisfy all concerns in that: 1. It conforms to a desirable 2-state 'value or error' KISS paradigm. 2. it contains all the information we'll ever need. 3. success code paths are now co-located, as are failure code paths. There is no need for N code-paths to cater for N states. Isn't this more maintainable? On 2 June 2017 at 18:11, Robert Ramey via Boost <boost@lists.boost.org> wrote:
On 6/2/17 8:42 AM, Peter Dimov via Boost wrote:
Robert Ramey wrote:
And wouldn't making these things immutable, eliminate a large part of the
whole controversy?
An immutable result/outcome would have no default constructor (because it's default-constructed only in cases you need to put the value into it later) which would eliminate the motivation for an empty state as something into which to default-construct... but that's only a small part of the whole controversy.
Right - so it would only eliminate a small part of the whole controversy.
Niall's argument for empty state does not rest entirely on default
construction. He argues that it makes Outcome a four-state variant instead of a tri-state one and having the fourth state is useful in certain scenarios when the function that returns it has a legitimate need to report four states.
That is, he wants to be able to express this:
enum class no_difference { _ }
expected<std::path, no_difference, std::error_code, std::exception_ptr> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... }
(or something along these lines, I may not have gotten the example entirely correct, but that's the spirit of it.)
OK - I guess then that "expected" wouldn't be consistent with my expectations for a function result - which for me is the main (or only) use case. In my world, I would expect
template<class T, class E> struct expected {...};
expected<std::path, no_difference> void return_first_difference( std::path const & p1, std::path const & p2 ) { // ... }
then use it like this
std::path p1 = "asdfasdfsdf"; std::path p2 = "34234324";
const expected<std::path, no_difference> result = return_first_difference(p1,p2);
if(result) // convertable to bool? // convert result to error_code const std::error_code e(result, error_catagory); throw ? std::system_error(e)
const std::path path = result;
I don't see why expected has to have more than two states. One for the legitimate result and one for anything else. The "anything else" doesn't have to be dealt with inside of "expected". Basically the E parameter is an "Escape" clause which is the user's responsibility to define.
There are different legitimate ways to draw the line; you could say three
states are it, for more use expected<>, or you could say, four states cover the majority of the legitimate cases, so let's have that.
Hmmm - maybe states and parameters are getting confused. An "Escape" parameter can contain an arbitrary number of states in one parameter.
Robert Ramey
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost
Richard Hodges wrote:
Forgive me chiming in late, but what is the semantic difference between:
enum class no_difference { _ }
expected<std::path, no_difference, std::error_code, std::exception_ptr>
and this:
using path_difference = std::variant<std::path, no_difference>; using error = std::variant<std::error_code, std::exception_ptr>;
expected<path_difference, error> return_first_diff(...);
There are various ways to represent the same four-state variant syntactically, and the main difference is in, well, syntax, when referring to it. You could, for example, use outcome<variant<std::path, no_difference>> (assuming a never-empty tri-state outcome) or outcome<optional<std::path>> At some point for a detailed comparison one would need to take a representative example and write down the user code under each alternative. You're right that in this case both "std::path" and "no_difference" seem more like values, although I don't remember how it was in Niall's original AFIO example.
Richard Hodges wrote:
Forgive me chiming in late, but what is the semantic difference between:
There are various ways to represent the same four-state variant syntactically, and the main difference is in, well, syntax, when referring to it. You could, for example, use
outcome<variant<std::path, no_difference>>
(assuming a never-empty tri-state outcome)
or
outcome<optional<std::path>>
Completely agree that in this case optional<std::path> is equivalent to variant<path, no_difference>. My concern is around allowing more than two
On 2 June 2017 at 18:43, Peter Dimov via Boost <boost@lists.boost.org> wrote: main code paths when testing the outcome. It seems to me that all outcomes will either 'pass' in some way or 'fail' in some way. These are the main concerns are they not? By all means the happy path might specialise happiness, but it's still happiness, right? we could argue that some process might partially complete, and that could be captured with: outcome<variant<result, partial_result_try_again>> A partial result is not a failure. Isn't the purpose of outcome to simply offer a non-throwing alternative to exceptions without requiring in/out variables? Or is there some wider use case I haven't thought of? If so, couldn't that be trivially composed as above?
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost
On 6/2/17 9:22 AM, Richard Hodges via Boost wrote:
Forgive me chiming in late, but what is the semantic difference between:
enum class no_difference { _ }
expected<std::path, no_difference, std::error_code, std::exception_ptr>
and this:
using path_difference = std::variant<std::path, no_difference>; using error = std::variant<std::error_code, std::exception_ptr>;
expected<path_difference, error> return_first_diff(...);
Doesn't the latter form satisfy all concerns in that: 1. It conforms to a desirable 2-state 'value or error' KISS paradigm. 2. it contains all the information we'll ever need. 3. success code paths are now co-located, as are failure code paths. There is no need for N code-paths to cater for N states. Isn't this more maintainable?
Right. By leaving the second parameter E as a template parameter, it can contain as much state or as little state as one wants. Your formulation might be considered more heavy weight - but hey, that's up to the user - not us. Robert Ramey
On 02/06/2017 16:42, Peter Dimov via Boost wrote:
Robert Ramey wrote:
Niall's argument for empty state does not rest entirely on default construction. He argues that it makes Outcome a four-state variant instead of a tri-state one and having the fourth state is useful in certain scenarios when the function that returns it has a legitimate need to report four states.
Nearly correct, but not quite. My argument in favour of an emoty state is because the implementation will implement an empty state internally anyway, so either you expose that publicly, or you don't. It's no difference for the implementation. You then list the use cases for a formal public empty state, and on balance I came out in favour. During the review quite a few people came to the same conclusion: on balance, it's better than the alternatives. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Niall Douglas wrote:
My argument in favour of an emoty state is because the implementation will implement an empty state internally anyway, so either you expose that publicly, or you don't. It's no difference for the implementation.
The same argument has been used in variant's case and the initial proposal did include a bona fide empty state. It was however swept under the carpet because never-empty, or even the illusion of never-empty, as in the case of C++17's variant, was found preferable. It's true that the implementation will have an empty state internally as an implementation detail, but it need not be user-visible. The argument for never having an empty state, in variant's case, is that having an empty state makes people who don't use it pay for it in more complex code that has to check for empty because visit, for example, has to handle all alternatives. It's better, there, to make the default never empty. People who want an empty state can always put an empty alternative in the list.
Le 03/06/2017 à 00:09, Peter Dimov via Boost a écrit :
Niall Douglas wrote:
My argument in favour of an emoty state is because the implementation will implement an empty state internally anyway, so either you expose that publicly, or you don't. It's no difference for the implementation.
The same argument has been used in variant's case and the initial proposal did include a bona fide empty state. It was however swept under the carpet because never-empty, or even the illusion of never-empty, as in the case of C++17's variant, was found preferable.
It's true that the implementation will have an empty state internally as an implementation detail, but it need not be user-visible.
The argument for never having an empty state, in variant's case, is that having an empty state makes people who don't use it pay for it in more complex code that has to check for empty because visit, for example, has to handle all alternatives. It's better, there, to make the default never empty. People who want an empty state can always put an empty alternative in the list.
The same argument justify IMHO the narrow contracts for the observers. Vicente
2017-06-02 17:17 GMT+02:00 Robert Ramey via Boost <boost@lists.boost.org>:
It just occurred to me that for a similar component I created some time ago for my own purposes, I (intentionally?) support of the assignment operator. Copy construction was permitted however. So the only way to use the component is:
checked_result<int> = function(...);
something
void function(checked_result & cr, ....) will be the same
as
void function(const checked_result & cr, ...)
because checked_result only contains const functions. That is, once constructed, it's immutable.
Personally I never use an interface which passes a mutable type to a function. To me it violates the mathematical idea of what a function is.
Doesn't this simplify things? Doesn't this eliminate the whole questions about about empty state, etc. Doesn't this better model the "mondad" concept we're trying to leverage on. And why the hell are we modifying and an object designed to model a result in the first place? It doesn't make logical sense in terms of we expect a "result" to be. I plunk down my money (a function) and ask for a pepperoni pizza. Either they say they don't have pepperoni and give me my money back or give me a pizza. I don't think of modifying this result - it's an artifact of history. And wouldn't making these things immutable, eliminate a large part of the whole controversy?
Just a thought. I think sometimes something is hard because we haven't thought enough about what we're really trying to do - what problem we're trying to solve.
The consumer of the `outcome` could just observe the state. But the producer may have legitimate reasons to change the value of `outcome` objects when preparing its final value. Also, some use cases require being able to store outcomes in containers. I guess design would be easier if we could see all use cases listed. Regards, &rzej;
On 6/2/17 8:53 AM, Andrzej Krzemienski via Boost wrote:
2017-06-02 17:17 GMT+02:00 Robert Ramey via Boost <boost@lists.boost.org>:
The consumer of the `outcome` could just observe the state. But the producer may have legitimate reasons to change the value of `outcome` objects when preparing its final value.
That is exactly what I'm questioning. I'm suggesting that changing the value of a "result" is indication of a fundamental mis-understanding what a "result" is. I'm suggesting that requiring that "expected" be immutable will actually improve it's conceptual power for users. It require them to use a more suitable type when "expected" is not suitable. Of course I'm guilty of speculating myself here. I'm questioning the premise "component X is useful, but it would be more useful if ....". Maybe we should keep simple things simple. BTW TL;DR; - I'm coming to the opinion that the usage of immutable objects is a key attribute of the functional programming ala haskel and that perhaps we should think about this more when we design stuff.
Also, some use cases require being able to store outcomes in container
LOL - I can't prove it, but this sounds like a very bad idea to me. But it also raises an entirely different issue. The requirement of default constructors for container elements is an artifact of the way containers are implemented. So here, an implementation artifact is bleeding over to the abstract conceptual design of the element. This is recipe for brain soup.
I guess design would be easier if we could see all use cases listed.
Ahhh - the pitfalls when designing on speculation about the future. Mix in design by committee and you've got ... gridlock. The discussions are interesting, but when they evolve into communal design from a simple review things get ... complicated. Maybe if we kept reviews more focused on thumbs up/down and criticism and support rather than "design" they might be less of a death march and we might encourage more people to participate in reviews. Robert Ramey
2017-06-02 18:42 GMT+02:00 Robert Ramey via Boost <boost@lists.boost.org>:
On 6/2/17 8:53 AM, Andrzej Krzemienski via Boost wrote:
2017-06-02 17:17 GMT+02:00 Robert Ramey via Boost <boost@lists.boost.org
:
The consumer of the `outcome` could just observe the state. But the
producer may have legitimate reasons to change the value of `outcome` objects when preparing its final value.
That is exactly what I'm questioning. I'm suggesting that changing the value of a "result" is indication of a fundamental mis-understanding what a "result" is. I'm suggesting that requiring that "expected" be immutable will actually improve it's conceptual power for users. It require them to use a more suitable type when "expected" is not suitable. Of course I'm guilty of speculating myself here. I'm questioning the premise "component X is useful, but it would be more useful if ....". Maybe we should keep simple things simple.
BTW TL;DR; - I'm coming to the opinion that the usage of immutable objects is a key attribute of the functional programming ala haskel and that perhaps we should think about this more when we design stuff.
Also, some use cases require being able to store outcomes in container
LOL - I can't prove it, but this sounds like a very bad idea to me. But it also raises an entirely different issue. The requirement of default constructors for container elements is an artifact of the way containers are implemented. So here, an implementation artifact is bleeding over to the abstract conceptual design of the element. This is recipe for brain soup.
I guess design would be easier if we could see all use cases listed.
Ahhh - the pitfalls when designing on speculation about the future. Mix in design by committee and you've got ... gridlock.
The discussions are interesting, but when they evolve into communal design from a simple review things get ... complicated. Maybe if we kept reviews more focused on thumbs up/down and criticism and support rather than "design" they might be less of a death march and we might encourage more people to participate in reviews.
In this spirit, I have got a question to Niall, or whoever who used the library in their real projects: did the use cases required to change the state of a once constructed `outcome`? Regards, &rzej;
The discussions are interesting, but when they evolve into communal design from a simple review things get ... complicated. Maybe if we kept reviews more focused on thumbs up/down and criticism and support rather than "design" they might be less of a death march and we might encourage more people to participate in reviews.
In this spirit, I have got a question to Niall, or whoever who used the library in their real projects: did the use cases required to change the state of a once constructed `outcome`?
I gave many examples throughout the review of changing the state of existing outcomes. Indeed, almost every AFIO object init function implementation does so, we construct an empty or valued result<T> on the stack and fill it in over time as part of the "two phase construction" used throughout AFIO. Also, in KernelTest, there is some constexpr code which statically constructs a std::array<outcome<T>, N> at compile time exactly matching the number of parameter permutations which will be run. After the test has finished, empty outcome<T> means test was skipped or death test failed, valued means test passed, errored means test errored, excepted means test threw an exception. So assignment of Outcomes, well I use it frequently. I've also passed in outcomes by lvalue ref to a set of filtering callbacks, and each callback may or may not change their state as part of their execution. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (6)
-
Andrzej Krzemienski
-
Niall Douglas
-
Peter Dimov
-
Richard Hodges
-
Robert Ramey
-
Vicente J. Botet Escriba