If you don't like reading long post, just read question #4 that synthetize the previous questions...
1. What does it mean for a template/macro to have ``typed`` and ``untyped`` parameters at the same time?
Can the compiler anticipate the semantic analysis phase for some arguments while not considering others?
For instance, in the following template, typ would be checked to be a string while the compiler does not care about the validity of code...
template dsl(typ: static[string]; code: untyped) =
...
2. Is it possible to have default parameter values in template?
The preceding example becomes:
template dsl(typ: static[string] = "quick"; code: untyped) =
...
I've tried it but I can't have both blocks below working:
dsl "fast":
# Use fast algorithm
echo "fast"
dsl:
# Use quick algorithm
echo "quick"
3. Identifier construction in template
Now I want to call different procedures depending on typ value. According to the indentifier construction section of the manual, backticks is the way to go.
template dsl(typ: static[string]; code: untyped) =
`typ Algo`()
...
dsl "fast":
# Call `fastAlgo`()
But the name of the procedure is interpreted as "fast"Algo that does not exist. Is it possible to remove the string quotes?
4. Why all this?
I have multiple bindings to different algorithms and I want to let the user select the one to use. When none is selected, there is a default one. The best I was able to write is below that does not compile when typ is not specified.
proc fastAlgo() =
echo "fastAlgo"
proc quickAlgo() =
echo "quickAlgo"
template dsl(typ: untyped; code: untyped) =
`typ Algo`()
code
dsl fast:
# Call `fastAlgo`()
echo "running"
dsl: <<=== type mismatch: got <void> but expected one of: template dsl(typ: untyped; code: untyped) first type mismatch at position: 2 missing parameter: code
# Call `quickAlgo`()
echo "running too"
How would you do this?
template dsl(typ: untyped; code: untyped) =
`typ Algo`()
code
template dsl(code: untyped) =
dsl(quick, code)
Yes. Thanks. Sometimes you don't see the obvious solution...
But I still would like to understand points #1 and #2...
For 1: Yes, the compiler can choose to not type check argument expressions when looking up a template or macro in select situations like foo(a, b); it doesn't work the same if you do a.foo(b), then a has to be typechecked. The point of having typed templates/macros is making it easier to overload/clearer to understand.
2: Your problem here is the argument order. foo(a): b is equivalent to foo(a, b), and the following doesn't work:
proc foo(a = 3, b: int) = echo a + b
foo(4) # type mismatch
I'm sure there's some implementation excuse for this to not be allowed but I don't know what it is. You just have to deal with it.
3: This seems to be an oversight, all static[string]s behave like this. I think this makes sense, since you can use string literals in backticks and they work normally.
A few more questions
5. Is it possible to store an ``untyped`` value to be used in other templates?
Trying to save typ for use in another dependent template will generate the error message Error: undeclared identifier: 'fast'.
template dsl(typ: untyped; code: untyped) =
let keepTyp = typ
`typ Algo`()
code
This error can make sense afterward when you think deeply about what the compiler is doing but finding the root cause is not obvious when you have a lot of code as the error location is not helpful...`
6. Is it possible to process ``untyped`` values in templates?
Apart from copying untyped values like code, are templates powerful enough to parse untyped arguments or do you need to switch to macros to do the job?
For instance, if I want to define a template options: that set various options values, like:
options:
opt1 = value1
opt2 = value2
Can it be done with templates only?
Probably and I'll do the jump soon, but I want to understand what is the scope of templates vs macros. The documentation about templates is not as detailed as other parts of the manual. Perhaps answers in that thread can be used to improve it.
Macros are the powerful tools to do all types of compile time magic, but templates should be used only when... TO BE COMPLETED.
@spip My general rule for using a template is that there is some block of code to write that is tedious to break into a proc (it needs access to lots of local vars) or it needs to avoid copies of large objects.
If there is any hint of needing to modify or generate symbols, I use a macro. Generally and succinctly my rule is:
Code replacement -> template
Code generation/modification -> macro
The documentation about templates is not as detailed as other parts of the manual.
The documentation is pretty complete, there's just not much more to say. It lists the differences between using a template and manually putting the template body into a program's source code, and there aren't many. Almost all of them have to do with symbol/identifier interaction between the template body and the rest of the code (binding and scoping), only one is a syntactic difference: the id construction with backticks that you already used. It is also the only thing which is actually evaluated at compile time in the template body. Apart from that, the body is just substituted, not executed.
Trying to save typ for use in another dependent template will generate the error message Error: undeclared identifier: 'fast'.
Nothing can be "saved" in the template body before it has been substituted, because its body statements are not executed at compile time. After substitution, a template parameter cannot be "saved" because it doesn't exist anymore then. What is still there is the substitution result of the template parameter, which is a different thing.
Even if the parameter was still around, it could not be stored as a let value because it's type, undeclared, is not a concrete one but a meta-type.
And all this aside, would you complain if this code snippet
template dsl(typ: untyped; myVar: untyped; code: untyped) =
let myLet = myVar
let keepTyp = typ
`typ Algo`()
code
produced an error when called with a myVar parameter which doesn't exist in the calling context of the template? How is the compiler to know that the first let statement is a regular substitution while the second one is supposed to "save" something it doesn't know about without throwing an error?
The only way to reuse typ I can think of is using it in a nested template, which has disadvantages:
template dsl(typ: untyped; code: untyped) =
block:
template superDsl(scode: untyped) =
`Super typ Algo`()
scode
`typ Algo`()
code
dsl fast:
# Call `fastAlgo`()
echo "running"
superDsl:
# Call `SuperfastAlgo`()
echo "running super"
6. Is it possible to process untyped values in templates?
Templates don't process, they substitute.
The documentation is pretty complete, there's just not much more to say.
In fact, perhaps I was only mislead as a non native English speaker by the fact that all examples in the Templates section use typed and untyped parameters and thinking that only these types were available. I had to read again the documentation this morning to find the subtle nuance:
The "types" of templates can be the symbols ``untyped``, ``typed`` or ``typedesc``. These are "meta types", they can only be used in certain contexts. Regular types can be used too; this implies that typed expressions are expected.
Don't know why the previous sentence can't be quoted in the forum...
There's no explanations either that overloading applies to templates too and in that case typed and untyped meta types can conflict with regular types.
And yes, the documentation is complete. Perhaps we should have a reference to Lazy type resolution for untyped in the Templates and Macros sections.
Can you find what the following code will print?
template foo(x: int; y: string) =
echo "foo1 " & $x & ", " & $y
template foo(x: string; y: int) =
echo "foo2 " & $x & ", " & $y
template foo(x: typed; y: typed) =
echo "foo3 " & $x & ", " & $y
#template foo(x: untyped; y: untyped) =
# echo "foo4 " & $x & ", " & $y
template foo(x: typed{lit}; y: typed) =
echo "foo5 " & $x & ", " & $y
template foo(x: typed{sym}; y: typed) =
echo "foo6 " & $x & ", " & $y
template foo(x: int; y: typed) =
echo "foo7 " & $x & ", " & $y
template foo(x: float; y: int; z: untyped = "No bar!") =
echo "foo8 " & $x & ", " & $y & ", " & $z
let x = 42
let y = "bar"
let z = 123.45
foo(x, y)
foo(y, x)
foo(x, x)
foo(y, y)
foo(x, z)
foo(-1, z)
foo(3.14, y)
foo(2.79, x)ΒΈ
Perhaps we should have a reference to Lazy type resolution for untyped in the Templates and Macros sections.
That's right, missed that one. Maybe untyped and typed should even get their own subsection under "Special Types".
Can you find what the following code will print?
I made a guessing game out of the code by adding discard readline(stdin) before every template invocation and I got one wrong: for some reason, I thought that typed{lit} would win over int for an integer literal parameter. Would be nice to have such little "quiz programs" in the tutorial, compiled to JavaScript.
Btw., the main reason I could give the longish reply further up is that I tried the exact thing you did ("saving" template parameters) before understanding templates and macros. Wasted a lot of time with frustrating trial-and-error back then instead of just asking. These questions help other people, too.