Closed metagn closed 2 years ago
Namespaced defines are fine with me. However! Namespaced or not, the approach does not scale, N different --defines create a configuration space of 2^N and already nobody, including myself, knows all the defines that the Nim core offers. It's better to create more fine-grained modules and packages that are independent of each other that don't use the feature at all. Of course, that's easier said than done.
Decided to just write a macro for this. Defines have definitely helped in a lot of my projects, but I understand that it can get messy quickly. Closing in favor of just #181, not a kneejerk reaction as I don't see much point to the RFC otherwise.
The macro I thought of is like so:
defines(prefix = "foo"):
const
bar = true
baz = 3
# becomes
const
bar = block:
when not declared(fooBar):
when true is bool:
const fooBar {.booldefine.} = true
elif true is int:
const fooBar {.intdefine.} = true
else:
const fooBar {.strdefine.} = true
fooBar
baz = block:
when not declared(fooBaz):
when 3 is bool:
const fooBaz {.booldefine.} = 3
elif 3 is int:
const fooBaz {.intdefine.} = 3
else:
const fooBaz {.strdefine.} = 3
fooBaz
Could be made more straightforward and expansible by defining a generic fromDefine
and just using strdefine
, but this is enough for now. declared(fooBar)
is so you can override it by using include
.
This proposal does not change any existing behavior, so no breaking changes.
Defines currently have 2 relevant problems I can think of, please mention if you know more:
-d:asyncBackend
somewhere but I cannot find it)This is a joint proposal with solutions for both of these issues, although the second one is lower priority because I don't know many real world situations where it is important and the solution is fairly difficult.
0. Use constants with define pragmas instead of
defined
Firstly, the convention should be for libraries to use
.booldefine
constants for their options instead ofdefined
(I believe #269 was trying to express this?). This should be pretty straightforward to adopt asconst foo = defined(foo)
is common.A good addition here would be to merge
.booldefine
,.intdefine
,.strdefine
into a single.define
pragma that infers the type of the define from the default value, if possible anyway. At the very least it should recognize type annotations. Then you could also use.push define
for a define section (which is already possible for bool/int/strdefine). Adefines:
macro could easily replicate these however.Another possible addition is that define pragmas could take an optional parameter that allows aliasing a define from the configuration to a differently named constant, as so:
The name conflict with the existing
.define
pragma should not be too much of a problem as they are used in different contexts, however in the worst case it can have another name.1. Namespacing (supersedes #181)
Now, we can solve the scoping issue by namespacing (here expressed with dots) the define constant with another pragma for now named
.defineNamespace
(or just.namespace
or whatever). It can just apply to all.define
s in a module if used as a top level statement, or you can use.push
.181 uses
{.define: "lib"}
to behave as{.defineNamespace: "lib", define.}
, which conflicts with the earlier alias syntax which I consider to be more intuitive.defineNamespace
being separate also has the benefit of being easier to push/pop, but this is not as important.Namespaces should also be able to have names with dots in them (namespace "lib.submod" in
lib.submod.foo
) but it is bad style to overuse this.Duplicate constants in separate modules for the same define should still be allowed (as is the case already I believe), this is for defines like
-d:asyncBackend
where a centralasyncdefines
module is not really easy to have.defined(lib.foo)
syntax can also still work, but it is not the same thing as the value offoo
beingtrue
, which you can see in the current behavior of.booldefine
.2. Parametric modules based on define constants
This part is much lower priority and is basically WIP. I will move it to a new issue if the preceding parts are implemented. It introduces a fair bit of complexity to the language and would take a lot of time and effort to do well, plus I don't have many examples of use cases. It likely isn't sound and has mistakes, so if you can think of improvements please say so: