[outcome] Ternary logic -- need an example

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
47 messages Options
123
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 16:03 GMT+02:00 Andrzej Krzemienski <[hidden email]>:

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

I can think of two ways of looking at the situation with a
value-initialized error code:

1. Class outocme<T> has an invariant: putting it into state where it stores
a value-initialized error code is a bug, close to undefined behavior, which
means, you can assert() inside and users and algorithms  can expect it
never happens.

2. Class outcome<T> can be in yet another state: "strange". So we have the
following states: valued, exceptional, errored, empty, and strange. And the
user when inspecting the state has to take into account all the five
possible states.

I guess the same applies to a null exception_ptr. My preference would be
#1, so that working with `outcome` is managable.

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
>>> 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.
>
> I can think of two ways of looking at the situation with a
> value-initialized error code:
>
> 1. Class outocme<T> has an invariant: putting it into state where it stores
> a value-initialized error code is a bug, close to undefined behavior, which
> means, you can assert() inside and users and algorithms  can expect it
> never happens.

I am afraid I don't understand. A value initialised error code is surely
a normal error code? Why would I need to assert stuff?

> 2. Class outcome<T> can be in yet another state: "strange". So we have the
> following states: valued, exceptional, errored, empty, and strange. And the
> user when inspecting the state has to take into account all the five
> possible states.

Every additional possible state generates more branches for the
compiler's optimiser to deal with. I would therefore reject any
additional possible states.

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-20 0:03 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> >>> 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.
> >
> > I can think of two ways of looking at the situation with a
> > value-initialized error code:
> >
> > 1. Class outocme<T> has an invariant: putting it into state where it
> stores
> > a value-initialized error code is a bug, close to undefined behavior,
> which
> > means, you can assert() inside and users and algorithms  can expect it
> > never happens.
>
> I am afraid I don't understand. A value initialised error code is surely
> a normal error code? Why would I need to assert stuff?
>
> > 2. Class outcome<T> can be in yet another state: "strange". So we have
> the
> > following states: valued, exceptional, errored, empty, and strange. And
> the
> > user when inspecting the state has to take into account all the five
> > possible states.
>
> Every additional possible state generates more branches for the
> compiler's optimiser to deal with. I would therefore reject any
> additional possible states.
>

I am not trying to propose any new logic here. I am trying to assign some
mental model to what `outcome<T>` currently does. Consider: some funciton
`f` is supposed to compute an `int`. But the computation may fail, in this
case I want the function to return the reason why it failed. Also, it is
possible that I will get some "internal error". For this reason funcio `f`
returns `result<T>`:

```
result<T> r = f();
```
Now, `r` can represent the following states:

```
if (r.has_value()) // value successfully computed
  {}
else if (r.empty()) // internal error
 {}
else if (r.error()) // reason why f failed
 {}
else if (r.has_error() && !r.error()) // what is that???
 {}
```

The last case is neither a value, nor a reason for failure, nor an
"internal error". How should a caller interpret it? Two possible answers:

1. There is a fourth state with no intuitive or sane interpretation.
2. Assume this never happens. If it happens it is a bug in calle site that
needs to be fixed.

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
On 05/19/2017 03:42 PM, Niall Douglas via Boost wrote:

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

Agreed.

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

When I perform an asynchronous HTTP operation that returns an error_code
then I prefer that the error_code contains a unified response, i.e. an
HTTP status code, a system error code (e.g. network problem), or an
internal library error code.

We can use the same solution as C++11 (std::system_category versus
std::generic_category)

   1. Define a native HTTP category that contains the raw HTTP status
      integer value.
   2. Define a generic HTTP category that contains enums for the HTTP
      status codes.
   3. Implement the equivalent() function on the native HTTP category
      that maps the native value to the enum.
   4. Mark the generic HTTP enum as an error condition.

That enables you to propagate the native value, so there is no loss of
information in case the HTTP status contains a hitherto unknown code,
and to compare it in a more user-friendly value using the enum.

You can find a tutorial here:

   http://breese.github.io/2017/05/12/customizing-error-codes.html

_______________________________________________
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 Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost
<[hidden email]> wrote:
> When I perform an asynchronous HTTP operation that returns an error_code
> then I prefer that the error_code contains a unified response, i.e. an
> HTTP status code, a system error code (e.g. network problem), or an
> internal library error code.

A nice thing about HTTP (if there is any such thing) is that the HTTP
"message" can be modeled as a self contained object.

The low-level result of the asynchronous operation (for example
boost::asio::error::connection_reset) reflects metadata about the HTTP
message and should not be part of that container.

_______________________________________________
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
On Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost
<[hidden email]> wrote:
> When I perform an asynchronous HTTP operation that returns an error_code
> then I prefer that the error_code contains a unified response, i.e. an
> HTTP status code, a system error code (e.g. network problem), or an
> internal library error code.

A nice thing about HTTP (if there is any such thing) is that the HTTP
"message" can be modeled as a self contained object.

The low-level result of the asynchronous operation (for example
boost::asio::error::connection_reset) reflects metadata about the HTTP
message and should not be part of that container.

However, the status code is part of the message and therefore should
be part of the container. I do not think it should be treated as
metadata, nor represented using error_code.

_______________________________________________
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
On Sat, May 20, 2017 at 3:12 AM, Bjorn Reese via Boost
<[hidden email]> wrote:
> When I perform an asynchronous HTTP operation that returns an error_code
> then I prefer that the error_code contains a unified response, i.e. an
> HTTP status code, a system error code (e.g. network problem), or an
> internal library error code.

I should have taken a bit more time before sending my reply -
apologies. I am bursting with things to say about this subject as you
can imagine so I will elaborate.

Consider the following:

    f is a function
    r is an unspecified result of performing some calculation on m
    m is an HTTP response message

    r = f(m)

If the calculation of r depends on the HTTP status, your proposal to
shove the status into an error_code would require:

    r = f(m, ec)

Where ec is an error_code. This introduces a problem, because now ec
can contain things that are not HTTP status codes, such as
boost::asio::error::connection_reset. f might not want or need to
handle metadata about the TCP/IP connection or other errors that are
not status codes. Now consider this:

    g is a function
    m' is an HTTP response message

    m' = g(m)

This example uses a functional style where a message m is transformed
by a function g into a new message. If we put the HTTP status in an
error_code not part of m or m', the signature would look like this:

    pair<m', error_code> g(m, error_code);

This signature imposes unnecessary burden on callers. It is also
confusing. Does the error_code in the return value indicate that g can
fail?

I hope readers are convinced that HTTP requests and responses should
be modeled as self-contained objects with all necessary fields
prescribed by rfc7230, and that the HTTP status should be neither
separate nor stored in an error_code.

_______________________________________________
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
Vinnie Falco wrote:

> However, the [HTTP] status code is part of the message and therefore
> should be part of the container. I do not think it should be treated as
> metadata, nor represented using error_code.

Consider this high-level function

    result<vector<char>> get( string const& url );

used like this

    auto r = get( "https://example.com/data.zip" );

This function can fail in many different ways, starting from the URL parser
(invalid URL), passing through the possibility of an invalid/unrecognized
URL scheme, a host that cannot be resolved, a TCP address that can't be
connected to, and finally, a 404 HTTP error code.

All these things should come back as std::error_code. There's no HTTP
message here on this layer to contain the status.


_______________________________________________
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 Sat, May 20, 2017 at 7:28 AM, Peter Dimov via Boost
<[hidden email]> wrote:
> All these things should come back as std::error_code. There's no HTTP
> message here on this layer to contain the status.

For this interface, I agree, but Bjorne made a blanket statement:

>When I perform an asynchronous HTTP operation that returns an error_code
>then I prefer that the error_code contains a unified response,

Obviously I don't agree with that; Beast has asynchronous HTTP
operations which return error_code, and they do not communicate the
response-status in that error_code. Nor should they, for the reasons
stated earlier.

_______________________________________________
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
On 05/20/2017 04:17 PM, Vinnie Falco via Boost wrote:

> I hope readers are convinced that HTTP requests and responses should
> be modeled as self-contained objects with all necessary fields
> prescribed by rfc7230, and that the HTTP status should be neither
> separate nor stored in an error_code.

Not really, but that is the subject for a review of a different library.

_______________________________________________
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 am not trying to propose any new logic here. I am trying to assign some
> mental model to what `outcome<T>` currently does. Consider: some funciton
> `f` is supposed to compute an `int`. But the computation may fail, in this
> case I want the function to return the reason why it failed. Also, it is
> possible that I will get some "internal error". For this reason funcio `f`
> returns `result<T>`:
>
> ```
> result<T> r = f();
> ```
> Now, `r` can represent the following states:
>
> ```
> if (r.has_value()) // value successfully computed
>   {}

No, the correct interpretation here is that the result returned has a
value, not that the value was successfully computed.

> else if (r.empty()) // internal error
>  {}
> else if (r.error()) // reason why f failed
>  {}
> else if (r.has_error() && !r.error()) // what is that???
>  {}

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.

> The last case is neither a value, nor a reason for failure, nor an
> "internal error". How should a caller interpret it? Two possible answers:
>
> 1. There is a fourth state with no intuitive or sane interpretation.
> 2. Assume this never happens. If it happens it is a bug in calle site that
> needs to be fixed.

You've got to remember all these potential state flow paths never occur
in any real world code base. You'll even see the compiler eliding in the
assembler output any handling of an empty state being returned if it can
see that that is not possible.

Same goes for if you return only ever a value. The result<T> effectively
"disappears" in the assembler generated, and it is as if you returned a
T directly.

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-20 20:01 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> > I am not trying to propose any new logic here. I am trying to assign some
> > mental model to what `outcome<T>` currently does. Consider: some funciton
> > `f` is supposed to compute an `int`. But the computation may fail, in
> this
> > case I want the function to return the reason why it failed. Also, it is
> > possible that I will get some "internal error". For this reason funcio
> `f`
> > returns `result<T>`:
> >
> > ```
> > result<T> r = f();
> > ```
> > Now, `r` can represent the following states:
> >
> > ```
> > if (r.has_value()) // value successfully computed
> >   {}
>
> No, the correct interpretation here is that the result returned has a
> value, not that the value was successfully computed.
>
> > else if (r.empty()) // internal error
> >  {}
> > else if (r.error()) // reason why f failed
> >  {}
> > else if (r.has_error() && !r.error()) // what is that???
> >  {}
>
> 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.

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

(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)

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`"?

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

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?


>
> > The last case is neither a value, nor a reason for failure, nor an
> > "internal error". How should a caller interpret it? Two possible answers:
> >
> > 1. There is a fourth state with no intuitive or sane interpretation.
> > 2. Assume this never happens. If it happens it is a bug in calle site
> that
> > needs to be fixed.
>
> You've got to remember all these potential state flow paths never occur
> in any real world code base. You'll even see the compiler eliding in the
> assembler output any handling of an empty state being returned if it can
> see that that is not possible.
>
> Same goes for if you return only ever a value. The result<T> effectively
> "disappears" in the assembler generated, and it is as if you returned a
> T directly.
>

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".

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


_______________________________________________
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
> > Anyway, the short description of function `error()` says, "Returns any
> > errored state in the transport, throwing an exception if empty."

Incidentally, "errored" and "valued" don't make sense to me in this context.
It's not an errored result or a valued result, nor an errored outcome or a
valued outcome, it's simply an error result or a value result, an error
outcome or a value outcome.

(The result or the outcome can still be highly valued by the user, of
course.)

(Exceptional outcome looks correct.)


_______________________________________________
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 20/05/2017 à 23:28, Andrzej Krzemienski via Boost a écrit :

> 2017-05-20 20:01 GMT+02:00 Niall Douglas via Boost <[hidden email]>:
>
>>> I am not trying to propose any new logic here. I am trying to assign some
>>> mental model to what `outcome<T>` currently does. Consider: some funciton
>>> `f` is supposed to compute an `int`. But the computation may fail, in
>> this
>>> case I want the function to return the reason why it failed. Also, it is
>>> possible that I will get some "internal error". For this reason funcio
>> `f`
>>> returns `result<T>`:
>>>
>>> ```
>>> result<T> r = f();
>>> ```
>>> Now, `r` can represent the following states:
>>>
>>> ```
>>> if (r.has_value()) // value successfully computed
>>>    {}
>> No, the correct interpretation here is that the result returned has a
>> value, not that the value was successfully computed.
>>
>>> else if (r.empty()) // internal error
>>>   {}
>>> else if (r.error()) // reason why f failed
>>>   {}
>>> else if (r.has_error() && !r.error()) // what is that???
>>>   {}
>> 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.
>
> 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
>
> (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)
>
> 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`"?
>
> 2. "any errored state"? -- not the specific error state previously set
> inside `outcome`?
>
> 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?
>
>
>>> The last case is neither a value, nor a reason for failure, nor an
>>> "internal error". How should a caller interpret it? Two possible answers:
>>>
>>> 1. There is a fourth state with no intuitive or sane interpretation.
>>> 2. Assume this never happens. If it happens it is a bug in calle site
>> that
>>> needs to be fixed.
>> You've got to remember all these potential state flow paths never occur
>> in any real world code base. You'll even see the compiler eliding in the
>> assembler output any handling of an empty state being returned if it can
>> see that that is not possible.
>>
>> Same goes for if you return only ever a value. The result<T> effectively
>> "disappears" in the assembler generated, and it is as if you returned a
>> T directly.
>>
> 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".
>
+1 for all

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
2017-05-21 0:13 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> >> 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.'
>

Yes, we agree here, as to current behavior of `o.error()`


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

```


