Boost logo

Boost :

Subject: Re: [boost] [outcome] Ternary logic -- need an example
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-05-22 20:52:20


>> 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/

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk