Quantcast

[outcome] Ternary logic -- need an example

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
45 messages Options
123
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[outcome] Ternary logic -- need an example

Boost - Dev mailing list
Niall Douglas wrote:

> Finally, if you did have some special E value to mean empty, you would
> have to write special checks for it in Outcome in order to give it the
> stronger abort type semantics it has - if you don't have it being given
> alternative semantics, then there is no point in having an empty state.
> If you are writing special checks, then you might as well just have a
> formal empty state in the first place.
>
> This argument can be generalised into the argument in favour of ternary
> logics over binary logics. Sure, 90% of the time binary logics are
> sufficient, but binary is a subset of ternary. And *you don't have to
> use* the "other" state in a ternary logic just because it's there. Just
> don't use it, so long as its implementation signals its unintentional
> usage with a very loud bang, it's a safe design.
>
> (There is an argument that Outcome's "very loud bang" isn't loud enough.
> I'd be pleased to hear feedback on that)


Niall, you mention "ternary logic" here, and I guess it has to do with
'module' `tribool` exposed by Outcome library. But I do not believe I have
seen in the documentation (I mean the tutorial section) how I am supposed
to use it, and how it improves my programs. Or I might have just missed it.
Could you give me an example?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
>> This argument can be generalised into the argument in favour of ternary
>> logics over binary logics. Sure, 90% of the time binary logics are
>> sufficient, but binary is a subset of ternary. And *you don't have to
>> use* the "other" state in a ternary logic just because it's there. Just
>> don't use it, so long as its implementation signals its unintentional
>> usage with a very loud bang, it's a safe design.
>>
>> (There is an argument that Outcome's "very loud bang" isn't loud enough.
>> I'd be pleased to hear feedback on that)
>
> Niall, you mention "ternary logic" here, and I guess it has to do with
> 'module' `tribool` exposed by Outcome library. But I do not believe I have
> seen in the documentation (I mean the tutorial section) how I am supposed
> to use it, and how it improves my programs. Or I might have just missed it.
> Could you give me an example?

It doesn't have much to do with tribool. outcome<T> and result<T>
deliberately take a three state based design with an explicit choice of
ternary instead of binary state. The original motivation was actually
for the basic_monad based, since removed, non-allocating future-promise
implementation where we needed a third state to indicate "pending", but
it turned out to be very useful in itself for simplifying usage,
eliminating writing boilerplate, and acting as an out-of-band signalling
mechanism.

The debate over ternary vs binary is long standing since the very
beginnings of computer science. This past C++ Now talk by the review
manager does a far better job of explaining than I could:
https://www.youtube.com/watch?v=gLJrOTFw6J0

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
2017-05-15 17:57 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> >> This argument can be generalised into the argument in favour of ternary
> >> logics over binary logics. Sure, 90% of the time binary logics are
> >> sufficient, but binary is a subset of ternary. And *you don't have to
> >> use* the "other" state in a ternary logic just because it's there. Just
> >> don't use it, so long as its implementation signals its unintentional
> >> usage with a very loud bang, it's a safe design.
> >>
> >> (There is an argument that Outcome's "very loud bang" isn't loud enough.
> >> I'd be pleased to hear feedback on that)
> >
> > Niall, you mention "ternary logic" here, and I guess it has to do with
> > 'module' `tribool` exposed by Outcome library. But I do not believe I
> have
> > seen in the documentation (I mean the tutorial section) how I am supposed
> > to use it, and how it improves my programs. Or I might have just missed
> it.
> > Could you give me an example?
>
> It doesn't have much to do with tribool. outcome<T> and result<T>
> deliberately take a three state based design with an explicit choice of
> ternary instead of binary state. The original motivation was actually
> for the basic_monad based, since removed, non-allocating future-promise
> implementation where we needed a third state to indicate "pending", but
> it turned out to be very useful in itself for simplifying usage,
> eliminating writing boilerplate, and acting as an out-of-band signalling
> mechanism.
>

Ok, so by "ternary" you mean "eihter value, or error, or just nothing". But
still, in the documentation we can see the "module"
`boost_lite::xxx::tribool`:
https://ned14.github.io/boost.outcome/group__tribool.html
Does this document only an implementation detail? If so, maybe you should
dropit from documentation, as it suggests that tribool is part of Outcome's
interface.
Or maybe it is part of Outcome's interface?
Also, what do you understand by "module" here?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
>> It doesn't have much to do with tribool. outcome<T> and result<T>
>> deliberately take a three state based design with an explicit choice of
>> ternary instead of binary state. The original motivation was actually
>> for the basic_monad based, since removed, non-allocating future-promise
>> implementation where we needed a third state to indicate "pending", but
>> it turned out to be very useful in itself for simplifying usage,
>> eliminating writing boilerplate, and acting as an out-of-band signalling
>> mechanism.
>
> Ok, so by "ternary" you mean "eihter value, or error, or just nothing".

It's more than that. Originally before I wrote any code, I drew up the
design on a whiteboard with the exact semantics for all possible
permutations of all possible operations based on the three state logic.
It was there that I decided that the valued state corresponds to ternary
value TRUE, the errored state corresponds to FALSE, and the empty state
to OTHER.

Now that choice is quite controversial. Here on boost-dev we bikeshedded
that choice for some weeks if I remember correctly. Many felt that empty
should be FALSE, and errored should be OTHER. But I'm sticking to my
guns on that, I still think my assignment is the right choice.

The OTHER state gets treated as truly other, and it's why it gets the
strongest abort semantics of any of the states. It is considered to be
the most abnormal of the three possible states by the default actions by
observer functions.

I can already tell that some people will want me to make the assignment
of the default actions customisable by policy class such that empty is
not treated so seriously. The good news is that that is very easy to do
for any end user (simply write a new policy class and go), but I have
chosen to not document it in the docs. If reviewers feel that
documenting how to make arbitrary custom editions of basic_monad (soon
to become basic_outcome) is important and to be encouraged, I can do that.

> But
> still, in the documentation we can see the "module"
> `boost_lite::xxx::tribool`:
> https://ned14.github.io/boost.outcome/group__tribool.html
> Does this document only an implementation detail? If so, maybe you should
> dropit from documentation, as it suggests that tribool is part of Outcome's
> interface.
> Or maybe it is part of Outcome's interface?

It is part of Outcome's interface, and hence why boost_lite::tribool is
documented (I drag it in specially from the boost-lite docs). You'll
find on every basic_monad there is:

constexpr operator boost_lite::tribool::tribool () const noexcept;

This lets you write:

outcome<T> v;
if(v == tribool::unknown) ...

> Also, what do you understand by "module" here?

The choice of the word "module" is imposed by doxygen. For some very odd
reason, it places documentation groups under a "Modules" heading. If
anyone knows how to tell it to do otherwise, please let me know.

Note that I depart for a business trip tomorrow and Wednesday. Expect no
replies until Thursday.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
On 16/05/2017 09:42, Niall Douglas via Boost wrote:
> I can already tell that some people will want me to make the assignment
> of the default actions customisable by policy class such that empty is
> not treated so seriously. The good news is that that is very easy to do
> for any end user (simply write a new policy class and go), but I have
> chosen to not document it in the docs. If reviewers feel that
> documenting how to make arbitrary custom editions of basic_monad (soon
> to become basic_outcome) is important and to be encouraged, I can do that.

Presumably people who want an empty state to not be treated as
error-like could simply use boost::optional<X> for T.

Although technically I suppose that's a different kind of empty state
than the one you're talking about, but I suspect it's what people are
really wanting when they say that.

I don't know if it would be worthwhile to try to optimise storage for
that case.



_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
Gavin Lambert wrote:

> I don't know if it would be worthwhile to try to optimise storage for that
> case. [result<optional<T>>]

An interesting point, because this is an argument in favor of returning by
value from result<>::value() - I can synthesize an optional<T> on demand
instead of storing one. (Return by reference mandates having an optional<>
object stored.)

Also applies to other possible combinations such as expected<expected<T,
E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle be
flattened into a single variant under the hood.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
2017-05-15 23:42 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> >> It doesn't have much to do with tribool. outcome<T> and result<T>
> >> deliberately take a three state based design with an explicit choice of
> >> ternary instead of binary state. The original motivation was actually
> >> for the basic_monad based, since removed, non-allocating future-promise
> >> implementation where we needed a third state to indicate "pending", but
> >> it turned out to be very useful in itself for simplifying usage,
> >> eliminating writing boilerplate, and acting as an out-of-band signalling
> >> mechanism.
> >
> > Ok, so by "ternary" you mean "eihter value, or error, or just nothing".
>
> It's more than that. Originally before I wrote any code, I drew up the
> design on a whiteboard with the exact semantics for all possible
> permutations of all possible operations based on the three state logic.
> It was there that I decided that the valued state corresponds to ternary
> value TRUE, the errored state corresponds to FALSE, and the empty state
> to OTHER.
>
> Now that choice is quite controversial. Here on boost-dev we bikeshedded
> that choice for some weeks if I remember correctly. Many felt that empty
> should be FALSE, and errored should be OTHER. But I'm sticking to my
> guns on that, I still think my assignment is the right choice.
>
> The OTHER state gets treated as truly other, and it's why it gets the
> strongest abort semantics of any of the states. It is considered to be
> the most abnormal of the three possible states by the default actions by
> observer functions.
>

The above explanation, treated in isolation, makes sense to me: empty is
the most abnormal state. But in the other thread, you say:

But the point being made (badly) in the example above was that you can

> use the empty state to indicate lack of find, and any errored state for
> errors during find etc. So, let me rewrite the above now it's morning
> and I am not babbling incoherently:
>
> outcome<Foo> found(empty);
> for(auto &i : container)
> {
>   auto v = something(i); // returns a result<Foo>
>   if(!v.empty())  // if not empty
>   {
>     found = std::move(v);  // auto upconverts preserving error or Foo
>     break;
>   }
> }
> if(found.has_error())
> {
>   die(found.error());
> }
> if(found)
> {
>   Do something with found.value() ...
> }
>

This seams to be implying something opposite: that empty can can be treated
as less abnormal than error.

It is my impression that the system of types in Boost.Outcome confuses two
meanings of "empty"

option<T> -- either T or "empty" -- in this case "empty" looks like "just
another state of T", as in boost::optional
result<T> -- either T or an error or "empty" -- in this case it means
"abnormally empty"

Am I right? If so, maybe you need two separate states "simply no T" and
"abnormal situation"?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
>> I don't know if it would be worthwhile to try to optimise storage for
>> that case. [result<optional<T>>]
>
> An interesting point, because this is an argument in favor of returning
> by value from result<>::value() - I can synthesize an optional<T> on
> demand instead of storing one. (Return by reference mandates having an
> optional<> object stored.)

I did consider returning optionals which might be empty instead of
throwing an exception. Syntactically, the user must then do the check
for state after the .value() if they want to avoid a throw, plus you now
get ugly outcome.value().value() everywhere. I felt that undesirable.

> Also applies to other possible combinations such as expected<expected<T,
> E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle
> be flattened into a single variant under the hood.

Earlier Expected proposals had all sorts of useful semantics for nested
expected. They have been removed from the most recent proposal.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
Niall Douglas wrote:
> I did consider returning optionals which might be empty instead of
> throwing an exception.

That's not what I was saying. I was saying that for result<optional<T>>, if
you return by reference, you have to store an optional<T> object inside your
result. But if you return by value, you don't have to store an optional and
you can reuse your existing tag instead of adding the extra bool flag. That
is, use variant<T, none, error_code> instead of variant<optional<T>,
error_code>.

> > Also applies to other possible combinations such as expected<expected<T,
> > E1>, E2>, ...

Similarly, expected<expected<T, E1>, E2> can use a single variant<T, E1, E2>
for storage, if it can return by value. Return by reference requires it to
have an actual expected<T, E1> object to which to bind the returned
reference.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
> This seams to be implying something opposite: that empty can can be treated
> as less abnormal than error.

You're forgetting locality. Locally to the find loop, the valued or
errored state returned by something() is the normal situation and you
don't care which it returns. Locally to the find loop, failing to find
what we are looking for is the abnormal situation.

The programmer looking at the find loop won't be thinking in terms of
errors or success of something(), but rather that the flow of execution
in the find loop is correct or incorrect.

I really should emphasise that this stuff, when used in practice, is
nothing like as complicated as this thread of discussion is making it
seem. Any programmer, even an undergrad, will look at that find loop and
know exactly what it means and how it works intuitively.

> It is my impression that the system of types in Boost.Outcome confuses two
> meanings of "empty"
>
> option<T> -- either T or "empty" -- in this case "empty" looks like "just
> another state of T", as in boost::optional
> result<T> -- either T or an error or "empty" -- in this case it means
> "abnormally empty"
>
> Am I right? If so, maybe you need two separate states "simply no T" and
> "abnormal situation"?

Empty is *defaulted* to the most abnormal state by Outcome's default
semantics. So if you call .error() on a valued Outcome, you get back a
default constructed error type, no exception thrown. Same goes for
.exception().

But if you call .error() or .exception() on an empty Outcome, you
*always* get an exception thrown. Therefore, if you write your Outcome
using code without state checks before observation i.e. you call
.error() without checking .has_error() beforehand, you get a stronger,
more abortive default action than if the Outcome were errored, valued or
excepted.

You the programmer may wish to avoid the default actions, in which case
you check state before access. You can then manually specify any other
action you prefer.

The "more abnormal" solely refers to my design choice of default
semantics only for the empty state. Reviewers may think those choices
misguided or plain wrong, and might suggest better default semantics.
For example, some might feel that any time one tries to observe state
where the state is different, you should always throw an exception.

That would be a very conservative design choice and I'd disagree with
it. But I would understand the rationale. I would also add that it is
trivial to wrap an Outcome with replacement observer functions which
change the default actions, or to customise the policy class to have
different defaults.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
2017-05-18 17:06 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> > This seams to be implying something opposite: that empty can can be
> treated
> > as less abnormal than error.
>
> You're forgetting locality. Locally to the find loop, the valued or
> errored state returned by something() is the normal situation and you
> don't care which it returns. Locally to the find loop, failing to find
> what we are looking for is the abnormal situation.
>
> The programmer looking at the find loop won't be thinking in terms of
> errors or success of something(), but rather that the flow of execution
> in the find loop is correct or incorrect.
>
> I really should emphasise that this stuff, when used in practice, is
> nothing like as complicated as this thread of discussion is making it
> seem. Any programmer, even an undergrad, will look at that find loop and
> know exactly what it means and how it works intuitively.
>

Ok, I misunderstood your example (maybe because I am not an undergrad :),
sorry. Indeed, it treate an empty state as the most abnormal.


>
> > It is my impression that the system of types in Boost.Outcome confuses
> two
> > meanings of "empty"
> >
> > option<T> -- either T or "empty" -- in this case "empty" looks like "just
> > another state of T", as in boost::optional
> > result<T> -- either T or an error or "empty" -- in this case it means
> > "abnormally empty"
> >
> > Am I right? If so, maybe you need two separate states "simply no T" and
> > "abnormal situation"?
>

Now, my above observation is invalid.


>
> Empty is *defaulted* to the most abnormal state by Outcome's default
> semantics. So if you call .error() on a valued Outcome, you get back a
> default constructed error type, no exception thrown. Same goes for
> .exception().
>
> But if you call .error() or .exception() on an empty Outcome, you
> *always* get an exception thrown. Therefore, if you write your Outcome
> using code without state checks before observation i.e. you call
> .error() without checking .has_error() beforehand, you get a stronger,
> more abortive default action than if the Outcome were errored, valued or
> excepted.
>
> You the programmer may wish to avoid the default actions, in which case
> you check state before access. You can then manually specify any other
> action you prefer.
>
> The "more abnormal" solely refers to my design choice of default
> semantics only for the empty state. Reviewers may think those choices
> misguided or plain wrong, and might suggest better default semantics.
> For example, some might feel that any time one tries to observe state
> where the state is different, you should always throw an exception.
>
> That would be a very conservative design choice and I'd disagree with
> it. But I would understand the rationale. I would also add that it is
> trivial to wrap an Outcome with replacement observer functions which
> change the default actions, or to customise the policy class to have
> different defaults.
>

My personal preference on this is that if you call `o.error()` before you
have confirmed you actually have an error, you are doing something wrong. I
would classify such situation as undefined behavior. But your choice fits
into the scope of undefined behaviour: if program can do anything, it might
as well return a default-constructed error_code.

On the other hand, you do loose something when you chose something else
than undefined behavior: the potential for such user bugs to be detected by
static analyzers.

But still, my initial concern remains somewhat un-addressed. I understand
that .empty() is treated as the most ubnormal state. I accept your choice
of throwing an exception upon a call to .exception(), I understand why
someone might want to type:

```
if (o.has_value()) {}
else if (o.has_error()) {}
else if (o.has exception()) {}
else {}
```

But I do not understand why in my program I would want to write:

```
if (o == tribool::unknown) {}
```

How is that better ina any way from `o.is_empty()`?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
> My personal preference on this is that if you call `o.error()` before you
> have confirmed you actually have an error, you are doing something wrong. I
> would classify such situation as undefined behavior. But your choice fits
> into the scope of undefined behaviour: if program can do anything, it might
> as well return a default-constructed error_code.

Ah but remember that outcome<T> and result<T> both guarantee that E will
be a type meeting the std::error_code contract.

Therefore calling o.error() on a valued outcome returning a default
constructed (null) error code is exactly correct: we return there is no
error. No undefined behaviour needed, and you can write your code using
Outcome with the hard assumption that o.error() will always return an
accurate view of the current state. No need to check .has_error(), or
anything like it.

> On the other hand, you do loose something when you chose something else
> than undefined behavior: the potential for such user bugs to be detected by
> static analyzers.

No static analyser that I am aware of can diagnose when someone is
causing UB in std::optional<T>. Writing one which didn't spew false
positives would be hard, even if the entire program were visible. After
all, maybe the user is _intentionally_ doing the reinterpret_cast by
effectively treating the common state as a union?

Remember, expected<std::error_code, std::error_code> is legal, though
Outcome's Expected doesn't allow it.

> But I do not understand why in my program I would want to write:
>
> ```
> if (o == tribool::unknown) {}
> ```
>
> How is that better ina any way from `o.is_empty()`?

I don't want to talk about this much as it's outside the scope of the
current review, but in functional programming using Outcome the tribool
can help make the logic much clearer. A common pattern I've used
personally is for true and false to select which branch of lambdas to
call next, and empty means to terminate processing immediately with no
further functions called.

I should emphasise that those extensions are disabled in the presented
library, and are out of scope for this review.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
2017-05-19 0:49 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> > My personal preference on this is that if you call `o.error()` before you
> > have confirmed you actually have an error, you are doing something
> wrong. I
> > would classify such situation as undefined behavior. But your choice fits
> > into the scope of undefined behaviour: if program can do anything, it
> might
> > as well return a default-constructed error_code.
>
> Ah but remember that outcome<T> and result<T> both guarantee that E will
> be a type meeting the std::error_code contract.
>

Ok, I remember, both error_code and exception_ptr are
default-constructible. It is not immediately clear to me that a
default-constructed error_code represents a no-error condition. In the blog
post by Chris Kohlhoff you refer to, he uses the following enum for
representing http error conditions:

```
enum class http_error
{
  continue_request = 100,
  switching_protocols = 101,
  ok = 200,
  ...
  gateway_timeout = 504,
  version_not_supported = 505
};
```
Which implies that numeric value 200 means no-error and a value initialized
`http_error` is meaningless. Maybe this does not affect the value of a
default-constructed `std::error_code`, but surely it adds to the confusion.


> Therefore calling o.error() on a valued outcome returning a default
> constructed (null) error code is exactly correct: we return there is no
> error.


Only under some definition of "correct". By correct, you probably mean "not
invoking UB" and "being compliant with your specification", but it is not
intuitive at all that a function should return this or that when invoked in
a context that is most likely a programmer's bug.


> No undefined behaviour needed, and you can write your code using
> Outcome with the hard assumption that o.error() will always return an
> accurate view of the current state. No need to check .has_error(), or
> anything like it.
>

Modulo this situation with `http_error::ok == 200`. But with this you are
also saying, the library provides two ways for checking if you have an
error:

o.has_error();  // option 1
o.error() == std::error_code{}; // option 2

And by describing clear semantics for option 2, you are saying, it is
equally fine to use option 2 for checking if we have an error. This
encourages the usage of option 2, but I would not want my colleague
programmers to start using this syntax, because I then cannot tell proper
usages from inadvertent omissions. And reading the value of `error()`
without having confirmed that some error occurred is almost surely a bug,
even if you can assign a well defined semantics in `boost::outcome` for it.


> > On the other hand, you do loose something when you chose something else
> > than undefined behavior: the potential for such user bugs to be detected
> by
> > static analyzers.
>
> No static analyser that I am aware of can diagnose when someone is
> causing UB in std::optional<T>. Writing one which didn't spew false
> positives would be hard, even if the entire program were visible. After
> all, maybe the user is _intentionally_ doing the reinterpret_cast by
> effectively treating the common state as a union?
>

Challenge accepted.

>
> Remember, expected<std::error_code, std::error_code> is legal, though
> Outcome's Expected doesn't allow it.
>
> > But I do not understand why in my program I would want to write:
> >
> > ```
> > if (o == tribool::unknown) {}
> > ```
> >
> > How is that better ina any way from `o.is_empty()`?
>
> I don't want to talk about this much as it's outside the scope of the
> current review, but in functional programming using Outcome the tribool
> can help make the logic much clearer. A common pattern I've used
> personally is for true and false to select which branch of lambdas to
> call next, and empty means to terminate processing immediately with no
> further functions called.
>
> I should emphasise that those extensions are disabled in the presented
> library, and are out of scope for this review.
>

I am fine with this scope disabled. However, `tribool` is still in scope,
so I consider it valid to ask about its usefulness (especially given that
all contexts in which it would prove useful is disabled). Maybe `tribool`
should be also removed from the scope?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
> Ok, I remember, both error_code and exception_ptr are
> default-constructible. It is not immediately clear to me that a
> default-constructed error_code represents a no-error condition.

A null error code is widely held to mean "no error". Ditto with
exception_ptr. In any ASIO completion handler, you'll see:

void asio_handler(const error_code &ec, ...)
{
  if(ec)
    handle_error();
  ...
}

> In the blog
> post by Chris Kohlhoff you refer to, he uses the following enum for
> representing http error conditions:
>
> ```
> enum class http_error
> {
>   continue_request = 100,
>   switching_protocols = 101,
>   ok = 200,
>   ...
>   gateway_timeout = 504,
>   version_not_supported = 505
> };
> ```
> Which implies that numeric value 200 means no-error and a value initialized
> `http_error` is meaningless. Maybe this does not affect the value of a
> default-constructed `std::error_code`, but surely it adds to the confusion.

I have absolutely no idea why Chris chose "http_error" for that enum. It
should have been "http_status" because those are the HTTP status codes
as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.

I am also highly unsure why you'd choose the error_code infrastructure
for these. It's not like almost all of the HTTP status codes (e.g. 306
Switch Proxy) can be any error condition except
resource_temporarily_unavailable.

Vinnie, what does Beast do?

>> Therefore calling o.error() on a valued outcome returning a default
>> constructed (null) error code is exactly correct: we return there is no
>> error.
>
>
> Only under some definition of "correct". By correct, you probably mean "not
> invoking UB" and "being compliant with your specification", but it is not
> intuitive at all that a function should return this or that when invoked in
> a context that is most likely a programmer's bug.

No, really a null error code meaning "no error here" is the widely held
interpretation. The reason I cannot categorically says it means no error
here is because the C++ standard guarantees that a null error code has
value 0 and the **system** category. And that's a defect, I think they
meant value 0 and the *generic* category, because if they had then the
standard would categorically guarantee a null error code means no error
here.

As it currently stands, the standard accidentally has made a null error
code mean "system dependent behaviour" which if it didn't mean "no error
here" would fundamentally wreck the Networking TS and the Filesystem TS.
Yay.

>> No undefined behaviour needed, and you can write your code using
>> Outcome with the hard assumption that o.error() will always return an
>> accurate view of the current state. No need to check .has_error(), or
>> anything like it.
>
> Modulo this situation with `http_error::ok == 200`. But with this you are
> also saying, the library provides two ways for checking if you have an
> error:
>
> o.has_error();  // option 1

This returns whether the outcome contains an error_code. Not whether
there is an error. A program might return an outcome containing a null
error_code. It probably is a bug, but Outcome can't reasonably enforce
how people misuse error_code, not least because of the C++ standard
defect above.

> o.error() == std::error_code{}; // option 2

Actually "!o.error()" but I think you meant to write that anyway.

This would be preferred over option 1. If the function returned an empty
outcome, this would throw, but that is probably a good thing.

> And by describing clear semantics for option 2, you are saying, it is
> equally fine to use option 2 for checking if we have an error. This
> encourages the usage of option 2, but I would not want my colleague
> programmers to start using this syntax, because I then cannot tell proper
> usages from inadvertent omissions. And reading the value of `error()`
> without having confirmed that some error occurred is almost surely a bug,
> even if you can assign a well defined semantics in `boost::outcome` for it.

Option 1 is misleading. Option 2 is correct, and means typing less
boilerplate e.g. if(o.error()) ... instead of if(o.has_error() &&
o.error()) ...

> I am fine with this scope disabled. However, `tribool` is still in scope,
> so I consider it valid to ask about its usefulness (especially given that
> all contexts in which it would prove useful is disabled). Maybe `tribool`
> should be also removed from the scope?

I believe you've convinced me to move tribool to under the advanced
operations macro. Thanks. Logged to
https://github.com/ned14/boost.outcome/issues/22

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
On Fri, May 19, 2017 at 6:42 AM, Niall Douglas via Boost
<[hidden email]> wrote:
>...
> I am also highly unsure why you'd choose the error_code
> infrastructure for these.

I agree, error_code for http status makes no sense at all for all the
reasons listed.

> ...
> Vinnie, what does Beast do?

This is the extent of it:
https://github.com/vinniefalco/Beast/blob/76f1084ecb010596d080ea96b9e47d15da649152/include/beast/http/message.hpp#L235

Thanks

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
2017-05-19 15:42 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> > Ok, I remember, both error_code and exception_ptr are
> > default-constructible. It is not immediately clear to me that a
> > default-constructed error_code represents a no-error condition.
>
> A null error code is widely held to mean "no error". Ditto with
> exception_ptr. In any ASIO completion handler, you'll see:
>
> void asio_handler(const error_code &ec, ...)
> {
>   if(ec)
>     handle_error();
>   ...
> }
>
> > In the blog
> > post by Chris Kohlhoff you refer to, he uses the following enum for
> > representing http error conditions:
> >
> > ```
> > enum class http_error
> > {
> >   continue_request = 100,
> >   switching_protocols = 101,
> >   ok = 200,
> >   ...
> >   gateway_timeout = 504,
> >   version_not_supported = 505
> > };
> > ```
> > Which implies that numeric value 200 means no-error and a value
> initialized
> > `http_error` is meaningless. Maybe this does not affect the value of a
> > default-constructed `std::error_code`, but surely it adds to the
> confusion.
>
> I have absolutely no idea why Chris chose "http_error" for that enum. It
> should have been "http_status" because those are the HTTP status codes
> as per https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
>
> I am also highly unsure why you'd choose the error_code infrastructure
> for these. It's not like almost all of the HTTP status codes (e.g. 306
> Switch Proxy) can be any error condition except
> resource_temporarily_unavailable.
>
> Vinnie, what does Beast do?
>
> >> Therefore calling o.error() on a valued outcome returning a default
> >> constructed (null) error code is exactly correct: we return there is no
> >> error.
> >
> >
> > Only under some definition of "correct". By correct, you probably mean
> "not
> > invoking UB" and "being compliant with your specification", but it is not
> > intuitive at all that a function should return this or that when invoked
> in
> > a context that is most likely a programmer's bug.
>
> No, really a null error code meaning "no error here" is the widely held
> interpretation. The reason I cannot categorically says it means no error
> here is because the C++ standard guarantees that a null error code has
> value 0 and the **system** category. And that's a defect, I think they
> meant value 0 and the *generic* category, because if they had then the
> standard would categorically guarantee a null error code means no error
> here.
>
> As it currently stands, the standard accidentally has made a null error
> code mean "system dependent behaviour" which if it didn't mean "no error
> here" would fundamentally wreck the Networking TS and the Filesystem TS.
> Yay.
>
> >> No undefined behaviour needed, and you can write your code using
> >> Outcome with the hard assumption that o.error() will always return an
> >> accurate view of the current state. No need to check .has_error(), or
> >> anything like it.
> >
> > Modulo this situation with `http_error::ok == 200`. But with this you are
> > also saying, the library provides two ways for checking if you have an
> > error:
> >
> > o.has_error();  // option 1
>
> This returns whether the outcome contains an error_code. Not whether
> there is an error.


Oh..


> A program might return an outcome containing a null
> error_code. It probably is a bug,


Interesting. I need to think about it. But my first response would be. If a
user cheats the system in this way, you should not try to rescue the
situation.


> but Outcome can't reasonably enforce
> how people misuse error_code, not least because of the C++ standard
> defect above.
>
> > o.error() == std::error_code{}; // option 2
>
> Actually "!o.error()" but I think you meant to write that anyway.
>
> This would be preferred over option 1. If the function returned an empty
> outcome, this would throw, but that is probably a good thing.
>

If I take your library, and also apply the "Sea of noexcept" approach, this
means that if I call `if(o.error())` to check if I have an error, I might
be triggering `std::terminate()`. Scary, but maybe this is ok, it you treat
empty outcome as a really abnormal state.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
> If I take your library, and also apply the "Sea of noexcept" approach, this
> means that if I call `if(o.error())` to check if I have an error, I might
> be triggering `std::terminate()`. Scary, but maybe this is ok, it you treat
> empty outcome as a really abnormal state.

As with all software libraries, you have to assume that the end user has
read the documentation and has some understanding of the standard library.

Niall

--
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
Le 18/05/2017 à 16:41, Niall Douglas via Boost a écrit :
>>
>> Also applies to other possible combinations such as expected<expected<T,
>> E1>, E2>, outcome<expected<T, E>>, and so on that can all in principle
>> be flattened into a single variant under the hood.
> Earlier Expected proposals had all sorts of useful semantics for nested
> expected. They have been removed from the most recent proposal.
>
Niall, are you talking of my proposals? If yes, could you recall me what
was there and was removed?

Best,
Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
Le 18/05/2017 à 16:50, Peter Dimov via Boost a écrit :

> Niall Douglas wrote:
>> I did consider returning optionals which might be empty instead of
>> throwing an exception.
>
> That's not what I was saying. I was saying that for
> result<optional<T>>, if you return by reference, you have to store an
> optional<T> object inside your result. But if you return by value, you
> don't have to store an optional and you can reuse your existing tag
> instead of adding the extra bool flag. That is, use variant<T, none,
> error_code> instead of variant<optional<T>, error_code>.
>
>> > Also applies to other possible combinations such as
>> expected<expected<T, > E1>, E2>, ...
>
> Similarly, expected<expected<T, E1>, E2> can use a single variant<T,
> E1, E2> for storage, if it can return by value. Return by reference
> requires it to have an actual expected<T, E1> object to which to bind
> the returned reference.
Do you believe that these function should return by value in the general
case or just for the nested expected?


Best,
Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: [outcome] Ternary logic -- need an example

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
Le 19/05/2017 à 09:01, Andrzej Krzemienski via Boost a écrit :

> 2017-05-19 0:49 GMT+02:00 Niall Douglas via Boost <[hidden email]>:
>
> No undefined behaviour needed, and you can write your code using
>> Outcome with the hard assumption that o.error() will always return an
>> accurate view of the current state. No need to check .has_error(), or
>> anything like it.
>>
> Modulo this situation with `http_error::ok == 200`. But with this you are
> also saying, the library provides two ways for checking if you have an
> error:
>
> o.has_error();  // option 1
> o.error() == std::error_code{}; // option 2
In the current standard expected proposal, we have an open point about a
has_error(exp, err) that return true if exp has no value and the errror
is the provided as parameter.
This has the advantage to not throwing.

> And by describing clear semantics for option 2, you are saying, it is
> equally fine to use option 2 for checking if we have an error. This
> encourages the usage of option 2, but I would not want my colleague
> programmers to start using this syntax, because I then cannot tell proper
> usages from inadvertent omissions. And reading the value of `error()`
> without having confirmed that some error occurred is almost surely a bug,
> even if you can assign a well defined semantics in `boost::outcome` for it.
>
>
>
>> Remember, expected<std::error_code, std::error_code> is legal, though
>> Outcome's Expected doesn't allow it.
Why?

Best,
Vicente

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
123
Loading...