SE-0261: Identifiable Protocol

I think it's not just an easy sell, it's a mandatory feature in the long term. It falls into a similar bucket to other key missing features such as the ability to disambiguate between different methods of the same name. I'd like to see us go through and mop these up some time soon.

7 Likes

That would be excellent and much better than using a KeyPath.

One question, how does @_implements work when the id attribute already exists but doesn't meet the Identifiable requirements?

ie:

struct Foo: Identifiable {
    // Not suitable for Identifiable
    var id: Bar
    // Would this be possible?
    @_implements(Identifiable, id)
    var uuid: String
}

Bearing in mind this isn't an official feature so the exact behavior of a real implementation would be TBD (and if you use the underscored version you should have no expectations to not be broken in future releases :), but this works:

protocol P {
    associatedtype A
    var foo: A { get }
}

struct S: P {
    var foo: String { "foo" }

    @_implements(P,foo)
    var bar: String { "bar" }
}

func f<T: P>(_ t: T) { print(t.foo) }

f(S()) // prints "bar"

edit: it appears there is a bug in associated type inference (shocking I know) that means if you define S.foo to return a different type, you need to explicitly typealias A = String to ensure it still works.

1 Like

In my post above, I mention that there are some cases where @_implements doesn't work if the conforming type already has a member with the same name as the requirement being satisfied (the expression evaluator treats them as identical and thus ambiguous). Is it reasonable that we could fix that as part of this feature, or would it have other bad implications?

Sounds like a great argument for standardising the name, so you always know where to reach for the ID of something that conforms to Identifiable, instead of tracking down a keypath and working out where it points.

I wouldn't want to be using an API where it seems I'm redundantly referring to customer.customerID and customer.customerName, etc. So I guess I would remap it using CodingKey.

1 Like

Doesn’t a concept already having a private full implementation improve its chances of acceptance?

The final feature needs to be properly designed β€” the current implementation is just whatever was needed to stabilize the standard library's ABI β€” but I'd consider that a fairly straightforward bug. In a non-generic context, the concrete implementation that doesn't fulfill the requirement should be called.

3 Likes

I really like this...but to be that person, would it need to remain an @ attribute?

I would argue that returning self from id is questionable design. Do you have a motivating example?

I don't have that much of a strong example, but I use a custom Unique<Key, Value> type to describe sections and items for the new NSDiffableDataSourceSnapshot type. Sections are almost always very simple so there is no need to provide any additional ID type, Key can just be Value, which makes it Unique<Section, Section> in my case.

  public enum Section: Hashable, Identifiable {
    case ...
    case ...
    
    public typealias Identity = Section
  }

Just as a follow-up here from the perspective of Combine. After a number of extensive adoption refactors we came to the conclusion that unfortunately we cant (at this moment) adopt Identifiable. It boils down to the fact that we need the existential form of Subscription and as the current generics implementation we cannot express a constrained existential via generics; i.e. if the type is Identifiable we constrain the identifier type to CombineIdentifier and then still be able to store as an existential. This functionality is needed since the items stored as a Subscription can come from multiple localities even in the same subscriber chain due to things like higher-ordered operators etc. That all being said, as soon as such a feature is possible (plus some other migration features for protocol parenting to allow us to adopt it) we are definitely going to strongly consider the adoption.

3 Likes

Thanks for the update and the effort you have put into exploring this @Philippe_Hausler.

Could you not use a type-erasing wrapper (some kind of AnySubscription) to overcome the existential problem?

There might be a performance cost (maybe. Not sure if existentials are that fast anyway), but at least the API would be sound.

Yea, that was a consideration in one of the refactoring efforts I did, however it did end up making some changes to the Combine core protocols in doing so. However it was decided that would be too risky of a change in this stage of development.

1 Like

@Ben_Cohen What is the status of this proposal? Has it been discussed by the core team?

@Philippe_Hausler Okay, so would it be correct to say it's more an issue of it not being practical to support in version 1.0 of Combine, rather than an intrinsic issue with the protocol's design?

Proposal Accepted

The proposal has been accepted. Thanks for everyone for participating.

3 Likes

Yup! Sorry for the delay, this was discussed and accepted recently. I'd suggest continuing any discussion around Combine in particular on a new thread in the discussion topic.

2 Likes

Can someone explain to me the need for

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)

I do not understand the need for iOS 13+ or any other plateform requirement for that matter

Hi Denis,

Now that Swift is part of the OS on those platforms, most new additions to the standard library will require availability of the OS in which they first shipped. New protocols or types needs to exist in the OS for them to be shared between different binaries (e.g. between the app and the frameworks that app uses). They simply won’t exist on older OSs.

1 Like