modelica / ModelicaSpecification

Specification of the Modelica Language
https://specification.modelica.org
Creative Commons Attribution Share Alike 4.0 International
101 stars 41 forks source link

Are name prefixes renamed during extends? #1397

Closed modelica-trac-importer closed 4 years ago

modelica-trac-importer commented 5 years ago

Reported by stefanv on 15 Jan 2014 20:39 UTC Consider the following:

package P
    model M
        Real x;
    equation
        x = K;
    end M;
    constant Real K = 5;
end P;

package P2 = P(K=10);

model Main
    P2.M m;
end Main;

At least two tools produce a model containing the equation Main.m.x = 10.

Now consider a very slight change:

package P
    model M
        Real x;
    equation
        x = P.K;  // <--- changed from 'K' to 'P.K'
    end M;
    constant Real K = 5;
end P;

package P2 = P(K=10);

model Main
    P2.M m;
end Main;

The same two tools now produce a model containing the equation Main.m.x = 5.

I find this surprising. Is it correct? The specification is, rather surprisingly, unclear on this issue.


Migrated-From: https://trac.modelica.org/Modelica/ticket/1397

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 15 Jan 2014 20:53 UTC OpenModelica produces m.x = 10 in both cases (because it remembers its local name is P so it does not lookup P from the top-level scope).

modelica-trac-importer commented 5 years ago

Comment by stefanv on 15 Jan 2014 21:01 UTC And that is what I would have expected.

modelica-trac-importer commented 5 years ago

Comment by Dag Brück on 15 Jan 2014 21:21 UTC Pardon me for asking, but what do you mean by "local name" here? And when does name lookup involve memory?

Dag

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 15 Jan 2014 21:34 UTC Well... When our lookup tries to lookup a P and the current frame is P, it remembers this name. Lookup always involves memory (you search frames starting with the current one, if not found there, remove a frame and keep searching). This is mostly about if you remove a frame (and its modifiers) even if it is the name you are looking for.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 15 Jan 2014 21:44 UTC I think the fact that different tools give different results for this example shows that the specification is really not clear about it and justifies the elevation of the severity of this ticket.

The important part that is not clear in the specifications for me is when the scoping of the names in the equations happens during the flattening process. Do we scope the equations in base classes before we introduce them into the class we flatten or not?

If we first scope the names in the equations of the base class (P is the base class of P2) then in both examples the equation in P.M is resolved to P.M.x = P.K. When prefix P is renamed to P2, in both examples we would get the equation P2.M.x = P2.K, since both K and P.K are local (as specified in the comment above) to P.

But if we first rename the package P to be package P2, apply the modification and only then scope the equations, we essentially end up looking at P2 (in the second example) as being:

 package P2
     model M
         Real x;
     equation
         x = P.K;
     end M;
     constant Real K = 10;
 end P2;

It is equivalent to renaming being applied only in the definition of P and not propagated anywhere inside the class.

modelica-trac-importer commented 5 years ago

Comment by jmattsson on 16 Jan 2014 09:12 UTC As I interpret the code, there is no renaming involved. P2 is a new package that inherits from P.

package P2 = P(K = 10);

// Is equivalent to:

package P2
  extends P(K = 10);
end P2;

// Except for scoping of RHS expressions in the modification
modelica-trac-importer commented 5 years ago

Comment by hansolsson on 16 Jan 2014 09:18 UTC Replying to [comment:4 sjoelund.se]:

Well... When our lookup tries to lookup a P and the current frame is P, it remembers this name. Lookup always involves memory (you search frames starting with the current one, if not found there, remove a frame and keep searching).

Ehmm... No.

The specification of lookup involves finding something in a scope. That does not involve memory of the lookup (obviously the classes are in memory).

The implementation of lookup may involve memory for different reasons, e.g.:

That use of memory in the implmentation may be quite important in practice. However, this additional memory is just a tool-specific optimization that should not influence results. And "I did a lookup for P and the current frame is P so skip that in the next step" - is one of those optimization.

modelica-trac-importer commented 5 years ago

Comment by pharman on 16 Jan 2014 10:26 UTC In my view in the second case Main.m.x should be 5. If the modeller has specified x = P.K; then why would that become x = P2.K;?

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jan 2014 16:06 UTC Jesper, the renaming refers to what Peter shows in his example. If the equation is x =P.K do we continue to see it as such when we are flattening P2.

Peter, my first thought about it would be like what you say. However, in Section 5.6.1 Partial Flattening of the specification it says: First the names of declared local classes and components are found. Here modifiers are merged to the local elements and redeclarations take effect (including redeclared elements, see Section 7.3). Then base-classes are looked up, flattened and inserted into the class. So it looks like the modifications and redeclarations should take effect before the base elements are flattened. In this case it means P.K must be set to 10 inside P, then P must be flattened and then only the elements of the base class are inserted into the class, that is P.K becomes P2.K. But that assumes that during the flattening we are scoping the equations. However, there is nothing in the description of the flattening process in the specification that I could find that would indicate scoping of equations as part of this flattening process described in Section 5.6.

modelica-trac-importer commented 5 years ago

Comment by jmattsson on 16 Jan 2014 16:44 UTC Replying to [comment:9 eshmoylova]:

Jesper, the renaming refers to what Peter shows in his example. If the equation is x =P.K do we continue to see it as such when we are flattening P2.

You talked about renaming in your descriptions of both alternatives - I took that to mean that you considered it as given that things should be renamed.

So it looks like the modifications and redeclarations should take effect before the base elements are flattened. In this case it means P.K must be set to 10 inside P, then P must be flattened and then only the elements of the base class are inserted into the class, that is P.K becomes P2.K. But that assumes that during the flattening we are scoping the equations. However, there is nothing in the description of the flattening process in the specification that I could find that would indicate scoping of equations as part of this flattening process described in Section 5.6.

I think you are mixing what classes are referred. My interpretation of that paragraph is that if the class being partially flattened contains any classes or components, then any modifications on the contained classes are merged, then any base classes of the class being partially flattened are applied.

Looking at it another way: Say that P2 also contains the declaration:

model M2
  P.M m;
end M2;

And that Main also contains the declaration P2.M2 m2;. I that case, you would of course get the equation Main.m2.m.x = 5;. Setting P.K to 10 inside P would mean that that equation would also be equal to 10. The modification K=10 isn't a modification on P, it's a modification on P2.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jan 2014 16:49 UTC Actually, it gets even more confusing with respect to flattening and equations. In Section 8.2 Flattening and Lookup in Equations it says: A flattened equation is identical to the corresponding nonflattened equation. Names in an equation shall be found by looking up in the partially flattened enclosing class of the equation.

It appears as if lookup of names in the equations and flattening are separate processes. The first sentence can be interpreted to mean that the names are not looked up because if we look them up the equation is not exactly identical to the corresponding nonflattened equation. The second sentence says that the names looked up in the partially flattened class but it is not the same as to say that the look up happens when partial flattening is performed. So when are the names in the equations looked up? It is especially confusing given that according to the definition of flattening in Section 1.3: The translation of a model described in Modelica to the corresponding model described as a hybrid DAE.... the purpose of flattening is to get the equations.

modelica-trac-importer commented 5 years ago

Comment by Dag Brück on 16 Jan 2014 17:06 UTC I think we have to consider the purpose of "extends". It was from the beginning designed as a composition mechanism, which means that extending inserts declaration with the same meaning as if they had been written inline (of course applying modifiers on extends).

This is different from inheritance in languages such as C++, where lookup of global names is made in the context of the base class. You may think either is right or wrong, but my very strong view is that they are different.

Dag

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jan 2014 17:18 UTC Replying to [comment:10 jmattsson]:

Looking at it another way: Say that P2 also contains the declaration:

model M2
  P.M m;
end M2;

And that Main also contains the declaration P2.M2 m2;. I that case, you would of course get the equation Main.m2.m.x = 5;. Setting P.K to 10 inside P would mean that that equation would also be equal to 10. The modification K=10 isn't a modification on P, it's a modification on P2.

Sorry for not being clear, it's hard to explain. I didn't mean to say that K=10 is a modification applied to anything in P. What I meant is that K=10 when we bring the elements of P into P2. Before we bring the elements of P into P2 we apply redeclarations and "flatten" them. So if the flattening means looking up the names in the equation, when we are flattening M, in order to bring it into P2, it is still inside P. And if flattening means scoping names in equations, we would resolve that equation to P.M.x = P.K since we are still looking at P. Only then we are bringing M into P2. What do you do with equation at this point?

