Boost logo

Boost :

Subject: Re: [boost] [next gen future-promise] What to call the monadic return type?
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2015-05-25 17:35:52


Le 25/05/15 11:37, Niall Douglas a écrit :
> Dear list,
>
> As AFIO looks likely to be finally getting a community review soon,
> I've made a start on a final non-allocating constexpr-collapsing next
> generation future-promise such that the AFIO you review is "API
> final". You may remember my experimentations on those from:
>
> http://boost.2283326.n4.nabble.com/Non-allocating-future-promise-td466
> 8339.html.
>
> Essentially the win is that future-promise generates no code at all
> on recent C++ 11 compilers unless it has to [1], and when it does it
> generates an optimally minimal set with no memory allocation unless T
> does so. This should make these future-promises several orders of
> magnitude faster than the current ones in the C++ standard and solve
> their scalability problems for use with things like ASIO. They also
> have major wins for resumable functions which currently always
> construct a promise every resumable function entry - these next gen
> future-promises should completely vanish if the resumable function
> never suspends, saving a few hundred cycles each call.
>
> Anyway, my earlier experiments were all very promising, but they all
> had one big problem: the effect on compile time. My final design is
> therefore ridiculously simple: a future<T> can return only these
> options:
>
> * A T.
> * An error_code (i.e. non-type erased error, optimally lightweight)
> * An exception_ptr (i.e. type erased exception type, allocates
> memory, you should avoid this if you want performance)
>
> In other words, it's a fixed function monad where the expected return
> is T, and the unexpected return can be either exception_ptr or
> error_code. The next gen future provides Haskell type monadic
> operations similar to Boost.Thread + Boost.Expected, and thanks to
> the constexpr collapse this:
>
> future<int> test() {
> future<int> f(5);
> return f;
> }
> test().get();
>
> ... turns into a "mov $5, %eax", so future<T> is now also a
> lightweight monadic return transport capable of being directly
> constructed.
>
> In case you might want to know why a monadic return transport might
> be so useful as to be a whole new design idiom for C++ 11, try
> reading
> https://svn.boost.org/trac/boost/wiki/BestPracticeHandbook#a8.DESIGN:S
> tronglyconsiderusingconstexprsemanticwrappertransporttypestoreturnstat
> esfromfunctions.
>
> However, future<T> doesn't seem named very "monadic",
Why? Because we don't have mbind or the proposed next?
> so I am
> inclined to turn future<T> into a subclass of a type better named.
Sub-classing should be an implementation detail and I don't see how a
future could be a sub-class of a class that is not asynchronous itself.
> Options are:
>
> * result<T>
sync or async result?
> * maybe<T>
we have already optional, isn't it?
>
> Or anything else you guys can think of? future<T> is then a very
> simple subclass of the monadic implementation type, and is simply
> some type sugar for promise<T> to use to construct a future<T>.
>
> Let the bike shedding begin! And my thanks in advance.

I'm not sure we are ready for bike shedding yet.

Some comments, not always directly related to your
future/promise/expected design, but about the interaction between future
and expected.

IMO, a future is not an expected (nor result or maybe). We can say that
a ready future behaves like an expected, but a future has an additional
state. Ready or not. The standard proposal and the future in
Boost.Thread has yet an additional state, valid or not.
So future has the following states invalid, not ready, valued or
exceptional.
We should be able to get an implementation that performs better if we
have less states. Would the future you want have all these states?

A future can store itself the shared state when the state is not shared,
I suppose this is your idea and I think it is a good one.Let me know if
I'm wrong. Clearly this future doesn't need allocators, nor memory
allocation.

We could have a conversion from an expected to a future. A future<T>
could be constructed from an expected<T>.

I believe that we could have an future operation that extracts an
expected from a ready future or that it blocks until the future is
ready. In the same way we have future<T>::shared() that returns a
shared_future<T>, we could have a future<T>::expected() function that
returns an expected<T> (waiting if needed).

If a continuation RetExpectedC returns an expected<C>, the decltype(f1)
could be future<C>

auto f1 = f.then(RetExpectedC);

We could also have a when_all/match that could be applied to any
probable valued type, including optional, expected, future, ...

optional<int> a;
auto f4 = when_all(a, f).match<expected<int>>(
         [](int i, int j ) { return 1; },
         [](...) make_unexpected(MyException) ; }
);

the type of f4 would be future<int>. The previous could be equivalent to

auto f4 = when_all(f).then([a](future<int> b) {
     return inspect(a, b.expected()).match<expected<int>>(
       [](int a, int b )
         { return a + b; },
       [](nullopt_ i, auto const &j )
         {
           return ???;
         }
     );
});

auto f4 = when_all(a, f).next(
         [](int i, int j ) { return 1; }
);

but the result of when_all will be a future.

The inspect(a, b, c) could be seen as a when_all applied to probably
valued instances that are all ready.

Best,
Vicente


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