5

What is the canonical way of updating a value (given a key and a new value) inside a boost::hana::map?

I tried using boost::hana::replace_if but it does not work on map as it is not a Functor - I can get it to work by converting the map to a tuple and then back to a map, but it sounds inefficient.

The alternative I'm currently using is calling map::erase_key followed by map::insert.

Is there any replace or update function designed for this purpose that I might be missing? Or is this the "canonical" way of updating a value?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416

2 Answers2

4

I don't think there is currently a canonical way to do this. If there are valid use cases for this, perhaps we could get a function in there to support it. The problem with hana::erase_key is that you will be creating a new map and then again with hana::insert. For the time being, using hana::unpack and then creating a new map is probably your best bet.

#include <boost/hana.hpp>

namespace hana = boost::hana;

template <typename NewPair>
struct replace_helper_t
{
  NewPair const& new_pair;

  template <typename Pair>
  constexpr decltype(auto) operator()(Pair&& p) const
  {
    return hana::if_(
      hana::equal(hana::first(new_pair), hana::first(p)),
      new_pair,
      std::forward<Pair>(p)
    );
  }
};

struct replace_t
{
  template <typename Map, typename NewPair>
  constexpr auto operator()(Map&& m, NewPair&& new_pair) const
  {
    return hana::unpack(std::forward<Map>(m),
      hana::on(
        hana::make_map,
        replace_helper_t<NewPair>{std::forward<NewPair>(new_pair)}
      )
    );
  }
};

constexpr replace_t replace{};

int main()
{
  auto my_map = hana::make_map(
    hana::make_pair(hana::int_c<7>, 7),
    hana::make_pair(hana::int_c<13>, 13),
    hana::make_pair(hana::int_c<23>, 23)
  );

  auto new_map = replace(my_map, hana::make_pair(hana::int_c<13>, 14.0f));

  BOOST_HANA_RUNTIME_ASSERT(new_map ==
    hana::make_map(
      hana::make_pair(hana::int_c<7>, 7),
      hana::make_pair(hana::int_c<13>, 14.0f),
      hana::make_pair(hana::int_c<23>, 23)
    )
  );
}

On another note, perhaps hana::map should be a Functor.

Jason Rice
  • 1,686
  • 1
  • 12
  • 17
  • 2
    I believe you need your `hana::hash(hana::first(new_pair)) == hana::hash(hana::first(p))` test to be `hana::equal(hana::first(new_pair), hana::first(p))`. Otherwise, if you replace the value associated to a key whose hash collides with any other key in the map, you'll replace the value for all the keys that share the same hash even if the keys are different. Basically you'll replace the value for all keys in the same bucket. And a nitpick, but using `hana::equal` might be better than `==` for generic code because it'll work with e.g. `std::integral_constant`. – Louis Dionne May 24 '16 at 22:06
  • 1
    Ah, that's right. I fixed my example and also changed the type of the value to demonstrate that it isn't simply mutating the value. – Jason Rice May 24 '16 at 22:39
2

Do you need to change the type of the value? If not, and if your value can be assigned to, you can use map[key] = new_value or equivalently hana::at_key(map, key) = new_value, as hana::at_key returns a reference.

If you need the type of the value to change, that's more tricky. We won't be able to do anything in-place, because the type of the map after replacing the value will be different from its type before replacing the value. Hence, we have to create a new map or some kind of modified view of that map (but views are not currently supported). Using erase_key and insert will indeed cause two maps to be created, which is inefficient. Instead, we could provide some kind of update function that would achieve the same, but would create only one copy of the map (the result). I believe we could also do better than erase_key + insert in terms of compilation times by providing our own function. I opened this issue to keep track of your request, since I think it is important to provide something like this; thanks.

Finally, I'd like to comment what Jason said:

On another note, perhaps hana::map should be a Functor.

hana::map could be made a Functor, but I'm not sure that transform could touch the keys of the map while still respecting the Functor laws. If that's not the case, you still wouldn't be able to say "replace the value with XXX if the key satisfies some predicate" using e.g. hana::replace_if. I tried (but failed so far) to find an example of functions that would break the laws if hana::transform basically transformed the sequence of underlying key/value pairs, but my intuition is that it's possible to find such an example. To be continued in issue #278.

Louis Dionne
  • 3,104
  • 1
  • 15
  • 35