Boost logo

Boost :

Subject: Re: [boost] [outcome] Ternary logic -- need an example
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-23 06:30:37


2017-05-22 22:52 GMT+02:00 Niall Douglas via Boost <boost_at_[hidden]>:

> >> 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_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`.
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;


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