For instance, why introduce {.pure.} with enums to require full qualification? If the compiler does not respect the pragma, there are syntax errors in the code. Typing and naming should be enough to avoid enums names clashes.
Wouldn't work with Nim's disambiguation rules. Think of enum values as nullary procs and you can see it would be ambiguous immediately.
Another example with {.procvar.} used to qualify a proc that can be passed to a procedural variable. This should be the default or the compiler should deduce it from analyze of the source, shouldn't it?
.procvar is a whole topic on its own and will eventually disappear from the language.
Or the {.global.} to qualify a global variable or {.borrow.} to borrow code from overloaded proc.
It's not clear what you propose here.
An important set of pragmas is used for foreign functions interfaces, importing headers or injecting code. Why not define a domain specific sublanguage to manage this?
You need to map such a DSL to builtin capabilities anyway, there is no way around it. We would need to document the builtins, you would argue the language is bloated by them. ;-)
But this large amount of semantic pragmas is breaking the simplicity of the language.
Agreed but there is no systems programming language out there without a pragma-like annotation system:
"Simple" GNU C has this https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html
Am I misunderstanding how pragmas are used in Nim?
No, you understand them just fine.
Is there a model logic behind all these pragmas?
Well you gave a nice taxonomy for them.
And will these "syntax" pragmas replaced by language keywords before version 1.0?
Introducing new keywords breaks code, so most are here to stay.
proc compile(code: string): {.compileTime.} NimNode =
This is a syntax error because the proper syntax would be:
proc compile(code: string): NimNode {.compileTime.} =
Partly related: Ada is in a similar state. In the rationale of Ada 83, pragmas were described as having no influence on the meaning of the program. Then, Ada 95 came along and used pragmas for all sorts of things, including some which changed the meaning of the code.
For Ada 2012, a solution was sought and found; it was a new part of the syntax named aspects. So, instead of this:
procedure Flush;
pragma Import (StdCall, Flush, "glFlush");
You could now write
procedure Flush with
Import => True, Convention => StdCall, External_Name => "glFlush";
This is pretty close to how Nim pragmas already work, as they can already be tied to some entity instead of being standalone. For me, the concept seems to be closer to aspects than to traditional pragmas.
C# and Java have attributes/annotations which have to be bound to some entity. They are also part of the reflection descriptors, so they are pretty different from what traditionally is called a pragma. However, some still have the concept of being a hint to the compiler (for example, @Synchronized in Java).
To summarize: Pragmas in a language are semantically placed on an axis somewhere between „compiler hints“ and „part of the language semantics“. The trend seems to be towards the latter, but the word pragma is usually bound to the former, so some languages call them differently. Nim calls them pragmas but also supports the annotation/aspect semantics, and enables users to define „new pragmas“ via macros. Which is conceptually not very different to what C# and Java do, it is only much more powerful because the semantics can be given in the macro implementation, while in Java, the semantics of an Annotation is defined somewhere else.
To summarize: Pragmas in a language are semantically placed on an axis somewhere between „compiler hints“ and „part of the language semantics“. The trend seems to be towards the latter, but the word pragma is usually bound to the former, so some languages call them differently. Nim calls them pragmas but also supports the annotation/aspect semantics, and enables users to define „new pragmas“ via macros. Which is conceptually not very different to what C# and Java do, it is only much more powerful because the semantics can be given in the macro implementation, while in Java, the semantics of an Annotation is defined somewhere else.
I think that the abuse of pragmas as a way to escape the language syntax rules breaks the "there must be no surprise" rule. I'm not against pragmas, on the contrary, but I would like to make them simple to understand and not interfere with the semantic of the language. If you write another Nim compiler or backend, you should be able to skip the pragma values that you don't understand without breaking the code. I'm not sure that's the case from what I've read in the documentation and I think that Nim breaks that rule with pragmas used in macro calls or syntax improvement.
From what I understand, we have 3 types of pragmas:
Initialy I thought that the case of the Foreign Language Interface had to be treated apart because there are many more pragmas defined. I've looked at the code in Nim's library and I've changed my opinion now. Most of the FFI pragmas are really directives to the compiler and so are real pragmas. Nim's pragma syntax gives a lot of flexibility to handle all special cases that could occur with new language integrations.
Macro pragmas that are a way to apply a macro statement without declaring a new block are really syntactic sugar and they could be declared with a new syntax like {:macro name and parameters:}. Using a different {: and :} syntax would prevent the reader that's really a user defined macro call context like in
proc page(title, content: string) {:htmlTemplate:} =
html:
head:
title: title
body:
...
I know that pragmas in C/C++ use the macro-processor syntax #pragma like traditional macros, but is it a reason to reproduce this bad design in Nim? In fact, C99 replaced #pragma by a new operator _Pragma.
Introducing new keywords breaks code, so most are here to stay.
Pragmas that should be language keywords, like the OOP or concurrency pragmas, should be replaced by new keywords. Yes, it can break existing code if someone has already used the identifier, but it is safer in the long term. And at least 4 chars less to type for the programmer!;-)
Why
proc `*` (x: Dollar, y: int): Dollar {.borrow.}
instead of
proc `*` (x: Dollar, y: int): Dollar = borrow
just like
proc `*` (x: Dollar, y: int): Dollar = discard
This clear separation of the syntax would probably make it easier having to mix real pragmas with macros and syntax pragmas, or applying multiple macros...
When we compare with other languages, I don't think they mix how pragmas must be interpreted (directives, macro calls, syntax). Java annotations are used by external tools or the compiler, but they don't change the syntax of the code. They have no direct effect on the operation of the code generated by the compiler and are real metadata. All new languages have some sort of annotations to allow metaprogramming and use of reflection: Go with field tags or directive in comments, Rust #[...] attributes, C# is using [...] attributes in place of Java's @annotation ones.
Well, perhaps all this is too much naive from me and theoretical and now is time for me to work a big project in Nim to get real pragma experience.
If you write another Nim compiler or backend, you should be able to skip the pragma values that you don't understand without breaking the code.
No, that rule doesn't exist for Nim pragmas. That might be a rule for C/C++ pragmas, but even for them it's an academic point of view: Ignore what #pragma packed means and your program compiles and silently uses wrong data layouts and crashes. "The compiler can ignore the pragmas it does not understand" is an empty statement.
I know that pragmas in C/C++ use the macro-processor syntax #pragma like traditional macros, but is it a reason to reproduce this bad design in Nim?
But many programming languages have first class and second class keywords so that the list of reserved identifiers stays manageable. How is it "bad design"? You seem to focus too much on the "pragma" term. It's an annotation. It can be an unimportant annotation but most of the time it's important.
Yes, it can break existing code if someone has already used the identifier, but it is safer in the long term.
How is it "safer"? Sorry but you cannot simply pick arbitrary positive adjectives to decorate your arguments. ;-)
Using a different {: and :} syntax would prevent the reader that's really a user defined macro call context ...
That would make the situation even more verbose, not better:
proc foo() {:macroTransform:} {.export.} =
...
Besides, it's of questionable value to remind everybody all the time that e.g. .async was implemented as a macro and not as a builtin compiler construct...