Open ericniebler opened 9 months ago
Comment by ericniebler Thursday Oct 28, 2021 at 18:55 GMT
FWIW, I agree with this point. I think it's called "just
" right now because that has some precedence in Rx, but s/r is not an Rx library. I think the precedence in FP takes precedence, so to speak.
In this case, I think the
execution::just
is taking a value and creating asender
with the value. This sounds like thatexecution::just
is the implementation ofpure
for applicative functor or thereturn
implementation for a monad.
Just so. We obviously can't call it return
, and pure
is also loaded in imperative programming languages. In the past, I have pushed for ready
, which has some precedence.
ATTN: @kirkshoop
Comment by kirkshoop Friday Oct 29, 2021 at 18:45 GMT
I don't like just()
I don't like pure()
because it is not describing the operation it is describing a property of the operation that is not unique to this operation.
I don't like return()
because it is a keyword in C++ and does not describe the operation IMO.
I don't like ready()
because it is not describing the operation it is describing a property of the operation that is not unique to this operation.
Naming is hard.
If we must change the name, I would like to submit datum()
for consideration. I was thinking that this was like iota()
but singular.
da·tum /ˈdādəm,ˈdadəm/ noun 1. a piece of information. "the fact is a datum worth taking into account" 2. a fixed starting point of a scale or operation. "an accurate datum is formed by which other machining operations can be carried out"
Comment by huixie90 Saturday Oct 30, 2021 at 21:19 GMT
@kirkshoop
Why ready
is not describing the operation? In the old future
world, we have this https://en.cppreference.com/w/cpp/experimental/make_ready_future whose name is very obvious
I am listing more functions and the property of these functions:
then
: Functor's fmap
I think the name then
is great.
just
: Monad's return
I like ready
. datum
is a bit like iota
, which doesn't really mean too much to the developer and the developer has to know what it is to understand what it is.
let_*
: Monad's chain
, or mbind
, or >>=
I don't like let
very much because let
in functional programming languages is usually associated with let-expression
Comment by kirkshoop Sunday Oct 31, 2021 at 14:08 GMT
Why
ready
is not describing the operation? In the oldfuture
world, we have this https://en.cppreference.com/w/cpp/experimental/make_ready_future whose name is very obvious
It does produce a sender that will complete inline. There will be other senders that complete inline, must all of them include ready in the name? Ready state has nothing to do with stored values that are sent when start is called. just(), just_done(), just_error() refers to the data not the implementation detail shared with many other algos. I could add an overload of just() that takes a scheduler and a pack of values and just() still matches the implementation, but ready() would not, unless the scheduler was the inline scheduler.
then
: Functor'sfmap
I think the name
then
is great.
I am not a fan, but it does match its purpose. map or transform make more sense to me. map, because it is common in other libraries and transform because that was the name chosen for the STL equivalent for sequences. using a name that matches future libraries is misleading IMO, future is eager and tends to be always async, sender is lazy and it is common for a sender to be sync. IMO, transform and map make it easier to transfer knowledge from other libraries in other languages.
just
: Monad'sreturn
I like
ready
.datum
is a bit likeiota
, which doesn't really mean too much to the developer and the developer has to know what it is to understand what it is.
I agree about datum being esoteric. some, just, generate (for a value, to match generator for sequences), and values, are all less esoteric and still mostly accurate to the functionality. We disagree about ready..
let_*
: Monad'schain
, ormbind
, or>>=
I don't like
let
very much becauselet
in functional programming languages is usually associated with let-expression
I am sympathetic to this. I'll let @lewissbaker weigh in on let_
Comment by huixie90 Monday Nov 01, 2021 at 11:10 GMT
@kirkshoop
just(), just_done(), just_error() refers to the data not the implementation detail
This is a great point and I totally agree. But you've listed all three possible states of stored data, and all of their names contain the world just
, which makes the word just
a bit redundant. Why don't we just remove the word just
from their names?
"But if we remove word
just
from the functionjust
, it will be an empty name", asked Bob
I think by just(x)
, you really mean just_value(x)
. if we remove just
, we can call it value
.
"But
value
,done
,error
on their own are overly overloaded names", asked Dave
Perhaps we can put them into a namespace. In std::ranges
library, those view
factories and view
adaptors are put inside the namespace std::views
. Can do we something similar here, i.e. put sender
factories and sender
adaptors into a namespace std::senders
. So similar to std::views::xxx
creates a std::ranges::xxx_view
, we can have std::senders::xxx
to create a std::execution::xxx_sender
(or unspecified if we want to give the flexibility to the implementations). so here we can call them:
std::senders::value
std::senders::done
std::senders::error
"There will be other senders that will complete with value/done/error, must all of them include value/done/error in the name?", asked Kevin
Well, no. Let's look at std::ranges
again. We have std::views::empty
to create a view
with no elements and std::views::single
to create a view
with one element. Of course, we have views that can have 0 element (a default constructed std::string_view
for example) or one element. We don't need to add empty or single into their names.
"Why do you keep referring to
std::ranges
?
That is a great library created by some random guy on the internet. I hope one day he will read this post and make the range-v3 repo active again
Comment by lewissbaker Monday Nov 01, 2021 at 12:24 GMT
let_*
: Monad'schain
, ormbind
, or>>=
I don't likelet
very much becauselet
in functional programming languages is usually associated with let-expressionI am sympathetic to this. I'll let @lewissbaker weigh in on let_
The intention was for let()
to actually be equivalent to a variable declaration in a coroutine.
e.g.
auto x = co_await foo();
co_return co_await bar(x);
should be roughly equivalent to:
let_value(foo(), [](auto& x) { return bar(x); })
Comment by kirkshoop Monday Nov 01, 2021 at 21:50 GMT
The intention was for
let()
to actually be equivalent to a variable declaration in a coroutine.
I think that it can be argued that this is an implementation detail that is required in order to perform the primary concern, which is to be the general form of 'transform' that can emit value packs and even a completly different signal.
Comment by lewissbaker Wednesday Nov 03, 2021 at 02:15 GMT
My main intention for the let
algorithm was not a general form of transform (although it is indeed a more general form of transform) but rather as a tool for keeping the result of one sender alive while another sender that depends on those values is executing. i.e. to introduce a lifetime scope that is asynchronous.
Comment by kirkshoop Wednesday Nov 03, 2021 at 04:09 GMT
Yes, I do agree that was your intent and that it motivated the current name. I agree that it is a name that is coherent with the intent.
Based on my experience in other systems, I believe that names that describe the data and signal movement through an algorithm are a better fit then names that describe an implementation-strategy or implementation-detail.
I prefer transform() and map() to then() because then() describes the ordering of procedures in time, which while accurate is not describing the data manipulation and signal mapping.
I prefer value_from_done() to upon_done() for the same reason.
I prefer value_to() or sender_from_value() to let_value() because to me the data and signal flow is absent from the name let_value().
Anyway. Names will be changed in LEWG at some point. So I have been content to keep names stable for now. Perhaps that will turn out to have been a mistake.
Comment by villevoutilainen Wednesday Nov 03, 2021 at 06:48 GMT
Anyway. Names will be changed in LEWG at some point. So I have been content to keep names stable for now. Perhaps that will turn out to have been a mistake.
It's a terrible mistake to operate based on the expectation that LEWG will change the names. They will, given half a chance, so what you need to do as proposal authors is to not give them that chance.
Bake your names, make them coherent, decide them with rationale, and document that rationale. Otherwise your paper will end up being delayed for a discussion cycle or two to discuss names from a clean slate, and you're going to miss the deadlines you strive for. Or worse - you'll end up with names decided in a 5-minute beauty pageant with no rationale whatsoever.
Comment by Mrkol Wednesday Dec 29, 2021 at 01:51 GMT
I second the idea that just
must mention value
in it's name to specify the channel the signal will use. The motivation is as follows.
When working with vulkan, not all asynchronous computations can (sometimes should) be waited for from the CPU side, even though they do produce values on the GPU. They first have to be chained with other operations that can be waited for. Therefore the value
channel is not applicable for those types of computations, a new channel is needed that signals that the computation was started successfully and the next operation in a chain can be dispatched. Same sort of logic applies to optimization of io_uring operations: they can be chained and/or grouped in a more efficient way without awaiting for the intermediate computation to produce any values. E.g. let_value(when_all(write1, write2, ...), sync)
can be optimized to use a single syscall afaik. Adding a "just" sender to that group of writes cannot be done via the value channel in case of such optimizations, as other writes do not actually produce any values. To be precise, we can use just_value
, but that would presumably disable the optimized overload as one of the operations wouldn't be an io_uring one.
So generally speaking, we cannot assume that all senders/receivers use the value channel, hence robbing it of it's default/implicit status. Channels have to be spelled out explicitly so that confusion does not arise in advanced applications.
P.S.
How about sync_value
, sync_error
and sync_done
?
Comment by LeeHowes Wednesday Dec 29, 2021 at 19:23 GMT
I'm not sure what sync
as a prefix is meant to suggest.
Renaming just
seems reasonable. The idea that GPU tasks shouldn't complete with set_value
is a dubious justification, though. If not completing with the success signal, what should GPU tasks complete with? Clearly in customisation chains they should skip that set_value
call if they can, but in the absence of a customisation chain we aren't signally that a task has started, we are signalling that it has completed. set_value
is the right signal for that.
Comment by Mrkol Wednesday Dec 29, 2021 at 22:57 GMT
sync
as a prefix means that we construct a computation that synchronously completes with the specified signal and arguments.
About the GPU stuff, I was mistaken. There are no operations that can be chained but not awaited from the CPU. With like 1 exception (present operations cannot be neither waited for nor chained), all operations can both be chained GPU-side and waited CPU-side, although a properly designed system should wait CPU-side only once per frame, at the end of a big chain. Ensuring that the optimization happens is critical, therefore it might make sense to wrap operations into senders that cannot signal value
to enforce proper usage.
If not completing with the success signal, what should GPU tasks complete with?
started
signal with a handle to a GPU-side synchronization primitive that can be used to manually chain it. Although only practice can show whether this actually is a good idea or not.
In any case, all of this stuff is to be worked out in the coming years. Right now the point is that assuming value
as a default in naming conventions might backfire long-term, so it should be specified explicitly.
What about with_value
, with_error
, and with_stopped
?
auto work = with_value(1) | then([](int i) {...});
with_value(1)
can also serve double-duty as an adaptor that adds an additional value to the value channel:
auto work = with_value(1) | with_value("hello") | then([](int, const char*) {...});
(obviously in the above expression with_value(1) | with_value("hello")
can be shortened to with_value(1, "hello")
, but the predecessor sender can be anything.)
Issue by huixie90 Wednesday Oct 27, 2021 at 11:37 GMT Originally opened as https://github.com/NVIDIA/stdexec/issues/223
Sorry if this is not the right place to send feedback from the "general public".
The name
execution::just
is a little bit miss leading for a functional programming developer. Usuallyjust
is associated withMaybe
. In C++ context, I'd expectjust
to create astd::optional
.In this case, I think the
execution::just
is taking a value and creating asender
with the value. This sounds like thatexecution::just
is the implementation ofpure
for applicative functor or thereturn
implementation for a monad.