> 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.
It is my understanding that `outcome<>` also offers other things:

   - being able to store either an error code or exception_ptr
   - other convenience interface without "contract widening", like
   BOOST_OUTCOME_TRY
   - other performance improvements

If I want to use the above advantages, but I dislike "contract widening"
(or "wooly semantics"), you leave me with no option.


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


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


_______________________________________________
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-22 22:52 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

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

_______________________________________________
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 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?

I'm saying that it *might* be, if reviewers think it should be so.

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

I did have documentation on how to do that, but one of the rounds of
Reddit review eliminated it because it assumed too much of the average
end user. Their argument was that 95% of end users don't want to know
that stuff, and keeping such documentation was a major turn off for
potential end users.

There was also a valid argument by them that if you are in the 5% who do
want to know, you'd end up studying the (well commented) source code
anyway, and anything in the public docs is likely redundant.

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

Here is the policy class for expected<T, E> personality:
https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1.0/detail/expected_policy.ipp#L407

Would you say it fairly self explanatory for those knowledgeable of CRTP?

The storage policy class can be found at
https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1.0/monad.hpp#L553.
It essentially wraps a "value_storage" which is the variant storage.
That is a standalone implementation found at
https://github.com/ned14/boost.outcome/blob/master/include/boost/outcome/v1.0/value_storage.hpp.

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-24 16:38 GMT+02:00 Niall Douglas via Boost <[hidden email]>:

> >> 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?
>
> I'm saying that it *might* be, if reviewers think it should be so.
>
> > 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.
>
> I did have documentation on how to do that, but one of the rounds of
> Reddit review eliminated it because it assumed too much of the average
> end user. Their argument was that 95% of end users don't want to know
> that stuff, and keeping such documentation was a major turn off for
> potential end users.
>
> There was also a valid argument by them that if you are in the 5% who do
> want to know, you'd end up studying the (well commented) source code
> anyway, and anything in the public docs is likely redundant.
>
> > 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`.
>
> Here is the policy class for expected<T, E> personality:
> https://github.com/ned14/boost.outcome/blob/master/
> include/boost/outcome/v1.0/detail/expected_policy.ipp#L407
>
> Would you say it fairly self explanatory for those knowledgeable of CRTP?
>
> The storage policy class can be found at
> https://github.com/ned14/boost.outcome/blob/master/
> include/boost/outcome/v1.0/monad.hpp#L553.
> It essentially wraps a "value_storage" which is the variant storage.
> That is a standalone implementation found at
> https://github.com/ned14/boost.outcome/blob/master/
> include/boost/outcome/v1.0/value_storage.hpp.
>
>
I am not entirely satisfied with this reply. I need this documented not
because I do not know how to do it (well, this also), but because this way
you commit to supporting this form of customization in subsequent releases.
Sure, I can peek into the code, but I do not know if it is something you
intend to keep in the new releases, or if it is just an implementation
detail, or even a bug, which you will "fix" in the next release.

I am also not sure I belong to this 5%. I complained about the wide
contracts, and your response was I should write my own customization. I
read this as saying it is relatively easy to customize. But now I am
hearing I need to look into the source files and copy that. But this is
like giving me a number of concrete models of a concept and expecting of me
to synthesize the concept. I cannot do it correctly, because there is a
number of ways to do that. I may arrive at one that happens to work in this
release, but will stop in the next.

On the other hand, I understand your position. First invest in the parts
that bring value to 95% of users, and only then proceed to the remaining
problems.

Regards,
&rzej;

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