Dag pointed it out well. I am only not clear on whether the lookup of names is made in the context of base class or in the context of the class it inherits. There is a case to be made in favor of looking up the names in the scope of the base class. Consider an example:

 package P
     model M
         Real x;
     equation
         x = K;
     end M;
     constant Real K = 5;
 end P;

model M3 = P.M

If we did not look up the names in the equation in M in its scope before putting them into M3 we would not be able to find K at all.

modelica-trac-importer commented 5 years ago

Comment by Dag Brück on 16 Jan 2014 17:28 UTC Yes. And I would argue that M3 is ill-formed because we cannot lookup P.K in M3.

This is different from C++ and similar languages.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jan 2014 17:30 UTC And just to clarify, I am not saying that the answer should be 5 or 10. I am saying that I do not know what the answer is. What we have in the specification right now can be and is interpreted to mean either way, and this is a serious issue. We have tools given different results and bot results can be argued to be correct according to the specification.

So what we need to do is to decide on how such cases should be handled and put it into the specification.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jan 2014 17:36 UTC Replying to [comment:14 Dag Brück]:

Yes. And I would argue that M3 is ill-formed because we cannot lookup P.K in M3.

This is different from C++ and similar languages.

In that case all models in the MSL Modelica.Media.Examples.Tests.Components and Modelica.Media.Examples.Tests.MediaTestModels are ill-formed. And probably many more others.

modelica-trac-importer commented 5 years ago

Comment by jmattsson on 16 Jan 2014 17:42 UTC Replying to [comment:13 eshmoylova]:

Sorry for not being clear, it's hard to explain. I didn't mean to say that K=10 is a modification applied to anything in P. What I meant is that K=10 when we bring the elements of P into P2. Before we bring the elements of P into P2 we apply redeclarations and "flatten" them. So if the flattening means looking up the names in the equation, when we are flattening M, in order to bring it into P2, it is still inside P. And if flattening means scoping names in equations, we would resolve that equation to P.M.x = P.K since we are still looking at P. Only then we are bringing M into P2. What do you do with equation at this point?

Putting it another way: if the equation contains P.K, then we have to assume that whoever wrote it meant P.K, and not "K in wherever package surrounds the model this equation is in". There is still only one P - the one where K is equal to 5.

Replying to [comment:15 eshmoylova]:

And just to clarify, I am not saying that the answer should be 5 or 10. I am saying that I do not know what the answer is. What we have in the specification right now can be and is interpreted to mean either way, and this is a serious issue. We have tools given different results and bot results can be argued to be correct according to the specification.

So what we need to do is to decide on how such cases should be handled and put it into the specification.

OK, I agree that the entire chapter on partial flattening is very confusing and needs a serious overhaul.

Replying to [comment:14 Dag Brück]:

Yes. And I would argue that M3 is ill-formed because we cannot lookup P.K in M3.

This is different from C++ and similar languages.

That would make extends almost impossible to use in all but the simplest models, since it would be far too complicated for the user to figure out what would happen if he/she were to extend a class that is declared in another library.

modelica-trac-importer commented 5 years ago

Comment by Dag Brück on 16 Jan 2014 17:42 UTC I think I was unclear. What I meant was that we cannot lookup K in the context of M3, and resolve it to P.K, because we are not in the scope of P when the lookup takes place.

I hope that was more clear :-)

modelica-trac-importer commented 5 years ago

Comment by jmattsson on 16 Jan 2014 17:46 UTC Replying to [comment:18 Dag Brück]:

I think I was unclear. What I meant was that we cannot lookup K in the context of M3, and resolve it to P.K, because we are not in the scope of P when the lookup takes place.

I hope that was more clear :-)

I think it was clear before as well.

If Elena's example with M3 should give a lookup error for K, then the implications would make extends and short class declarations in practice useless.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 16 Jan 2014 19:16 UTC To my mind, the declaration, package P2 = P(K=10);, means, "make a copy of P in which K = 10, and then rename that copy to P2". The only question then is whether every reference to P within the package is renamed or not.

And also, in the first example, isn't the "K" in the equation x = K; equivalent to "P.K"?

modelica-trac-importer commented 5 years ago

Comment by stefanv on 16 Jan 2014 19:40 UTC Raising the priority on this, since there seems to be no agreement, and different tools do it differently.

modelica-trac-importer commented 5 years ago

Comment by choeger on 16 Jan 2014 19:50 UTC This discussion seems to be about lexical vs. dynamical scoping. Kudos for spotting the issue! Dynamic scoping means K=10 and lexical scoping means K=5.

Both are technically feasible, but as far as I know, Modelica has always been a lexically scoped language. There is even a special way to handle dynamic scoping as well using inner/outer. So if we go for the k=10 case, we just decided that inner/outer is not necessary anymore. As has been pointed out, several existing models have been written using lexical scoping and would need to be fixed. Additionally, AFAIK dynamic scoping is considered way more complicated to learn for beginners (AFAIK this is one of the core problems people have with javascript).

concluding, I'd prefer to stick with lexical scoping, but in any way, please let's not make some "compromise" decision, where we end up with a little bit of both and tons of confusion.

modelica-trac-importer commented 5 years ago

Comment by adrpo on 19 Jan 2014 00:54 UTC See also #1327.

modelica-trac-importer commented 5 years ago

Comment by choeger on 20 Jan 2014 08:47 UTC Replying to [comment:23 adrpo]:

See also #1327.

I don't really think this one is related, as the lookup direction is "downwards" and B is replaceable there.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 6 Feb 2014 20:39 UTC Can we agree on what we want the answer to be for P.K in the second example? Then we can try to work out how we need to clarify the specifications.

modelica-trac-importer commented 5 years ago

Comment by jmattsson on 7 Feb 2014 09:07 UTC Replying to [comment:25 eshmoylova]:

Can we agree on what we want the answer to be for P.K in the second example? Then we can try to work out how we need to clarify the specifications.

That sounds good - I vote 5.

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 7 Feb 2014 09:20 UTC I'd say 10 still, with .P.K giving 5.

modelica-trac-importer commented 5 years ago

Comment by kurzbach on 7 Feb 2014 09:25 UTC I vote for 10 in the first and 5 in the second example.

modelica-trac-importer commented 5 years ago

Comment by adrpo on 7 Feb 2014 09:27 UTC I don't think it matters what we want. Probably we'll *need* to do as some unnamed tool does because otherwise *a lot* of Fluid and Media models will fail :)

modelica-trac-importer commented 5 years ago

Comment by pharman on 7 Feb 2014 09:40 UTC I also vote for 10 in the first and 5 in the second.

modelica-trac-importer commented 5 years ago

Comment by choeger on 7 Feb 2014 12:13 UTC Clearly lexical, P.K=5 in *both* cases. It shouldn't be allowed to modify lexically bound values, *unless* there is a clear evaluation order defined.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 7 Feb 2014 15:06 UTC There is no lexical P.K in the first example Christoph.

I vote 10 and 5 for the sole reason that our's is one of the tools that currently produces those values. :-)

But ultimately, Adrian is correct. I suspect a lot of code might break if we make it 5 in both cases or 10 in both cases.

modelica-trac-importer commented 5 years ago

Comment by choeger on 7 Feb 2014 15:29 UTC Replying to [comment:32 stefanv]:

There is no lexical P.K in the first example Christoph.

You are right, its K in the first and P in the second example that are found in the lexical scope.

But ultimately, Adrian is correct. I suspect a lot of code might break if we make it 5 in both cases or 10 in both cases.

Well, the problem is that these two solutions are the only ones where I would guess one could find a general rule ;). Until now I have no idea, how K could possibly evaluate different from P.K. In other languages this kind of construct would be first-class modules: Everything inside a module is implicitly parameterized with the concrete module instance at hand (e.g. during import you instantiate a module, pass it to the imported class and then get that class). This would make inner/outer redundant (at least its lookup semantics). So if we can modify a constant inside a package, the result should be the same, regardless how the value is found.

On the other hand, if we do not need this whole fancyness, I'd say we remove the possibility by forbidding modifications to reach "through" the scope chain.

And btw. I do not consider this whole "tool X already does it and everybody relies upon it" a good strategy to define a language.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 7 Feb 2014 16:39 UTC Well, as I see it, it should be 10 for the first and 5 for the second.

The package P2=P(K=10); introduces a copy of "P" (in which K is 10) and this copy of "P" has the same lexical parent as the original "P" (because we would otherwise have dynamic scoping). (Below I assume this is global scope i.e. P is a top-level package. The result would be the same if inside a package - just more text below). (This is according to section 7.)

The P2.M has lexical parents: copy of "P", followed by global scope (same as P). (This might not explicitly be written in the spec, but is implied and is not the issue.)

