C 23 std::optional is finally getting some very useful additions.
Since my knowledge of FP is very primitive I am wondering what is the syntax for the following two operations(that according to my googling are 2 basic monadic operations):
- monadic bind
- monadic return
My best guesses are:
monadic bind is transform
monadic return is just C 17 std::optional constructor(8)
CodePudding user response:
mbind (doesn't exist, I'm mimicking Haskell's >>=)
In C -like pseudo-code, the monadic binding, let's call it mbind, should have such a syntax:
C<U> mbind(C<T>, std::function<C<U>(T)>);
i.e. it should take a monad C on some type T, a function that "pulls the inside of that monad out" and turns into a monad C on a (not necessarily) different type U, C<U>, and gives you back that C<U>.
transform (the free one)
The transform you mention, first of all, is a member function, and it has a signature kind of this
C<U> C<T>::transform(std::function<U(T)>);
but let's rewrite its signature as it would be if it was a free function:
C<U> transform(C<T>, std::function<U(T)>);
so as you see it takes a C<T> and applies a function from T to U right inside the functor C, thus resulting in a C<U>.
So there's a difference.
To better understand what the difference is, try passing transform a C<T> and a function with signature the one that mbind expects, std::function<C<U>(T)>.
What do you get? Remember that transform applies the function "right inside the functor, without pulling anything out", so you get a C<C<U>>, i.e. one more functorial layer.
mbind, instead, with the same two arguments, would have given you a C<U>.
And how can you go from what transform(x, f) returns to what mbind(x, f) returns, i.e. from a C<C<U>> to a C<U>? You can just flatten/join/collapse/whatever-you-want-to-name-it the two functorial levels, via what's called join in Haskell and flatten in some other language.
Obviously if you can do that (and you can for C = std::optional), than those "functorial layers" were actually "monadic layers".
So is there an mbind-like thing?
As suggested in the other answer there's the and_then member function.
Is it the non-existing mbind I've mentioned above? Not quite, but it is quite: .and_then is to mbind what .transform is to transform.
And where's flatten/join, by the way?
You might wonder if there's this utility in C 23. I have absolutely no clue, I'm barely aware of what C 20 offers.
But some knowledge of Haskell helps me give you an idea of why it might be unnecessary to add it to the standard.
What happens if you do this?
auto optOfOpt = std::make_optional(std::make_optional(3));
auto whatIsThis = optOfOpt.and_then(std::identity);
Yeah, that's it.
CodePudding user response:
Not quite.
In Haskell syntax, bind is of the form m a -> (a -> m b) -> m b, which corresponds to satisfying this concept (for all A, B, F)
template <class Fn, class R, class... Args>
concept invocable_r = std::is_invocable_r_v<R, Fn, Args...>;
template <class Bind, template <class> M, class A, class B, invokable_r<M<B>, A> F>
concept bind = invocable_r<Bind, M<B>, M<A>, F>;
That's and_then (with this bound to the first argument). transform is fmap (with this bound to the second argument), which is the Functor operation (all Monads are Functors).
fmap is of the form f a -> (a -> b) -> f b.
template <class Fmap, template <class> M, class A, class B, invokable_r<B, A> F>
concept fmap = invocable_r<Fmap, M<B>, M<A>, F>;
The difference is in the return type of the function being bound or mapped.
Another example of this distinction is .NET's linq Select vs SelectMany
Another nitpick is that the monad laws discuss expressions, not statements, so you'd have to wrap the constructor in a function.
