modelica / ModelicaSpecification

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

MCP-0022: Converting from Integers to enumeration values #1842

Closed modelica-trac-importer closed 5 years ago

modelica-trac-importer commented 5 years ago

Modified by dietmarw on 2 Dec 2015 17:53 UTC The Integer() function can be used to convert from an enumeration value to the corresponding integer, but there is no way to convert the other way.

It is possible to set up an array to do such a mapping for a specific enumeration type:

type Colors = enumeration ( RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW );

model Main
    parameter Colors ItoC[:] = {c for c in Colors};
    Colors c;
    Integer i;
equation
    c = ItoC[i];
end Main;

This has two drawbacks:

  1. It has to be done separately for each enumeration type.

  2. It is difficult for a tool to recognize that this is what is intended, and so it will potentially generate much less efficient code (i.e. some sort of array lookup) than it would if the intent were clear (i.e. a no-op, since internally, enumerationsare integers).

I would like to suggest that the name of the enumeration type be usable as a function to convert to that type:

    c = Colors(i);

For consistency with other cases where enumerations and Booleans are treated similarly, the same should be true for the Boolean type:

    b = Boolean(j);

Documents


Reported by stefanv on 24 Nov 2015 16:03 UTC The Integer() function can be used to convert from an enumeration value to the corresponding integer, but there is no way to convert the other way.

It is possible to set up an array to do such a mapping for a specific enumeration type:

type Colors = enumeration ( RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW );

model Main
    parameter Colors ItoC[:] = {c for c in Colors};
    Colors c;
    Integer i;
equation
    c = ItoC[i];
end Main;

This has two drawbacks:

  1. It has to be done separately for each enumeration type.

  2. It is difficult for a tool to recognize that this is what is intended, and so it will potentially generate much less efficient code (i.e. some sort of array lookup) than it would if the intent were clear (i.e. a no-op, since internally, enumerationsare integers).

I would like to suggest that the name of the enumeration type be usable as a function to convert to that type:

    c = Colors(i);

For consistency with other cases where enumerations and Booleans are treated similarly, the same should be true for the Boolean type:

    b = Boolean(j);

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

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 24 Nov 2015 16:14 UTC It would make for a simpler compiler implementation if Colors was not different in 3 contexts (a typename, a built-in operator, and as iterator). A similar proposal would be to allow it instead as both expression and type, evaluating the name to an array:

c = Colors[i]
b = Boolean[j]

Right now, one has to detect Boolean as an expression and handle it differently for iterators and anywhere else. Why not allow it somewhere else as well:

b = max(Boolean)
modelica-trac-importer commented 5 years ago

Comment by choeger on 25 Nov 2015 06:02 UTC I agree with Martin here, it seems quite inconsistent when the same expression has two completely different meanings based on an only partially defined context.

However, I find it also disturbing, that you cannot convert every integer into an enumeration. How about error handling?

p.s.: Where in the spec is it stated that an enumeration is an integer? IMO there are other legit encodings: What about a long? What about an algebraic datatype?

modelica-trac-importer commented 5 years ago

Comment by stefanv on 25 Nov 2015 12:29 UTC Note that Boolean is already used as a type name and as an iterator, and Integer and String are already used as both type name and built-in operator (it makes no sense to use either of those as an iterator).

If anything, my proposal simply makes thingsmore consistent. But yes, it does make more sense to allow Boolean and any enumeration type to be used as an array. Actually, so long as the array is never instantiated, using Integer as an array could be allowed as well, and then max(Integer) would return the maximum representable Integer.

PS: It is nowhere stated that an enumerationis an integer (although I suspect it is in most implementations), but since one can use Integer(e) to convert an enumerationto an integer, there is at least some sort of mapping between enumeration and integer values, so there ought to exist a mechanism to convertfrom and integer.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 25 Nov 2015 13:36 UTC Replying to [comment:3 stefanv]:

Note that Boolean is already used as a type name and as an iterator, and Integer and String are already used as both type name and built-in operator (it makes no sense to use either of those as an iterator).

If anything, my proposal simply makes thingsmore consistent. But yes, it does make more sense to allow Boolean and any enumeration type to be used as an array. Actually, so long as the array is never instantiated, using Integer as an array could be allowed as well, and then max(Integer) would return the maximum representable Integer.

I agree, and I think that having a consistent syntax for different conversions is desirable - especially since it is consistent with how you construct other variants, e.g. Complex(2) to generate a Complex number - and String(2.3) to stringify a number.

Treating the enumeration (and Boolean) as a constant array seems elegant, but I believe it might cause more confusion.

An advantage with Colors(...) is that we could imagine adding options - e.g. to specify how to handle illegal values (such that Colors(-1, clamp=true) returns the same as Colors(1) - whereas the default is to just generate an error).

PS: It is nowhere stated that an enumerationis an integer (although I suspect it is in most implementations), but since one can use Integer(e) to convert an enumerationto an integer, there is at least some sort of mapping between enumeration and integer values, so there ought to exist a mechanism to convertfrom and integer.