So searching for "K" in P2.M finds "K" in copy of "P", which has value 10. Searching for "P.K" in P2.M finds no "P" in copy of P", next step finds "P" in global scope, and then "M" inside it with value 5. (This is defined in section 5.3.1 and 5.3.3).

But why not simplify the example to:

package P
    constant Real M1 = K;
    constant Real M2 = P.K;
    constant Real K = 5;
end P;

package P2 = P(K=10);

model Main
    Real a[:]={P2.M1,P2.M2,P2.K};
end Main;

If "P2.K" wasn't 10 modifiers on packages wouldn't work. If "P2.M1" was different from "P2.K" (which is 10) binding equations in classes wouldn't work (the same mechanism used for propagating parameters in models).

So let us focus on "P2.M2":

The difference between "P2.M1" and "P2.M2" is explained above, so next question is what could make them the same (already noted by Stefan V in comment 20): They would be the same if we added a new step to the instantiation that before inheriting from "P" we look up all names in "P" and resolve them to local names if possible; and in that case using ".P.K" would give the same result. (Section 7 states that some things are done before copying - this would then be included.)

I don't see the benefit of that - it would just make it impossible to refer to the unmodified "P.K" inside "P".

There have been uses of the "P.K" style in Modelica.Media - however, many (or all) were removed since "P" is in that case partial and you should not use elements of partial packages (restriction in 5.3.2).

modelica-trac-importer commented 5 years ago

Comment by adrpo on 7 Feb 2014 19:50 UTC

There have been uses of the "P.K" style in Modelica.Media - however, many (or all) were removed since "P" is in that case partial and you should not use elements of partial packages (restriction in 5.3.2).

Not really, not really at all. Still a bit of "P.K" style in Modelica.Media :)

Search for:

The usage of qualification in Modelica.Media is rather random. (Just search for R134aData to see what I mean.) This points to the fact that users don't really understand that P.K and K could be different if P is modified.

What's worse is that some of these packages are used as replaceable Medium. Good luck figuring out by looking at the code which of the definitions are used (the modified or the original one in this case). Sort of redeclare package Medium = P(K=10).

From my side I thought that the only way to get the original declaration is to use full qualification .P.K and that P.K is equivalent to K in the scope of P. This way is clear that everything pointing to the same scope that does not start with "." is equivalent.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 7 Feb 2014 21:51 UTC Replying to [comment:34 hansolsson]:

The package P2=P(K=10); introduces a copy of "P" (in which K is 10) and this copy of "P" has the same lexical parent as the original "P" (because we would otherwise have dynamic scoping).

And this copy of "P" is not the same as "P2"?

The P2.M has lexical parents: copy of "P", followed by global scope (same as P). (This might not explicitly be written in the spec, but is implied and is not the issue.)

What is the scope in which copy of "P" is introduced? Why doesn't "P2.M" have "P2" as a parent?

So searching for "K" in P2.M finds "K" in copy of "P", which has value 10. Searching for "P.K" in P2.M finds no "P" in copy of P", next step finds "P" in global scope, and then "M" inside it with value 5. (This is defined in section 5.3.1 and 5.3.3).

Searching for "P.K" in P2.M finds no "P" in copy of "P". Then it should look up in the class that encloses the copy of "P". Is the copy of "P" also called "P"? Then we'll find the copy, where K is 10.

modelica-trac-importer commented 5 years ago

Comment by adrpo on 7 Feb 2014 22:50 UTC Replying to [comment:36 eshmoylova]:

Lexical scoping for package P2=P(K=10); means:

  1. make a copy of P in the lexical scope of P ("." in this case) with name P2
    package P
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 5;
    end P;
    // copy of P with name P2
    package P2
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 5;
    end P2;
  2. replace K in the new P2 with 10
    package P
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 5;
    end P;
    // copy of P with name P2, apply (K = 10)
    package P2
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 10;
    end P2;

As you can see M2 still points to P.K and P is available outside P2 in the same scope, so P2.M2 = P.K (original declaration) = 5. This is not a very good example as P and P2 are in the same scope. Is easier to understand if P is in another scope than P2.

