SE-0306: Actors

Can you provide more examples regarding why we need inheritance (instead of protocols) when using actors?

Can you provide more examples regarding why we need inheritance (instead of protocols) when using actors?

I don't think the bar for including or not including some functionality should be whether we "need" it or
not. The list of things we have but never "needed" is huge and ever-expanding. Probably the best example would be property wrappers, because it is just a convenient language feature that didn't enable anything entirely new (beyond convenient syntax), we didn't "need" them.

That being said, as it relates to actor inheritance, the things I think we should be considering are whether enabling it would be actively harmful or overly burdensome in some way, and whether those issues outweigh the convenience of inheritance.

1 Like

And to provide a concrete example of a scenario where I would personally choose the convenience of subclassing, instead of using protocols, Heterogenous collections of a generic base type:

With subclassing:

class Pet<T> {
    let someGenericProperty: T

    init(_ t: T) {
        self.someGenericProperty = t
    }
}

class Dog: Pet<String> {
    // Dog specific stuff goes here.
}

class Cat: Pet<String> {
    // Cat specific stuff goes here.
}

// Hooray, it was just this easy :)
let stringPets: [Pet<String>] = [Dog("Fido"), Cat("Stephen")]

Without subclassing:

protocol Pet {
    associatedtype T
    var someGenericProperty: T { get }
    init(_ t: T)
}

final class Dog: Pet {

    typealias T = String

    let someGenericProperty: T

    init(_ t: T) {
        someGenericProperty = t
    }

    // Dog specific stuff goes here.
}

final class Cat: Pet {

    typealias T = String

    let someGenericProperty: T

    init(_ t: T) {
        someGenericProperty = t
    }

    // Cat specific stuff goes here.
}


// Nope, error, "Protocol 'Pet' can only be used as a generic constraint because it has Self or associated type requirements"
let stringPets: [Pet] = [Dog("Fido"), Cat("Stephen")]

Yes, I can work around the error with some type erasure, but it's just too annoying, I prefer the convenience of subclassing here. So, extending this to actors, if I might ever want to have a heterogenous collection of a generic base actor, then I would want actors to have inheritance, simply for convenience's sake. Now, I have no idea if that is something I would ever want at this point, but it's something to think about.

2 Likes

I would argue that this is more an argument against the current state of POP type erasure ergonomics than it is an argument for inheritance.

In prior threads one of the authors (Doug???) put forth a few examples of where inheritance could be useful in an Actor. Those examples exist and I don’t think anyone is arguing that they don’t. I’m just not convinced that they carry their water. I think the downsides in terms of language complexity outweigh the benefits long term.

5 Likes

Yes it is, but let's say we do not allow actor inheritance. There will likely be a very long interim where I would have to wait for better POP type erasure ergonomics before I would even consider a design like that using actors, same goes for this sort of design with structs. Imo, that would be a shame considering that we can enable that design with actors "today" by just allowing actor inheritance.

I only mentioned this particular example because I feel the discussion about the pros and cons of actor inheritance to this point has been rather one-sided and entirely focused on trying to prove whether "we really need it".

Does enabling this design "today" outweigh the metadata overhead and app startup time issues of inheritance, I don't know, but I wanted to add at least one advantage of inheritance over POP to the discussion.

I'd recommend separating the discussion of inheritance into three possible levels. It is equally valid to choose any of these paths for actors:

  1. No support for inheritance.
  2. Support for inheritance but without designated/required initializers, class methods, etc.
  3. The full model that Swift supports for classes (as the proposal suggests).

