modelica / ModelicaSpecification

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

MCP-0007: Match Expressions and Recursive Records #1503

Open modelica-trac-importer opened 6 years ago

modelica-trac-importer commented 6 years ago

Reported by petfr on 3 Jun 2014 08:42 UTC

The MCPI-0007 Match expressions and Recursive Records design proposal is discussed at this ticket.

The most recent version v5, makes a comparison, pros and cons, between the uniontype and the case record design alternatives.

Associated files are also at the the MCPI trunk.


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

modelica-trac-importer commented 6 years ago

Comment by choeger on 3 Jun 2014 09:29 UTC Thanks for the update.

A few remarks:

Conclusion: The proposal seems to suffer from a minor case of the NIH syndrome ;). I'd prefer to not vary from widely accepted and tested practice without strong reason.

modelica-trac-importer commented 6 years ago

Comment by hansolsson on 18 Jun 2014 07:11 UTC I agree with the previous comment, and consider the case:

match x in fooBar()
case RCONST() then x.expr1;
case ADD() then x.expr1+x.expr2;

To me (and others) this half-pattern matching is confusing.

If proper pattern matching leads to problems I would propose a different variant to make it clear that these are not patterns for pattern matching and found some inspiration in Modula-2:

One could either use a different keyword for the match:

matchtype x in fooBar()
case RCONST then x.expr1;
case ADD then x.expr1+x.expr2;

alternatively add the keyword for the cases:

match x in fooBar()
if type RCONST then x.expr1;
if type ADD then x.expr1+x.expr2;

The second approach would allow us to combine patterns and type-matching:

match x in fooBar()
if type RCONST then x.expr1;
if ADD(a,a) then 2*a;
if type ADD then x.expr1+x.expr2;

The names “case type” or “type case” would be too confusing to me; but it is just naming.

modelica-trac-importer commented 6 years ago

Comment by petfr on 20 Nov 2014 10:11 UTC From rfranke, via e-mail:

meanwhile I also remembered the discussed "killer feature" motivating MetaModelica: It was the ability to automatically adapt existing models to changes in a used library. For instance I replaced all "dqo" with "dq0" in release 0.3 of PowerSystems - just google for dq0 and you find the right things - google for dqo and you find a lot of misleading stuff as well. Would be great if there was a standardized way to provide a script adapting all models that use an old version of PowerSystems .

Generally I think that the missing feature "conversion scripts" hinders a lot of innovation in the current development of MSL.

modelica-trac-importer commented 6 years ago

Comment by hansolsson on 20 Nov 2014 11:14 UTC Replying to [comment:3 petfr]:

From rfranke, via e-mail:

meanwhile I also remembered the discussed "killer feature" motivating MetaModelica: It was the ability to automatically adapt existing models to changes in a used library. For instance I replaced all "dqo" with "dq0" in release 0.3 of PowerSystems - just google for dq0 and you find the right things - google for dqo and you find a lot of misleading stuff as well. Would be great if there was a standardized way to provide a script adapting all models that use an old version of PowerSystems .

Generally I think that the missing feature "conversion scripts" hinders a lot of innovation in the current development of MSL.

