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
Related
function check(str,arg;type=DataType,max=nothing,min=nothing,description="")
#argcheck typeof(arg)==type
#argcheck arg>min
#argcheck arg<max
#argcheck typeof(description)==String
return arg
end
function constr(name,arg,field)
return :(function $name($arg,$field)
new(check($name,$arg,$field))
end)
end
macro creatStruct(name,arg)
code = Base.remove_linenums!(quote
struct $name
end
end)
print(arg)
append!(code.args[1].args[3].args,[constr(name,arg.args[1].args[1],arg.args[1].args[2])])
code
end
macro myStruct(name,arg)
#creatStruct name arg
end
#myStruct test12 (
(arg1,(max=10))
)
In my code above I'm trying to build a macro that Creates a struct, and within the struct, you can define an argument with boundaries (max, min) and description, etc.
I'm getting this error:
syntax: "#141#max = 10" is not a valid function argument name
and every time I'm trying to solve it, I get another error like:
LoadError: syntax: "struct" expression not at top level
So, I think my Code/Approach is not that cohesive. Anybody can suggest tips and/or another Approche.
You're attempting to make an argument name max with a default value of 10. The error is about max=10 not being a valid name (Symbol), while max is. The bigger issue is you're trying to put this in the struct expression instead of a constructor method:
struct Foo
bar::Float64
max::Int64
end
# constructor
Foo(bar, max=10) = Foo(bar, max)
So you have to figure out how to make an expression for a method with default values, too.
Your second error means that structs must be defined in the top-level. "Top-level" is like global scope but stricter in some contexts; I don't know the exact difference, but it definitely excludes local scopes (macro, function, etc). It looks like the issue is the expression returned by creatStruct being evaluated as code in myStruct, but the LoadError I'm getting has a different message. In any case, the error goes away if I make sure things stay as expressions:
macro myStruct(name,arg)
:(#creatStruct $name $arg)
end
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.
We got three functions. The first one defines type alias for Boolean condition
type Set = Int => Boolean
I understand that this is the alias definition. Now the second fucntion
def contains(set: Set, elem: Int): Boolean = set(elem)
calls the (Int=>Boolean) on elem:Int.
QUESTION 1: Where is the logic of the function under Set?
I mean, do I have to pass the Set function actual parameter (in which case the contains is a higher order function) when calling contains eg. for even numbers set:
val in:Boolean = contains({x=>(x%2)==0},2)
In the third function:
def singletonSet(elem: Int): Set = set => set == elem
Question 2: Where does the set come form? Its not in the formal parameter list.
QUESTION 1: Yes, you have to pass a Set which would be the "implementation" of the function. The point of this exercise (Odersky's course?) is to show that a Set can be defined not as a collection of items (the "usual" definition of a set), but rather as a function that says whether an item is included in the set or not. So the Set is the function.
QUESTION 2: set is the name given to the argument of the anonymous function we're returning here: Since singletonSet's return type is Set, which as we've said is actually a function of type Int => Boolean, we return an (anonymous) function. To create such a function, one uses the syntax x => f(x), where x is any name you'd like and f(x) is an expression using it (or not).
1) Since a Set is a function, contains is indeed a higher order function which takes a function and an element of the appropriate type and applies the function to the element. The logic of it is that sets are being represented by Boolean-valued functions where an element evaluates to true if and only if it is in the corresponding set. The function contains evaluates the function at the element and returns its value, which is either true or false depending on whether or not it is in the set.
2) singleton returns an anonymous function, one that evaluates to true if and only if the input (set) equals the element in question.
Is it possible to define and use a function like this?
generate_mode_matrix_and_mode_frequency_and_Hw(generate_Hw: true);
function generate_mode_matrix_and_mode_frequency_and_Hw(generate_Hw)
if generate_Hw ## NOTICE: this argument is optional
.....
end
end
What I want is to specify the name of the argument when passing it. In ruby it's called hash.
The point of doing this is that, when using it, the coder know what the true mean without comment. This is
Compare this 2:
generate_mode_matrix_and_mode_frequency_and_Hw(generate_Hw: true)
generate_mode_matrix_and_mode_frequency_and_Hw(true)
Clearly, the first one is more clear.
Notice: generate_Hw is an optional argument. So without specifying it, the function would also work.
You can use something similar to Property\Value pairs:
function Foo(varargin)
for n=1:2:nargin
switch varargin{n}
case 'var1'
var1 = varargin{n+1};
case 'var2'
var2 = varargin{n+1};
end
end
In this example if you use Foo('var1',value) then var1 would get the desired value. If you don't specify the pair 'var1',value in the input, then var1 will not exist in Foo.
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).