Define how set assigns through missing paths and constructs new data #15

Closed
opened 2025-10-11 11:00:01 +00:00 by UserCurt · 3 comments
UserCurt commented 2025-10-11 11:00:01 +00:00 (Migrated from codeberg.org)

If creating a struct from scratch, an existing idiomatic approach is to assign a field on each line in MATLAB:

S = struct(); % not necessary if you assume S does not exist beforehand
S.A.X = 1;
S.A.Y = 2;
S.B.X = 3;
S.B.Y = 4;

Implementation of this proposal would allow for creation of structs from scratch using mpath expression syntax. This could be implemented as a variant of mpath.set:

S = mpath.set("{A,B}/{X,Y}", {1,2; 3,4}, MultiSet=true)

Dimension specifiers could be set for additional clarity or to match the size of the value being assigned.

Perhaps MultiSet could be assumed to be true when using this variant of mpath.set.

This syntax would treat structs specially over tables or classes. It is unclear if this could translate to classes. To translate this over to tables, separate named argument or function would be needed. Alternatively, new functions could be considered completely rather than using mpath.set:

mpath.struct("{A,B}/{X,Y}", {1,2; 3,4})
% returns: struct(A=struct(X=1, Y=2), B=struct(X=3, Y=4))

mpath.table("{A,B}/{X,Y}", {1,2; 3,4})
% returns: table(table(1, 2, VariableNames=["X", "Y"]), table(3, 4, VariableNames=["X", "Y"]), VariableNames=["A", "B"])

