What's the difference between C++23's optional::transform and optional::and_then?
Asked Answered
E

3

15

C++23 adds some "monadic-style" functionality regarding optionals, as methods of optional<T>:

optional<T>::and_then() (and ignoring qualifiers of this):

template<class F> constexpr auto and_then(F&& f); 

Returns the result of invocation of f on the contained value if it exists. Otherwise, returns an empty value of the return type.

optional<T>::transform() (and ignoring qualifiers of this):

template<class F> constexpr auto transform(F&& f);

Returns an std::optional that contains the result of invocation of f on the contained value if *this contains a value. Otherwise, returns an empty std::optional of such type.

So, aren't these two functions doing the same thing?

Exigible answered 26/11, 2022 at 9:30 Comment(2)
I'm surprised thatn when you asked this question, using that title wording and those tags... the existing answered question didn't popup among the suggested: What are monadic bind and monadic return for C++23 optional? Because beside the slight difference in perspective (what's the difference between X and Y vs what is Y), it's an exact duplicate imho.Foreignism
@Foreignism I think the Haskell perspective and the mentions of Haskell in the answer to the other question make it too cluttered and distracting as an answer to this one. I have added a link to that answer in my own answer below.Exigible
E
22

Suppose you have an optional<T1> value.

  • transform() lets you pass your optional to functions like T2 foo(T1 x);
  • and_then() lets you pass your optional to functions like optional<T2> bar(T1 x);

... and get an optional<T2> at the end. So, transform() will "unbox", apply the function, then "re-box" the function's output into an optional, while and_then() will not do the "re-boxing" at the end, expecting the function to return a boxed value on its own.

You might also think of transform like std::transform: You apply a function to "each element"; in a container, it may be any number of elements, and in an optional, it's either 0 or 1 elements.

See also this question.

Exigible answered 26/11, 2022 at 9:30 Comment(4)
@StoryTeller-UnslanderMonica: You're right, but - then my answer doesn't really explain why this even makes sense. I mean, if we get an optional both with and_then and with transform - what's the use of having both of them?Exigible
Fluent API is enough reason for me personally.Pergola
@Exigible The point of monadic operations is to keep the object as an optional, and only unwrap them after you've finished all operations. and_then expects functions that were written for optional, where transform makes non-optional functions usable in optional context.Hurff
In case anyone might come for the std::expected side, and_then is actually and_then_when_valid, or_else is actually or_else_when_invalid taking expected<T1,E2> fred(E1 x). In other words, and_then automatically passes through E1 (nullopt_t) and or_else automatically passes through T1. Yes, transform_error and or_else are the std::exptected's the counterpart of transform and and_then and their parameter function take a parameter of E1 instead of T1.Prerequisite
L
1

and_then only takes functions of type T -> std::optional<U> (whereas transform is free to take functions returning any type).

If you just transform with such a function you will get a std::optional<std::optional<U>>.

and_then just then flattens the std::optional<std::optional<U>> into an std::optional<U>.

That's all monads are: transform composed with a type level flatten. Think range<range<U>> and future<future<U>>.

Lavabo answered 27/11, 2022 at 7:35 Comment(3)
"That's all monads are". Not quite. You can write such a thing for map (with a fixed key type), but map is not a monad. Not that it matters...Philanthropist
@n.m. thanks I assume you mean std::map<T, std::map<T, U>> can be flattened but isn't a monad. I'll think about it laterLavabo
Yes that's what I mean.Philanthropist
P
0

and_then is monadic bind aka flatmap aka >>= and transform is functorial map.

One can express map in terms of bind generically, but not the other way around, because a functor is not necessarily a monad. Of course the particular monad of std::optional can be opened at any time, so both functions are expressible in terms of ordinary pre-C++23 std::optional API. Thus the question why the C++ standard defines both functions is no better than the question why it defines any of the two. Perhaps the Standard wishes to give the programmer a standard functorial interface and a standard monadic interface independently. Either interface is useful and important on its own right.

Philanthropist answered 26/11, 2022 at 19:32 Comment(7)
C++ programmers have different notions associated with the words "bind", "map" and "flatmap"; and the operator >>= does not exist in C++. Please explain all of this in your answer, or provide appropriate links.Exigible
@Exigible I have considered this idea and decided against it. This answer is not, and doesn't want to be, an introduction to functional programming.Philanthropist
But the target audience of your answer does not understand what you are talking about.Exigible
@Exigible I am of a somewhat different opinion about my target audience. If I'm wrong, anyone can write a better answer or edit this one.Philanthropist
@Exigible Technically, C++ does have an operator called >>=, and I wouldn't be surprised if operator overloading were flexible enough to let you make it mean and_then.Heteroousian
@JosephSible-ReinstateMonica: Indeed, C++ has a "left-shift-equals" operator. But that's not the operator >>= n.m. was referring to. However - that is an interesting idea.Exigible
This reply is only just more cryptic than the standard wording. It is not comprehensible for average C++ programers, even some of the elite. Nobody can edit such an answer propperly, except the author himself.Duffy

© 2022 - 2024 — McMap. All rights reserved.