Design reverse write-back planning alongside forward traversal #48

Open
opened 2026-07-28 21:44:56 +00:00 by UserCurt · 0 comments
Owner

Issue #45 reuses the chunks created during forward traversal to perform mutation during write-back. For example, the planner might produce:

/A/B<1>/C/D

MPath then uses the /A/B chunk both to retrieve its selected output and to write the modified output back. The planner currently only considers retrieval using matlab.indexing.IndexingContext.Expression, while ignoring matlab.indexing.IndexingContext.Assignment in mutation operations.

The current implementation essentially assumes:

  • listLength behaves consistently in both indexing contexts.
  • If parent.(indexOps) retrieves a child, then parent.(indexOps) = child supports writing it back.

This issue should determine how reverse write-back should be planned when those assumptions are insufficient.

Option 1: Reuse the forward plan

Use the same chunks in both directions:

Forward:    /A/B<1>/C/D
Write-back: /A/B<1>/C/D

This is the current implementation. It has one plan and keeps resolved paths easy to understand, but it depends on both assumptions above.

Having the forward and write-back consistent means resolve will cleanly return only one option.

Option 2: Choose shared boundaries for both directions

Plan forward traversal and reverse write-back together, requiring both directions to use the same boundaries:

Forward:    /A<1>/B<1>/C/D
Write-back: /A<1>/B<1>/C/D

The shared plan may use more boundaries than query traversal alone would require, but the forward and write-path plans will be identical and valid.

Having the forward and write-back consistent means resolve will cleanly return only one option.

Option 3: Split forward chunks during write-back

Build the forward plan first, then divide its chunks further when assignment requires it:

Forward:    /A   /B<1>/C/D
Write-back: /A<1>/B<1>/C/D

The write-back planner may split /A/B if it cannot use the full /A/B for assignment. It cannot combine operations across boundaries chosen during forward traversal.

This is more flexible than options 1 and 2, but the extra write-back boundaries are not visible in the original resolved path without changing the behavior of resolve.

Option 4: Plan both directions with independent boundaries

Plan both directions together, but allow each direction to have different chunks boundaries:

Forward:    /A/B<1>/C/D
Write-back: /A<1>/B/C<1>/D

This allows the write-back planner to combine /B/C even though the forward plan places a boundary between them.

This is the most flexible option, but the different boundaries between forward and write-back would not be represented by resolve without needing to change its behavior.


It's unclear whether it is reasonable to require forward and write-back plans to be shaped identically or not. Choosing an option could require changing the resolved-input equivalence contracts.

direct = mp.set(data, values, ...); % or remove

resolved = mp.resolve(data, ...);
replayed = resolved.set(data, values, ...); % or remove

isequaln(direct, replayed)

There may also be ramifications with the stability under bare resolve and string round trip contracts. A mutation-aware resolved pointer should not lose or change its boundaries when resolved again, and if reverse-plan information ever becomes part of pointer syntax, rendering and reparsing must preserve it. Choosing an option where the forward and write-back plans are different could necessitate modifying resolve so the user can effectively see the corresponding plan.

The selected option should inform how the shared write-back from #47 is done.

Issue #45 reuses the chunks created during forward traversal to perform mutation during write-back. For example, the planner might produce: ```text /A/B<1>/C/D ``` MPath then uses the `/A/B` chunk both to retrieve its selected output and to write the modified output back. The planner currently only considers retrieval using `matlab.indexing.IndexingContext.Expression`, while ignoring `matlab.indexing.IndexingContext.Assignment` in mutation operations. The current implementation essentially assumes: - `listLength` behaves consistently in both indexing contexts. - If `parent.(indexOps)` retrieves a child, then `parent.(indexOps) = child` supports writing it back. This issue should determine how reverse write-back should be planned when those assumptions are insufficient. ## Option 1: Reuse the forward plan Use the same chunks in both directions: ```text Forward: /A/B<1>/C/D Write-back: /A/B<1>/C/D ``` This is the current implementation. It has one plan and keeps resolved paths easy to understand, but it depends on both assumptions above. Having the forward and write-back consistent means `resolve` will cleanly return only one option. ## Option 2: Choose shared boundaries for both directions Plan forward traversal and reverse write-back together, requiring both directions to use the same boundaries: ```text Forward: /A<1>/B<1>/C/D Write-back: /A<1>/B<1>/C/D ``` The shared plan may use more boundaries than query traversal alone would require, but the forward and write-path plans will be identical and valid. Having the forward and write-back consistent means `resolve` will cleanly return only one option. ## Option 3: Split forward chunks during write-back Build the forward plan first, then divide its chunks further when assignment requires it: ```text Forward: /A /B<1>/C/D Write-back: /A<1>/B<1>/C/D ``` The write-back planner may split `/A/B` if it cannot use the full `/A/B` for assignment. It cannot combine operations across boundaries chosen during forward traversal. This is more flexible than options 1 and 2, but the extra write-back boundaries are not visible in the original resolved path without changing the behavior of `resolve`. ## Option 4: Plan both directions with independent boundaries Plan both directions together, but allow each direction to have different chunks boundaries: ```text Forward: /A/B<1>/C/D Write-back: /A<1>/B/C<1>/D ``` This allows the write-back planner to combine `/B/C` even though the forward plan places a boundary between them. This is the most flexible option, but the different boundaries between forward and write-back would not be represented by `resolve` without needing to change its behavior. --- It's unclear whether it is reasonable to require forward and write-back plans to be shaped identically or not. Choosing an option could require changing the resolved-input equivalence contracts. ```matlab direct = mp.set(data, values, ...); % or remove resolved = mp.resolve(data, ...); replayed = resolved.set(data, values, ...); % or remove isequaln(direct, replayed) ``` There may also be ramifications with the stability under bare resolve and string round trip contracts. A mutation-aware resolved pointer should not lose or change its boundaries when resolved again, and if reverse-plan information ever becomes part of pointer syntax, rendering and reparsing must preserve it. Choosing an option where the forward and write-back plans are different could necessitate modifying `resolve` so the user can effectively see the corresponding plan. The selected option should inform how the shared write-back from #47 is done.
UserCurt added this to the Someday milestone 2026-07-28 21:44:56 +00:00
Sign in to join this conversation.
No description provided.