Boost logo

Boost :

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


>> Similarly to the value returned case, Outcome does not involve itself
>> into whether a T value returned is valid or not in exactly the same way
>> as it does not involve itself into whether a E value returned is valid
>> or not.
>
> If you are saying "I just give you a tool for storing either T or
> error_code or std::exception_ptr or nothing, and I do not care how you use
> it", I can accept that. But the semantics of `o.error()` seem to contradict
> that a bit: as though you were in fact trying to workaround for potential
> mis-usages of your library.

I very much came at the default actions as a means of saving the
programmer from typing boilerplate.

The specific actions I chose have been mostly the same since day one. I
think there was one which turned out to be a mistake during the past two
years of using the library in my code, I changed it. Otherwise they've
been tested in the real world for a quite a while now.

> Given what you already said about semantics of function `error()`, I
> consider the documentation of this function insufficient:
> https://ned14.github.io/boost.outcome/structboost_1_1outcome_1_1v1__xxx_1_1policy_1_1monad__policy__base.html#a6d5a06127d3ab8aa317635cfef1ada6a

Good point. Logged to https://github.com/ned14/boost.outcome/issues/26

> (BTW, note that there is something wrong with the links. If I click on it,
> I do not get any more details for `error()` but instead get "Detailed
> Description" of boost::outcome::v1_xxx::policy::monad_policy_base)

It worked here fine.

> Anyway, the short description of function `error()` says, "Returns any
> errored state in the transport, throwing an exception if empty."
>
> 1. I wish you didn't use this word "transport" as a noun. It always
> confuses me. Do you mean "either `option` or `resutl` or `outcome`"?

Transport is a noun meaning a device which conveys something.

> 2. "any errored state"? -- not the specific error state previously set
> inside `outcome`?

If we refer to state, we mean the variant in Outcomes.

> 3. It does not mention your algorithm: if `has_value() == true`, returns a
> value-initialized error code; if `has_exception() == true`, returns
> `error_type((int) monad_errc::exception_present, monad_category())`
>
> 4. "Throwing exception if empty" -- what exception?

All the above is covered in the tutorial, but I agree it needs to be in
the reference docs too. It will be fixed.

> I trust you that all these additional guarntees cost nothing at run-time.
> My concerns are not really about a potential run-time overhead, but about
> what is a correct usage of the library and what is a buggy usage. For
> instance, if you changed the semantics of function `error()` to:
>
> Requires: `has_error() == true`.
> Returns: the error_code stored in `*this`.
>
> This would make the understanding of the interface simple, it would clearly
> indicate when the users do something wrong, you could still implement your
> "rescue semantics", but I when I am doing the code review for my
> colleagues, I have something objective to rely on: "hey, you are breaking
> the precondition, you are extracting the error even though it is not
> there". Now, with the rescue semantics, I cannot say a word in the code
> review because the other programmer will respond, "But I learned the
> detailed rescue semantics, and I figured out it is exactly what I need."
> <-- the code does what the programmer intended, but is difficult to
> maintain, because it relies on the rescue semantics.
>
> By "rescue semantics" I mean, "id you do not have an error_code to return,
> just fabricate one".

I get what you're saying. The fact there are no preconditions on
.error() means that calling it is always a valid thing to do, and not by
definition a bug nor a code smell.

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.

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.

In the end it's up to each end user and their use case. If an end user
does a lot of code correctness auditing and peer review, depending on
that team's style they may prefer the boilerplate to always be spelled
out for easier auditing. Or they may prefer tighter, denser code so more
logic can be reviewed per screenful at once.

It really does depend on the team, the code base, and what you're
looking for. As I've mentioned in other threads, Outcome really is my
only library where I give people - including myself - a choice in what
style or use pattern or convention to use. Because every team, code base
and company is different.

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