Agreed, it is just a matter of inverting that function. And the only reasonable alternative to using integer is to use another integer type (as can be specified in C++11).

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 25 Nov 2015 13:53 UTC Replying to [comment:3 stefanv]:

PS: It is nowhere stated that an enumerationis an integer

12.9.1.1 - The external C mapping does include enumeration. It is possible to declare a function like:

model M
  type MyEnum = enumeration(A,B,C);
  function intMyEnum
    input Integer i;
    output MyEnum e;
  external "C" annotation(Include="int intMyEnum(int i) {return i;}");
  end intMyEnum;
  MyEnum e = intMyEnum(1);
end M;
modelica-trac-importer commented 5 years ago

Comment by choeger on 25 Nov 2015 14:16 UTC Stefan: Just because something is weird already, does not mean we should make things even weirder. Afaik, right now, Boolean and Integer expressions have one meaning. Your proposal would require us to introduce two distinct meanings (one as an conversion and one as an interator) depending on the context just for a slight only perceived convenience. Of course, this is traditional Modelica style, so we probably introduce it... I also propose to introduce another special variant: When the enumeration is applied to a real-value, the value is either floored (when the name of the surrounding class has an even number of letters) or ceiled (when that is not the case).

Hans: The function is not invertible (at least the inverse is not a function), or do we handle the ints modulo the number of cases?

Martin: This is quite implementation dependent and error-prone. What happens with this function in case of an overflow?

My 2ct: If the purpose is really that function, why not have every enumeration contain an implicit function of_int and to_int ?

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 25 Nov 2015 14:34 UTC Replying to [comment:6 choeger]:

@Hans: The function is not invertible (at least the inverse is not a function), or do we handle the ints modulo the number of cases?

