Closed augb closed 1 year ago
What if it's just a decorator, for example the alias dtype struct could have just a decorator on top rather than multiple keywords, or same with other expressions, or an expression inside the struct. As for the name, I don't really care.
Another option would be to use a succinct sigil, e.g. !let
and !var
, to denote compile-time variables. You could use the same syntax for compile-time control flow, e.g. !if
. This would give us a consistent syntax for all compile-time code, allowing users to quickly identify the parts of a function that run at compile-time.
Currently, compile-time if
requires a @parameter
decorator, which feels a bit clunky to me, and it isn't able to decorate Python's ternary if
-expressions. In contrast, a sigil is able to annotate any keyword or expression, no matter the context in which it appears. Thus, sigils are perhaps future-proof in a way that decorators are not.
Also, I think @parameter
is inconsistent with how Python decorators work? As I understand it, decorators are defined to execute at runtime, and they act as wrappers around function objects, whereas @parameter
does something very different.
comptime is really obvious to Zig folk, but that's not really our audience. You're right that alias may not be the right word to use here either. Aligning this around "parameter" could be a good way to go, but I'm curious if there are other suggestions.
Once nice thing about "alias" is that it is more obvious for the trivial cases like alias my_magic = 12312
or alias Int8 = SIMD[DType.si8, 1]
. That doesn't make it the right thing, but it is a nice thing.
comptime is really obvious to Zig folk, but that's not really our audience. You're right that alias may not be the right word to use here either. Aligning this around "parameter" could be a good way to go, but I'm curious if there are other suggestions.
I think my point is not to copy Zig; rather, it is to make clear that the value is a compile-time value. I'm open to other suggestions, but I think it might be better to err on the side of clarity.
Once nice thing about "alias" is that it is more obvious for the trivial cases like
alias my_magic = 12312
oralias Int8 = SIMD[DType.si8, 1]
. That doesn't make it the right thing, but it is a nice thing.
alias
suggests that it could be at runtime or compile-time (as it is currently used). While I agree that your examples make perfect sense, these are still compile-time expressions, if I am understanding the docs correctly. The keyword alias
gives no indication of this, neither does aligning around "parameter".
I could see the possibility of a future need for the keyword alias
that can be used in a runtime-only context. Current use excludes that possibility.
Aligning this around "parameter" could be a good way to go, but I'm curious if there are other suggestions.
@lattner, could you elaborate on what you mean by this?
Updated: grammar
+1, I wasn't arguing for alias, just explaining the though process.
Aligning this around "parameter" could be a good way to go, but I'm curious if there are other suggestions.
I'm just saying that if we replaced the keyword "alias x = 42" with "parameter x = 42", then we can say "it's a declaration of a parameter" and that "parameters are all compile time expressions."
No love for the !let
(or any other sigil) idea I mused about above? 🥲
No love for the !let (or any other sigil) idea I mused about above? 🥲
Not really, alias (regardless of what it is called) is a declaration of a thing. We need spoken vocabulary for programmers to describe these things. It isn't just about encoding things in source code for the compiler, it is allowing humans to communicate ideas as well.
Also, "let" values are not aliases. They've very different. A let isn't mutable after it is initialized, which is a flow sensitive property, e.g. this is allowed:
let x : Int
if cond:
x = foo()
else:
x = bar()
use(x)
which isn't allowed for aliases.
Chris, I actually 100% agree with you that verbalizability is important. I'm a huge fan of English keywords.
Allow me to direct this conversation towards a core concern that I see with Mojo's metaprogramming. The core concern has nothing to do with keywords, it has to do with the model of compile-time execution that Mojo presents to users.
Although I was superficially proposing a sigil, my underlying intention was to propose a syntax that leads to a simpler model of compile-time execution. The code sample you gave above is a good starting point. You say that aliases can't be initialized in the same way that a let
binding can. Why not? Having a single semantics for variable binding—irrespective of whether the binding occurs at compile-time or run-time—would reduce the number of concepts that people need to learn to do metaprogramming. Additionally, it leaves open the possibility of (eventually) making Mojo's metaprogramming language as powerful as its base language.
If Mojo is to offer run-time constructs such as let
and if
at compile-time, the syntax should reflect this.
Let's imagine that let
can be used at compile-time. You'll need a way to request the compile-time version. A sigil such as !
or ~
(or whatever) could be used for this:
~let x : Int
~if cond:
x = foo()
~else:
x = bar()
This was the proposal I was attempting to make in my original post. The only reason I was proposing a sigil is that keywords seem harder to read:
comp let x : Int
comp if cond:
x = foo()
comp else: // Putting the keyword here ensures that "if" and "else" remain aligned.
x = bar()
But as I stare at this longer, I'm thinking that maybe the keyword is just as readable as a sigil. Regardless, the main idea is to have fewer concepts to learn. In the above examples, there's no alias
, there's just let
. The compile-time language is the same as the run-time language.
Let's ponder a few more syntaxes that are compatible with this model of execution.
Instead of putting sigils and keywords next to every statement, maybe chunks of code that are purely compile-time could be placed in their own block:
fn foo[flag: boolean]():
compile:
let x: int
if flag:
x = foo()
else:
x = bar()
run:
...
And if a compile
block has only one line of code, you could collapse it back into a keyword:
compile let x = 2
This would be the equivalent of an alias
declaration.
The examples above can be situated in compile
blocks because they don't mix compile-time and run-time code. But the following example from the Mojo docs does mix compile-time and run-time:
struct SIMD[type: DType, size: Int]:
...
fn reduce_add(self) -> SIMD[type, 1]:
@parameter
if size == 1:
return self[0]
elif size == 2:
return self[0] + self[1]
To represent this using the compile/run
syntax, you'd need to interleave compile
and run
:
struct SIMD[type: DType, size: Int]:
...
fn reduce_add(self) -> SIMD[type, 1]:
compile:
if size == 1:
run: return self[0]
elif size == 2:
run: return self[0] + self[1]
This syntax—wherein one uses run
to "escape" back to the run-time context—is reminiscent of string interpolation, and template languages like JSX.
Or maybe—going back to the earlier syntax—it is cleaner to prefix each line that runs at compile-time with the compile
keyword:
struct SIMD[type: DType, size: Int]:
...
fn reduce_add(self) -> SIMD[type, 1]:
compile if size == 1:
return self[0]
compile elif size == 2:
return self[0] + self[1]
Here, each line that doesn't begin with the compile
keyword executes at run-time. There is no need to "escape" out of the compile-time context using the run
keyword. I suspect this syntax would be easier to understand.
To reiterate: my core concern is not about renaming the alias
keyword, it is about the model of compile-time execution that Mojo presents to users. My assertion is that it would be conceptually simpler to offer a single set of constructs that can be used at both compile-time and run-time. Users wouldn't need to learn any additional concepts (like alias
) to do metaprogramming. They just need a way to specify the time of execution.
This model might not end up being practical. I suppose it depends on what kind of metaprogramming system the Mojo devs are interested in building. But crucially: it would be possible to make gradual progress towards this model. The starting point would be to move Mojo's existing metaprogramming capabilities into this model, i.e. to replace alias
with compile let
, and @parameter if
with compile if
.
Feel free to bikeshed about the compile
keyword, of course. (Call it comptime
if you like.) In fact, maybe compile let x = 2
can even be abbreviated to compile x = 2
. In that case, the metaprogramming model I've proposed would be entirely compatible with the rest of the discussion happening in this thread. We can bikeshed on the alias
keyword, with the knowledge that it is a shorthand for a compile-time let
binding.
At the very least, I believe this model of compile-time execution is worth seriously considering. The ultimate goal is simplicity — compile-time execution works the exact same way as run-time execution, except it also has the ability to "interpolate" run-time code.
For compile-time execution to support all of the capabilities of run-time execution, it must be possible to invoke arbitrary functions. These functions would need to obey certain restrictions, such as not being able to do I/O. This doesn't require any support from Mojo's type system: if an I/O call is made at compile time, it would be sufficient to just abort compilation.
But this is a "stretch goal". It would be perfectly reasonable to disallow function calls at compile-time, and only allow basic operations such as arithmetic. It's a simple restriction that is easy to understand, and it leaves open the possibility of supporting function calls in the future.
That said, certain kinds of I/O are extremely useful at compile-time. For example, the Rust compiler allows external files to be loaded into a string or byte array. One could imagine using this capability to parse a JSON file at compile time and load its data into a Mojo struct.
I don't have any strong feelings, but just wanted to mention how another language does it. Nim uses const
for this. Developers quickly learn that const
means "compile-time", the same will probably happen with alias
but if you want to reuse something another language already uses then const
might be a good way to go.
That said, re-reading the docs for alias
I get the feeling that it actually is strictly an alias and thus the alias
keyword makes sense for it. Does the compiler forcefully evaluate expressions in an alias at compile-time? i.e. will alias x = 5 + aVariable; print(x)
fail at compile-time because aVariable
is not known at compile-time or is this simple expression replacement and x
is just filled with 5+aVariable
in the print
? In Nim const x = 5 + aVariable; echo(x)
doesn't compile.
I'm just saying that if we replaced the keyword "alias x = 42" with "parameter x = 42", then we can say "it's a declaration of a parameter" and that "parameters are all compile time expressions."
@lattner, parameter
gets closer, but I still think it is too overloaded in programming circles. After all, parameters are in the call signatures of a vast number of function definitions.
alias: named parameter expressions
From the docs, "named parameter expressions" are really "named (compiler) parameter expressions", aren't they? By using parameter
for a narrow meaning such as this, I think we are adding to the cognitive burden of a developer.
... Nim uses const for this. Developers quickly learn that const means "compile-time", the same will probably happen with alias but if you want to reuse something another language already uses then const might be a good way to go.
@dom96 I like the idea of using const
. It aligns with the concept of an unchanging value. C#, Rust, and many others already have the const
keyword. Having this determined at compile-time doesn't throw any significant hurdles in the way, as far as I am concerned.
If const
is problematic for reasons that are not immediately apparent to me, I would argue for compexp
(instead of comptime
). compexp
= "compiler expression". I think this pretty clearly identifies what follows.
Does the compiler forcefully evaluate expressions in an alias at compile-time?
@dom96, my understanding is alias
is always about compile-time named parameter expressions.
Added a discussion that may have relevance to this issue here: #177.
It might even make sense to be able to just mark expressions and bindings as compiletime. In this case a keyword may be the better choice. With this it may look like the following:
comptime SOMETHING = 1234
if comptime(some_fn()):
do_abc()
else:
do_xyz()
some_call(SOMETHING, comptime(do_it()), get_user_input())
Reasoning here would be, that it is more flexible and allows partially marking things as compiletime. This however assumes that a reasonable optimizer is in place, which can detect a compiletime static condition and perform dead code elimination accordingly.
In this case it becomes questionable whether it makes sense to mark functions as compiletime (or even provide that ability). The call-site would control whether the called code is evaluated at compiletime, and if so, everything down the call tree is. At definition-site (as in, the fn declaration) it may then only make sense to mark it as compiletime if it should be required to be called in a compiletime context.
Regarding the naming, I think something like const
or comptime
would be a good idea, since it has a very clear intention. I like comptime
the most out of all the proposed names so far, since it very clearly states what it does.
I updated my post above with a more detailed proposal for Mojo's metaprogramming syntax. Please check it out folks 🙏. In short: I think it's worth developing a clear, and easy-to-understand model for compile-time execution. Once this is done, it will be easier to figure out a good name for specific constructs such as alias
.
By focusing too narrowly on the name for the alias
construct, we may be missing the forest for the trees.
Also, I agree with the above post ^ that comptime
makes sense as a modifier in multiple contexts, including expressions.
@Janrupf proposes:
if comptime(some_fn()):
And in my previous post, one of the syntaxes I propose is:
comptime if some_fn():
Which achieves what @parameter
achieves today. Unlike @Janrupf's proposal, it doesn't rely on dead code analysis. Instead, it offers a rock-solid guarantee that the branch will be eliminated at compile-time. But I suppose Mojo could formally specify that the former syntax eliminates the branch, in which case, either syntax would suffice.
I think using comptime
to evaluate expressions would be wonderful in other situations, such as the other example that @Janrupf gave:
some_call(SOMETHING, comptime(do_it()), get_user_input())
To summarize my view at this point in the discussion, I offer the following:
I started this as a replacement for the keyword alias
; however, I'm seeing the benefit of aligning @parameter
and alias
under a single keyword such as const
, compexp
or comptime
.
While I understand the choice of alias
and @parameter
, respectively, I feel these are too overloaded with meaning from other languages and common usage of these terms in programming.
If possible, having a single keyword, as opposed to a mix of a keyword and a decorator, would be preferable.
This is veering off into discussion land. An issue is not the right place for something like this. Please start a new discussion (via Github discussions in this same repo) if you would like to continue this conversation.
Also FYI we already have compile-time ifs, in the form of @parameter if a + b
If Github issues aren't the appropriate place to discuss language features, then perhaps the "Feature Request" option should be removed from the list of issue templates. Because otherwise, I predict discussions such as this will be commonplace. (Because people are obviously going to respond to Feature Request issues with their thoughts about the feature.)
That's probably a good idea. That said, certain feature requests are less controversial than others (e.g. implement __add__
on Python object or something). I suspect this one should have been "[RFC] Rename the alias
keyword"
Perhaps the template should be "Missing feature" instead then. To stop people requesting a redesign of things that have already been implemented.
Makes sense me to @goldiegadde. Although the problem will not go away. I suspect there will be more "please move this to an RFC" in the future.
Discussion started in #190.
Request
Rename keyword
alias
tocomptime
.Motivation
from the Mojo docs
In the above definition, it is clear that an
alias
is really acomptime
expression as opposed to strictly just another name for an expression. While it is likely there is good reason for using the keywordalias
, I would suggest that this will lead to confusion, especially among newbies.By using a keyword of
comptime
, the intention is clearly understood, whereas, the keywordalias
is, while possibly technically "correct", not as clear. Aliases are typically used in other programming languages as a means of referring to a named entity (often modules) by another name. Many of these uses are in languages without the concept of compile-time expressions. In Python, for example, module aliases are created using theas
keyword.Description and Requirements
The term "alias" already has a history of usage that the Mojo usage may be inadvertently going against the grain on. Using a keyword such as
comptime
, has the benefits of clearly indicating the purpose of the expression and avoiding confusion that the current keywordalias
may engender.