This would translate into NamedArray (not yet publicly released) nicely as mpath.NamedArray(...) or similar (see #23).

Dimension for tables could specify the level within the table.

If creating a struct from scratch, an existing idiomatic approach is to assign a field on each line in MATLAB: ```matlab S = struct(); % not necessary if you assume S does not exist beforehand S.A.X = 1; S.A.Y = 2; S.B.X = 3; S.B.Y = 4; ``` Implementation of this proposal would allow for creation of structs from scratch using mpath expression syntax. This could be implemented as a variant of `mpath.set`: ```matlab S = mpath.set("{A,B}/{X,Y}", {1,2; 3,4}, MultiSet=true) ``` Dimension specifiers could be set for additional clarity or to match the size of the value being assigned. Perhaps MultiSet could be assumed to be true when using this variant of `mpath.set`. This syntax would treat structs specially over tables or classes. It is unclear if this could translate to classes. To translate this over to tables, separate named argument or function would be needed. Alternatively, new functions could be considered completely rather than using `mpath.set`: ```matlab mpath.struct("{A,B}/{X,Y}", {1,2; 3,4}) % returns: struct(A=struct(X=1, Y=2), B=struct(X=3, Y=4)) mpath.table("{A,B}/{X,Y}", {1,2; 3,4}) % returns: table(table(1, 2, VariableNames=["X", "Y"]), table(3, 4, VariableNames=["X", "Y"]), VariableNames=["A", "B"]) ``` This would translate into NamedArray (not yet publicly released) nicely as `mpath.NamedArray(...)` or similar (see #23). Dimension for tables could specify the level within the table.
Owner

This issue is being broadened past struct creation without existing data. It should define how set (and the chunk planner) handles concrete missing path suffixes and how to handle circumstances where MPath needs to explicitly know the newly formed datatype.

Current behavior

Currently, set incrementally addresses each DotExact. When an intermediate member is missing from a struct, MPath creates another struct.

It can also replace an existing non-struct intermediate value with a struct:

data = struct(A=1);
result = mpath.set(data, "/A/B", 2);
% result.A is now a struct with field B

This implicit struct creation should change as it prevents MATLAB or a custom class from receiving the original chained assignment (indexOp array) and deciding what that assignment means.

Native assignment first

When the path suffix after the last successfully retrieved value consists of fully specified indexing operations, MPath should preserve that suffix as one assignment operation (chunk) and delegate it to MATLAB. For example:

data.A.B.C.D = value;

should not require MPath to create A, B, and C separately. MATLAB handles this naturally for structs, while a custom class may define different behavior.

If part of the path has already been materialized, the remaining concrete suffix can still be assigned together:

child = data.A;
child.B.C.D = value;
data.A = child;

In MPath terms, traversal might produce a route such as:

/A<1>/B/C/D

The first chunk /A<1> would be the normal materialized traversal and write-back route. However, the second chunk /B/C/D is a concrete assignment-only suffix for non-existing elements, beginning after the last materialized parent.

Missing paths and chunking

Once traversal reaches a missing member, the read-oriented chunk planner cannot test the rest of the path because there is no value to retrieve or inspect. The same path suffix may still be valid when used for assignment. So the mutation planning should distinguish:

  • chunks that were successfully materialized during traversal
  • a concrete suffix that is only evaluated during assignment

The assignment-only suffix should normally remain intact rather than being split into inferred intermediate structs.

Explicit chunk boundaries may still require MPath to materialize an intermediate value. Forward and reverse chunk planning for those cases is related to #45. Shared write-back routes across multiple actions are tracked by #47.

No implicit type inference

MPath should not infer intermediate container types from path syntax.

MissingPolicy="create" should mean that MPath preserves an eligible concrete missing suffix and attempts native assignment through it. It should not mean that every missing intermediate is assumed to be a struct.

MPath may still require an intermediate value when:

  • an explicit boundary requires retrieving it
  • a wildcard or another data-derived matcher follows the missing point
  • the native container rejects chained creation
  • construction begins without an existing root value

When MPath must continue traversal through a missing intermediate value, it should not guess what type of container to create. It should report an error unless the caller explicitly specifies the required type or how to construct it.

A future API could provide this information through a factory, prototype, or schema. Specialized functions could make the choice directly: mpath.struct would create structs, while mpath.table would create tables. In each case, the API or caller selects the type rather than MPath inferring it from the path.

Policy behavior

The design should define how native creation interacts with:

  • MissingPolicy="create" and "prune"
  • NonmissingRequirement
  • UniformPaths and UniformValues
  • target dimensions and value mapping
  • direct versus resolve-first assignment
This issue is being broadened past struct creation without existing data. It should define how `set` (and the chunk planner) handles concrete missing path suffixes and how to handle circumstances where MPath needs to explicitly know the newly formed datatype. ## Current behavior Currently, `set` incrementally addresses each `DotExact`. When an intermediate member is missing from a struct, MPath creates another struct. It can also replace an existing non-struct intermediate value with a struct: ```matlab data = struct(A=1); result = mpath.set(data, "/A/B", 2); % result.A is now a struct with field B ``` This implicit struct creation should change as it prevents MATLAB or a custom class from receiving the original chained assignment (`indexOp` array) and deciding what that assignment means. ## Native assignment first When the path suffix after the last successfully retrieved value consists of fully specified indexing operations, MPath should preserve that suffix as one assignment operation (chunk) and delegate it to MATLAB. For example: ```matlab data.A.B.C.D = value; ``` should not require MPath to create `A`, `B`, and `C` separately. MATLAB handles this naturally for structs, while a custom class may define different behavior. If part of the path has already been materialized, the remaining concrete suffix can still be assigned together: ```matlab child = data.A; child.B.C.D = value; data.A = child; ``` In MPath terms, traversal might produce a route such as: ```text /A<1>/B/C/D ``` The first chunk `/A<1>` would be the normal materialized traversal and write-back route. However, the second chunk `/B/C/D` is a concrete assignment-only suffix for non-existing elements, beginning after the last materialized parent. ## Missing paths and chunking Once traversal reaches a missing member, the read-oriented chunk planner cannot test the rest of the path because there is no value to retrieve or inspect. The same path suffix may still be valid when used for assignment. So the mutation planning should distinguish: - chunks that were successfully materialized during traversal - a concrete suffix that is only evaluated during assignment The assignment-only suffix should normally remain intact rather than being split into inferred intermediate structs. Explicit chunk boundaries may still require MPath to materialize an intermediate value. Forward and reverse chunk planning for those cases is related to #45. Shared write-back routes across multiple actions are tracked by #47. ## No implicit type inference MPath should not infer intermediate container types from path syntax. `MissingPolicy="create"` should mean that MPath preserves an eligible concrete missing suffix and attempts native assignment through it. It should not mean that every missing intermediate is assumed to be a struct. MPath may still require an intermediate value when: - an explicit boundary requires retrieving it - a wildcard or another data-derived matcher follows the missing point - the native container rejects chained creation - construction begins without an existing root value When MPath must continue traversal through a missing intermediate value, it should not guess what type of container to create. It should report an error unless the caller explicitly specifies the required type or how to construct it. A future API could provide this information through a factory, prototype, or schema. Specialized functions could make the choice directly: `mpath.struct` would create structs, while `mpath.table` would create tables. In each case, the API or caller selects the type rather than MPath inferring it from the path. ## Policy behavior The design should define how native creation interacts with: - `MissingPolicy="create"` and `"prune"` - `NonmissingRequirement` - `UniformPaths` and `UniformValues` - target dimensions and value mapping - direct versus resolve-first assignment <!-- ## Cases requiring explicit handling Add defined behavior for: - an entirely missing exact path - an exact missing suffix after an existing parent - an existing noncontainer intermediate value - a wildcard before a missing exact suffix - a data-derived matcher after the first missing member - explicit chunk boundaries through missing values - tables, value objects, handle objects, and customized indexing - construction without an existing root value Tests should include a custom class that distinguishes receiving one complete chained assignment from receiving several incremental assignments. Resolved-input equivalence and the grouping rules established for `set` should remain satisfied. -->
UserCurt changed title from Create struct from scratch using set to Define how set assigns through missing paths and constructs new data 2026-07-27 09:16:32 +00:00
Owner

Root values should also be considered in this issue and not handled specially or differently. For example, assigning /A on a non-container root and assigning /X/A when data.X is a non-container should behave the same.

Root values should also be considered in this issue and not handled specially or differently. For example, assigning `/A` on a non-container root and assigning `/X/A` when `data.X` is a non-container should behave the same.
Owner

Completed by ab1236b. Member creations are now mainly done through data.(indexOps) = ....

Creating something from nothing is deemed out of scope for set so is not part of this commit.

Completed by `ab1236b`. Member creations are now mainly done through `data.(indexOps) = ...`. Creating something from nothing is deemed out of scope for `set` so is not part of this commit.
Sign in to join this conversation.
No description provided.