Enum cases as protocol witnesses

Off-topic: I‘m not sure I follow. Iff the other feature were allowed as written right now then a protocol requirement for a function of type (A) -> X would be satisfied by a conforming type which provides a function of type (A, B) -> X while B would have an explicit default value, which would reduce it to (A) -> X.

I assume this also would translate to this pitch. A protocol requirement for a static function with type (A) -> Self should be satisfied by an enum case with a (A, B) payload while B would have a default value.

Can‘t we just reduce one parameter and have the same trick for () -> X and (B) -> X (with default value) functions, which again would translate to our enums?!

What am I missing? :thinking:

1 Like

This will always be a compile time error unless DEBUG is defined, won't it?
But more importantly: What is the purpose of MyEnum?

At least one potential user already wrote that the result of this pitch is confusing.
Just because you perceive something as a hole, you can't generalize and declare that it a fact that the current behavior is a bug.
When the compiler becomes more complicated, and user documentation becomes more complicated, I'd say it's doubtful at least that this is a simplification.

I really would prefer reading about the concrete issues you ran into which would be mitigated by this pitch instead...

I don't want to speculate how that would work right now (at least not in this thread, the manifesto is a better place for such discussions). I think we would have to change some things around because case foo doesn't have type (T.Type) -> () -> T but rather (T.Type) -> T (and case foo(Void) is (T.Type) -> (Void) -> T) so perhaps we might need to tweak how we generate the witness thunk in this case (or the function for the getter) in order to allow case foo to satisfy static func foo().

Which comment? I don't see anyone being confused, but several complaints that other developers will be confused.

Looks like it. The #if should be around only static var testInstance: Self { get }. Can't change it now :slight_smile: The intention is still clear though.

It's example pseudocode. MyEnum is obviously not a production name...if you want a full example I'm sure you can think of one :stuck_out_tongue_winking_eye:

Sure, but yet again you‘re talking about case foo without any (). I wasn‘t referring to that though.

I think in that case we would have to change the outcome of SE-0155 which banned case foo(). There isn't a way to write a case foo with type (T.Type) -> () -> T today.

I moved that discussion into the other thread. Protocol Witness Matching Mini-Manifesto - #9 by DevAndArtist

1 Like

It is at least an undisputable fact that no-value cases currently are indistinguishable from static var caseName: Self { get } and that cases with associated values are indistinguishable from static func caseName(value: Value) -> Self, except for one major omission: The opportunity to allow them to function as protocol conformances.

This is fact.

Wether or not this makes your or someone else's mental model simpler or more complex, I guess depends on how your mental models looks, something I have no insight into. Whether it can be considered a bug, is dependant on this.

But that this is currently an inconsistency is clear and not mere opinion.

Imho it's not particular helpful to discuss such questions, but as it is still a question:

No, actually I can't. So far, there are many claims that this is a super useful addition, but not a single good example.

Imho words like "fact" and "indisputable" are used way to excessively...
Of course you can easily distinguish static var caseName: Self { get } and case caseName:
Those are two different things for the compiler, and everyone looking at the declaration will see see that difference as well - afaik case isn't just syntactic sugar for static let.

Let's stop making bold claims, bring examples instead! :slight_smile:

Fine, I accept your point about wording. But this is fact: They can currently be used interchangeably using the same syntax.

enum MyEnum: Defaultable {
    case default
}

Is, IMO, markedly easier to write, more readable, and more maintainable than

enum MyEnum: Defaultable, TestInstanceable {
    case _default
    static var default: MyEnum { .default }

If you find more clarity with the extra verbosity, you'd still be able to use that form. Regardless of your opinion, I (and others) find it better. Is there a reason it being available would hurt your usage of the language?

So the main motivation for this change is that it makes it more convenient to do stuff that nobody needs?
Please consider the actual question in your answers:

So far, this is a solution in search for a problem...

2 Likes

I agree. There have not been strong examples.

Maybe they were hidden in non-obvious ones (for those with a certain previous understanding) but the ones that i have seen truly felt like "in search of a real substantial problem".

I think this is getting really inappropriate and almost offensive now.

5 Likes

I was always very suspicious of the rationale for that change and this question, I think, shows why!

I always found SE-0036 a bit wrong too, but after posting about it in this thread and trying to come with an example of something dubious I realize it left me with the wrong impression all these years.

SE-0036 actually made case behave exactly like a static variable. Contrary to what its title says ("Requiring Leading Dot Prefixes") and some parts of the text ("We propose to mandate a leading dot."), there's no actual requirement for a leading dot in situations where you wouldn't also need one for a static variable. There's even an example of this in the "Detail Design" section. What was removed is the ability to refer to it as if it was also a non-static variable.

3 Likes

So you see no possible reason for anyone to ever have a protocol with a static member that is covered by an enum case? I guess you do you...I've wanted to do this several times before. If you're looking for something closer to a real-world example, how bout mocking? (hastily written pseudo code example, don't get bent out of shape if there's a minor issue)

struct Book: Codable, Equatable {
    let title: String
    let sortOrder: Int

    #if DEBUG
    static var mockInstance: Book { Book(title: "The Hitchhiker's Guide to the Galaxy", sortOrder: 1) }
    #endif
}

enum Genre: String, Codable {
    case scifi

    #if DEBUG
    case mockInstance
    #endif
}

// In Unit Test module

protocol Mockable {
    static var mockInstance: Self { get }
}

extension Book: Mockable { }
extension Genre: Mockable { }

func testCodableType<T>(_ type: T.Type) where T: Codable & Mockable & Equatable {
    do {
        let jsonEncoded = try JSONEncoder().encode(type.mockInstance)
        let jsonDecoded = try JSONDecoder().decode(type, from: jsonEncoded)
        XCTAssert(type.mockInstance == jsonDecoded)
    }
    catch {
        XCTFail(error.localizedDescription)
    }
}

func runTests() {
    testCodableType(Book.self)
    testCodableType(Genre.self)
}

runTests()

Anyway given your needlessly-combative tone I'm not willing to discuss this further with you.

1 Like

I wholeheartedly despise rhetoric maneuvers like pseudo-excuses, but I'm genuinely sorry if my tone actually inhibits a constructive debate. It's just that I asked for examples four times before, and this is the first variation with some sort of success...
So, I'd be glad if you would stay in a discussion that others avoided completely - but that's up to you.

I'll leave aside the question wether the proposed Mockable is a good idea, but focus on wether this concept needs changes to the language.
I'd happily admit that this design would benefit from the pitch, but I think there's good reason to prefer an approach that is already possible:

In the test module, you could add

extension Genre: Mockable {
    static var mockInstance: Genre {
        return .scifi
    }
}

which not only frees you from the need of new language features, but also allows you to simplify the productive code, because there would be no need for conditional compilation anymore. Hence, there would also be no need to introduce a DEBUG declaration, and the test code would be contained in a single place instead of being spread in three different files.
I really can't see any downsides of the status quo at all - but I still welcome if anyone could correct me or come up with another example.

Best regards,
Tino

2 Likes