If we want to be technical Colors is right-invertible, since Colors(Integer(e)) is the identity mapping for elements of the enumeration. (Assuming I didn't mess up left and right.)

@Martin: This is quite implementation dependent and error-prone. What happens with this function in case of an overflow?

I hope that Modelica tools detect array indices out of bounds, and treat that as an error.

My 2ct: If the purpose is really that function, why not have every enumeration contain an implicit function of_int and to_int ?

Those names do not look like normal Modelica names due to underscore and the abbreviation "int".

modelica-trac-importer commented 5 years ago

Comment by choeger on 25 Nov 2015 18:59 UTC Replying to [comment:7 hansolsson]:

If we want to be technical Colors is right-invertible, since Colors(Integer(e)) is the identity mapping for elements of the enumeration. (Assuming I didn't mess up left and right.)

It is definitely not (since Integer() is not surjective) and in fact it is neither, since Color() is not a function. In both cases, you'd assume that every Integer maps to at least some element of Color which it does not.

I hope that Modelica tools detect array indices out of bounds, and treat that as an error.

Sorry, but "hope" makes a pretty bad specification. As Martin demonstrates, we do have a specification for an implementation but we do not have a specified result.

Those names do not look like normal Modelica names due to underscore and the abbreviation "int".

Yeah right. Let's forget about the idea, since it will be *impossible* to change these names. And the color of the shed is the most important thing to discuss...

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 25 Nov 2015 19:10 UTC Replying to [comment:8 choeger]:

I hope that Modelica tools detect array indices out of bounds, and treat that as an error.

Sorry, but "hope" makes a pretty bad specification. As Martin demonstrates, we do have a specification for an implementation but we do not have a specified result.

We actually have min and max-values for all enumerations, and assertions added for them. So the external function should do bounds checking before accepting the value (I don't think we do though).

modelica-trac-importer commented 5 years ago

Comment by choeger on 26 Nov 2015 08:03 UTC Replying to [comment:9 sjoelund.se]:

We actually have min and max-values for all enumerations, and assertions added for them. So the external function should do bounds checking before accepting the value (I don't think we do though).

By "we" do you mean omc? IMO there is no mapping between integers and enumerations in the specification - hence there cannot be a max nor min value.

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 26 Nov 2015 08:51 UTC Replying to [comment:10 choeger]:

By "we" do you mean omc? IMO there is no mapping between integers and enumerations in the specification - hence there cannot be a max nor min value.

No. We as in MA. Check 4.8.5.1, which specifies min, max, and an assertion for all enumeration values. There is a mapping from enumeration to Integer, and there is an external C mapping to and from int to enumeration. To me, it all seems rather clear.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 09:08 UTC Replying to [comment:8 choeger]:

Replying to [comment:7 hansolsson]:

If we want to be technical Colors is right-invertible, since Colors(Integer(e)) is the identity mapping for elements of the enumeration. (Assuming I didn't mess up left and right.)

It is definitely not (since Integer() is not surjective) and in fact it is neither, since Color() is not a function.

The proposed Colors function (defined from Integer to the enumeration Colors) is surjective (and right-invertible), since there for any enumeration member exists an integer input that produces that enumeration value.

I hope that Modelica tools detect array indices out of bounds, and treat that as an error.

Sorry, but "hope" makes a pretty bad specification. As Martin demonstrates, we do have a specification for an implementation but we do not have a specified result.

That's why the specification states that tools should check array indices, and what the legal array indices are (however, tools may optionally suppress this). And as M. Sjölund pointed at the same time there is something similar for enumerations in general.

I was just hoping that tools follow this.

Those names do not look like normal Modelica names due to underscore and the abbreviation "int".

Yeah right. Let's forget about the idea, since it will be *impossible* to change these names. And the color of the shed is the most important thing to discuss...

There is a proposed name Colors(), which have some advantages - and the alternative Colors[] that has some other advantages.

I believe both have merits, but I prefer Colors() - since it is similar to constructors for records, operator records, and to the String-function; additionally we could add the 'clamp' option to specify how values out of bounds are handled. (Obviously we could add both variants - but it doesn't look elegant.)

You instead proposed Colors.from_int() - and there are a number of problems with it; and to me the only difference between Colors() and Colors.from_int() is the naming of the function.

modelica-trac-importer commented 5 years ago

Comment by choeger on 26 Nov 2015 12:11 UTC Replying to [comment:12 hansolsson]:

The proposed Colors function

Hans, it is not even a function. It is not left-total. Not a function.

I was just hoping that tools follow this.

There is no specification of how an integer maps to an enumeration, so how in hell should a tool follow something that does not even exist? For starters: Does an enumeration start with 1 or 0? Does the encoding increase in definition order? Lexically? Randomly? Can a tool use the complete integer range for all enumerations or does it have to start with 1 (or 0) for every instance again? What is an enumeration value, that is the question at stake here.

Apparently, there is a paragraph answering these issues. However, the bigger issue below remains.

You instead proposed Colors.from_int() - and there are a number of problems with it; and to me the only difference between Colors() and Colors.from_int() is the naming of the function.

When the only difference is the naming, how can there be a "number of problems"? Propose a name that suits you and be done with it.

There is, however, one big problem with Colors(): it overloads the meaning of the expression Color it might be an iterator or a function - depending on the context.

E.g. when I pass it as an argument to a function, there are no braces so is it the former or the latter?

This is the kind of "design" that makes current Modelica such a hodge podge of hard to understand and impossible to implement special cases and special special exceptions. This is neither easy to read, easy to write nor easy to implement. I cannot begin to understand how someone can favor a language design like that. Who is the target audience of such a mess?

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 26 Nov 2015 12:15 UTC Replying to [comment:13 choeger]:

There is no specification of how an integer maps to an enumeration

4.8.5.2 says:

Integer(E.e1) =1, Integer(E.en) = n, for an enumeration type E=enumeration(e1, ..., en)

I will assume that the numbers in-between are in ascending order.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 12:58 UTC Replying to [comment:13 choeger]:

Replying to [comment:12 hansolsson]:

The proposed Colors function

Hans, it is not even a function. It is not left-total. Not a function.

We also have the functions asin, acos, sqrt in Modelica; none of them are left-total.

We still call them functions - and we loosely think of asin as the inverse of sin since asin(sin(x))=x - even if asin R->R is not defined for every real value and asin(sin(x))=x only holds around 0.

So, similarly as we say that asin is the inverse of sin the proposed Colors function is the inverse of Integer with Colors-enumeration input. [Actually asin is more problematic.]

If we wanted to be strict it is possible to handle by specifying that the domain of Colors is a sub-set of integers, specifically 1..n, where n=size({e for e in Colors}, 1).

Or to summarize: It is possible to make this argument as stringent as needed; but fundamentally the Colors-function inverts the mapping Integer(...) from Colors-member to Integers.

I don't see it as meaningful to split hairs over this, but if someone wants to make it strict according to some formal definition - please go ahead.

modelica-trac-importer commented 5 years ago

Comment by choeger on 26 Nov 2015 13:04 UTC Replying to [comment:15 hansolsson]:

We also have the functions asin, acos, sqrt in Modelica; none of them are left-total.

In fact, they are functions if we take into account that their domain is a floating point representation including !NaN. But your point is that the result of !sqrt(-1.) is unspecified and hence the result of !Color(1024234589238445) can also be left unspecified. Why should we specify anything then?

I don't see it as meaningful to split hairs over this, but if someone wants to make it strict according to some formal definition - please go ahead.

Yeah right, let's just get some work done. It's not like we are defining some precise technical standard here ....

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 13:08 UTC After an internal discussion I believe one important question is the following:

What is the reason for mapping from Integer to an Enumeration, i.e. why is this feature needed?

The reason for asking that question is that it might indicate that something else is missing, e.g.:

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 13:17 UTC Replying to [comment:16 choeger]:

Replying to [comment:15 hansolsson]:

We also have the functions asin, acos, sqrt in Modelica; none of them are left-total.

In fact, they are functions if we take into account that their domain is a floating point representation including !NaN. But your point is that the result of !sqrt(-1.) is unspecified and hence the result of !Color(1024234589238445) can also be left unspecified. Why should we specify anything then?

I was going to leave this discussion, but I believe I need to clarify what the Modelica specification says - since the description above is wrong.

The function sqrt(v) is defined as: Returns the square root of v if v>=0, otherwise an error occurs. Argument v needs to be an Integer or Real expression.

So similarly Colors(i) should return Colors.ei if 1<=i<=size({e for e in Colors},1), and otherwise an error occurs. [Argument i needs to be an Integer expression.]

Possibly Colors(i, clamp=b) should return Colors.ei if 1<=i<=n=size({e for e in Colors},1), and Colors.e1 if i<1 and b is true, and Colors.en if i>n and b is true, and otherwise an error occurs. [Argument i needs to be an Integer expression, and b a Boolean expression.]

Returning NaN for sqrt(-1) is not correct in Modelica; and if we include NaN we might also consider the different variants of NaN.

modelica-trac-importer commented 5 years ago

Comment by choeger on 26 Nov 2015 13:30 UTC Replying to [comment:18 hansolsson]:

So similarly Colors(i) should return Colors.ei if 1<=i<=size({e for e in Colors},1), and otherwise an error occurs.

So error handling is finally covered. Now would you please elaborate on how to unambiguously distinguish the iterator/function overloading you propose?

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 14:05 UTC Replying to [comment:19 choeger]:

Replying to [comment:18 hansolsson]:

So similarly Colors(i) should return Colors.ei if 1<=i<=size({e for e in Colors},1), and otherwise an error occurs.

So error handling is finally covered. Now would you please elaborate on how to unambiguously distinguish the iterator/function overloading you propose?

I mentioned generating errors for out-of-bounds values already in comment 4; but I didn't specify all the details.

For distinguishing: Currently if the iteration range or array dimension is declared as Colors it is the entire range of the enumeration.

So,

  1. As type: type E=Colors; parameter Colors a; // Declares a parameter of type Colors.

2a. As short-hand for enumeration-range: parameter Integer x[Colors]; // Uses Colors as the size and declares that x must be indexed with elements of the enumeration Colors. 2b. As short-hand for enumeration-range: {e for e in Colors} uses Colors as a short-hand for Colors.e1:Colors.en

  1. As functions returning the elements: Colors(i) returns the i:the enumeration element.

The case (2) can be distinguished, since it is the entire enumeration type without any argument - and just in certain contexts (array declaration and iterator-expression). That should already be handled in tools - and isn't that complicated. The case (3) vs. (1) is the same as record constructors vs. record declarations.

If we instead consider Colors[i] we could mentally merge cases (3) and (2b), but (2a) doesn't really fit - unless we want to generalize to arrays starting from another index than 1, which I believe will cause more problems that it is worth.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 26 Nov 2015 14:17 UTC Conversion of Booleans?

I noticed that the proposal also had Boolean(b) similarly as Colors(e).

I don't think that is a good idea, since a Boolean is in C normally mapped to 0, 1 - whereas enumerations in Modelica start at 1 (I believe that mapping is missing from the external C-interface, #1846). And arrays can be indexed using Booleans - even if array indices normally start at 1 in Modelica.

So, by not providing the conversion between Boolean and Integers we require users to think when converting between integers and Booleans - instead of almost mixing them with enumerations.

In addition Boolean(a) is longer than (a==1) - which already does the job.

modelica-trac-importer commented 5 years ago

Comment by choeger on 26 Nov 2015 18:17 UTC Case 2a is indeed simple enough to handle.

Distinction between 2b and 3 is still unclear however. Does your definition of context mean that the following statement is illegal:

{e for e in if Colors(1) == Red then Colors else {Green}}

We could assume that the Modelica syntax allows for a clear distinction of a function in an application, since functions are not expressions. But this kind of overloading should be very carefully specified, which in turn is a lot of work, makes the language even harder to read and does not seem to provide any benefit IMO.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 26 Nov 2015 20:12 UTC The above statement would be legal iff the following is currently legal:

{e for e in if Colors.min = Colors.Red then Colors else {Colors.Green}}

(Note: I'm assuming you meant to write Colors.Red and Colors.Green in your code in comment 22, since Red and Green alone would not be allowed.)

In other words, it boils down to whether the use of an enumeration type name (or Boolean)within a range vector expression is legal, or whether it's only legalas a range vector expression.

This is, in my opinion, orthogonal to the issue of allowing the use of Colors(i) to convert from Integer to Colors.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 26 Nov 2015 20:13 UTC Replying to [comment:17 hansolsson]:

After an internal discussion I believe one important question is the following:

What is the reason for mapping from Integer to an Enumeration, i.e. why is this feature needed?

One of our developers asked for it. I will find out why.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 26 Nov 2015 20:41 UTC Thinking about this some more, I'm leaning towards what Martin said in comment 1. If we simply changed the part of the specification that states,

The Boolean type name or an enumeration type name can be used to specify the dimension range for a dimension in an array declaration and to specify the range in a for loop range expression;

to,

The Boolean type name or an enumeration type name can be used to specify the dimension range for a dimension in an array declaration and to specify a range vector;

then all existing functionality is preserved, and converting from an integer value to an enumeration value becomes just simple indexing.

A range vector is already an array. J:K is equivalent to {i for i in J:K}.

So, it seems odd that currently, using Colors as an expression is not allowed, yet {c for c in Colors} is allowed.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 27 Nov 2015 08:34 UTC We don't have a useful expression concept in the language, so it should not come as a surprise that what looks like an expression in one place cannot be used in what looks like some kind of expression context somewhere else.

I totally agree with Hans' comment:17, and I even question the convenience of the current mapping from enumerators to Integer. Apart from low level interfaces to the outside world, like the external language interface and file storage schemes, what are the good uses of mapping from enumerators to Integer? I guess that reliable code that does not deal with the outside world should generally stay away from mapping back and forth between enumerations and Integer.

That said, I agree that we need to introduce something for the inverse mapping of Integer(•). However, when doing this, we should not encourage everyday users to increase their sloppy use of Integer to represent enumerator values. Hence, I think syntactical convenience is undesirable, and just as I find Integer(e) misleadingly convenient, I find both Colors(i) and Colors[i] equally misleadingly convenient.

I would suggest a new symmetric pair of keywords:

e = integer_to_enumeration( Colors, i ); /* Same as Integer(•). */
i = enumeration_to_integer( Colors, e ); /* Inverse of Integer(•). */

(Note that these are special forms, not function calls, since Colors is not some kind of expression.)

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 27 Nov 2015 16:22 UTC Replying to [comment:25 stefanv]:

Thinking about this some more, I'm leaning towards what Martin said in comment 1. If we simply changed the part of the specification that states,

The Boolean type name or an enumeration type name can be used to specify the dimension range for a dimension in an array declaration and to specify the range in a for loop range expression;

to,

The Boolean type name or an enumeration type name can be used to specify the dimension range for a dimension in an array declaration and to specify a range vector;

then all existing functionality is preserved, and converting from an integer value to an enumeration value becomes just simple indexing.

My problem with this variant is Real x[Colors];.

I had some problem figuring out why it would be a problem until I realized that suddenly "Colors" would be a legal Modelica expression (it isn't currently - so it is linked to the comment below), but in this context the expression would be illegal and a different interpretation is used.

I find that a bit confusing.

Trying to solve it by generalizing to Real x[1:10];, Real x[2:10];, and Real x[{2,3,5,6}]; seems to add too much complication for too little gain. (General maps seems like a better solution.)

Replying to [comment:23 stefanv]:

The above statement would be legal iff the following is currently legal:

{e for e in if Colors.min = Colors.Red then Colors else {Colors.Green}}

(Note: I'm assuming you meant to write Colors.Red and Colors.Green in your code in comment 22, since Red and Green alone would not be allowed.)

In other words, it boils down to whether the use of an enumeration type name (or Boolean)within a range vector expression is legal, or whether it's only legalas a range vector expression.

Currently it is 'as', not 'within'.

At least that's my understanding, based on the fact that we haven't defined what the expression Colors mean - and thus we don't know the type of if ... then Colors else {Colors.Green}.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 27 Nov 2015 16:36 UTC Replying to [comment:17 hansolsson]:

After an internal discussion I believe one important question is the following:

What is the reason for mapping from Integer to an Enumeration, i.e. why is this feature needed?

We are, at the moment, rewriting Modelica.Electrical.Digital.Sources.Table, because the current implementation is, at the very least, bad, and probably wrong. Since the desired behaviour of the digital table is very similar to that of Modelica.Blocks.Sources.IntegerTable, we wanted to leverage that, simply mapping the inputs from Logic to Integer, and the outputs from Integer back to Logic.

We're planning on filing a separate ticket about the digital table once we've got a proposed implementation, which is why you're all hearing about it here in this ticket first.

So, one possible use case is the reuse of integer code to implement corresponding behaviour for enumerations.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 27 Nov 2015 16:53 UTC Replying to [comment:28 stefanv]:

Replying to [comment:17 hansolsson]:

After an internal discussion I believe one important question is the following:

What is the reason for mapping from Integer to an Enumeration, i.e. why is this feature needed?

We are, at the moment, rewriting Modelica.Electrical.Digital.Sources.Table, because the current implementation is, at the very least, bad, and probably wrong. Since the desired behaviour of the digital table is very similar to that of Modelica.Blocks.Sources.IntegerTable, we wanted to leverage that, simply mapping the inputs from Logic to Integer, and the outputs from Integer back to Logic.

We're planning on filing a separate ticket about the digital table once we've got a proposed implementation, which is why you're all hearing about it here in this ticket first.

So, one possible use case is the reuse of integer code to implement corresponding behaviour for enumerations.

Ok, I understand - and I agree that it is good to reuse such an implementation.

Note that this implies that vectorized calls of the conversions are important, in this case: Integer(x) for a vector x.

That also works for Colors[ {1,2} ], but for matrices the corresponding Colors[ [1,2;3,4] ] will no longer work, whereas a function would normally vectorize for that case.

I don't know if that use-case is important, but just wanted to add that.

modelica-trac-importer commented 5 years ago

Comment by choeger on 27 Nov 2015 16:55 UTC So to summarize:

the construct

IDENT_1 in IDENT_2

has three different meanings:

And the proposal is to do the same for

IDENT ( expression )

I do not like the current situation, since it means that the intended meaning of the expression is not context-free. This is, in general, bad language design IMO. Hence I oppose the introduction of the second form and propose the removal of the first.

@stefan: Why not write that procedure in Modelica? It shouldn't be that long.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 27 Nov 2015 18:13 UTC I don't consider the first two to be two different meanings. They can both be considered as "IDENT_2 is the name of a type".

Thus for IDENT(expression), which also already has two different meanings, my proposal doesnot add a third.

Clearly, removal of the first form is not in the cards, unless someone wants to write update scripts for every tool.

Yes, it's not hard to write in Modelica. But much of what most languages provide is not hard to write in that language. One could write Integer (for a given enumeration) in Modelica as well:

function ColorToInteger
    input Color c;
    output Integer i;
algorithm
    if c == RED then i := 1;
    elseif c == GREEN then i := 2;
    ...
    end if;
end ColorToInteger;

In fact, we could throw away all the features, and just program our simulations in machine language. This will greatly simplify our job specifying Modelica, since any language that can be implemented in a few thousand transistors should be easy to specify completely and exactly.

Please remember that we are trying to design a language that is easy for engineers to use, not necessarily one that is easy for theoretical computer scientists to love. The design of Modelica, even in the absence of graphical interfaces provided by specific tools, is as much a user interface design project as a language design project.

modelica-trac-importer commented 5 years ago

Comment by choeger on 27 Nov 2015 18:59 UTC Replying to [comment:31 stefanv]:

I don't consider the first two to be two different meanings. They can both be considered as "IDENT_2 is the name of a type".

Thus for IDENT(expression), which also already has two different meanings, my proposal doesnot add a third.

Yes you do propose a third meaning. The set of builtin types is known a priori and can, in principle, be used in a context free analysis.

Yes, it's not hard to write in Modelica. But much of what most languages provide is not hard to write in that language.

I do not know any language that does so, with the notable exception of syntactic sugar. In fact, I consider it masochistic if implementors themselves advocate more and more special cases. Try this at the ocaml devel mailing list, or with the scala guys, you will soon learn that the common practice is generalization, not specialization. So yes, Integer() is a perfect example for an uncessary special case. Just implicitly introduce the function and you are done. But nope, Modelica needs to be more complex.

In fact, we could throw away all the features, and just program our simulations in machine language.

How about removing some features and work on a sound foundation? Languages that have more domains and way more users than Modelicai usually have much simpler implementations. Shouldn't we consider this or do you really think that Modelica is unique?

Please remember that we are trying to design a language that is easy for engineers to use, not necessarily one that is easy for theoretical computer scientists to love. The design of Modelica, even in the absence of graphical interfaces provided by specific tools, is as much a user interface design project as a language design project.

You really think that it is a good idea to pile special case upon special case in order to create a language that is "easy for engineers to use"? And what does "easy" mean? In my experience, engineers have trouble to grasp (open) recursion - but we do have that in Modelica! In your example, the tool cannot know from the line x = Colors(10), whether Colors is meant to be a function or not. It complicates things. Now if someone just misspelled a function, how would a meaningful error message look like: "foo is not a function. And it does not seem to be an enumeration either." Of course, this kind of syntactic overloading may be necessary sometimes, but in this case all we need to do is to introduce an implicit function.

Even in UI design people use data (A/B testing et al.) to figure out what is a good idea and what is not.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 27 Nov 2015 19:45 UTC

The set of builtin types is known a priori and can, in principle, be used in a context free analysis.

IDENT(expression) can also be a record constructor, and the set of record types is not known a priori. And the distinction between a built-in type like Integer and a library defined type like Logic is not at all immediately obvious to the user. So that Integer(x) works and Logic(x) does not, is a special case.

Try this at the ocaml devel mailing list, or with the scala guys, you will soon learn that the common practice is generalization, not specialization.

I guess that's why ocaml and scala are so wildly popular and used so widely.

Languages that have more domains and way more users than Modelica usually have much simpler implementations.

Languages that have more domains have to be more general. Modelica is a very specialized language, and should be easy to use for what it is meant for.

You really think that it is a good idea to pile special case upon special case in order to create a language that is "easy for engineers to use"?

It is not a special case. It is merely extending what already works for Integer, String, and record types. Restricting this to the built-in types would be a special case.

In your example, the tool cannot know from the line x = Colors(10), whether Colors is meant to be a function or not.

Actually, it can easily know this at the point where it needs to know. I have not seen a requirement stated anywhere that Modelica has to be amenable to context-free analysis.

Now if someone just misspelled a function, how would a meaningful error message look like

"foo is not a function" is perfectly adequate, since in my proposal, Colors wouldbe a function in that context (and Integer and record constructors are also already considered functions in that context).

all we need to do is to introduce an implicit function.

That isn't any less of a special case than just using the enumeration type name as the special function. It's still a special built-in, and needs to be handled specially inside the tool.

Even in UI design people use data (A/B testing et al.) to figure out what is a good idea and what is not.

This is true, albeit impractical for a group like the MA. Fortunately, most of our organizations also have users that we can talk to. We're not just developing a language in total isolation.

modelica-trac-importer commented 5 years ago

Comment by choeger on 27 Nov 2015 20:24 UTC Replying to [comment:33 stefanv]:

IDENT(expression) can also be a record constructor, and the set of record types is not known a priori.

But it is a proper function. You cannot use the same expression as a iteration-range expression. I do not have anything against such syntactic sugar, but I oppose the idea of a specification in the form of "X means Y except when Z, then it means Q". It is not a big problem to have a type name implicitly define a function. Nor is there an issue when the same typename implicitly defines an iteration range. But both is just begging for inconsistency in the spec and bugs in the implementation.

I guess that's why ocaml and scala are so wildly popular and used so widely.

Of course neither Scala nor OCaml, small academic toy languages they are, have the earth shattering impact of Modelica with its Millions upon Millions of developers.

This is true, albeit impractical for a group like the MA. Fortunately, most of our organizations also have users that we can talk to. We're not just developing a language in total isolation.

Than please go and ask your developers if they are aware of the already existing usecases of the name of an enumeration and if they want to deal with a third.

modelica-trac-importer commented 5 years ago

Comment by sjoelund.se on 27 Nov 2015 20:55 UTC Replying to [comment:17 hansolsson]:

What is the reason for mapping from Integer to an Enumeration, i.e. why is this feature needed?

In the past, some Modelica tools automatically converted Integer into an enumeration, and this pattern was even used in the MSL. So that's an additional thing users are already dealing with. OpenModelica even has a flag to enable this behavior since so much legacy code depends on it (and our partners asked us to implement it). Part of the reason ours partners did not want to change code was that it was rather inconvenient to do so (needed to create one function for each enumeration, etc).

modelica-trac-importer commented 5 years ago

Comment by dag on 28 Nov 2015 13:50 UTC For a discussion of language design and the compromises I highly recommend "The Design and Evolution of C++" by Bjarne Stroustrup (Addison-Wesley).

I was a member of the ANSI C++ committee for several years and head of the Swedish delegation to the corresponding ISO committee. If you have any questions about our experiences standardizing a language with major impact, feel free to contact me directly (dag.bruck@3ds.com).

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 30 Nov 2015 08:59 UTC Replying to [comment:26 henrikt]:

I would suggest a new symmetric pair of keywords:

e = integer_to_enumeration( Colors, i ); /\* Same as Integer(•). \*/
i = enumeration_to_integer( Colors, e ); /\* Inverse of Integer(•). \*/

(Note that these are special forms, not function calls, since Colors is not some kind of expression.)

This is another variant - and we could also imagine having just one keyword ( e.g. convert(i, from=Integer, to=Colors); or easier: convert(Colors, i);. This might also open up the possibility of conversion between different enumerations. (Probably a better keyword than convert is needed.)

This is similar to e.g. static_cast in C++ - and one benefit is that it is easy to unambiguously find them.

So, we now have the following variants (all can perform the conversion - even for vectors):

I might have missed some benefits for some of the variants. I deliberately only listed the benefits, but I don't believe that all benefits have the same weight and we should just take the one with the largest number.

modelica-trac-importer commented 5 years ago

Comment by choeger on 30 Nov 2015 13:38 UTC It should be obvious by now that I am in favor of variant 1. Just one addition: We are not restricted to the "call-like" syntax. We could also (even additionally) specify:

These examples are semantically equivalent, so it is largely a matter of taste.

modelica-trac-importer commented 5 years ago

Comment by henrikt on 30 Nov 2015 14:30 UTC Replying to [comment:37 hansolsson]:

* integer_to_enumeration or convert

  • Easy to distinguish and find
  • Can convert matrices
  • Could support additional arguments (e.g. clamp=true)
  • Works for conversion in both directions

I might have missed some benefits for some of the variants. I deliberately only listed the benefits, but I don't believe that all benefits have the same weight and we should just take the one with the largest number.

The benefit of this that is missing in the list is that it does not hide potentially dangerous low-level conversion behind misleadingly convenient syntax. (Compare how reinterpret_cast< T * >( p ) in C++ is preferred over the light-weight syntax (T *) p inherited from C.) The explicit and striking syntax is perfectly suited for the low-level task that Stefan and his colleges are working on.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 30 Nov 2015 14:37 UTC On the other hand, I suspect that in virtually every implementation out there, thisisn't a dangerous low-level conversion. In fact, it is probably a no-op, plus some range checking.

Personally, I prefer the Color(i) syntax because it is consistent with the inverse operation Integer(c), and with record-constructor syntax. This makes it more intuitive.

If we change to some more awkward syntax, then I think we need to get rid of the existing mechanisms too, or we will have simply introduced yet another inconsistency in the language. Of course, getting rid of the existing mechanisms is a major, backward-incompatible change, whereas the Color(i) syntax does not introduce any incompatibilities.

modelica-trac-importer commented 5 years ago

Comment by choeger on 1 Dec 2015 09:13 UTC Replying to [comment:40 stefanv]:

On the other hand, I suspect that in virtually every implementation out there, thisisn't a dangerous low-level conversion. In fact, it is probably a no-op, plus some range checking.

It is at least dangerous in the sense that it might cause a simulation-time error, which is impossible to detect beforehand.

If we change to some more awkward syntax, then I think we need to get rid of the existing mechanisms too, or we will have simply introduced yet another inconsistency in the language.

+1 for "getting rid" of it. However, constructors are proper functions, and AFAIK only used in this one context with one meaning. So semantically, they are different.

Of course, getting rid of the existing mechanisms is a major, backward-incompatible change, whereas the Color(i) syntax does not introduce any incompatibilities.

Again +1 for the incompatible change. Since Modelica 4 will not happen in this decade, we should not try to stay backwards compatible. After all it is a new language version, we are working on. And besides, a conversion script should be easy to do for each tool, since you argued how easy it is to detect such a case ;).

A fourth option would be to follow Stefans proposal, consider the case Colors(i) a (possibly failing) constructor and replace the iterator meaning with a proper syntax. Something like:

Whatever we decide here, we should do some cleanup and remove some overloading from the syntax.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 1 Dec 2015 12:30 UTC Replying to [comment:41 choeger]:

Since Modelica 4 will not happen in this decade, we should not try to stay backwards compatible.

I'm not talking about Modelica 4. I'm talking about Modelica 3.x. If we're only going to consider this for Modelica 4, then I won't even bother writing an MCP.

I'm afraid that in the real world of users, not staying backward compatible is not an option.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 1 Dec 2015 12:44 UTC By the way, the equation c = Colors(i) isn't any more dangerous than the equation i = j where i.min <> j.min or i.max <> j.max. A tool's existing mechanism for dealing with that will probably already deal with this issue, or can readily be extended to deal with it.

modelica-trac-importer commented 5 years ago

Comment by hansolsson on 1 Dec 2015 13:18 UTC Replying to [comment:39 henrikt]:

Replying to [comment:37 hansolsson]:

* integer_to_enumeration or convert

  • Easy to distinguish and find
  • Can convert matrices
  • Could support additional arguments (e.g. clamp=true)
  • Works for conversion in both directions

I might have missed some benefits for some of the variants. I deliberately only listed the benefits, but I don't believe that all benefits have the same weight and we should just take the one with the largest number.

The benefit of this that is missing in the list is that it does not hide potentially dangerous low-level conversion behind misleadingly convenient syntax.

The first benefit ("Easy to distinguish and find") was intended to capture the non-hiding, but written in a positive way while being as neutral as possible.

I guess that could have been clearer.

(Compare how reinterpret_cast< T * >( p ) in C++ is preferred over the light-weight syntax (T *) p inherited from C.)

Additionally reinterpret_cast, static_cast, and const_cast clearly shows the purpose of the specific cast - compared to (T*)p.

modelica-trac-importer commented 5 years ago

Comment by dag on 1 Dec 2015 13:34 UTC Furthermore, the syntax (T*) can have different meanings depending on what declarations precede it (for example, if the full type definition including any inheritance relationship has been parsed). So it is not only a question of "what did the programmer think when writing (T*)?" The new improved const_cast, static_cast, dynamic_cast have very distinct purposes and requirements.

I don't see that we have as serious a problem in Modelica as the one C++ inherited from C.

modelica-trac-importer commented 5 years ago

Comment by choeger on 1 Dec 2015 18:10 UTC Replying to [comment:42 stefanv]:

I'm afraid that in the real world of users, not staying backward compatible is not an option.

We are releasing backwards incompatible versions on a regular basis (at least when the right people request an incompatible change), so obviously it is an option in the real world of everyone. Incompatible changes happen in all languages I am aware of, be it in what you might consider examples irrelevant toys from the ivory tower, say ML, Lisp, Scala or heavy weight "industrial strength" languages like C/C++ or Java.

I would rather deprecate a rather exotic feature and stratify the semantics of expressions than add an additional special case that needs to be handled.

How about the following compromise: Since your proposal does not need any additional syntax, we introduce it, deprecate the special iterator syntax and introduce a replacement for that? Once tools pick up the deprecation, they might offer automatic rewriting into the proper form. Removal of the iterator syntax can be done in later versions and we end up with one meaning for the expression Colors.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 1 Dec 2015 18:20 UTC I will write up an MCP (for Modelica 3.x), we can all discuss it in Oberpfaffenhofen, and you can file a separate ticket to deprecate the special iterator syntax (with its own MCP of course). The two issues are, in my view, orthogonal, and I'm not going to let the dislike of an unrelated feature drag down the discussion of an idea.

modelica-trac-importer commented 5 years ago

Comment by stefanv on 1 Dec 2015 21:20 UTC I've submitted the first part of the MCP (the overview):

https://svn.modelica.org/projects/MCP/MAinternal/MCP-0022_IntegerToEnumeration/MCP-0022_IntegerToEnumeration_Overview.html

I will complete and submit the other part (spec changes) tomorrow.

modelica-trac-importer commented 5 years ago

Comment by dietmarw on 2 Dec 2015 08:09 UTC Stefan, you should also add a new ticket especially for that MCP (see e.g., #1730) where then the MCP can be discussed. You might wanna close then perhaps this ticket with a pointer to the new MCP ticket.

modelica-trac-importer commented 5 years ago

Modified by dietmarw on 2 Dec 2015 17:53 UTC