Le 26/05/15 01:09, Niall Douglas a écrit :
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.
Neither result, holder nor value conveys the fact that the value can not be there. maybe<T> has already the connotation T or nothing as optional. There is no place for error_code and exception_ptr. If the type is not related to thread synchronization, here they are some alternative names: probably_value<T>, probable<T>?
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
which does most of the work of a monad already. monad<> has no knowledge of synchronisation. I am simply proposing subclassing monad
with a thread unsafe subclass called <insert name here>. It has almost the same API as future as it shares a common code implementation.
I had the impression that you wanted to me future a subclass of <insert name here>. I don't share this design.
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)
This is quite close to expected, isn't it?
* 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.
Ah, then the to be named type has the additional ready state. This is closer to a valid future with the addition of the promise (setting) interface.
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.
I have no problem. There are not proper and dirty monads. Purist will give you the good name. I see several nested monads. IIUC, the type doesn't has the invalid state, isn't it?
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
( [](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
( [](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::vectorstd::function to hold the continuations.
Why do you need to store store a vector of continuations. You have just one. What am I missing?
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. As others I'm waiting for a written proposal. But I think this is not on your plans.
Vicente