As per the title, I'd like to understand how to make std::vector
an Applicative
but not a Monad
(well, not yet). This is just for the sake of exploring and understanding Boost.Hana and functional programming.
Functor
In hana/ext/std/vector.hpp
, the Functor
instance of std::vector
is commented out, however, it seems to work fine (compiler explorer) if I uncomment it or copy it in my code.
Applicative
However, there's no code in there to make it an Applicative
(and, in turn, neither for making it a Monad
), so I've looked into hana/concept/applicative.hpp
to understand what must be implemented to do it myself. It needs an _impl
ementation of ap
and lift
.
As regards
lift
, would the following be a good enough implementation?template <> struct lift_impl<ext::std::vector_tag> { template <typename T> static std::vector<std::decay_t<T>> apply(T&& value) { return {std::forward<T>(value)}; } };
On the other hand, since
hana/lift.hpp
contains a default implementation forSequence
s,template <typename S> struct lift_impl<S, when<Sequence<S>::value>> { template <typename X> static constexpr decltype(auto) apply(X&& x) { return hana::make<S>(static_cast<X&&>(x)); } };
I could alternatively just make
std::vector
aSequence
, which is easier:template <> struct Sequence<ext::std::vector_tag> { static constexpr bool value = true; };
Likewise,
hana/ap.hpp
also has an implementation forSequences
, but that useshana::chain
, as so it assumes that aMonad
instance is defined in the first place.But what if I want to make
std::vector
anApplicative
but not aMonad
? I should customizeap
myself. Is the following correct?template <> struct ap_impl<ext::std::vector_tag> { template<typename F, typename X, typename Y = std::decay_t<decltype((*std::declval<F>().begin())(*std::declval<X>().begin()))>> static constexpr std::vector<Y> apply(F&& f, X&& x) { std::vector<Y> result; result.reserve(f.size() * x.size()); for (auto const& f_ : f) { for (auto const& x_ : x) { result.emplace_back(f_(x_)); } } return result; } };
The result
At first sight I thought it works, because in this simple case (compiler explorer) it does give the correct result:
int main()
{
auto vplus = std::vector{std::plus<int>{}};
auto vec = hana::ap(
std::vector{std::plus<int>{}}, // wrapping 1 object in a vector
std::vector<int>{10,20},
std::vector<int>{1,2});
BOOST_HANA_RUNTIME_ASSERT(vec == std::vector<int>{11,12,21,22});
}
Disappointment
However I see some weakness in my solution:
- I'm using
std::vector
instead oflift
to wrap 1 function into a vector, which looks a bit non idiomatic, I believe; - As soon as I think about the case where using
std::vector
instead oflift
would be required, i.e. when I want to apply more than 1 function, I hit against the fact that, for instance, I can't putstd::plus
andstd::minus
in the same vector because they are function objects of different types.
For hana::tuple
(or std::tuple
if I defined the required instances for it), it's a piece of cake, because it can hold heterogeneous types:
// TUPLE EXAMPLE
auto tuple = hana::ap(
hana::make_tuple(std::plus<int>{}, std::minus<>{}),
hana::make_tuple(10,20),
hana::make_tuple(1,2));
BOOST_HANA_RUNTIME_ASSERT(tuple == hana::make_tuple(11,12,21,22,9,8,19,18));
My question
Is there a way to make std::vector
an Applicative
in such a way that the // TUPLE EXAMPLE
would work for std::vector
?
Probably std::function
and/or type erasure are necessary to accomplish the task?