[outcome] Requesting pre-review of Boost.Outcome tutorial
Dear Boost, I am hoping to bring Boost.Outcome, a C++ 14 library providing a factory and family of policy driven lightweight monadic value-or-error transports, into the Boost peer review queue by March 2017 followed shortly thereafter by an ACCU talk in April if my talk proposal passes their programme committee. To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me: 1. Does it make sense? 2. Do you think you could use Outcome in your own code after reading it? 3. Do you think you would want to use Outcome in your own code after reading it? 4. Are there any missing parts which are showstoppers? My thanks in advance. Be aware the install stuff described at the start doesn't work yet. The reference documentation is also quite patchy thanks to doxygen not understanding CRTP. All that stuff and a fair bit more remains to fix before it's ready to enter the Boost peer review queue. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Am 11.11.2016 um 20:27 schrieb Niall Douglas:
Dear Boost,
I am hoping to bring Boost.Outcome, a C++ 14 library providing a factory and family of policy driven lightweight monadic value-or-error transports, into the Boost peer review queue by March 2017 followed shortly thereafter by an ACCU talk in April if my talk proposal passes their programme committee. To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense? Yes, it does. Especially for embedded development, this seems very useful. 2. Do you think you could use Outcome in your own code after reading it? I'd think so. 3. Do you think you would want to use Outcome in your own code after reading it? Yes. 4. Are there any missing parts which are showstoppers? I'm not entirely sure, I'd have to use it.
Is there a way to switch the behaviour depending on whether exceptions are available? I.e. throw if they are, elsewise use the result? Or would you consider this a horrible Idea? The reason for me would be, that I could write a library which's user could use it both ways, depending on what he has available. I think if I wanted to provide a way to have a function-call using an exception or an error_code, I'd go with the C++11 way, i.e. distinguish this in the signature. So for me the main use-case would be, that exceptions might not be available.
My thanks in advance. Be aware the install stuff described at the start doesn't work yet. The reference documentation is also quite patchy thanks to doxygen not understanding CRTP. All that stuff and a fair bit more remains to fix before it's ready to enter the Boost peer review queue.
Niall
On 11 Nov 2016 at 21:23, Klemens Morgenstern wrote:
an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense? Yes, it does. Especially for embedded development, this seems very useful.
Oh great. Thank you. I was worried it was too long and/or boring and/or confusing.
Is there a way to switch the behaviour depending on whether exceptions are available? I.e. throw if they are, elsewise use the result? Or would you consider this a horrible Idea?
Already present. All exceptions are thrown via a user redefinable macro. Each individual throw gets its own macro. When exceptions are disabled in the compiler, the default implementation dumps a stacktrace and terminates the process, but user code can do anything else it feels like. I would add that it should be the case that if properly used Outcome never throws an exception, not ever. Therefore it should never terminate the process. One thing I'll add before peer review is an optional facility that it will cause a link error if your code ever could cause an exception to throw. This should aid debugging.
The reason for me would be, that I could write a library which's user could use it both ways, depending on what he has available. I think if I wanted to provide a way to have a function-call using an exception or an error_code, I'd go with the C++11 way, i.e. distinguish this in the signature. So for me the main use-case would be, that exceptions might not be available.
This is major use case intention for Outcome and exactly what I use it for myself. Thanks for the feedback Klemens. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Am 11.11.2016 um 22:37 schrieb Niall Douglas:
On 11 Nov 2016 at 21:23, Klemens Morgenstern wrote:
an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense? Yes, it does. Especially for embedded development, this seems very useful. Oh great. Thank you. I was worried it was too long and/or boring and/or confusing. I am familiar with the problem, because I'm actually planning to work on an stm32 periph-library, for which I need a solution for that exact problem.
I just had another idea (I hope I didn't miss that): it would be awesome if there was some way to deal with objects, that might have an errornous state. I.e. something like that: outcome<path> p1("-invälid#value"); //error set outcome<path> p2("valid-value"); //not set int i = p2->something_errornous(); //now I have a set error. I'm not sure how to do that, but maybe something like this: the ctor checks through std::is_nothrow_constructible if the last argument can be std::error_code or something like that and then takes this one to capture the error; elsewise it catches the exception. And for the member-functions you could use some indirect call, like this: p2.invoke(&path::something_errornous); Which then calls the matching function with an std::error_code or the exception. I'm not sure if that should return outcome<int> or int and put the error into p2. This would be useful for a library, which uses the std::error_code or exception pattern, because it could be integrated with your library very convienently.
Is there a way to switch the behaviour depending on whether exceptions are available? I.e. throw if they are, elsewise use the result? Or would you consider this a horrible Idea? Already present. All exceptions are thrown via a user redefinable macro. Each individual throw gets its own macro. When exceptions are disabled in the compiler, the default implementation dumps a stacktrace and terminates the process, but user code can do anything else it feels like.
I would add that it should be the case that if properly used Outcome never throws an exception, not ever. Therefore it should never terminate the process.
One thing I'll add before peer review is an optional facility that it will cause a link error if your code ever could cause an exception to throw. This should aid debugging. Intersting, how would that work? And if I disable exceptions (-fno-exceptions) I'd already get an error.
The reason for me would be, that I could write a library which's user could use it both ways, depending on what he has available. I think if I wanted to provide a way to have a function-call using an exception or an error_code, I'd go with the C++11 way, i.e. distinguish this in the signature. So for me the main use-case would be, that exceptions might not be available. This is major use case intention for Outcome and exactly what I use it for myself.
Thanks for the feedback Klemens.
Niall
On 11 Nov 2016 at 23:33, Klemens Morgenstern wrote:
I just had another idea (I hope I didn't miss that): it would be awesome if there was some way to deal with objects, that might have an errornous state. I.e. something like that:
outcome<path> p1("-invälid#value"); //error set
outcome<path> p2("valid-value"); //not set int i = p2->something_errornous(); //now I have a set error.
I'm not sure how to do that, but maybe something like this: the ctor checks through std::is_nothrow_constructible if the last argument can be std::error_code or something like that and then takes this one to capture the error; elsewise it catches the exception.
And for the member-functions you could use some indirect call, like this:
p2.invoke(&path::something_errornous);
Which then calls the matching function with an std::error_code or the exception. I'm not sure if that should return outcome<int> or int and put the error into p2.
This would be useful for a library, which uses the std::error_code or exception pattern, because it could be integrated with your library very convienently.
I'm not sure if I understood you correctly, but you might be wanting to do pattern matching based on the contents. Outcome has that, so: outcome.match(callable) will call the appropriate overload of callable(T | error_code | exception_ptr). This is very handy. We can also do monadic bind on the state e.g. outcome >> [](error_code){ /* only called if outcome contains an error_code. We can return a new type of outcome here used by subsequent bind operators i.e. we can be an error_code filter/converter */ }; There are also operators: outcome<int> | 4; // If outcome is empty, errored or excepted, return outcome<int>(4) outcome<int> & 5; // If outcome has value, return outcome<int>(5) If however you were talking about tombstone values i.e. special values of T with special meaning, Outcome does not currently provide that but could be extended to do so by someone who isn't me very easily (pull requests welcome). SG14 already were interested in an Outcome with packed state using tombstones to reduce storage size, Outcome does already store bools and void and all outcome state into a single packed byte when possible.
I would add that it should be the case that if properly used Outcome never throws an exception, not ever. Therefore it should never terminate the process.
One thing I'll add before peer review is an optional facility that it will cause a link error if your code ever could cause an exception to throw. This should aid debugging.
Intersting, how would that work? And if I disable exceptions (-fno-exceptions) I'd already get an error.
I'll probably use the preprocessor to mangle together a location identifying extern function declaration which I call in a throw but doesn't exist. Under optimisation, the compiler will elide all such calls as being unreachable code and thus the program links successfully. If any could be called, the program will fail to link. I've implemented this before in other projects, it works fine on all the major compilers but needs to not be defaulted on as it confuses the hell out of most end users. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
On 11 Nov 2016 at 23:33, Klemens Morgenstern wrote:
I just had another idea (I hope I didn't miss that): it would be awesome if there was some way to deal with objects, that might have an errornous state. I.e. something like that:
outcome<path> p1("-invälid#value"); //error set
outcome<path> p2("valid-value"); //not set int i = p2->something_errornous(); //now I have a set error.
I'm not sure how to do that, but maybe something like this: the ctor checks through std::is_nothrow_constructible if the last argument can be std::error_code or something like that and then takes this one to capture the error; elsewise it catches the exception.
And for the member-functions you could use some indirect call, like this:
p2.invoke(&path::something_errornous);
Which then calls the matching function with an std::error_code or the exception. I'm not sure if that should return outcome<int> or int and put the error into p2.
This would be useful for a library, which uses the std::error_code or exception pattern, because it could be integrated with your library very convienently. I'm not sure if I understood you correctly, but you might be wanting to do pattern matching based on the contents.
Outcome has that, so:
outcome.match(callable) will call the appropriate overload of callable(T | error_code | exception_ptr). This is very handy.
We can also do monadic bind on the state e.g.
outcome >> [](error_code){ /* only called if outcome contains an error_code. We can return a new type of outcome here used by subsequent bind operators i.e. we can be an error_code filter/converter */ };
There are also operators:
outcome<int> | 4; // If outcome is empty, errored or excepted, return outcome<int>(4)
outcome<int> & 5; // If outcome has value, return outcome<int>(5)
If however you were talking about tombstone values i.e. special values of T with special meaning, Outcome does not currently provide that but could be extended to do so by someone who isn't me very easily (pull requests welcome). SG14 already were interested in an Outcome with packed state using tombstones to reduce storage size, Outcome does already store bools and void and all outcome state into a single packed byte when possible. Ok, I did miss the match, call, but that's basically to get the error out of the outcome. I meant the other way around: let outcome automatically capture the errors of the ctor automatically. I've tried to put together an example, but I failed to get it right with
Am 12.11.2016 um 09:53 schrieb Niall Douglas: the parameter forwarding, so that (https://godbolt.org/g/UdKzFs) doesn't work properly. I'm not sure if there is a good solution for that, but it hopefully demonstrates what I'm talking about. But since it's not as easy as I hoped, it maybe should not be part of outcome, but rather a wrapper holding one. On the other hand, having a solution that at least works with ctors, seems important to me, since you could have RAII with that while disabling exceptions.
On 12 Nov 2016 at 14:10, Klemens Morgenstern wrote:
Ok, I did miss the match, call, but that's basically to get the error out of the outcome. I meant the other way around: let outcome automatically capture the errors of the ctor automatically. I've tried to put together an example, but I failed to get it right with the parameter forwarding, so that (https://godbolt.org/g/UdKzFs) doesn't work properly. I'm not sure if there is a good solution for that, but it hopefully demonstrates what I'm talking about. But since it's not as easy as I hoped, it maybe should not be part of outcome, but rather a wrapper holding one. On the other hand, having a solution that at least works with ctors, seems important to me, since you could have RAII with that while disabling exceptions.
I think the code you show in godbolt would make an excellent free function helper e.g. make_outcome_nothrow<T>(Args...). I'm not keen on adding to the emplacement constructor as that adds to all monad construction costs whether you use the facility or not. I've added the free function as a todo, it'll be in the presented Outcome next Spring. Thanks for the idea Klemens. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Hi, On 2016-11-11 20:27, Niall Douglas wrote:
Dear Boost,
I am hoping to bring Boost.Outcome, a C++ 14 library providing a factory and family of policy driven lightweight monadic value-or-error transports, into the Boost peer review queue by March 2017 followed shortly thereafter by an ACCU talk in April if my talk proposal passes their programme committee. To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense? No, I think not that the documentation makes sense in the current form.
It implicitely assumes that the reader is familiar with a) c++ commitee speech (LEWG) and b) with future standard classes, e.g. expected. While some of those things are explained later on, up to the point where they are, their usage and discussion is confusing and distracting. Currently i have to scroll over several pages of text before I see what your library is about. Even when jumping directly to "Introducing outcome", I have still to scroll over a wall of text and a quite long synopsis before seeing the first example - which again is not as vanilla as it should be.
2. Do you think you could use Outcome in your own code after reading it?
3. Do you think you would want to use Outcome in your own code after reading it?
probably not. It misses any simple vanilla example to just play around
with. The only example given has so much clutter, that it makes it hard
to even find the parts relevant to outcome (it took me roughly 30
seconds to parse the function signature of file_create. I first could
not even find result
4. Are there any missing parts which are showstoppers? Unable to rate this. The above are show stoppers.
Best, Oswin
On Friday, November 11, 2016 7:27:22 PM CST Niall Douglas wrote:
To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense?
I really liked the initial history of error handling design patterns -- it was clear and I appreciated seeing the same running example through the different design approaches. However, when it came to "Introducing Outcome", it no longer felt like an introduction. I was expecting another iteration of the running example using the new library but instead was dropped deep into a design comparison with "Expected". Not knowing anything about the design of Expected, this is where I stopped reading. I skimmed to the end, but essentially understood nothing from it. The basic_monad class template was not motivated and went way over my head. The "Examples of usage" section contained too much obscuring detail and had too many extra things in it like the BOOST_OUTCOME_* macros which were only explained after the code.
2. Do you think you could use Outcome in your own code after reading it?
Nope. For reference: I'm a moderately-skilled programmer but mostly of applications, not reusable libraries. And I'm not immersed in the C++ working group papers. Regards, -Steve
On 11 Nov 2016 at 17:36, Steve M. Robbins wrote:
To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense?
I really liked the initial history of error handling design patterns -- it was clear and I appreciated seeing the same running example through the different design approaches.
Cool.
However, when it came to "Introducing Outcome", it no longer felt like an introduction. I was expecting another iteration of the running example using the new library but instead was dropped deep into a design comparison with "Expected". Not knowing anything about the design of Expected, this is where I stopped reading. I skimmed to the end, but essentially understood nothing from it. The basic_monad class template was not motivated and went way over my head. The "Examples of usage" section contained too much obscuring detail and had too many extra things in it like the BOOST_OUTCOME_* macros which were only explained after the code.
This is really useful, thanks.
I think what I might do is to relocate the WG21 speak discussion to
elsewhere and maybe even relocate any mention of expected
Hi, On 2016-11-12 09:53, Niall Douglas wrote:
I think what I might do is to relocate the WG21 speak discussion to elsewhere and maybe even relocate any mention of expected
to elsewhere. Just focus on an Outcome based design to continue the theme you mentioned.
I would appreciate that as well. It might be useful to come up with an almost trivial example for the two main use-cases you describe: 1. interfacing to an environment that does not allow exceptions (noexcept) 2. expected errors vs unexpected errors. Start your documentation with a 5 minute tutorial using these two examples and discuss everything else afterwards. Best, Oswin
2016-11-11 20:27 GMT+01:00 Niall Douglas
Dear Boost,
I am hoping to bring Boost.Outcome, a C++ 14 library providing a factory and family of policy driven lightweight monadic value-or-error transports, into the Boost peer review queue by March 2017 followed shortly thereafter by an ACCU talk in April if my talk proposal passes their programme committee. To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense?
2. Do you think you could use Outcome in your own code after reading it?
3. Do you think you would want to use Outcome in your own code after reading it?
4. Are there any missing parts which are showstoppers?
My thanks in advance. Be aware the install stuff described at the start doesn't work yet. The reference documentation is also quite patchy thanks to doxygen not understanding CRTP. All that stuff and a fair bit more remains to fix before it's ready to enter the Boost peer review queue.
Hi Niall, I am very curious about your library, and about the subject. I would like evaluate it. But I am having problems going through the documentation. I was only able to figure out that it deals with returning either T or an information about failure, and that you believe it is the fastest solution you can get. But other than that, I am confused. I am very found of Robert Ramey's advice given in this talk: https://youtu.be/ACeNgqBKL7E?t=4m48s And I would summarize it as follows, does the first page of the documentation allow me to see and understand in 5 min what this library is for and how I will be using it. I admit that I am a bit impatient, but this also might be an important feedback for you. I suppose, I am not the only impatient programmer that would be a potential user of your library, but may be discouraged by the documentation. It is clear that you have put a lot of effort into the documentation, but I still cannot find my way around it. It starts with installation guide, then design rationale, then the history of error reporting in c++, then the long synopsis, and only at the end do we find an example, but it is quite complicated, and not quite well annotated, uses long names, and I am not even sure which names are part of boost::outcome, and which names are only invented for the sake of the example. I could only figure out that we have macro BOOST_OUTCOME_TRY https://ned14.github.io/boost.outcome/monad_8hpp.html#aa977bf0b7aded30c781c3..., which is a sort of control flow. Really, some simple, annotated examples should be the first thing we see in the docs. In fact, there is a TODO at the top saying, "Add a vanilla use case snippet here". I would really like to see such example. For an example of what I mean, I recommend looking at the first pages of the documentation for Boost.Optional: http://www.boost.org/doc/libs/1_62_0/libs/optional/doc/html/index.html Additionally, words "monad" and "monadic" appear quite often in the documentation. I fail to see how this is supposed to help understand the library. I am not familiar with FP, and this term distracts my efforts to understand what this library is for. I hope these remarks will help. Regards, &rzej;
On 22 Nov 2016 at 1:33, Andrzej Krzemienski wrote:
I am very curious about your library, and about the subject. I would like evaluate it. But I am having problems going through the documentation. I was only able to figure out that it deals with returning either T or an information about failure, and that you believe it is the fastest solution you can get. But other than that, I am confused.
:(
I am very found of Robert Ramey's advice given in this talk: https://youtu.be/ACeNgqBKL7E?t=4m48s And I would summarize it as follows, does the first page of the documentation allow me to see and understand in 5 min what this library is for and how I will be using it.
I think for single purpose libraries where the reader already knows
what they want and why they are reading the documentation e.g.
serialisation then Robert's advice is gold.
I think it's much, much harder for libraries which provide
fundamental primitives because the uninitiated don't understand what
the point of the primitive is yet. Remember the old unique_ptr vs
shared_ptr vs auto_ptr discussions back in the day? People really
struggled to see what was so bad about auto_ptr, after all, "it
worked". It's a similar problem here. For the uninitiated, you are
going to have to walk them through the baby steps to get their brain
into the right gear to understand the point of the new primitive at
all, and that takes time. I think you need that for 80-90% of
potential Outcome users whom are not going to be familiar with
expected
I admit that I am a bit impatient, but this also might be an important feedback for you. I suppose, I am not the only impatient programmer that would be a potential user of your library, but may be discouraged by the documentation. It is clear that you have put a lot of effort into the documentation, but I still cannot find my way around it.
It starts with installation guide, then design rationale, then the history of error reporting in c++, then the long synopsis, and only at the end do we find an example, but it is quite complicated, and not quite well annotated, uses long names, and I am not even sure which names are part of boost::outcome, and which names are only invented for the sake of the example. I could only figure out that we have macro BOOST_OUTCOME_TRY https://ned14.github.io/boost.outcome/monad_8hpp.html#aa977bf0b7aded30c781c3..., which is a sort of control flow.
Really, some simple, annotated examples should be the first thing we see in the docs. In fact, there is a TODO at the top saying, "Add a vanilla use case snippet here". I would really like to see such example. For an example of what I mean, I recommend looking at the first pages of the documentation for Boost.Optional: http://www.boost.org/doc/libs/1_62_0/libs/optional/doc/html/index.html
That's a great suggestion. I'll mirror that example exactly. (BTW the todo's have been added based on feedback received here since I asked for feedback, they were not there originally).
Additionally, words "monad" and "monadic" appear quite often in the documentation. I fail to see how this is supposed to help understand the library. I am not familiar with FP, and this term distracts my efforts to understand what this library is for.
LEWG wants the monadic aspects of expected
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Niall Douglas Sent: 11 November 2016 19:27 To: boost@lists.boost.org Subject: [boost] [outcome] Requesting pre-review of Boost.Outcome tutorial
Dear Boost,
I am hoping to bring Boost.Outcome, a C++ 14 library providing a factory and family of policy driven lightweight monadic value-or-error transports, into the Boost peer review queue by March 2017 followed shortly thereafter by an ACCU talk in April if my talk proposal passes their programme committee. To that end, I've written an explanation and a sort of tutorial explaining the history and purpose of Outcome at https://ned14.github.io/boost.outcome/ and I'd greatly appreciate if people could tell me:
1. Does it make sense?
Amazing that something as handling the unexpected can be complicated, but yes, I think it does. As you and others have observed, explaining the "evolution of ideas" is too convoluted with the 'how to do it now'. Splitting the documentation into "What it is and how to do it" from "Why it's like this and how it is better" is one way. Re-ordering could also work. For users, start with the very, very, very simple to prevent TL;DRitus switching readers off. Working examples that the user can pull and run will sell well.
2. Do you think you could use Outcome in your own code after reading it?
Probably.
3. Do you think you would want to use Outcome in your own code after reading it?
Possibly (because I rarely care much about speed, or indeed reliability). But I can see that others will care greatly. (And I care greatly that computer systems are so intermittently dysfunctional - all my consumer devices are riddled with dodgy code that only works mostly).
4. Are there any missing parts which are showstoppers?
Not that I can (or would be able) to spot. Keep going! Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830
On 22 Nov 2016 at 10:54, Paul A. Bristow wrote:
1. Does it make sense?
Amazing that something as handling the unexpected can be complicated, but yes, I think it does.
As you and others have observed, explaining the "evolution of ideas" is too convoluted with the 'how to do it now'.
Splitting the documentation into "What it is and how to do it" from "Why it's like this and how it is better" is one way. Re-ordering could also work.
For users, start with the very, very, very simple to prevent TL;DRitus switching readers off.
Working examples that the user can pull and run will sell well.
Thanks for the feedback Paul. Much appreciated. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (6)
-
Andrzej Krzemienski
-
Klemens Morgenstern
-
Niall Douglas
-
Oswin Krause
-
Paul A. Bristow
-
Steve M. Robbins