Dynamic scoping for package P2=P(K=10); means:

  1. make a copy of P with name P2
    package P
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 5;
    end P;
    // copy of P with name P2
    package P2
     constant Real M1 = K;
     constant Real M2 = P.K;
     constant Real K = 5;
    end P2;
  2. and replace all references to P inside P2 (except .P) with P2
    // copy of P with name P2 with all P inside P2 renamed to P2
    package P2
     constant Real M1 = K;
     constant Real M2 = P2.K;
     constant Real K = 5;
    end P2;
  3. replace K in the new P2 with 10
    // apply (K = 10)
    package P2
     constant Real M1 = K;
     constant Real M2 = P2.K;
     constant Real K = 10;
    end P2;

As you can see, with dynamic scoping means P2.M2 = 10.

modelica-trac-importer commented 5 years ago

Comment by choeger on 8 Feb 2014 11:09 UTC Hans, the example you call lexical scoping does not use lexical scoping at all. It is precisely a dynamic scope example:

package Orig
  package P
    constant Real M1 = K;
    constant Real M2 = P.K;
    constant Real K = 5;
  end P;
end Orig;

package User 
  package P constant Real NO_K_HERE = 0; end P;
  package P2 = Orig.P; //dynamic scoping, cannot find P.K
end User;

Following your semantics, this leads to:

package User 
  package P constant Real NO_K_HERE = 0; end P;
  package P2
    constant Real M1 = K;
    constant Real M2 = P.K; // OOOPS
    constant Real K = 5;
  end P2;
end User;

As a general rule: When you carry the P.K around, without changing it to its definition-site meaning, you got dynamical scoping. Lexical scoping means: P always uses its definition-site scope.

Lexical scoping means:

package User 
  package P constant Real NO_K_HERE = 0; end P;
  package P2
    constant Real M1 = K;
    constant Real M2 = .Orig.P.K; // Now that works
    constant Real K = 5;
  end P2;
end User;
modelica-trac-importer commented 5 years ago

Comment by hansolsson on 10 Feb 2014 08:45 UTC Replying to [comment:36 eshmoylova]:

Replying to [comment:34 hansolsson]:

The package P2=P(K=10); introduces a copy of "P" (in which K is 10) and this copy of "P" has the same lexical parent as the original "P" (because we would otherwise have dynamic scoping).

And this copy of "P" is not the same as "P2"?

I would say that "P2" consists of a copy of "P" (and more if we use the long form of extends) - not that they are the same.

The P2.M has lexical parents: copy of "P", followed by global scope (same as P). (This might not explicitly be written in the spec, but is implied and is not the issue.)

What is the scope in which copy of "P" is introduced? Why doesn't "P2.M" have "P2" as a parent?

The copy of "P" still has global scope as lexically enclosing scope (same as "P"), but it is not "introduced there".

The simplest way is to view it as a link only going in one direction - so copy of "P" only references global scope as lexically enclosing scope - it is not added to the global scope. (Which makes sense since we would otherwise fill up the global scope with different classes.)

I believe that is one reason the spec uses the term "lexically enclosing scope" instead of "lexical parent".

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 10 Feb 2014 09:05 UTC Replying to [comment:38 choeger]:

Hans, the example you call lexical scoping does not use lexical scoping at all. It is precisely a dynamic scope example:

#!mo
package Orig
  package P
    constant Real M1 = K;
    constant Real M2 = P.K;
    constant Real K = 5;
  end P;
end Orig;

package User 
  package P constant Real NO_K_HERE = 0; end P;
  package P2 = Orig.P; //dynamic scoping, cannot find P.K
end User;

I think you are mis-attributing this or that I have not made myself clear.

Here "=Orig.P" constructs a copy of "Orig.P" (keeping the enclosing lexical scope) and adds that copy to P2 (still keeping the enclosing lexical scope).

Thus in this copy the declaration of M2 will have the following enclosing lexical scopes: copy of "Orig.P", Orig, global scope - and "P.K" will find Orig.P.K.

So the contents of "package User" doesn't matter inside copy of "Orig.P", i.e it is not dynamic scoping.

modelica-trac-importer commented 5 years ago

Comment by choeger on 10 Feb 2014 10:52 UTC Replying to [comment:40 hansolsson]:

I think you are mis-attributing this or that I have not made myself clear.

Maybe. You said that the P in P2 is found outside of P2, which implies dynamical scoping. But you also said, it's a bad example, so maybe I just didnt get your point.

Thus in this copy the declaration of M2 will have the following enclosing lexical scopes: copy of "Orig.P", Orig, global scope - and "P.K" will find Orig.P.K.

