I wasn’t able to reproduce the behavior you report. Can you confirm that this behavior still occurs for you on 0.10.2?
The 5.0 / 1.0 / 4.0 output appears if those ActualObject masses carry their own default sum(...). In this case, the expression is evaluated against the reduced structure.
All objects have a mass attribute with a default value which is then used in evaluation. null just means a sequence of 0 elements which then propagates empty sequences further down, e.g. ActualObject::suboject1::subobject2.mass == null, and sequence concatenation ignores empty sequences.
Thanks guys. I know about the behavior you are mentioning, but the point is that MainObject is an element that’s part of a reusable library of sorts, where I want to define the calculations of the attributes. Then ActualObject is the object that “uses” the library elements and where I’d like to calculate the attribute values using the calculations defined in the reusable element.
I know null is an empty sequence, and that’s exactly the point. In ActualObject I am “selecting” which contained parts I want to have be part of the object, and I want mass to automatically reflect that. Like, if I downselect some elements I expect the mass to decrease…
So I know that if I “copy” the calculation to the attributes of the ActualObject, it works. However, I am trying to see if this reusability approach would also work, so that I don’t have to copy the calculations.
Do you guys think there’s any way this is possible?
PS: I’m running on 0.10.2 and I still have the same behavior as I reported initially.
Oh, did you perhaps mix up expected and actual outputs? If so, the evaluation is currently very basic, and does not pick up inherited feature values from a given feature. Instead, you can evaluate ObjectWithMass::mass on different scopes, or extract mass and follow redefinitions to a feature with value.
Indeed, so in the _calc_mass Python function you can see I am setting the scope to the part I am evaluating the attribute of.
I know that if I change the attribute I am evaluating the ancestor of the attribute that does have the calculation as its feature value, then I actually do get the correct results. For example, if I evaluate MainObject::mass with scope ActualObject, I do get the value I am expecting.
However, here the problem is that I need to traverse the attribute’s ancestors to find the first one that has a feature value. Shouldn’t this be done “automatically”, as in: the feature value is something that is inherited through a redefinition?
It is a bit of a can of worms, especially around default values. For example, this is valid syntactically, and semantically according to the specification:
attribute def A { attribute y; }
attribute a : A { attribute :>> y default 5; }
part def P {
attribute x : A = a;
}
part def Q :> P {
attribute :>> x { attribute :>> y = 6; }
}
Do we use the inherited value expression by Q::x (P::x meaning a), or Q::x itself, when evaluating x.y in scope of Q?
My preference is to handle everything uniformly as much as possible, unless specification mandates otherwise. Perhaps, mixing bodies with non-annotating elements and (inherited) feature values should be a diagnostic, in which case resolving inherited feature values would make sense.
You provide a very good example, I guess it should be specified more clearly in the standard…
Maybe Q::x::y could be interpreted as being a redefinition of a::y (because P::x “is” a)? That would clear up the ambiguity.
Anyway I would say that if I have an attribute that is only redefining another attribute and adding nothing else (no feature value, no body) then when evaluating it, it should probably be evaluated as the first ancestor that has a feature value or a body. What do you think?
That was just a simple example. Changing to = (a, a) would break again - we cannot rely on feature value evaluation for this, even symbolic.
Makes sense but before implementing it I would like to clarify (inheritance) semantics around feature values. I do not want to have to rip it out/replace it in the future if the intended semantics are different to what we expected/implemented.