Default Implementation in Protocols

To me protocols are like Java interfaces: API contracts that allow implementation not to be assumed when you think about the behaviour you are confirming to. I am. It sure what changed in programming best practices to see increased code coupling as a good alternative.

Sure, they added default implementation in Java too, but dispatching rules are a lot less confusing and in Swift, beside needing them for value types, I already see protocols too close to structs and classes and going one step beyond what IMHO they are meant to represent.

Protocols with default implementation, ability to add properties/storage, etc... and we are inching towards C++ multiple inheritance land quickly.

Since Swift already allows default implementations, this would just be providing the most intuitive way to define them. I see viewing the pure contract of a protocol as the job of an IDE.

Swift allows it, but does not prescribe not
to ever think of improving.

Default implementations, while they are needed for value types and static dispatching can be quite a nice boost to performance, also increased the chance of making mistakes more easily (“I though I was overriding the method though...”) and the worse “sin”, so to speak, of changing execution path based not on the instance of the class that is asked to perform a task, but essentially on how you ask it/which cap you wear when you ask it to perform a task.

2 Likes

Well yeah, Swift protocols are much closer to Scala traits than they are Java interfaces. They’re a step shy from being true mixins.

3 Likes

I found it useful to follow this to it's likely end.

If you add default function implementation, I think it follows that also adding them to properties, static functions, static properties, and initializers will be either innevitable, or constantly called for by users. This will enable protocols like this:

protocol Animal {

    static var entityName: String = "Animal"
    var name: String = "Unknown"

    init(name: String) {
        self.name = name
    }
    
    func makeNoise() {
        print("bark")
    }
    
    static func doAThing() {
        print("I did a thing!")
    }
}

Looks an awful lot like a concrete Type...

Further, with current semantics, you cannot call Animal.doAThing() or Animal.entityName. If they have an implementation within the protocol, they look even more like they should be accessible.

It's right there! why can't I access it?

It's something some users already want and can be confused by. In that environment the case against it will be esoteric and unsatisfying, so I think it's likely protocols will have a static-accessible interface when a default implementation is available.

So in this world, the practical differences between a protocol and a class will more/less narrow to not being able to instantiate protocols directly, and structs/protocols not being able to adhere to classes.

In the environment I can even see the "right" way to do things being to write almost everything in protocols, and have classes/structs basically just be a definition of a group of protocols with almost no actual code, moving closer to a trait/mixin model.

While an interesting possibility (at to me), it's a very different norm from the way Swift is currently used and should be moved toward intentially. Not from wanting a convenient way to write default implementations.

I'm unsure of a better way to approach this specifically while leaving protocols alone. However given the desire for Abstract Classes and the Increasing usage of Type Erasure, I think the effort should go toward finding a solution that satisfies as many of the current pain points around protocols (including default implementations) as possible.

7 Likes

I think this is a good point. Sometimes, I feel that parts of Swift suffer from a NIH syndrome in terms of programming best practices. We have decades of OOP experience (of course, with various wildly varying opinions about how to do things, but at least there is literature, etc.) and we also have a lot of research and some (more limited) best practices in PFP like Haskell, so we have ideas about how to use e.g. interfaces and abstract classes and how to use typeclasses in Haskell, and the benefits and drawbacks, especially in large codebases, are well documented.

By contrast, protocol-oriented programming is, to my knowledge, a Swift-only thing, and given that the language is so young and really big code bases probably don't exist yet, I'm not sure it's warranted to push so strongly for a novel code organisation approach (particularly, if it's enforced) without a much stronger rationale behind it.

1 Like

No, I wouldn't say it's unique at all. It's what happens when you allow protocols/interfaces to define stored properties as well as define defaults/other things on them. From what I've seen people call protocol-oriented programming, it's basically mixin programming sans stored properties on the protocols themselves.

And, given that there are now protocols that act differently when conformed to when the type is declared versus in an extension somewhere else (e.g. synthesised conformances for Equatable and `Hashable), I don't think it's a stretch to think it's possible that stored properties in protocols might be allowed some day. The rule would be something like they would be automatically included if you conformed to the protocol at the declaration point of the type, but you would have to provide a matching computed property for a retroactive conformance.

I just hope @Alejandro isn't discouraged by the long debate...

This isn't a change that gives Swift a new shape, as it only adds a simpler way to express something that is possible for a long time.
It won't invalidate any code that you can write now, so all arguments against it seem moot to me:
It's pushing a personal preference not by making that style more powerful, but by rendering a valid alternative more ugly than it has to be.

On the other hand, the evident benefits of this pitch haven't been discussed much yet:
First of all, it can save us some keystrokes - but that's only the basis, because code that you don't write can never contain any errors.
Today, when you refactor a method signature with a default implementation, you always have to remember to do that exact same change in a second location. If you forget this, you are lucky if that is a simple error that's caught by the compiler, because in many situations, this can break a library severely without even causing a warning.

Especially in combination with the suggestion to allow final methods in protocol declarations as well, this is not only more concise than the code we have to write today, but also doesn't suffer from the confusion caused by methods defined in protocol extensions.

8 Likes

+1 for allowing default implementations in protocols (even if it isn't the main style I would use)
+1 for using final to signify non-customization points

Ideally, we could find a way to switch to using final to specify non-customization points in general for protocols. That is, I would love it if we could have customization points in extensions (at least within the same file/submodule). It would have to be a two step process: First requiring all methods in an extension which aren't defining the default for a customization point to be marked final. Then in a later version, allowing methods to not be marked final, and to have those be customization points.

Even without allowing new customization points, I think having to mark static-dispatch extension methods as final is a win for usability, since it will clarify the current behavior which is a bit subtle at the moment.

+1 to this +1. As I tried to argue up-thread, I think this is a preliminary to be resolved before resolving the actual topic of this thread.

+1 to this too. I think the problem is significant enough to warrant this "gentle source code breakage" strategy.

1 Like

I find @GetSwifty's comment to be quite persuasive, and one that I hadn't considered. Previously, I alternated between being mildly positive to neutral about this proposal. Now, I think it's quite right to wonder if in fact this proposal is giving Swift protocols (and, those being a major part of Swift, the language itself) a new shape altogether.

One needn't look to some undefined future end for this prediction: @Jon_Hull's suggestions today would radically change the way that protocols are written in the language.

Sure, it is true that merely allowing default implementations in protocols would not invalidate any current code: indeed, it would be a non-starter if it did. However, it also doesn't enable anything new that can't be expressed straightforwardly today; it is purely a syntactic expansion of the language. I cannot see why a major upheaval in the language at this stage ought to be initiated without major gains.

1 Like

This is going quite a bit too far. As you said yourself, this pitch only expands the syntactic options available. I would add in a very reasonable way. It won't always make sense to provide default implementations in a protocol body but it would be nice syntactic sugar in some cases. A linter rule to ban implementations in protocol declarations shouldn't be too tough to implement. In the past, we have often deferred this kind of syntactic preference to individual teams and linters. I think that seems appropriate here as well.

4 Likes

I am referring specifically to @GetSwifty's excellent analysis of what this proposal would set in motion. I refer you to the endpoint that he envisions, quite persuasively:

I don't find that logic compelling. Design decisions should not be made based on relatively minor syntactic sugar such as this. They should be made based on semantic considerations which do not change at all with this pitch. Teams who are afraid this is a slippery slope they don't want to explore would be free to use a linter to enforce their chosen policy.

I tend to agree with @GetSwifty's comments, which also I touched on very early on this thread. In my mind this takes protocols one step too close to encouraging true trait/mixin style programming. Now I would love for Swift to maybe one day have a mixin/trait kind to handle this, but allowing implementations in protocol bodies would in my mind cheat protocols into thinking they're meant for this style of programming.

OK. I suppose we'll disagree here on whether @GetSwifty's logic is compelling.

This argument cuts both ways. What's the point of creating a feature where a key argument for its adoption is that a linter can then prohibit its use?

Yes, in the past, we have said that new language features shouldn't be added solely for the purpose of enforcing style, instead deferring to linters. The take-away point from that precedent, in my view, is that new language features shouldn't be added solely for the purpose of style, whether to restrict or augment, not that we should instead augment the language with new styles and then defer to linters.

That is not at all what I said. I said it would be an option for teams who don't wish to use the feature. I also said "it would be nice syntactic sugar in some cases". Personally, I would be likely to use it when the implementations are very short, especially when there is no other reason to extend the protocol. I think that would be a reasonable way to use it without obscuring the declarations.

My opinion in general is that this would be a nice convenience to have available but it is certainly not a top priority addition either. That is why I have been silent up until now. I jumped in because I think the FUD about the impact of this pitch on Swift style is a bit over the top.

OTOH, I do think there is a stronger case to be made against allowing final members in the protocol declaration itself rather than requiring them to be in an extension. Requiring default implementations to be provided in an extension requires duplicating the declaration which is a minor, but solvable, source of friction. This is especially true today when the declarations can get out of sync with no help from the compiler (this is a separate issue that should be solved independently someday but is still relevant to the discussion). final members do not suffer from this duplication so the potential benefit of allowing them in the protocol declaration is minimal.

1 Like

I'm not sure I see a world where we have something closer to mixins as the dystopian hellscape it is being talked about as here, but I also think we are a long way away from that, since the key feature needed for something like mixins is storage in protocols, which is a Swift 12 thing at best (as much as I would like it to be sooner). I don't see this as bringing us much closer by itself.

If there is a slippery slope, then we are already on it, since this proposal seems like a natural extension of the current language. That said, I can see people with different styles choosing to either utilize it or stick to the current style. It may not be everyone's cup of tea.

I don't know that it is that radical. In my mind, it is just making visible something which is both invisible and also a significant source of confusion at the moment. I can't take credit for the brilliance of reusing final, which really has the perfect semantics here, as that was @Nevin's idea.

Having to mark non-customization points as final really helps to make clear that they won't be dynamically dispatched. I'd be happy just if we implemented that on the status quo without enabling new customization points. I just think it will be an obvious extension at some point in the future. The current location-based pattern will seem silly once we have a keyword that can describe exactly what we want in a fined-grained way.

The final part should probably be it's own proposal. I also don't see it as having anything to do with mix-ins.

10 Likes