That still sounds rather dynamic to me. In a lexically scoped language, I'd expect the following scopes: Orig.P , Orig, global. So I would expect a modification of K not to effect P2.M1, since its value is bound in lexical scope.

If I understand you correctly now, you make this distinction to allow for modifications of elements inside P2 to also effect any elements depending on the modified element, right? So you expect P2.M1 to be whatever P2.K is?

but if you do that, isn't the result just explicit dynamic scoping by modification with a fallback to lexical scoping, if an element is not modified?

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 10 Feb 2014 12:53 UTC Replying to [comment:41 choeger]:

Replying to [comment:40 hansolsson]:

I think you are mis-attributing this or that I have not made myself clear.

Maybe. You said that the P in P2 is found outside of P2, which implies dynamical scoping. But you also said, it's a bad example, so maybe I just didnt get your point.

Thus in this copy the declaration of M2 will have the following enclosing lexical scopes: copy of "Orig.P", Orig, global scope - and "P.K" will find Orig.P.K.

That still sounds rather dynamic to me. In a lexically scoped language, I'd expect the following scopes: Orig.P , Orig, global. So I would expect a modification of K not to effect P2.M1, since its value is bound in lexical scope.

Well, it is dynamic in a the same sense as virtual functions/dynamic dispatch (think of the constants as virtual functions with no arguments) in a lexically scoped language - not as dynamic scoping.

When looking at M1 to find K the behavior could be described as:

(When we had replaceable base-classes it was messier.)

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 10 Feb 2014 19:32 UTC What I would like to achieve with this discussion is to come up with a clear description of the flattening process that can be used to update Section 5.6 Flattening.

What is clear at this point:

  1. Most of us agree that if a user writes P.K inside a package K he/she wants to have P.K in that place regardless of the modifications that could be applied to K when P is extended.
  2. There is no clear explanation in the specification of why it would happen this way during the flattening.
  3. The use of "lexical" vs. "dynamic" is confusing even for the experts in the programming languages. It will probably even more confusing for a user with no computer science background.
  4. The use of enclosing classes in the name look up is clear. What are the enclosing classes when a class is extended is not clear. The phrase "lexically enclosing" (section 5.2) has been interpreted different by different people.

I suggest to consider an example and describe step-by-step how it would be flattened. Then we can use this description and the example to update Section 5.6. Here is the example that I suggest to use.

package Orig
  constant Real O = 1;
  package P
    constant Real K = 5;
    model M
      Real x[3];
    equation
      x[1] = K;
      x[2] = P.K;
      x[3] = O;
    end M;
  end P;
end Orig;

package Modif = Orig(O = 2, K = 10);

model Main
  constant Real O = 3;
  package P2 = Modif.P(K = 15);
  Modif.P.M m1;
  P2.M m2;
end Main;

The objective is to flatten the model Main and obtain the equations for m1.x and m2.x. I would like us to come up with a description of each step of flattening, taken into consideration the following objectives.

  1. Provide description of what steps are taken during the partial flattening (section 5.6.1) and the flattening (section 5.6.2).
  2. Specify when the equations are scoped and when the modifications are scoped.
  3. Avoid using the words "lexical" and "dynamic". Instead specify enclosing classes at each look up.
  4. Clarify if a class can have multiple enclosing classes, that one more than one ordered set of enclosing classes. For example, if we make a copy of Orig.P can it have two set of enclosing classes: one starting with Main and up, and the second one starting with Orig and up?
  5. Whenever a copy is made, specify the enclosing classes for it. Also specify whether the enclosing class sees the copy. For example, if we make a copy of Orig.P and apply the modifications. When we are searching for P inside Orig, do we see the modified copy or the original P.

Hans, would you like to start? How do you see the flattening happening for this model?

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 12 Mar 2014 16:58 UTC Ok, I did that exercise (and slightly modified the example).

It is attached as a power-point presentation (with comments - should probably be expanded) - I didn't have time to make it into a nicer format.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 14 Mar 2014 12:10 UTC Language group discussion: Sebastien Furic: Can be strictly defined using closures (in this case reduction at the last moment, not inside lambda expressions). Sven Erik verified that all agree that 10 is the right answer for the original model (without P.K). Gerd made the argument that P2=P(K=10) does not really contain a P.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 14 Mar 2014 13:27 UTC Two other variants are

package P
  model M
    import P.K;
    Real x= K;
  end M;
  constant Real K=5;