The model Swift has for classes (#3) is extremely complicated due to compatibility issues with legacy Objective-C design patterns that we had to embrace in Cocoa (e.g. things like CoreData), which have nothing to do with actors. I don't see any defensible reason to bring this complexity to actors unmodified.

The few arguments I've heard in favor of inheritance for actors (including those above) are for #2. Such a simplified model for actor inheritance could be built and would not carry with it all the cons of #3. However, we don't have such a design on the table today, and I don't think we want to slow down the actor feature itself to design, build, and prototype it out.

The natural solution to this is to start with actors that do not support inheritance. If there is a compelling reason to add it in the future, then we can evaluate the best path to doing so (e.g. a #2 model) and add that extension in time. This would be based on practical experience with Actors, not proactively adding complexity in case it is useful.

In terms of "language evolution over time", it is much easier to expand a small feature than to take away power from a larger feature.

-Chris

32 Likes

So as I understand, in some cases you may want a collection of actors and each of them have different behaviors? There are multiple design patterns to achieve this. For instance, we can pass different parameters (closures) to a single actor initializer, and we can also inject the customization behavior into an actor's generics parameter (same object type constructed in different ways). I'm not sure if we will support actors type-erased to actor protocol existentials, but this may also be a direction.

That said, since Swift is designed to be a POP language, when we are considering adding polymorphism to our business logic, a lot of approaches can be considered before resorting to class inheritance. Of cause protocol abstraction is not a silver bullet, but class inheritance is also never a first option to Swift programmers.

I like Chris' suggestion that we can always pull in a limited inheritance for actors in the future, if we find that a compelling use case arises.

So I'd rather not hash this out further in this thread, if you want clarification on the example I provided DM me and I can elaborate.

3 Likes

The question of actors as classes, including inheritance, was discussed extensively at the end of last year. Raising the topic again when nothing seems to have changed seems unproductive. If this were a green field discussion for a language without existing async code, class hierarchies, and design patterns, it might be a worthwhile discussion. However, Swift will be 7 years old this year and has millions of lines of code in the wild with millions more it needs to interoperate with. Introducing another reference type with different capabilities seems counterproductive at this point

This isn't really true. People just read way, way too much into a single WWDC presentation. Swift is a multiparadigm language, with no paradigm meant to rule them all. It's highly unlikely that protocols will ever reach feature parity with classes in Swift.

8 Likes

That is what bugs me the most about the proposals - there was a lot of discussion and concerns, but the proposals pretends that the consensus was to support inheritance. So raising the topic is 100% valid.

As i said before the capabilities of classes and actors are very different. You can add the legacy inheritance model of classes to look more alike, but that does not make them more equal.

5 Likes

The claim that you need inheritance for programming is simply wrong. Look at Erlang if you need a counterexample. Very nice actor model, very nice language, no inheritance.

It is not about that I can simply ignore a feature I do not like. The complexity won't go away.

6 Likes

There are certainly cases where inheritance is the best tool for a problem. At the end of the day, it's a choice between complexity and value. Otherwise each feature that anyone can think of should be added since it could be the best tool in some situation.

1 Like

Mostly +1

Yes, concurrency is cumbersome today in Swift.

Mostly yes. I feel inheritance still needs discussion, and could easily moved to a different proposal while allowing actors itself to move forward.

I played a little with a preview of these feature in swift.

I have followed the concurrency threads (no pun intended) on and off for some time.

Very good point. With classes there simply was no other way to do it while supporting legacy features. It was not a decision based on best knowledge how to improve the language, it was a requirement.

Do we really want this feature set in a brand new, very important feature that will stay forever just because it is already implemented? If we need inheritance there is certainly a better inheritance model.

5 Likes

Except, there is a burden. There's the burden of the metadata which @Chris_Lattner3 mentioned that harms performance. There's also the burden of complexity. Some say developers already have to learn the complexity of classes so passing it down to actors would be no burden. I used to agree with that, but now I think differently. I'm still open for a counterpoint, though. My reasoning is, with actors being a proper reference type, with all of its new capabilities, do we really need classes? I might be wrong, but I can envision a fully functional application that is, most importantly, well designed and can function completely fine without classes. This leads me to disagree with the argument that passing the complexity of classes to actors is no burden. IMO, it is a burden. I also don't like the argument that classes are good because they're being used since forever. That's an appeal to tradition fallacy that doesn't fit with Swift's goals, as I understand it. As discussed, the main reason we have classes in Swift today is because of the burden of Objective-C compatibility, which is not a requirement for actors.

3 Likes

Consensus isn't necessary, convincing the Core Team is. The thread from last year went 111 posts and there wasn't much movement on either side. It seems unlikely that your bringing the issue up repeatedly in this thread will change anything.

Some actual design guidance should be offered by the proposal but I'd guess that after actors are merged, you aren't going to just make all classes you have actors. This is for many reasons, including the overhead of the actor runtime, the limitations actors place on users and API designers, and the fact that they won't backward deploy (unless they're making the concurrency feature portable?). Even if you did want to do this, without inheritance it would be impossible to use actors where we use classes most of the time anyway. You can't have it both ways.

No one's making that argument. What is being stated is that yes, actors need to be compatible with Obj-C, otherwise you can't interoperate at all with millions of lines of existing code. And since they need to inherit from Obj-C to do that, any limit on inheritance after that point just seems arbitrary. But it's not just Obj-C, there may be interop with other languages that also needs inheritance, and involving actors would be both natural and expected.

I'll say it again: the simple fact of the matter is that we have 7 years of Swift and 30 years of Objective-C to work with. Not having inheritance would make it far harder than necessary for existing codebases and existing developers to adopt actors. And all because of the "burden" of slightly less optimal internals? Come on. Oh, and there's the small matter that there's nothing else in Swift that replicates all of the capabilities of inheritance at the moment, so not having it would force actor users into an awkward design space with no clear way out.

Of course all of these points were discussed at length in the thread I linked to and there doesn't seem to be any new points being made, so I still don't see the benefit to rehashing the discussion yet again.

2 Likes

I'm pretty sure the second option here wasn't mentioned as a possibility in that other thread:

This second option seems appealing to me, and seems like a good reason to defer the question of actor inheritance to another proposal.

2 Likes

That was a debate about a pitch - this is the review.

I do not understand your claim - why should it be not possible to interoperate with old code? You would not be able to inherit from classes even if actors support class based inheritance.

Again, I can not see how it makes it harder to interact with Obj-C without actor inheritance. Can you give an example?

2 Likes

Maybe there is a forth option:

  1. An opt in attribute for the full inheritance model (like @objc).

Most of the swifters against actor inheritance even class saying that there's complexity/burden of it. Yes, it has; but how much? 1% more or less .. ? Is it really that harmful to runtime? Actually there're various burdens heavier than inheritance already exist there should be tackled/reduced first. Inheritance is NOT the culprit IMHO.

Every feature has its own complexity/burden underneath, either in compile time or runtime. ResultBuilder has its own burden, PropertyWrapper has its own complexity also, and affect performance in some cases; but both of them bring much more convenience, coding efficiency and expressivity for developers, the value we get far outweigh the complexity/burden we bear. The language design need balancing between different factors. It's NOT necessary to make Swift an anti-Class language.

Actor as its original name actor class implies, should preserve class attributes and capabilities; and tons of old objc framework/libraries need to adopt the new concurrency model as time goes, actor with class-like inheritance can help a lot to naturally bridge across different worlds.

1 Like