Using bs-deriving, I can derive e.g. show instances using [#deriving show]. However, it's not clear how I would use the same derivation but providing a custom show instance for a specific datatype.
Example:
[#deriving show]
type bar = |Bar(int);
[#deriving show]
type foo = |Foo(int, bar);
Using the above example, how would I change Bar to print its integer in e.g. hexadecimal?
You should be able to use #printer to define your own printer function like this:
[#deriving show]
type bar = Bar([#printer fmt => fprintf(fmt, "0x%x")] int);
fprintf is a locally defined function which takes a formatter, a format string and a number of values as specified by the format string. For brevity in this case we partially apply it to avoid having to explicitly pass the int value. It's equivalent to (fmt, n) => fprintf(fmt, "0x%x", n).
The format string specifies that the number should be formatted as hexadecimal with lowercase letters (the %x part) and prefixed with 0x. So 31 would output 0x1f.
Related
Motivation
Modelica does store units of measurement (e.g. SI units and Non-SI units) as an attribute with regard to a variable. Here is an example for a Non-SI-unit:
type Time_months = Real( quantity = "Time", unit = "mo", displayUnit = "months" )
Since for models in economics it will be rather akward to give rates in seconds, I would like to write a rather general unit conversion function that will allow to convert units of time. So ideally a function to convert to another time base should work with three inputs and one output:
input Real timeValue "the value of time to be converted";
input String timeBaseA "the time base for timeValue, e.g. \"mo\" ";
input String timeBaseB "the time base to convert to, e.g. \"yr\" ";
output Real convertedTimeValue "the result of the conversion";
Questions
If we assume that a variable for some time value already has a specific unit attribute (e.g. "mo") it would make sense to use that meta information within a model.
Question 1: How can meta information like unit be accessed within a model?
Ideally something like the following would be great:
String timeBaseA := timeValue.unit;
or
String timeBaseA := getUnit( timeValue ) "some function to read unit information";
Question 2: How can meta information like unit be assigned within a function?
In the example we would of course like to return the output value with the correct unit of time. So ideally we would like to have:
output Real convertedTime( quantity = "Time", unit = strTimeBaseB )
Unfortunately, using an input will give rise to an error as the variability is different: The unit attribute should have constant variability but the input variable has parameter variability. (Using a function - which would be nice - also fails for the same reason.)
Regarding Question 1:
I have never used Wolfram SystemModeler, but the Modelica Language Specification 3.4 says in chapter 4.8 (Predefined Types and Classes):
The attributes of the predefined variable types (Real, Integer, Boolean, String) ... cannot be accessed using dot notation, and are not constrained by equations and algorithm sections.
Regarding Question 2:
I think it is only possible to define the unit of a variable on declaration from a literal or from a final parameter - at least this is what I observed in Dymola.
Alternative - use operator records
You could use operator records for your task. This will allow you to store the time in seconds and convert it to what ever needed when the value comes to use.
Operator records allow you to define several function to create them, compare or add them, convert to String, etc.
See the brief example below, where a operator record Time is defined, which can be created with two different constructor functions from seconds or days and can be converted to Strings with day or seconds
operator record Time
Integer s "Second";
encapsulated operator 'constructor'
import Time;
function from_s
input Integer s "Seconds";
output Time t(s=s);
algorithm
end from_s;
function from_d
input Integer d "Days";
output Time t(s=d*24*3600);
algorithm
end from_d;
end 'constructor';
encapsulated operator 'String' "Convert Time to string"
import Time;
function formated
input Time t;
input String format = "s" annotation(choices(choice="s" "seconds", choice="d" "days"));
output String str;
algorithm
if format == "d" then
str :=String(t.s/24/3600);
else
str :=String(t.s);
end if;
end formated;
end 'String';
encapsulated operator function '==' "Compare time records"
import Time;
input Time t1;
input Time t2;
output Boolean result "= t1 == t2";
algorithm
result := t1.s == t2.s;
end '==';
end Time;
Usage:
import Modelica.Utilities.Streams.print
t1 = Time(d=12) // create record using day constructor
t2 = Time(s=3600*24*2) // create record using second constructor
print(String(t1, format="s")) // prints 1036800
print(String(t1, format="d")) // prints 12
print(String(t2, format="s")) // prints 172800
print(String(t2, format="d")) // prints 2
See Modelica Spec 3.4 Chapter 14 "Overloaded Operators" for details.
Note: This was tested with Dymola 2019, not with Wolfram SystemModeler
In Modelica usually every variable is computed based on SI units. Then you have displayUnits to plot them in a different unit (not affecting the actual computation).
I don't know about SystemModeler, but in Dymola the conversion between the unit (of computation) and the displayUnit (only for plotting) is handled by a pre-defined script (displayUnit.mos). It can be extended by the user to contain custom displayUnits. The code for the display units related to time is shown below. I extended it to have week (w) additionally to the predefined ones.
// Syntax:
// defineUnitConversion(<unit>, <derived unit>, <scale>, <opt. offset>);
// Time
defineUnitConversion("s", "ms", 1000);
defineUnitConversion("s", "min", 1/60);
defineUnitConversion("s", "h", 1/3600);
defineUnitConversion("s", "d", 1/86400);
defineUnitConversion("s", "w", 1/604800);
This can then be selected in plots manually or as the default ´displayUnit´ via Modelica.SIunits.Time t(displayUnit = "w") = ...;
The disadvantage is, that this extension has to be done in a file in the install directory. So it has to be changed again after re-installing the tool or when using a different computer.
If there are numerical reasons to not compute solutions in seconds (e.g. because values would get to big), the solution would be the nominal attribute, which enables a scaling of the variables.
BTW: I think months are not a very good unit of time as they can have 28 to 31 days. That's why I chose weeks in my example.
You could use conversion like is done in the MSL, for example the function Modelica.SIunits.Conversions.to_degC which has the signature:
function to_degC
input Temperature Kelvin "Kelvin value";
output NonSIunits.Temperature_degC Celsius "Celsius value";
end to_degC;
This works, but you need one such function for each unit you want to convert between (which is why most calculations are done using SI-units).
When you have variable parameters in a macro, for instance
<#macro m a b c...>
Do you have to pass a minimum of 3 arguments or 2 while calling the macro? Does the parameter c here have to have at least 1 value? Also is there any way to specify a parameter as null by default?
<#macro name param1 param2 ... paramN>
...
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
Where:
name: name of macro variable. It's not an expression. It follows the
same syntax as like top-level variable references, like myMacro or
my-macro. However, it can also be written as a string literal, which
is useful if the macro name contains characters that can't be
specified in an identifier, for example <#macro "foo~bar">.... Note
that this string literal does not expand interpolations (as
"${foo}").
param1, param2, ...etc.: the name of the local variables store the
parameter values (not expression), optionally followed by = and the
default value (that's an expression). The default value can even be
another parameter, for example <#macro section title label=title>.
The parameter name uses the same syntax as like top-level variable
references, so the same features and restrictions apply.
paramN, the last parameter may optionally has 3 trailing dots (...),
which indicates that the macro takes a variable number of parameters
and the parameters that doesn't match any other parameters will be
collected in this last parameter (also called the catch-all
parameter). When the macro is called with named parameters, paramN
will be a hash containing all of the undeclared key/value pairs
passed to the macro. When the macro is called using positional
parameters, paramN will be the sequence of the extra parameter
values. (Inside the macro, to find out which was the case, you can
use myCatchAllParam?is_sequence.)
Therefore as you can see macro does not have any limitation to take N parameters.
This structure creates a macro variable (in the current namespace, if you know namespace feature). If you are new to macros and user-defined directives you should read the the tutorial about user-defined directives.
Macro variable stores a template fragment (called macro definition body) that can be used as user-defined directive. The variable also stores the name of allowed parameters to the user-defined directive. You must give value for all of those parameters when you use the variable as directive, except for parameters that has a default value. The default value will be used if and only if you don't give value for the parameter when you call the macro.
The variable will be created at the beginning of the template; it does not mater where the macro directive is placed in the template.
Example: Macro with parameters:
<#macro test foo bar baaz>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#-- call the macro: -->
<#test foo="a" bar="b" baaz=5*5-2/>
Output:
Test text, and the params: a, b, 23
Example: Macro with parameters and default parameter values:
<#macro test foo bar="Bar" baaz=-1>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#test foo="a" bar="b" baaz=5*5-2/>
<#test foo="a" bar="b"/>
<#test foo="a" baaz=5*5-2/>
<#test foo="a"/>
Output:
Test text, and the params: a, b, 23
Test text, and the params: a, b, -1
Test text, and the params: a, Bar, 23
Test text, and the params: a, Bar, -1
However, about last part of your question there is an explanation:
The null reference is by design an error in FreeMarker. Defining a custom null value - which is a string - is not a good idea for the reasons you mention. The following constructs should be used instead:
Macro and function parameters can have a default value, so the
callers can omit them
To check if a variable is null, you should use the ?? operator: <#if
(name??)>
When you use a variable that can be null, you should use the !
operator to specify a default value: name!"No name"
To check if a sequence (or a string) is empty, use the ?has_content
builtin: <#if (names?has_content)>
You can specify an empty sequence as default parameter value in a macro, and simply test whether it's empty.
When you have variable parameters in a macro, you don't have to pass a value for the last argument.
For example:
<#macro m a b c...>
a = ${a!}
b = ${b!}
<#list c?keys as attr>
${attr} = ${c[attr]}
</#list>
</#macro>
<#m a='A' b='B' />
<#m a='A' b='B' c='C' d='D'/>
Will output:
a = A
b = B
a = A
b = B
c = C
d = D
I'm trying to write a DAC macro that gets as input the name of list of bits and its size, and the name of integer variable. Every element in the list should be constrained to be equal to every bit in the variable (both of the same length), i.e. (for list name list_of_bits and variable name foo and their length is 4) the macro's output should be:
keep list_of_bits[0] == foo[0:0];
keep list_of_bits[1] == foo[1:1];
keep list_of_bits[2] == foo[2:2];
keep list_of_bits[3] == foo[3:3];
My macro's code is:
define <keep_all_bits'exp> "keep_all_bits <list_size'exp> <num'name> <list_name'name>" as computed {
for i from 0 to (<list_size'exp> - 1) do {
result = appendf("%s keep %s[%d] == %s[%d:%d];",result, <list_name'name>, index, <num'name>, index, index);
};
};
The error I get:
*** Error: The type of '<list_size'exp>' is 'string', while expecting a
numeric type
...
for i from 0 to (<list_size'exp> - 1) do {
Why it interprets the <list_size'exp> as string?
Thank you for your help
All macro arguments in DAC macros are considered strings (except repetitions, which are considered lists of strings).
The point is that a macro treats its input purely syntactically, and it has no semantic information about the arguments. For example, in case of an expression (<exp>) the macro is unable to actually evaluate the expression and compute its value at compilation time, or even to figure out its type. This information is figured out at later compilation phases.
In your case, I would assume that the size is always a constant. So, first of all, you can use <num> instead of <exp> for that macro argument, and use as_a() to convert it to the actual number. The difference between <exp> and <num> is that <num> allows only constant numbers and not any expressions; but it's still treated as a string inside the macro.
Another important point: your macro itself should be a <struct_member> macro rather than an <exp> macro, because this construct itself is a struct member (namely, a constraint) and not an expression.
And one more thing: to ensure that the list size will be exactly as needed, add another constraint for the list size.
So, the improved macro can look like this:
define <keep_all_bits'struct_member> "keep_all_bits <list_size'num> <num'name> <list_name'name>" as computed {
result = appendf("keep %s.size() == %s;", <list_name'name>, <list_size'num>);
for i from 0 to (<list_size'num>.as_a(int) - 1) do {
result = appendf("%s keep %s[%d] == %s[%d:%d];",result, <list_name'name>, i, <num'name>, i, i);
};
};
Why not write is without macro?
keep for each in list_of_bits {
it == foo[index:index];
};
This should do the same, but look more readable and easier to debug; also the generation engine might take some advantage of more concise constraint.
Somehow, System.String:Format exists but does not seem to works.
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE System.String:Format("Hello {0}", strValue) VIEW-AS ALERT-BOX.
The result was "Hello C:\temp\run.p" instead of "Hello Sebastien".
So I decided to create an equivalent function.
How is it possible to declare a method with undetermined number of parameters?
Example:
METHOD PUBLIC INTEGER Calculate(
INPUT iMultiply AS INTEGER
,INPUT iInt1 AS INTEGER
,INPUT iInt2 AS INTEGER
...
,INPUT iIntX AS INTEGER):
RETURN iMultiply * (iInt1 + iInt2, ..., iIntX).
END METHOD.
DISPLAY Calculate(10, 1, 2, 3). /* Result: 60 */
DISPLAY Calculate(2, 1, 1, 1, 1, 1). /* Result: 10 */
Thank you!
Sebastien
I'm not entirely sure what you are trying to accomplish here. For your first bit of code, you could simply do this:
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE "Hello " + strValue VIEW-AS ALERT-BOX.
Or sometimes it is useful to use the SUBSTITUTE function...
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE SUBSTITUTE("Hello &1", strValue) VIEW-AS ALERT-BOX.
When you used {0} in your code sample, you were using a run-time parameter (an argument, if you like. {0} is the name of the program, {1} is the first argument for the program, and so on. I don't recommend using run-time arguments - you can't compile that code.
With regards a variable number of parameters for a function, that cannot be done in the OpenEdge ABL. However, you can create classes with overloaded methods. It probably isn't as clean and elegant as you'd like, but it will work. You'd create a class with a bunch of overloaded methods like this:
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL):
...do some stuff...
END METHOD.
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL, deValue2 AS DECIMAL):
...do some stuff...
END METHOD.
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL, deValue2 AS DECIMAL, deValue3 AS DECIMAL):
...do some stuff...
END METHOD.
And so on. The code above will give you the same method (Calc()) with 1, 2, or 3 parameters.
Hope this helps.
You cannot have a method with undetermined number of parameters in ABL.
You should not look for workarounds if you can fix the root cause.
This will work as expected:
MESSAGE System.String:Format("Hello ~{0~}", "Sebastien")
VIEW-AS ALERT-BOX INFO BUTTONS OK.
The difference to your version are the tilde characters before the curly braces. The braces have a special meaning in OpenEdge because they are used for compile time functions (includes, preprocessor directives). {0} is replaced by the procedure name at compile time.
The tilde is used to escape the curly braces.
This is from OpenEdge Help:
{ } Argument reference
References the value of an argument that a procedure passes to a called external procedure file or to an include file.
ABL converts each argument to a character format. This conversion removes the surrounding double-quotes if the parameter was specified as a character string constant in the RUN statement or include file reference.
When one procedure is called from another and arguments are used, ABL recompiles the called procedure, substituting the arguments that the calling procedure passes, and then runs the called procedure.
~ Special character
The tilde (~) is an escape character that causes the AVM to read the following character literally. A tilde followed by three octal digits represents a single character. Use it as a lead-in to enter the special characters shown in Table 2. In a procedure, a tilde followed by something other than the items in Table 2 is ignored. For example, "~abc" is treated as "abc". (This may not work as expected when passing parameters to an include file.) The items in Table 2 are case sensitive.
If all your parameters are of the same data type you could use an "indeterminate array".
Define the method parameter like this:
METHOD PUBLIC VOID Calc(INPUT numberArray AS INTEGER EXTENT):
DEFINE VARIABLE iEntriesInArray AS INTEGER NO-UNDO.
DEFINE VARIABLE iCnt AS INTEGER NO-UNDO.
DEFINE VARIABLE iTemp AS INTEGER NO-UNDO.
iEntriesInArray = EXTENT(numberArray).
DO iCnt = 1 TO iEntriesInArray:
iTemp = numberArray[iCnt].
END.
END METHOD.
And call it like this:
DEFINE VARIABLE numberArray AS INTEGER EXTENT NO-UNDO.
DEFINE VARIABLE arrayExtent AS INTEGER NO-UNDO.
arrayExtent = 5.
EXTENT(numberArray) = arrayExtent.
myClass1:Calc (INPUT numberArray).
I have a variable called 'int' with alot of data in it. I would like to find a way to programically rename this variable with a user input. So I can query the user indentifcation information about the data, say the response is 'AA1', I want either rename the variable 'int' to 'AA1' or make 'AA1' a variable that is identical to int.
A problem using the input command arises because it allows the user to assign a value to an already created varialbe, instead of actually creating a variable name. Would using the eval function, or a variation of it, help me achieve this? Or is there an easier way?
Thanks!
Just for the record, int is a rather poor variable name choice.
That aside, you can do what you want as follows
say foo is the variable that holds a string that the user input. You can do the following:
% eliminate leading/trailing whitespace
foo = strtrim(foo);
a = regexp('[a-zA-Z][a-zA-Z0-9_]*',foo));
if numel(a) == 0
fprintf('Sorry, %s is not a valid variable name in matlab\n', foo);
elseif a ~= 1
fprintf('Sorry, %s is not a valid variable name in matlab\n', foo);
elseif 2 == exist(foo,'var')
fprintf('Sorry, %s already in use as a variable name.');
else
eval([foo,' = int']);
end
Assuming int (and now foo) is a structure with field named bar, you can read bar as follows:
barVal = eval([foo,'.bar']);
This is all somewhat clunky.
An alternative approach, that is far less clunky is to use an associative array, and let the user store various values of int in the array. The Matlab approach for associative arrays is Maps. That would be my preferred approach to this problem. Here is an example using the same variables as above.
nameValueMap = containers.Map;
nameValueMap(foo) = int;
The above creates the association between the name stored in foo with the data in the variable int.
To get at the data, you just do the following:
intValue = nameValueMap(foo);