1

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 _implementation 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 for Sequences,

        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 a Sequence, which is easier:

    template <>
    struct Sequence<ext::std::vector_tag> {
        static constexpr bool value = true;
    };
    
  • Likewise, hana/ap.hpp also has an implementation for Sequences, but that uses hana::chain, as so it assumes that a Monad instance is defined in the first place.

    But what if I want to make std::vector an Applicative but not a Monad? I should customize ap 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 of lift 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 of lift 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 put std::plus and std::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?

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • `std::vector` cannot be a `hana::Sequence`. See this previous answer: https://stackoverflow.com/a/63419720/800347 – Jason Rice Mar 15 '21 at 19:10
  • Does you question boil down to storing different functions in a vector? It seems you've already answered it. – Jason Rice Mar 15 '21 at 19:15
  • @JasonRice I forgot that question. As regards this one, much of what I've written, I reasoned about it while crafting the question. I was kind of hoping that my deductions were wrong... – Enlico Mar 15 '21 at 19:21
  • Boost.Hana's interfaces are geared more towards satisfying their own self-consistent laws which allows you to make use of the free model when extending it. Idiomatic/convenient interfaces are easy to put on top of these. – Jason Rice Mar 15 '21 at 19:42

0 Answers0