The compiler doesn't enforce O(1) semantics on subscript
because it can't; but this is not a prescription that it shouldn't where it is possible. In other words, we do not have to enable semantically nonsensical conformances simply because we can make the syntax fit, if there are alternative options to enable the semantically sensible use cases without doing so.
Can an enum have two cases seconds(Int)
and seconds(Double)
now? I was under the impression that this was not supported: SE-0155 still requires cases to have "distinct full names."
It's not possible now, but I believe it would be reasonable for overloading to work. I don't know if it should work without any labels though.
Anyway, in this case, you will have to provide a manual implementation for seconds(Double)
but the remaining requirements could be satisfied by the cases directly.
So, hypothetically, Apple couldâve done this instead:
extension DispatchTimeInterval: SchedulerTimeIntervalConvertible {
public static func seconds(_ s: Double) -> Self {
return DispatchTimeInterval.seconds(Int((s * 1000000000.0).rounded()))
}
// Remaining requirements already satisfied by cases
}
Of course if in the future we allow overloading and DispatchTimeInterval
gains a seconds(Double)
then the above would simply become extension DispatchTimeInterval: SchedulerTimeIntervalConvertible {}
.
Similarly, the compiler canât enforce that enums only witness these âfactoryâ static func
requirementsâit can only add syntactic hoops that someone has to jump through in order to witness the non-factory requirements (either with underscored cases or some alternative method to this proposal). We could introduce annotations like @constantTime
to force protocol conformers to syntactically document their time complexity (even if conformers went on to violate that contract), but itâs been deemed sufficient so far to allow protocols to declare most of their semantics as documentation. Iâm not convinced that this issue rises to the level of importance (as opposed to say, mutating
vs. nonmutating
) to require syntactic barriers to conformance.
Right, SE-0155 allows foo(x:)
and foo(y:)
to be distinct cases; the implementation work is not done.
However, allowing foo(_:)
and foo(_:)
to be overloaded with only differences in type is explicitly disallowed by SE-0155 and would not be supported without further proposals, unless there's an enum evolution proposal I've missed along the way somewhere.
The more interesting question to consider, in my view, is whether we may ever implement generic cases, and with that whether a generic case would be able to count as the implementation for both seconds
static function requirements.
For the utility of annotating factory semantics, see prior discussions such as the following: [Proposal] Factory Initializers.
The proposal says:
A drafted version of this proposal considered allowing "overloaded" declaration of enum cases (same full-name, but with associated values with different types). We ultimately decided that this feature is out of the scope of this proposal.
I am not sure if such a change would need to go through evolution again though.
Most definitely so, I'd imagine. It was explicitly scoped out of the prior proposal (as in, not accepted as part of it).
I suppose. You can sort-of get around it with static
functions though. For example, this is valid today:
enum Foo {
case seconds(Int)
static func seconds(_: Double) -> Self {
return Foo.seconds(...)
}
}
or
enum Foo {
case seconds(Int)
static func seconds(_ s: Double) -> Self {
return Foo.seconds(Int(s))
}
}
or even this:
enum Foo {
case seconds(s: Int)
static func seconds(s: Double) -> Self {
return Foo.seconds(s: Int(s))
}
}
Thanks for the thread! A couple of thoughts:
I'm not sure that that thread makes the case for annotating factory semanticsâit seems like what was settled on there was to allow return
/self
assignment within convenience
initializers, which actually hides the factory semantics below the API. But even if we allow for factory semantic annotations on concrete types, that still doesn't necessarily imply that it should be relevant at the protocol
level. We don't allow class
-bound protocol to specify convenience
initializers today.
Also, if a protocol author has created what is essentially a factory static func
requirement on the protocol, but for whatever reason has not marked it as such, why should the burden then be placed on conforming enum
types to jump through hoops in order to conform properly?
I'm still not convinced that the FloatingPoint
examples from above are really such a bad fit. I think the objection arises from the fact that those static func
requirements feel more like transformations than strictly factories, at least in part because they take Self
-typed params as input. Are there examples of static func
requirements that take no Self
-typed arguments but still don't seem like a good fit? Drawing the line along those boundaries would boil down a rule something like "static func
requirements may not be satisfied by an indirect
case".
Good!
Swift has generally avoided requiring boilerplate-y annotations like this. We often put semantic requirements in documentation and expect conformers to be responsible in meeting those requirements. I don't see why requiring a factory annotation would make the language better here.
In the numerics domain, I think it is extremely unlikely that anyone would even consider writing an enum with cases that fulfill static requirements. I don't think we need the absence of an annotation to guide people away from that.
This is largely irrelevant to the present discussion which focuses on cases where we want factory method requirements. The thread you reference discusses supporting factory initialilzers - i.e. those which are allowed to return a subtype of Self
- as a preferred alternative to factory methods in some cases.
This is in many respects the opposite problem. In the example I gave, I explained how I decided to use initializer requirements where I would have preferred a factory method requirement (so users would not have to compromise their case
names). In the thread you link people want to use an initializer where they currently have to use a factory method.
Hold up. This is (a) conflating language semantics (*) with the semantics of the functions being expressed in the language; and (b) flipping the argument presented in the pitch on its head:
I think the FloatingPoint
example is at least one major instance where we can see that the similarities in language semantics fall short. It bears examining whether the argument from the point of similarity motivating this pitch is sufficiently strong to extend the slam dunk case of conformance to static property requirements to static function requirements.
(*) I am guilty of the above as well at times. To me, the term means: Two language features x and y have similar semantics to the extent that they can be used in similar ways by users of the language to accomplish a particular task (e.g., computed properties and stored properties, class methods and static methods). Semantics here would be in contradistinction to how the underlying implementation of the features may be similar or differ at the level of the compiler or runtime, or how superficially the spelling of x may be similar or differ from y. This is altogether distinct from the semantics of any particular thing that I create using language feature x or y (for instance, a specific computed property foo
or bar
). It is true that the language does not usually enforce the semantics of foo
or bar
; but by construction, the language does enforce language semantics.
We are going far afoot from the issue of enum conformance to protocols, but we are not venturing into an alien planet in terms of language semantics here. The question is very much live in the sense that we are discussing: How do we want to spell factory patterns in Swift? And in what way should features with distinct spellings but share these language semantics (i.e., that of factories) interact in terms of conformances, subclassing, etc.?
Now, perhaps you would argue that factory patterns should not have their own spellings--which gets us to the same endpoint in terms of what you're arguing concretely here: one shouldn't need to annotate static function requirements as factories and the language shouldn't care.
But I find that argument unconvincing for two reasons: (1) in the case of enum cases, we have just agreed that cases with associated values do have the language semantics of factories, so we already have one way of spelling this in the language; (2) as revealed in the thread I linked to, there is demand for a second way of spelling factories in the language and in fact the underlying implementation already exists to support Obj-C and just doesn't yet have a Swift-native syntax.
I donât understand this objection. No one is proposing that all static functions that return self are semantically identical to cases with associated values. No one is proposing to âblessâ anything.
This proposal is centered around the fact that (1) enums currently already exposes static constructors for its cases, and that these follow the same language semantics as static functions, including leading dot syntax, referencing the bare constructor without invoking it, etc. and that (2) sometimes it is useful to make these constructor functions (or simple cases) take the role of protocol requirements.
My case was similar. I was experimenting with a zero-bit integer type. That is not allowed for signed integers, but is OK for unsigned integers. I first made an empty struct
, then I realized that I could make the single state an enum
instead. It should be seamless if I called that case .zero
, but the issue of this thread prevents this (for now).
Is this still actively being discussed? This is something I've wanted to do (and was surprised to find was not already supported) a few times over the past few months, and this thread is the only discussion I can find about it.
Thank you!
That reminds me on my first thought about the pitch â and it looks like that has been ignored completely:
There is still no official documentation about this feature at all, isn't it? At least I couldn't find it in Index of /swift-book/LanguageGuide.
Imo this situation is quite unsatisfactory â how are people supposed to know what code means which depends on undocumented behavior?
There is documentation available for it. If you look at the âProtocol Declarationâ section here: Declarations â The Swift Programming Language (Swift 5.7) and scroll down a bit you can see it.