diff --git a/immer/extra/persist/README.rst b/immer/extra/persist/README.rst index 566c5c39..845ac78b 100644 --- a/immer/extra/persist/README.rst +++ b/immer/extra/persist/README.rst @@ -29,6 +29,7 @@ of the original containers would be lost: we will have multiple independent vect This library allows to apply the transformation function directly on the nodes which allows to preserve structural sharing. Additionally, it doesn't matter how many times a node is reused, the transformation needs to be performed only once. +.. _first-example: First example ------------- @@ -145,6 +146,78 @@ As you can see in the resulting JSON, nested types are also serialized with pool is serialized instead of its content. +Transformations with pools +-------------------------- + +Suppose, we want to apply certain transforming functions to the ``immer`` containers inside of a large document type. +The most straightforward way would be to simply create new containers with the new data, running the transforming +function over each element. However, this approach has some disadvantages: + +- All new containers will be independent, no structural sharing will be preserved and the same data would be stored + multiple times. +- The transformation would be applied more times than necessary when some of the data is shared. Example: one vector + is built by appending elements to the other vector. Transforming shared elements multiple times could be + unnecessary. + +Let's look at a simple case using the document from the :ref:`first-example`. The desired transformation would be to +multiply each element of the ``immer::vector`` by 10. + +First, the document value would be created in the same way: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-prepare-value + :end-before: intro/end-prepare-value + +The next component we need is the pools of all the containers from the value: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-get_auto_pool + :end-before: end-get_auto_pool + +The ``get_auto_pool`` function returns the output pools of all ``immer`` containers that would be serialized using +pools, as controlled by the policy. Here we use the default policy ``hana_struct_auto_policy`` which will use pools for +all ``immer`` containers inside of the document type which must be a ``hana::Struct``. + +The other required component is the ``conversion_map``: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-conversion_map + :end-before: end-conversion_map + +This is a ``hana::map`` that describes the desired transformations to be applied. The key of the map is an ``immer`` +container and the value is the function to be applied to each element of the corresponding container type. In this case, +it will apply ``[](int val) { return val * 10; }`` to each ``int`` of the ``vector_one`` type, we have two of those in +the ``document``. + +Having these two parts, we can create the new pools with the transformations: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-transformed_pools + :end-before: end-transformed_pools + +At this point, we can start converting the ``immer`` containers and create the transformed document value with them, +``new_value``: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-convert-containers + :end-before: end-convert-containers + +In order to confirm that the structural sharing has been preserved after applying the transformations, let's serialize +the ``new_value`` and inspect the JSON: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-save-new_value + :end-before: end-save-new_value + +And indeed, we can see in the JSON that the node ``{"key": 2, "value": [10, 20]}`` is reused in both vectors. + + Policy ------ diff --git a/test/extra/persist/test_for_docs.cpp b/test/extra/persist/test_for_docs.cpp index 4ce12f31..5e0c168c 100644 --- a/test/extra/persist/test_for_docs.cpp +++ b/test/extra/persist/test_for_docs.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "utils.hpp" #include @@ -265,3 +266,68 @@ TEST_CASE("Custom policy", "[docs]") // include:end-doc_2-load REQUIRE(value == loaded_value); } + +TEST_CASE("Transform into same type", "[docs]") +{ + const auto v1 = vector_one{1, 2, 3}; + const auto v2 = v1.push_back(4).push_back(5).push_back(6); + const auto value = document{v1, v2}; + + // include:start-get_auto_pool + const auto pools = immer::persist::get_auto_pool(value); + // include:end-get_auto_pool + + // include:start-conversion_map + namespace hana = boost::hana; + const auto conversion_map = hana::make_map(hana::make_pair( + hana::type_c, [](int val) { return val * 10; })); + // include:end-conversion_map + + // include:start-transformed_pools + auto transformed_pools = + immer::persist::transform_output_pool(pools, conversion_map); + // include:end-transformed_pools + + // include:start-convert-containers + const auto new_v1 = + immer::persist::convert_container(pools, transformed_pools, v1); + const auto expected_new_v1 = vector_one{10, 20, 30}; + REQUIRE(new_v1 == expected_new_v1); + + const auto new_v2 = + immer::persist::convert_container(pools, transformed_pools, v2); + const auto expected_new_v2 = vector_one{10, 20, 30, 40, 50, 60}; + REQUIRE(new_v2 == expected_new_v2); + + const auto new_value = document{new_v1, new_v2}; + // include:end-convert-containers + + // include:start-save-new_value + const auto policy = + immer::persist::hana_struct_auto_member_name_policy(document{}); + const auto str = immer::persist::cereal_save_with_pools(new_value, policy); + const auto expected_json = json_t::parse(R"( +{ + "pools": { + "ints": { + "B": 5, + "BL": 1, + "inners": [ + {"key": 0, "value": {"children": [2], "relaxed": false}}, + {"key": 3, "value": {"children": [2, 5], "relaxed": false}} + ], + "leaves": [ + {"key": 1, "value": [30]}, + {"key": 2, "value": [10, 20]}, + {"key": 4, "value": [50, 60]}, + {"key": 5, "value": [30, 40]} + ], + "vectors": [{"root": 0, "tail": 1}, {"root": 3, "tail": 4}] + } + }, + "value0": {"ints": 0, "ints2": 1} +} + )"); + REQUIRE(json_t::parse(str) == expected_json); + // include:end-save-new_value +}