Enum cases as protocol witnesses

I’ll happily do whatever I can to help bring the protocols and generics to full fruition!

Speaking of which, that use case would vastly benefit from the ability to typealias constrained protocols:

public typealias Window = InputEventHandler where Window.InputEvent: CanCloseInputEvent & CanMinimizeInputEvent

func doSomethingWith<W>(window: W) where W: Window {
    // ...
}

Could you provide more code, please?

Maybe. But people are certainly already confused by the current incoherent behavior, where something that looks like duck, sounds like a duck, behaves like a duck — can’t conform to the Duck protocol.

Why would you find it more confusing to fill the whole? Enums already exposes all its case constructors as static functions. This is the current behavior. However it can’t be used to fulfill a contract that asks for exactly the same interface it provides.

That’s super confusing!

8 Likes

It was confusing to me when I realized it wasn't possible. I think I even had to look it up after messing around with it for a bit.

1 Like

Count me among the people who were confused, and then disappointed, to discover that the conformance proposed here wasn't already possible the first time I tried it.

So, I don't think the argument that "[t]his feature can confuse people" holds water. It's a hypothetical argument of concern but lacking evidence, whereas the posts in this thread are concrete evidence from multiple people that the current state of the language is already confusing.

2 Likes

Same here, when I needed the functionality.

1 Like

To be fair, there’s no reason to not believe @Tino when he says that it is confusing to him. I don’t doubt that for some, this may come as a surprise if implemented.

However, I also don’t think that his counter argument holds any more true than the opposite. I was baffled to realize that the already provided static constructor function couldn’t be used to satisfy protocol requirements.

But there is clear that evidence that at least a few find it natural the way it is.

However, the proposed functionality is purely additive, doesn’t remove anything from Tino’s “I can copy a bunch of lines from a type, and turn it into a protocol”, and it clearly fills a confusing void for others. Me included.

Just accept that people can have opinions different than yours - that should be obvious enough to not need any debate. (:slight_smile: looks like the first sentence already got outdated :+1: )
Yes, if somebody declares a protocol Duck, and if he wants cases to act as static functions, he might be surprised and frustrated that his enum Eider can't conform to that protocol without boilerplate.

But, you know what: I have no idea what to do with a duck-protocol, and probably never will :duck:
Just think a moment how many people will never ever feel the need to declare an enum that conforms to a protocol in the way outlined in the pitch - there's not the slightest chance that they would ever suffer from the problem that it is not possible to do so.

Don't forget that this here is the penthouse in the ivory tower of Swift, and the wast majority of developers has problems completely different from what is discussed here.
Look how long it took until finally someone could demonstrate a reasonably good example, while in the meantime, there have been many pointless claims how inconsistent Swift is now, and how often the change has been missed (strange that hardly anyone could remember one of those situations).

I think we should leave our tiny bubble at least enough to acknowledge concerns like this instead of brushing them away.
Actually, I'm neither confused by the current nor by the proposed behavior, and never cared about the outcome of this specific pitch - but it's concerning how one-eyed discussion has been.

1 Like

Regardless of whether he wants it or not, cases already do act as static functions. That’s the entire reason this has come up.

Then this pitch does no harm to them, and is of no relevance. This is purely additive, with no demonstrated adverse effects.

Sure, but what makes their concerns more or less real than the concerns of the proposal author and its supporters? We are also users if Swift, not language theorists or compiler engineers. But real users with real world concerns.

But this proposal fills a hole for us, while (as far as I can see) not making any harm for those with different needs and problems.

1 Like

Not much harm - but that is more than nothing.

Yes, many developers who'd just shrug their heads about this topic would never encounter the special behavior in their codebase, and what you cannot see can also not confuse (at least in this context ;-).
But no one has thought about documentation yet, and I think this has relevance.

Once again, I'm not arguing against this pitch, just against the imbalance in the discussion.

2 Likes

And just to be clear: I’m fine with people having different opinions. I just don’t like having my opinion dismissed, in the same breath that the opposing opinion is presented as canon. Your objection is not more or less representative of the broader community than mine.

We need to differentiate between facts and preference. That enums already have static functions acting as case constructors is fact. That they cannot be used to satisfy protocols requiring a static function with the exact same signature is also fact.

That I don’t like the combination of those facts is my opinion. And yours is the opposite. That’s fine.

My duck example is a tongue-in-cheek example of the facts, not the opinions. A hypothetical function that is generic over the Duck protocol could be changed to instead accept one of your Eiders, and still compile — without any other changes. All calls that worked on Duck would also work on Eider. But still; you cannot currently declare conformance. To me, and a bunch of other people, this is confusing.

