Boost logo

Boost :

Subject: Re: [boost] [next gen future-promise] What to call the monadic return type?
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2015-05-25 19:09:57


On 25 May 2015 at 23:35, Vicente J. Botet Escriba wrote:

> > However, future<T> doesn't seem named very "monadic",
> Why? Because we don't have mbind or the proposed next?

No, merely the name "future<T>"!

future<T> is fine for a thread safe monad. But for a faster, thread
unsafe one, I was asking here purely for names.

Names suggested so far are maybe, result, holder, value.

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

It's not a problem. My future<T> subclasses an internal
implementation type monad<T, consuming> which does most of the work
of a monad already. monad<> has no knowledge of synchronisation.

I am simply proposing subclassing monad<T, consuming> with a thread
unsafe subclass called <insert name here>. It has almost the same API
as future as it shares a common code implementation.

> > Options are:
> >
> > * result<T>
> sync or async result?

A result<T> has most of the future<T> API with the set APIs from
promise<T>. So you might do:

result<int> v(5);
assert(v.get()==5);
v.set_value(6);
assert(v.get()==6);
v.set_exception(foo());
v.get(); // throws foo
v.set_error(error_code);
v.get(); // throws system_error(error_code)

> > * maybe<T>
> we have already optional, isn't it?

True. But I think a monadic transport supersets optional as it can
have no value. So:

result<int> v;
assert(!v.is_ready());
assert(!v.has_value());
v.get(); // throws no_state.

The compiler treats a default initialised monad identically to a void
return i.e. zero overhead.

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

My aim is to track, as closely as possible, the Concurrency TS.
Including all its bad decisions which aren't too awful. So yes, I'd
keep the standard states. I agree absolutely that makes my monad not
expected, nor even a proper monad. I'd call it a "bastard C++ monad
type" of the kind purists dislike.

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

Yes, either the promise or the future can keep the shared state. It
always prefers to use the future where possible though.

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

Absolutely agreed.

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

expected integration is very far away for me. I'm even a fair
distance from continuations, because getting a std::vector to
constexpr collapse is tricky, and you need a
std::vector<std::function> to hold the continuations. My main goal is
getting AFIO past peer review for now.

However, I have been speaking with Gor @ Microsoft, and if I
understand how his resumable functions implementation expands into
boilerplate then non-allocating future-promise means he can simplify
his implementation quite considerably, and improve its efficiency. No
magic tricks for future shared state allocation needed anymore.

I'll get my prototype working enough to submit to Microsoft first. If
Gor is interested, he'll need to shepherd getting the MSVC optimiser
to stop being so braindead when faced with this pattern. Gabi also
told me at C++ Now to send this problem to him too as I was gently
teasing him about how badly MSVC does here compared to everything
else, and he said he'd do what he could to make them fix the
optimiser.

Niall

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



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