Skip to content

Commit

Permalink
Describe transforming into the same type
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sparus committed Aug 13, 2024
1 parent 48d25e3 commit ea67620
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
73 changes: 73 additions & 0 deletions immer/extra/persist/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand Down Expand Up @@ -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<int>`` 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
------

Expand Down
66 changes: 66 additions & 0 deletions test/extra/persist/test_for_docs.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <catch2/catch_test_macros.hpp>

#include <immer/extra/persist/cereal/with_pools.hpp>
#include <immer/extra/persist/transform.hpp>

#include "utils.hpp"
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -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<vector_one>, [](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
}

0 comments on commit ea67620

Please sign in to comment.