Bringing this up, is not the same as dismissing your opinions. The status quo is after all just how it is, regardless of how one feels about it.

9 Likes

I think I’ve spoken enough, and I’m not contributing in bringing this discussion forward, I believe. I’m sorry for that.

Let’s bring other aspects on the table, and don’t let me discourage further conversation.

1 Like

Coming from other languages, enums in general are already somewhat confusing in Swift, since they encompass three different (but related) concepts:

enum FiniteListOfStates {
  case foo
  case bar
}
enum FiniteListOfStatesWithDifferentNamesValuesEtcOrListOfConstantValues : String {
  case foo: “Hello”
  case bar: “World”
}
enum KindaWeirdButPowerfulCouplingOfStateAndNonConstantValues {
  case foo(Int)
  case bar([String : Double])
}

Whether we shouldn't make them any "worse" or whether we might as well double-down on them being "different" is, of course, up for debate.

(Personally, I'm in favor of this pitch. Aside from pointing out where I think some of the contention is coming from, I don't think I have anything to say on the matter that hasn't already been said.)

2 Likes

This makes it sound like Swift is different from all other languages, but it is certainly not the first to implement enums as algebraic data types, some even share exactly the same syntax, although that is not always the case.

By all means, I see that coming from most other imperative languages, or most object oriented languages, that this may seem new. But it is far from new.

3 Likes

Fair point... I should've said something more like "coming from other languages that I've used".

Countless times I needed this and got really confused when finding that it was not possible. This is perfect because protocols with associated types are more flexible to be used in conditional conformance than concrete types, for example, and being allowed to move part of the contract to the protocol allows me to have better extensions and write better frameworks.

It'd be really consistent to have a way to generalize the type member of classes, structs and enums, regardless if they are called class, static or case. The token static in the protocol can be a bit misleading, yes, but it represents type members and enum should not be left behind, as its cases already behave as such!

Huge +1 if this turns into a proposal.

1 Like

The purpose of a pitch thread is to shape and improve a proposal to the point that it is ready for review. Review is not a rubber-stamp; plenty of proposals have been rejected during review. It's okay to say that you don't think a pitch is a good idea, but if other people simply disagree and you've reached an impasse, please just leave the pitch thread alone and wait to leave your feedback during the review.

16 Likes

Not having time to dig through source code is different than not being able to find examples. We are all entitled to state our opinion; it’s worth acknowledging that an opinion with an example will hold more water and maybe even sway more people, but that doesn’t make it wrong of anyone to express their feelings for or against a pitch.

I would argue that this particular issue raised earlier is not merely "minor" but altogether separable from this pitch:

The overall gist of the pitch is that there exist enums which, in their semantics, completely meet the requirements of conforming to protocols but currently cannot.

There is nothing an author can do to make conformance possible: saying that one can rename the cases is tantamount to saying that one can conform an entirely different type to the protocol in question. Rather, it comes down to limitations in the language itself that can only be lifted by evolving the language itself.

Therefore, I agree entirely with the author of this pitch that we have an unnecessary hole in the language that would benefit from the solution that has been pitched.

That one cannot write a case named foo() does not prevent any enum from conforming to any protocol. The deficiency for which a solution is being pitched has to do with Swift's rules around what can satisfy protocol requirements (cf. the pitch author's companion mini-manifesto), not Swift's rules about enums. I would go so far as to argue that nothing to do with enum syntax (and it is true that there are many warts there from the unfinished work of evolving the syntax) is directly relevant to the pitch here.


Now, as to the proposed solution itself:

I think it's a slam dunk for enum cases with no associated values to fulfill static, get-only property requirements. In many circumstances already, users don't have to care whether a certain enum member is a case without associated values or a static get-only property, any more than they have to care whether it's a computed property or not. (I believe in fact there are some members in enums vended by swift-corelibs-foundation and Apple's Foundation which differ in exactly this way.)

I'm not as certain that the fit is nearly as perfect for cases with associated values fulfilling static function requirements. Consider typical cases with associated values, like:

// Example from _The Swift Programming Language_
case upc(Int, Int, Int, Int)
case qrCode(String)

I simply don't know that static function requirements semantically resemble such cases, and in fact I can't think of any static functions in the standard library that read like this. Although there are superficial similarities in the way they may be invoked, I don't think of static functions and cases with associated values as interchangeable--at least not to the same degree as cases without associated values and static properties.

6 Likes

That's a good point, although static func qrCode(_ code: String) -> Self and case qr(String) will be considered a match, which I think makes sense (I do apologise for not adding this example to the proposal text, I will update it now).