end P;
package P2=P(K=10);

and

package P
  model M
    Real x= .P.K;
  end M;
  constant Real K=5;
end P;
package P2=P(K=10);

Almost everyone agrees that Main.m.x=5 for those (exception is Sebastien who thinks it is a matter of how we choose and the choice should be consistent everywhere).

Should be clarified more, not clear when. Should also release 3.3r1 as soon as possible.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 22 Mar 2014 09:42 UTC Replying to [comment:45 hansolsson]:

Language group discussion: Sebastien Furic: Can be strictly defined using closures (in this case reduction at the last moment, not inside lambda expressions). Sven Erik verified that all agree that 10 is the right answer for the original model (without P.K). Gerd made the argument that P2=P(K=10) does not really contain a P.

Looking at S. Furic's comment I now notice that one part was not clear in the powerpoint, and in the specification (unless I missed it): the lookup first finds the partially flattened element (i.e. element with modifiers) - not the flattened element (i.e. where modifiers have been applied to the bitter end). This also ensures that outer modifiers can override inner ones.

This explains why the specification in 5.3.3 needs to specify that global constants are flattened to actually get the value.

modelica-trac-importer commented 5 years ago

Comment by adrpo on 12 Apr 2014 06:47 UTC Thanks Hans for your nice power point! Is a bit of a mess in there but it does clarify some things in the specification.

However, there are more things that are not very clear in the specification with regards to flattening. One of them is how you handle self references. One example is this model:

model SelfReference
  package P
    type T = Real(start = 1);
    package L
      replaceable package M = P(T(start = 3));
      model X
        parameter M.T t = 10;
        parameter M.L.M.T u = 10;
      end X;
    end L;
  end P;

  P.L.X x;
  parameter P.L.M.L.M.T y = 10;
  package U = P.L(redeclare package M = P);
  U.M.L.X i;
end SelfReference;

If you follow the specification it would mean that in order to flatten M in P.L one would need to flatten P which would flatten M which would flatten P and so on.

There are several ways to handle this and I guess Modelica tools do it differently:

  1. when flattening M only clone the contents of P inside M that are actually referenced: T and its dependencies (this is the more complicated variant).
  2. when flattening M realize that P is a self reference and clone contents of P inside M with exception of M.
  3. do cloning of P inside M lazily (sort of like 1)

I think is important to clarify this in the specification as this affects subtyping because the type of P.L.M is incomplete. Can P.L.M be used instead of P?

Depending on how tools implement the self referencing they might not even be able to flatten the model.

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 14 Apr 2014 18:59 UTC Thank you, Hans, for the presentations. And thanks to everyone who participated in the discussion. Sorry, I was not replying earlier. I have a few comments about the process described in the presentation. But before we discuss that, I would like to get a clarification of what we mean by "flattening".

It seems that there are three different things that the term flattening is used to describe.

Since, we are discussing the formulation of Section 5.6 Flattening Process, I think we should start by clarifying:

modelica-trac-importer commented 5 years ago

Comment by eshmoylova on 16 Jun 2014 02:51 UTC I hope that this ticket will get looked at during the meeting this week. Since I can't be there personally, here is my contribution to the discussion.

I think that one of the sources for the confusion about how the flattening process should proceed comes from interpreting flattening as it is described in definition in Section 1.3 (see my previous comment). In the light of that definition, I would expect to have a DAE system at the end of the flattening. However, from Hans’s presentation it looks like the goal of the flattening process described in 5.6 is to collect all the elements of the class and the scopes (i.e. enclosing classes) for all elements, modifications, and equations. This process leaves the names in the modifications and equations as they are, without resolving them to the fully specified names. I think it should be said so explicitly in this section. Perhaps the definition in 1.3 needs to be changed. Or a new name for the process described in 1.3 should be given.

It may also help to add clarification on how the base classes are flattened. As I understand from Hans's presentation, when a base class is being flattened, a copy of the class defining it is made, and the redeclarations and modifications are applied to the copy. The copy is flattened, and the elements are inherited into the class that extends it. The enclosing classes for these inherited elements remain to be the copy of the base class.

Similarly, when a local component is flattened, it is its type that needs to be flattened. The copy of the type class is made. The modifications and redeclarations that belong to the component are applied to this copy, and the copy is flattened. The enclosing class for the copy remains the enclosing class of the original.