Oh, right! Inside a const function, naturally every value (when called in a compile-time context) would need to be compile-time known as well. So inside a const function, every let would be essentially const too (except when called in run time) and var would indicate a compile-time mutable value.
No, that would be just const. We would need reconst for higher-order functions though, just like map needs rethrows.
By that definition, and given the equivalence of static constructors and enum cases in many parts of the language, doesn't that mean any constant value from a static would be a literal? In that case, wouldn't the meaning of literal become too nebulous to be useful? We can say Swift hasn't rigorously defined what a literal actually is, but I don't think it's useful to expand the definition beyond usefulness.
No. Just because a static variable can be const and can be used similarly to an enum case doesn't mean they're the same thing. Enum cases are literals, static variables (even const ones) aren't, though they can be initialized from literals such as string literals or enum cases.
A literal is something that, at compile-time, is replaced by a call to a StaticString initializer that accepts a pointer to the value. This is nested in a series of additional initializers that, when run, ultimately produce the desired instance.
Thatâs my understanding, anyway, from a practical perspective. From the perspective of the grammar, itâs quite well-documented.
In terms of implementation, enumeration cases are very similar to literals: they are compiled down to a pointer to data. In terms of the grammar, however, they are clearly distinct.
Ultimately, what compile-time constants boil down to is running Swift code during compilation. As such, I think we should also discuss the potential security, performance, and reliability implications. How should compile-time code report errors? How would it be debugged? Could maliciously-written compile-time code somehow attack the compiler? If so, how could that be prevented?
Iâd also like to know if it would be possible to automatically evaluate code at compile-time under the right conditions, in a similar manner to automatic function inlining. The halting problem might foil that, of course.
My main problem with const as another let or var type thing is that let and const are so similar, itâs not clear that itâs a different type of thing.
And in my opinion it is more of a type thing than a type of variable, because functions canât limit whether their arguments are let or var (Iâm not counting inout because that requires an &, itâs not the same as passing a const) yet they can require that the argument is a const String.
Another reason for not adding it as another let or var kind of thing, is that javascript uses var, let and const, and theyâre used completely differently.
I dislike const anywhere in the keyword. const is an artifact of the compile time evaluation, so why not name it after what it does? Some variant of: compexpr, comptime, comp. I second @stackotter, the distinction between let and const is somewhat arbitrary.
I'm just dreaming, but it seems compile-time expressions could play well with meta-programming and the AST.
Even tho I donât think talking about the specific name memas a lot right now (I think there are other questions that need answers) I canât avoid throwing out âbakedâ as another keyword that I prefer way over const.
I think compexpr or constexpr could be nice if used like:
let name = constexpr "stackotter"
let number = constexpr 1
func nameAndAge(name: constexpr String, age: constexpr Int) -> constexpr String
I havenât seen anyone else mention it, but we also need to decide how guarantees about the functionâs return value are denoted, because if a pure function has both arguments as constexpr, then itâs return will also be constexpr and it could be used to create other constexpr values:
let name = constexpr "The queen"
let age = constexpr 254
let s = constexpr nameAndAge(name, age)
Another reason for prefixing expressions with it instead of replacing let/var is that it is a guarantee about the expression (expr) not the storage mode as such.
I think it makes sense, the thrown error should propagate up the stack up to the âcompile time interpreterâ and that should report it as a compile time error. Hopefully with a file/line.
That makes no sense to me. In my mental mode of just running Swift at compile time, that will give you an optional. Doesnât matter that is comptime or not.
I think the pitch includes the usecase where only some arguments are compile time known, and that guarantee is just used during optimisation, but does not result in a compile time known return value.
Yes fair enough. I still think that we should have some idea of whatâs the end goal for Swift here. Iâm sure smarter people than me already thought about this but Iâm afraid we are doing things now just for variables when there may be a better solution if though holistically.
I think there are almost 2 separate goals at the moment (that are related).
Allow for more aggressive optimisation of certain functions by guaranteeing which of their arguments are known at compile time (a form of currying).
Create a way for complex compile time values to be created in the first place
For the second, I think marking function return values as constexpr (replace with your preferred keyword) is very important. Marking the return value would likely be done by prefixing the return type with constexpr because it is semantically a guarantee about the return value.
I think using constexpr as a prefix for expressions (and types in function signatures) is the most consistent notation.
If constexpr was instead a replacement for let it would be quite strange prefixing a literal argument with constexpr (because you can't do that with let).
Here is some example code that makes use of both aspects (creating compile-time values, and 'currying' functions).
let name = constexpr "stackotter"
// It is made clear that the function will return a `constexpr` and can be used
// to create further `constexpr` values.
func greet(_ name: constexpr String) -> constexpr String {
return "Hello, \(name)!"
}
let greeting = constexpr greet(name)
// My only issue is that this is starting to get a bit too verbose. Perhaps
// if a function is called in an expression that is marked as `constexpr`,
// it is already clear beyond doubt that the arguments must all be
// `constexpr` too?
// Alternative: `let otherGreeting = constexpr greet("Taylor")`
let otherGreeting = constexpr greet(constexpr "Taylor")
// This is an example of using `constexpr` to perform a form of
// compile-time 'currying' (partially executing the function as
// much as possible when only some of the arguments are known).
func curriedAdd(_ lhs: constexpr Int, _ rhs: Int) -> Int {
return lhs + rhs
}
let two = 2
let threeAddTwo = curriedAdd(constexpr 3, 2)
It felt really natural writing that example use case, and I think the notation makes the semantics extremely clear at each call site. For example, if literals were not clearly marked as constexpr, the semantics of curriedAdd would not be clear at all when used to define threeAddTwo.