388 | | The expected outcome is a shared_ptr to a handle_type, the unexpected outcome is a std::error_code, and the catastrophic outcome is the throwing of bad_alloc. Code using `openfile()` can either manually check the expected (its bool operator is true if the expected value is contained, false if the unexpected value) or simply unilaterally call `expected<T>.value()` which will throw if the value is unexpected, thus converting the error_code into an exception. As you immediately note, this eliminates the need for two `openfile()` overloads because the single monadic return based implementation can now perform ''both'' overloads with equal convenience to the programmer. On the basis of halving the number of APIs a library must export, use of expected is a huge win. |
389 | | |
390 | | However I am still not happy with this semantic encapsulation because it is a poor fit to opening files. Experienced programmers will instantly spot the problem here: the `open()` call doesn't just return success vs failure, it actually has five outcome categories: |
| 388 | The expected outcome is a shared_ptr to a handle_type, the unexpected outcome is a std::error_code, and the catastrophic outcome is the throwing of bad_alloc. Code using `openfile()` can either manually check the expected (its bool operator is true if the expected value is contained, false if the unexpected value) or simply unilaterally call `expected<>.value()` which will throw if the value is unexpected, thus converting the error_code into an exception. As you will immediately note, this eliminates the need for two `openfile()` overloads because the single monadic return based implementation can now perform ''both'' overloads with equal convenience to the programmer. On the basis of halving the number of APIs a library must export, use of expected is a huge win. |
| 389 | |
| 390 | However I am still not happy with this semantic encapsulation because it is a poor fit to what opening files actually means. Experienced programmers will instantly spot the problem here: the `open()` call doesn't just return success vs failure, it actually has five outcome categories: |
394 | | 3. Temporary failure, please retry later: EBUSY, EISDIR, ELOOP, ENOENT, ENOTDIR, EPERM, EACCES (depending on changes on the filing system, this could disappear or appear at any time) |
| 394 | 3. Temporary failure, please retry later: EBUSY, EISDIR, ELOOP, ENOENT, ENOTDIR, EPERM, EACCES (depending on changes on the filing system, these could disappear or appear at any time) |
429 | | 3. Returning a monadic transport means you can now program monadically against the result e.g. `value_or()`, `then()` and so on. Monadic programming is also a formal specification, so you could in some future world use a future clang AST tool to formally verify the mathematical correctness of some monadic logic. That's enormous for C++. |
430 | | |
431 | | You may have noticed though the (Strongly) being in brackets, and if you guessed there are caveats then you are right. The first big caveat is that the expected<T, E> implementation in Boost.Expected is very powerful, but unfortunately has a big negative effect on compile times, and that rather ruins it for practical use for a lot of people. The second caveat is that integration between Expected and Future-Promise especially with resumable functions in the mix is currently poorly defined, and using Expected now almost certainly introduces immediate technical debt into your code. |
432 | | |
433 | | The third caveat is that I personally plan to write a much lighter weight monadic result transport which isn't as flexible as expected<T, E> (and probably hard coded to a T, error_code and exception_ptr outcomes) but would have negligible effects on compile times, and very deep integration with a non-allocating all-constexpr new lightweight future-promise implementation. Once implemented, my transport may be disregarded by the community, evolved more towards expected<T, E>, or something else entirely may turn up. |
434 | | |
435 | | In other words, I recommend you very strongly consider ''some'' mechanism for cleanly matching C++ semantics with what a function does now that C++ 11 makes it possible, but I unfortunately cannot categorically recommend one solution over another at the time of writing. |
| 438 | 3. Returning a monadic transport means you can now program monadically against the result e.g. `value_or()`, `then()` and so on. Monadic programming - if and only if there is no possibility of exception throws - is also a formal specification, so you could in some future world use a future clang AST tool to formally verify the mathematical correctness of some monadic logic if and only if all the monadic functions you call are `noexcept`. That's enormous for C++. |
| 439 | |
| 440 | You may have noticed though the (Strongly) in the title of this section being in brackets, and if you guessed there are caveats in the above then you are right. The first big caveat is that the expected<T, E> implementation in Boost.Expected is very powerful and full featured, but unfortunately has a big negative effect on compile times, and that rather ruins it for the majority of people who only need about 10% of what it provides (and would rather like that to be quick to compile). The second caveat is that integration between Expected and Future-Promise especially with resumable functions in the mix is currently poorly defined, and using Expected now almost certainly introduces immediate technical debt into your code that you'll have to pay for later. |
| 441 | |
| 442 | The third caveat is that I personally plan to write a much lighter weight monadic result transport which isn't as flexible as expected<T, E> (and probably hard coded to a T, error_code and exception_ptr outcomes) but would have negligible effects on compile times, and very deep integration with a non-allocating all-constexpr new lightweight future-promise implementation. Once implemented, my monadic transport may be disregarded by the community, evolved more towards expected<T, E>, or something else entirely may turn up. |
| 443 | |
| 444 | In other words, I recommend you very strongly consider ''some'' mechanism for more closely and cleanly matching C++ semantics with what a function does now that C++ 11 makes it possible, but I unfortunately cannot categorically recommend one solution over another at the time of writing. |