There is a principle that if something can be static, it should be static. That's safe programming 101. You declare and enforce invariance of your entity (function or variable) and that it has no side effects on the instance. Let alone the concurrency implications.
That's what I mean by "uncomfortable" with defining an instance entity where it should have been static.
As for the optionality of Self., I think the principle behind this decision in other OOP languages is that it shouldn't concern the caller whether something is static or not, plus the status of that thing can change with no effect on the rest of the code, i.e. no need to make changes. There is of course good and bad in this, but my argument still stands that if globals can be used directly then why can't statics?
Well, if we're talking about what's "101" then you should perhaps also consider: This abstract rule does not necessarily mean a language has to achieve this safety by attaching the keyword static to things. A calculated property of an instance that does not rely on the state of the instance is "static" in exactly the same way a static property of a type is. But anyway, I fail to see why this would be safer per se, because:
is exactly the problem. A variable being static does not imply there can be no side effects on instances. This would only be the case if it is constant. If it is instead mutable, you do introduce side effects: between the type and instances, and possibly even between instances: You change the static property in one place and all of a sudden each instance is affected as a side effect.
And for that reason I think it is generally better to enforce being explicit with static properties using Self..
I dare say that it is much more likely that older OOP languages just "went with the flow" ("it has always been like this") than there being much design choice behind this, precisely because what you attribute to things being static is simply not true, see above.
That argument does indeed stand and is a good question. For above-mentioned reasons I consider global variables generally evil (another programming 101 thing, btw), and global functions questionable in most cases. They're okay if they really have a widespread use-case and are side-effect free (excluding maybe logging), but I prefer things to be tucked into a case-less enum, usually.
Fair point, but what I meant was, when you e.g. call a static function from within an instance context, you know that the function can not have a side effect on the instance (barring singletons and other perversions of course). Let's call this invariance instead if you wish.
The principle "if something can be static, should be static" is actually a good one that I always try to follow. When you declare something static you enforce a certain style and essentially make a statement about the thing (function or variable) that it is invariant and possibly even pure in functional terms.
And because I also want to encapsulate my static entities in some namespace rather than declare them global (globals are evil!), a lot of the times e.g. a class where those entities are used, it's where it gets pretty annoying that I should prefix the calls with Self. where in other languages it wouldn't be necessary.
So should I change my style? I notice most people don't follow this design rule and have invariant entities defined as instance ones in Swift. Who knows, maybe because they are avoiding the ugly thing that's Self.?
Oh, this is interesting. I had never thought of computed properties simply as a way to express that the property should not be stored (though it's an obvious consequence of computing its value on the fly).
I agree with the rest of your comments on how Height in this case should be an instance property rather than a static property (even if the desired behavior is that all instances of MyButton have the same Height value).
The main downside of using computed properties to express that the property should not be stored (and thus avoid increasing the memory footprint of the type instances) is that you can't express that the value is constant (as computed let is not allowed). To me, writing var for something that I want to be constant at compile time feels very, very wrong. The need for @inlinable for me further signals that there's a lack of expressivity here.
I wonder if in a world where computed lets existed that would have become more prevalent than (mis)using static for properties that conceptually should be instance properties. Feels to me like this:
var height: CGFloat { return 44 }
Puts a lot of people off due to the use of var, so the alternative becomes:
static let height: CGFloat = 44
Where it's clear that the compiler understands this property is a compile-time constant from the beginning. Yet the ideal solution would be:
let height: CGFloat { return 44 }
As here the property is both constant and not stored, while remaining an instance property.
Wonder if something like that would target the root cause of the problem better than the more symptomatic fix of dropping the requirement of Self. before static properties (which I don't think is a good idea for the reasons stated in this thread). This is assuming that a significant percentage of the use cases where a lot of static properties are used (and therefore lots of Self.) stem from using static for things that should be instance properties. Personally, I've frequently seen the pattern similar to the one above where all UI measurements (like Height) of a view are stored in static properties.
In theory, I agree with everyone who opposes this; but in practice, I prefer to have what is being proposed. Self really should be optional on static properties.
Let's be honest. Which one would you write if Self were optional?
struct U {
static let v: Int = 9
// ...
// ...
var w: Int {Self.v * Self.v * Self.v}
func f() -> Int {
Self.v * Self.v * Self.v * Self.v * Self.v
}
}
struct U {
static let v: Int = 9
// ...
// ...
let w: Int = v * v * v
func f() -> Int {
v * v * v * v
}
}
It is impossible to create a programming language that prevents untrained humans from misbehaving, which Swift is trying to do.
PS: I am surprised that the following does compile.
struct U {
static let v: Int = 9
// ...
// ...
let w: Int = v * v * v
}
Gotta be the one with Self. Makes intentions explicit instead of relying on unit tests to verify correct behavior later when the instance version is added, or vice versa.
However, one has to ask whether the language is really helping when it allows two variables with the same name to coexist in two scopes that are closely related.
This is a non-example: it wouldn't make sense for w and f not to be also static if they are computing multiples of another static value, and if they are static then you wouldn't need to qualify with explicit Self.
Attempt to create an example where an instance member needs to use a mix of static and instance properties, sometimes from another type, while naming them all single letters, and I suspect you'll quickly appreciate qualifying some of them explicitly.
The point I was trying to make was this: please let me use naked static variables when there is no ambiguity. Wouldn't this make the language more pleasant to use?
Yep, it's because the context of instance property initialization is static, you can't use instance entities here unless you mark it lazy in which case it becomes var and Self. becomes mandatory again.
Come on, of course f() could mix instance and static entities. In fact it's very typical e.g. for graphical components to mix constant and variable parameters. Or you start with say a constant height then one day you make it variable, i.e. configurable, and now you have to remove Self. potentially in many places.
As evident from this post it's a matter of taste. My typical solution to this is to make the constant private global, because I can't fully trust the compiler that it will optimize a computed instance property.
While it's true that it's impossible to prevent developers from writing terrible code, it's an extremely important property of a language that it nudges developers in the right direction.
Compiler ambiguous or human-reader ambiguous? The explicitness of Self. adds some context to the places where a static property is used that can help understand what the code is doing with simple local reasoning. Even if the code is unambiguous to the compiler, there's some context lost for the human that is reading that code, and this should also factor into the discussion as well.
I also worry that it'd add some confusion to language newcomers about what static itself is doing exactly, by letting them use those properties as if it were instance properties in some places but not in others.
I'll just reiterate that I think it would be worth exploring how to fix the latter instead
Not a great argument because why not enforce self. like some languages do? In the same spirit also introduce global.? And why stop there, add local. and everything will be incredibly explicit and clear to a 5-year old
You can never be explicit enough, or else you'd end up reinventing Java (or god forbid PL/I).
Any good modern programming language always looks for a good balance between succinctness, expressiveness and readability, just that different languages prioritize the three things differently. I always had the impression that Swift prioritizes succinctness and it's why it's currently my favorite language.
And it is also why I feel like Self. is so un-Swifty, from day one of learning the language. Can't help, sorry
Then, how come the use of self is optional in non-static member functions. Why not apply the same restriction to absolutely make sure that the developer is not accidentally using a local variable that's shadowing an instance variable. If that was the case, then we wouldn't be having this discussion today because the language is consistent in applying the restrictions that "nudges developers in the right direction."
But there are the same restrictions: you donât need explicit self on instance members when you operate at instance level, and you donât need explicit Self on static members when you operate on static level. Proposed changes instead make it less similar, making static members more exceptional in that.
Mostly because globals are already exceptional in this regard. Statics are similar to globals except they are confined to a namespace, which is a preferred way of defining static/global entities in general.
My point is that Self. becomes a barrier of sorts when you make a decision to move your static thing inside a namespace. My question is, why?
Well, because as you say later on in the post, itâs a balance self is indeed required when accessing instance properties and methods outside an instance context.
To be clear, Iâm not arguing that the language should be fully explicit and âclear to a 5-year oldâ. Just pointing out that just because the compiler can prove that something is unambiguous it doesnât mean that it is not ambiguous to a human reader, and that this is something that should be discussed too. And so far there are a few examples in this thread on how such an ambiguity may be introduced.
If a local variable shadows an instance variable thatâs something you can reason about locally. Just by looking at the local code snippet itâs possible to see that the variable in use is a local variable. This ambiguity does not require global reasoning, which IMHO is an important distinction.
You probably mean closures, and that's actually an interesting case.
Enforced self. in closures was introduced in one of Swift 5.x iterations for multiple reasons, one of them being that capturing self in a closure may have far reaching consequences, up to memory leaks in your app. It's too easy to screw it up! I'd say deciding between strong and weak class self in closures is one of the trickiest corners of the language that people tend to ignore and often just resort to weak because they don't fully understand whether strong would be safe or not.
But in any case enforcing self. in closures makes a good developer pause for a moment and think about it. (P.S. and there's always an escape hatch with [self] if you want to reduce noise.)
Now, what are the consequences of not knowing whether something is static or not? Zero, no consequences. In fact using static properties and functions is typically safer and less harmful in terms of side effects, so why "punish" the developer with an enforced qualifier?
Take a look:
var body: some View {
withAnimation {
// ...
}
}
In a world where Self. is optional, do you care if withAnimation is static, global, or an instance function? Similarly, there's the withXXXContinuation family in the standard library.
In fact withAnimation is a global function in SwiftUI and I think I know why it's not static within View
There might be legitimate reasons to want static property, e.g. 1) ability to use it in static methods or when initialising other instance variables as below, 2) instance property of the form let x = C() might be undesirable as it takes up space in each and every instance and 3) a computed instance property var x: C { C() } might be undesirable as it instantiate C every time which might be expensive.
Personally I do not have an issue with Self.property or T.property and actually think it makes Swift better as it is less ambiguous. We could probably make a bare "." work as well to mean "T.", this would be similar to what we do for enum constants.
It compiles because you are in a static context there effectively, so the prefix "Self." is optional (albeit it could still be used). And if you attempt to use an instance variable when initialising another instance variable â that won't compile.
For static constants, I canât think of a counterexample right now. So (tentatively) nothing against it. But for static vars? The potential for unexpected side effects is huge, as one may be inadvertently modifying a value used in all instances of the type (instead of just for the specific instance at hand). Or worse, for other users of the MyButton type. Imagine the very leading example of this thread, using a static var:
struct MyButton: View {
static var height: CGFloat = 44
var body: some View {
// ...
.frame(height: Self.height)
}
}
If Self was not required, one could do something like:
func expandHeight() {
height = 128
}
Now, is this changing height for this instance of MyButton? Or is this changing a property of the MyButton type, that will change the height value used by all buttons? One would need to go look up the definition of height and see whether itâs static or not. That's the kind of local reasoning that would be impeded if the Self was dropped.
In current Swift, height in the snippet above could be:
A local variable (but we can readily see it's not defined within the scope of expandHeight(), so it's not that in this case).
An instance variable of the enclosing struct/class.
A global property.
Ignoring global properties for a second here, we know that the effects of modifying height are limited to either the local scope or the specific instance of the enclosing object. If naked accesses to static properties were to be allowed, the potential effects could cause long distance effects, as we'd need to consider a fourth option:
A static property of the enclosing struct/class.
Global functions and properties already have this issue, and are not required to be prefixed by anything to make that explicit. However, I think global functions/variables are known to be dangerous if used wrong, so in my experience they are given a lot more thought when being written (and there's few of them in a typical codebase). I don't think that's true for static properties and functions, particularly not those used for UI like the example above.
I realize that in this particular made-up scenario, if height needs to be mutable, most developers would choose to use an instance property rather than a static var. But static vars do exist and are used (for different reasons), and being able to modify their values without realizing that the property is not an instance property could unexpectedly make changes far outside the bounds of the instance.
Seems like two of the three points would be solved if the language supported let x: C { C() } (or any other way of creating non-stored constants that the compiler is guaranteed to inline for callers).
Not only that, a static var emits a concurrency warning with Swift 5.10 even before you try to use it (so does any non-isolated global var), while static let doesn't. So with this scenario out of the way, what's left here?