Pre-Pitch: Explicit protocol fulfilment with the 'conformance' keyword

I vaguely recall a proposal to have the Swift compiler parse doc comments and emit warnings when certain things didn’t line up. I forget the specific example, but the idea that immediately comes to mind is warning when parameter documentation gets out of sync with the declaration.

Rather than adding “helper” attributes, how do people feel about a parsed doc comment? - Implements: MyProtocol

The pitch introduces the conformance keyword as a short-hand for a programmer intent. Quoting the pitch:

The conformance keyword means: "this declaration is intended by the programmer to fulfill a protocol requirement".

I have started to wonder if the intent was well-defined, and the keyword well-chosen, after @pyrtsa has reminded us that many users struggle with the subtle distinction between protocol requirements and protocol extensions.

Most readers of the forum are now very familiar with Swift protocols, and know how and when to expect static or dynamic dispatch. But we also know that this is a difficult step on the way to Swift proficiency.

I can imagine a Swift course about static vs. dynamic dispatch that goes this way:

[TEACHER] When you want to make sure that your method is dynamically dispatched, do insert the xxxx keyword in front of your declaration, like this:

extension Rectangle: Shape {
    // Dynamic dispatch for the draw() method
    xxxx func draw() { ... }
}

If the compiler does not produce any error, you're good to go. If the compiler produces an error, then you probably missed the condition for dynamic dispatch: the dynamic method must be declared inside the protocol definition, not in an extension.

If I sum up:

// Enable dynamic dispatch by defining the method inside the protocol definition
protocol Shape {
    func draw()
}

// Grant your conforming types with dynamic dispatch with the xxxx keyword:
extension Rectangle: Shape {
    xxxx func draw() { ... }
}

Please forgive this foray into the realm of the imaginary.

Yet, I think that the correct wording for the conformance intent should be "this declaration is intended by the programmer to be dynamically dispatched".

Dynamic dispatch is the intent. The conformance is a mean that achieves this intent.

Dynamic dispatch is also a frequent cause of struggle for Swift newcomers. By making the intent of dynamic dispatch explicit with a keyword that looks like it does, we'd greatly help those newcomers.

By this line of reasoning, a better keyword than conformance would just be... dynamic. But Swift already uses dynamic for ObjC runtime.

So why not just... dyn:

protocol Program {
    func run()
}

struct HelloWorld: Program {
    // OK
    dyn func run() { ... }
    
    // error: function crash() does not fulfill any requirement of the protocol 'Program'.
    dyn func crash() { ... }
}

dyn is less a mouthful than conformance. It is just as abbreviated as func and var. It has the potential for avoiding hours of misunderstanding in many young Swifters.

That sounds neat but equally introduces a new context-sensitive keyword which might not carry its own weight.

…Which reminds me we already have a keyword which closely relates to what we're talking about: protocol. Why not:

protocol Program {
    func run()
}

struct HelloWorld: Program {
    // OK
    protocol func run() { ... }
    
    // error: function crash() does not fulfill any requirement of the protocol 'Program'.
    protocol func crash() { ... }
}
3 Likes

Yes, I thought about protocol as well. But it brings even less information than conformance:

protocol P {
    func foo()
}

extension P {
    func bar() { }
}

struct S: P {
    // - Both are methods of the protocol P.
    // - Both are related to P conformance (one dynamic, one static).
    // - Only one is dynamically dispatched.
    func foo() { }
    func bar() { }
}

Focusing on the dynamic dispatch feature more than the protocol conformance technique could really reveal fruitful, in terms of general understanding of the suggested change to the language. If we care about the mental model that we want to project in users' minds, it's important to use a vocabulary that matches. dynamic would have been perfect, but ObjC came first :man_shrugging:.

Anyway, I don't want to derail the pitch. The fundamentals remain the same, it's just that we may need to think a little bit more about the choice of the keyword itself, and how it is understood.

Speaking as an actual teacher, since there’s a hypothetical teacher making an appearance here:

I’d much rather teach conformance or override or even impl than dyn. The latter feels too much like it takes the language implementer’s viewpoint: “conforms to / implements a protocol requirement” or even “is polymorphic” are all design intents; “dynamic dispatch” is an implementation detail — one which the compiler could potentially even optimize away in some situations without violating the intent!

Perhaps it’s just my “start from concepts down, not from the metal up” teaching approach, but “dynamic dispatch” is a term we introduce to students much, much later than “abstraction,” “implementation,” “interface,” and even “polymorphism.” It’s not something I’d enjoy explaining to Swift language newcomers with my teaching hat on. ¯\_(ツ)_/¯

Regardless, it seems to me the first question is just whether this feature should exist at all before we haggle too much over its name.

11 Likes

Thanks for telling your experience as an actual teacher :+1:

Regardless, it seems to me the first question is just whether this feature should exist at all before we haggle too much over its name.

I don't know how we'll get an answer to this question :sweat_smile:

1 Like

Indeed, and if it should - then in what form. I started to like this form:

or an alternative similar constructs:

conforming extension Foo: Name {
    func foo() {...} // note: no prefix
}
extension Foo: protocol Name {
    func foo() {...} // note: no prefix
}

on the following grounds:

  • it is shorter as there's no "conformance" next to every function.
  • it obviously groups protocol conformances into a block where no extra things could be (which I believe is more win than a loss).
  • that no extra thing could be there is easier to grasp, understand and teach, as obviously you can't add some random function into a block whose name specifically says it is about "protocol conformance", whilst in the "list of conformance funcs" form it is not so obvious whether you can or cannot add other functions into the mix, which makes the corresponding language ruling about it somewhat arbitrary.
4 Likes

There’s a lot to be said for this structure. The obvious downside is that it would force closely related helper functions into a separate code block, where they might be distant from their context and thus harder to understand.

1 Like

Thanks @tera. Your question has already been asked above, and the last answer still applies:

How are you going to handle conforming API's entities to my own protocols? E.g. I have a protocol ApplicationBadgeNumberAccessor with property applicationIconBadgeNumber and conform UIApplication to it, so won't it break the logic?

Hello @edu.art,

I suppose your code looks like:

protocol ApplicationBadgeNumberAccessor {
    var applicationIconBadgeNumber: Int { get set }
}

extension UIApplication: ApplicationBadgeNumberAccessor { }

You have no reason no use conformance here, because you do not provide the implementation of UIApplication.applicationIconBadgeNumber: this property is ready-made. The conformance keyword is never required. You would not have to change anything in your code. The behavior of your program would not be modified.

I know @_implements is already mentioned in the future direction, but I wonder if this would be the opportunity to unify things right now:

protocol Program {
    func run()
}

struct HelloWorld: Program {
    // OK
    @implements(Program)
    func run() { ... }
    
    // error
    @implements(Program)
    func crash() { ... }
}

and with extension:

protocol Program {
    func run()
}

struct HelloWorld { }

@implements(Program)
extension HelloWorld: Program {
    // OK
    func run() { ... }
    
    // error
    func crash() { ... }
}
1 Like

Yes, that's exactly the intention. The pitch does not use the same syntax, but it has exactly the same effect (see the Precision of conformance intents section of the pitch):

struct HelloWorld: Program {
    // OK: matches Program.run() - and only Program.run()
    conformance // instead of @implements(Program)
    func run() { ... }
    
    // error: function crash() does not fulfill any requirement of the protocol 'Program'.
    conformance // instead of @implements(Program)
    func crash() { ... }
}

The pitch introduces conformance(Program) as a future direction, in order to solve a few known limits of the pitch (which are really not frequently met).

The pitch also introduces conformance(Program.run) as a future direction, a public version of @_implements.

Now, this:

This idea comes frequently, but it remains an idea that has problems. It is mentioned in the Alternatives Considered section of the pitch, since people keep on suggesting it.

2 Likes

Good, I personally have a stronger preference for @implements(Protocol) over conformance.

2 Likes

Yes, the naming was not really discussed yet. It is surprising, considering the great love of this forum for bike-shedding :wink:

Also, no one was able to justify why an attribute (@xxx) would be better than a keyword. I am personally preferring a keyword due to the similarity of conformance with override, and the fact that attributes are syntactically heavy. This pitch introduces a token that should be very frequently used. The pitch even recommends that source code editors automatically embed the keyword during autocompletion of protocol methods. To me, this justifies something that is visually light and does not draw attention like @attributes do.

Anyway, this is the pitch phase: everybody is encouraged to question the pitched design, and suggest alternatives.

1 Like

As far as the name goes, "implements protocol" definitely has a nicer sound to my ear than "conformance protocol". I prefer the attribute because it is easier to visually distinguish it from the rest of the code. implements(Protocol) standing on its own reads a bit weird, it looks like a function call.

1 Like

implements(MyProtocol) reads well, you're right.

Yet, one value of the pitch is the ability to provide the protocol from the enclosing scope (type definition or extension), and this spares the repetition of the name of the protocol:

extension MyType: MyProtocol {
    // Both must be MyProtocol requirements (and MyProtocol only):
    conformance func foo() { ... }
    conformance func bar() { ... }
    func other() { ... }
}

Would a naked implements read as well?

extension MyType: MyProtocol {
    implements func foo() { ... }
    implements func bar() { ... }
    func other() { ... }
}

This is a little confusing to me. First, all three functions are actually implementing something, even the third one. Next, the fact that the naked keyword is related to a protocol is not obvious.

Now there are many other words that were not considered yet: fulfill, requirement, ...

Right. In my view @implements cannot be used naked. The protocol being implemented has to be specified. The attribute can be used either on methods or on extensions.

1 Like

A coding style used by many Swift developers groups requirements in a dedicated extension. This pitch acknowledges this frequent practice, and uses it as a way to reduce the visual impact of the new feature.

I agree with that, though the easy fix is to use the attribute on the methods instead of the whole extension.