I believe that is a separate issue. There is a topic for standarizing those "conversion scripts" (at least the currently useful ones, we don't need the special cases for Modelica 1->2 anymore), and pattern matching should not be a blocker for discussing that.

Lots of tools for different languages have such "refactoring" possibilities, without relying on match expressions.

For Modelica I know that Dymola and Dymola Behavior Modeling App have settings such that just changing the name of the variable (or class) automatically updates all existing uses and also creates the "conversion script", and I would assume it is similar for the other Modelica tools.

Yes, I can see that pattern matching would make it easier to write a "refactoring package" that a tool could use to implement the "conversion script" handling - but the user just wants the conversion to work automatically.

modelica-trac-importer commented 6 years ago

Comment by sjoelund.se on 20 Nov 2014 12:06 UTC Replying to [comment:4 hansolsson]:

Dymola and Dymola Behavior Modeling App have settings such that just changing the name of the variable (or class) automatically updates all existing uses and also creates the "conversion script", and I would assume it is similar for the other Modelica tools.

Yes, I can see that pattern matching would make it easier to write a "refactoring package" that a tool could use to implement the "conversion script" handling - but the user just wants the conversion to work automatically.

Exactly. The "conversion script" generated by Dymola will not work automatically in any other tool (that I know of). If there existed a standardised representation for the abstract syntax tree, there could be a library, say, ModelicaConversions that would make it very simple to create conversion scripts.

myConversion.mos:

ast := getLoadedAst(); // Some built-in
ast := ModelicaConversions.rename(ast=ast, fromName="Modelica.Blocks.Continuous.Integrator.k", toName="Modelica.Blocks.Continuous.Integrator.k1", classesToUpdate={"MyLibrary"});
updateLoadedAst(ast); // Modify the loaded ast to be the updated one

And the library would contain the actual code to update the abstract syntax tree. It is a very nice application anyway.

modelica-trac-importer commented 6 years ago

Comment by sjoelund.se on 20 Nov 2014 12:10 UTC For the OpenModelica prototype on how match-expressions and union types work: Basic-Exercise-MetaModelica.onb (The latest nightlies crash for the invalid cons-expressions, so simply don't evaluate the offending cell)

Evaluate cells by marking the cell and pressing shift+enter.

modelica-trac-importer commented 6 years ago

Comment by hansolsson on 20 Nov 2014 14:47 UTC Replying to [comment:5 sjoelund.se]:

Replying to [comment:4 hansolsson]:

Dymola and Dymola Behavior Modeling App have settings such that just changing the name of the variable (or class) automatically updates all existing uses and also creates the "conversion script", and I would assume it is similar for the other Modelica tools.

Yes, I can see that pattern matching would make it easier to write a "refactoring package" that a tool could use to implement the "conversion script" handling - but the user just wants the conversion to work automatically.

Exactly. The "conversion script" generated by Dymola will not work automatically in any other tool (that I know of). If there existed a standardised representation for the abstract syntax tree, there could be a library, say, ModelicaConversions that would make it very simple to create conversion scripts.

My point is that creating the ModelicaConversions library is not simple even with match expressions and a normal ast-library (and as far as I know a standarized ast-library is not on the agenda, and not planned for Modelica 3 as far as I know).

Thus we shouldn't have this topic blocking the discussion of conversion scripts, and we shouldn't try to sell match-expressions as the solution to the problem of conversion scripts.

And the library would contain the actual code to update the abstract syntax tree. It is a very nice application anyway.

I would say that the conversion scripts are not good at demonstrating an ast-library, since they sort of operate on the ghost of the ast of the last version.

During normal refactoring you can rename "dqo" to "dq0" in all classes by looking up "dqo" and atomatically replacing them all by "dq0" (that would be a good example of using an ast-library), but when using the conversion script we only have the new version and cannot find the "dqo" to replace.

Yes, a good ast-library may have the primitives to find them - but I wouldn't call it a good example of the use of the ast-library.

Similarly the procedural style of renaming items one-by-one by calling ModelicaConversions.rename is both a bit inefficient and would require that the conversion script is ordered; i.e. users could break a sequence of calls of ModelicaConversions.rename when they rearrange them to clean them up.

modelica-trac-importer commented 6 years ago

Modified by otter on 18 Sep 2015 10:16 UTC

modelica-trac-importer commented 6 years ago

Modified by otter on 18 Sep 2015 10:20 UTC

modelica-trac-importer commented 6 years ago

Modified by otter on 18 Sep 2015 10:20 UTC

modelica-trac-importer commented 6 years ago

Modified by dietmarw on 2 Dec 2015 08:31 UTC

modelica-trac-importer commented 6 years ago

Comment by hansolsson on 8 Mar 2016 09:35 UTC Trying to write down my comments about union.

In Haskell/ML we do things like Option=None | Some a In Ada/Modula-2 the variant records are similar (and type-safe and allow matching).

We should have references to these and other languages in the proposal so that we can actually compare.

I don't see any languages where the same syntax is used for defining classes inside unions as for defining normal classes, and I would prefer such a separation between the variants and other local classes.

The possibilities I could see for improving this would be: 1 Use components for variants - not record 2 Use some other separator than ";" 3 Use some other tag than record for the variants

Current proposal:

  union OptionType
    replaceable record OptionType=Any;
    record None end None;
    record Some OptionType a; end Some;
  end OptionType;

Variant 1:

  record None end None;
  union OptionType
    replaceable record OptionType=Any;
    None none;  
    OptionType some;
  end OptionType;

Variant 2:

  union OptionType
    replaceable record OptionType=Any;

    record None end None or  // <--- or some other symbol but not ";"
    record Some OptionType a end Some; 
  end OptionType;

Variant 3A:

  union OptionType
    replaceable record OptionType=Any;

    Variant None end None;
    Variant Some OptionType a; end Some;
  end OptionType;

Variant 3B (Ada-like):

  union OptionType
    replaceable record OptionType=Any;

    if None then end if;
    if Some then OptionType a; end if;
  end OptionType;
modelica-trac-importer commented 6 years ago

Comment by sjoelund.se on 10 Mar 2016 09:56 UTC Some different ways to deal with "type variables" in a (current) Modelica-like fashion:

1.

partial union Option
  replaceable type T=None constrainedby Any;
  record NONE
  end NONE;
  record Some
    T value;
  end Some;
end Option;

This has the problem that any function you declare is rather... useless:

function getValue
  input Option opt;
  output '???' value;
end getValue;

Unless you put all functions inside the union (similar to operator record):

partial union Option
  replaceable type T=None constrainedby Any;
  record NONE
  end NONE;
  record Some
    T value;
  end Some;
  function getValue
    input Option opt;
    output T value = match opt case SOME(value) then value; end match;
  end getValue;
end Option;
  1. Similar to 1. you could instead put the "type variable" in a package:
partial package Option
replaceable type T=None constrainedby Any;
partial union OptionType
  record NONE
  end NONE;
  record Some
    T value;
  end Some;
end OptionType;

function getValue
  input Option opt;
  output T value = match opt case SOME(value) then value; end match;
end getValue;
end Option;
  1. and 2. both have the problem that these are not really type variables. There is no polymorphism here and no re-use of functions (you will create one function for each separate type T). What MetaModelica does is:

union Option<T> // No longer partial!
  record NONE
  end NONE;
  record Some
    T value;
  end Some;
end Option;

function getValue<T>
  input Option<T> opt;
  output T value = match opt case SOME(value) then value; end match;
end getValue;

Note that MetaModelica also allows all of 1, 2 and 3, so they are not mutually exclusive.

How to deal with subtyping can be done in different ways. What you would like to do is create aliases like:

union OptionInteger = Option(redeclare T=Integer);
union OptionInteger = Option<Integer>; // MetaModelica style
union IntegerOption = Option(redeclare T=Integer);

Should OptionInteger and IntegerOption be type-compatible? It is easy to deal with this with the MetaModelica style because short class declaration like this is treated as an alias for the nominal type Option plus the type variables which also need to be subtype. So our algorithm is:

  1. Check the nominal type (short class declarations without modifiers keep the original nominal type; redeclarations, etc create a new nominal union)
  2. Check subtyping of all type variables

The question is if the Modelica version should treat OptionInteger and IntegerOption as the same type or not. They could be if the union was restricted in such a way that type variables were clearly defined as a separate concept, but I suspect it would be hard to do it in the way of 1 and 2. The style introduced in 3 is very explicit and treats type variables as separate in a way that makes it easier to write generic functions that can be re-used (but this requires tools to be able to pack all variables in a way that the record here always has the same size regardless of the type T).

If the simple style of 1 or 2 is used and we decide T is actually not a type variable, the subtyping is very simple: it is the nominal type OptionInteger or IntegerOption, and you need to extend either the union or package in order to add additional generic functions to these classes.