yeah, I immediately wondered about that with the std::vector monad, turning a single loop with three elemental functions into three. I wondered if the compiler can fuse the loops back again, otherwise there would be three passes over memory instead of one.
Things like std::ranges would do a single iteration with the functions applied all together on each element. Would translate more or less to the imperative way of doing it. I have attended a few talks recently mentioning that higher level, more expressive abstractions, actually end up allowing the compiler to carry out better optimizations. Ending up being faster in the end, contrary to what you would think. Either way, the talk was more going over the concepts, rather than worrying over performance. It should have been mentioned that if you're on a hot path within a low latency environment: always carry benchmarks when choosing ur poison. For a general purpose use case, however, you would win on average, if you go for the simpler, more expressive and understandable way of doing things.
The main reason to write an algorithm in a functional way: "So, here I wrote an imperative version of this [...] there's nothing particularly wrong with this code [...] but I didn't really feel very fulfilled writing this code..." ok...
I've recently started paying attention to the sender/receiver proposal. Receivers, representing some sort of callback, remind me a lot of continuation passing style. There is one of eric niebler's talk from cppcon 21 where he explains the nesting of operation_state, senders, and receivers, and it looks a lot like an explanation on CPS. Squinting your eyes hard enough, it is possible to see a bit of the Cont monad spill from senders/receivres. Haskell with Cont monad: factorialS :: Int -> Cont r Int factorialS x = reduceS (*) 1 (rangeS x) where rangeS x = return [1..x] reduceS op init xs = fmap (foldl op init) xs C++'s libunifex library: auto factorialS (int x) { return range_stream{1, x + 1} | reduce_stream(1, [](int acc, int x){ return acc * x; }); } One difference I've found so far is that recursive definitions of functions are not possible - skill issue? - due to senders being template expressions (the whole expression must be in the code at compile time). But recursive schemes can be achieved through sender algorithms anyways ;)