two protocols with the same method name

Brent ask a really good question: 'so we have to decide if "two
unrelated protocols declare the same requirement" is a strong enough
signal on its own to conclude that something's wrong'.
I believe it is strong enough.

Also, as I've thought about it more, I've realized that if we allow protocol extensions to add conformances, we can cleanly resolve the cases where two protocols *don't* conflict. For instance, if PrivatePhotos and PublicPhotos are supposed to be compatible:

  protocol PhotosType {
    func photos() -> String
  }
  extension PrivatePhotos: PhotosType {}
  extension PublicPhotos: PhotosType {}
  
  // Now that the relationship between PrivatePhotos and PublicPhotos has been retroactively modeled,
  // we can conform one type to both protocols.

···

--
Brent Royal-Gordon
Architechies

I Like this C#'s "Explicit Interface Implementation".
The cast to call is understandable, it's good, but just think about. If we
use "self.Marriageable.ring" or "variable.Marriageable.ring" will make more
symmetrical with the implementation? "dot protocol" can be used to all
"explicit call", ambiguous or not.

···

Em dom, 10 de jan de 2016 às 21:49, Andrew Bennett via swift-evolution < swift-evolution@swift.org> escreveu:

Sorry if this is already mentioned, but I quite like C#'s "Explicit
Interface Implementation" approach:

Explicit Interface Implementation - C# Programming Guide - C# | Microsoft Learn

Basically:

var Marriageable.ring: String? { ... }
var CallReceivable.ring: String? { ... }

to call it you could do self.ring if it was unambiguous, otherwise:

(self as Marriageable).ring
(self as CallReceivable).ring

On Mon, Jan 11, 2016 at 9:51 AM, Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:

> By giving warning simply for same name, it will be quite annoying when
the project run into this situation without any wrong. For example:
>
> protocol ForwardIndexType : _Incrementable {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> }
>
> extension ForwardIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> @warn_unused_result
> public func distanceTo(end: Self) -> Self.Distance
> }
>
> protocol BidirectionalIndexType : ForwardIndexType
> extension BidirectionalIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> }

Firstly, for methods and subscriptors the "name" would actually encompass
the entire signature, so `advancedBy(_:)` and `advancedBy(_:limit:)` would
not conflict because they have different signatures.

Secondly, `ForwardIndexType` and `BidirectionalIndexType` are *not*
unrelated protocols—one of them conforms to the other. Thus, we can assume
that `BidirectionalIndexType` knows about `ForwardIndexType`'s `advancedBy`
methods and intends for its versions to have compatible semantics.

If instead `BidirectionalIndexType` did *not* conform to
`ForwardIndexType`, and `RandomAccessIndexType` tried to conform to both
`ForwardIndexType` and `BidirectionalIndexType`, *then* we would get an
error, because two independent protocols would have declared `advancedBy(_:
Self.Distance) -> Self` methods and it's possible they meant for them to
have different semantics.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I don't think self.Marriageable.ring is a good idea. As self.Type
conforms Marriageable protocol, and a protocol is not a property of a
class/struct/enum.

Let's go back with the definition of the Protocol.

A protocol defines a blueprint of methods, properties, and other

requirements that suit a particular task or piece of functionality. The
protocol can then be adopted by a class, structure, or enumeration to
provide an actual implementation of those requirements. Any type that
satisfies the requirements of a protocol is said to conform to that
protocol.

In addition to specifying requirements that conforming types must
implement, you can extend a protocol to implement some of these
requirements or to implement additional functionality that conforming types
can take advantage of.

So if there are some independent protocols accidentally sharing the same
vars or functions, this does not violate the definition of the protocol.
Should the compiler warns the programmer? Maybe, as we can not sure there
must be conflicts. So I think the alert level is between warning and
silence.

Let's also see the solutions. The solutions in other languages inspire us
what to do. One is to change the properties names, the other is to cast the
type to the protocol.

I think the prior method is much easier to go with. As currently, you
can't name a store properties in extension, and you can't have two
properties with the same name in extension. However, there is also a
problem after you rename the property. As it is no longer the name that
defined in the protocol, you can use it with (self as Protocol).name. So
maybe we can use lable to solve the problem.

Basically:
           type A: Marriageable, CallReceivable {

     var Marriageable.ring ring: String? { ... }
     var CallReceivable.ring ringtone: String? { ... }
}

to call it :

(self as Marriageable).ring or self.ring
(self as CallReceivable).ring or self.ringtone

zhaoxin

···

On Mon, Jan 11, 2016 at 8:17 AM, Wallacy via swift-evolution < swift-evolution@swift.org> wrote:

I Like this C#'s "Explicit Interface Implementation".
The cast to call is understandable, it's good, but just think about. If we
use "self.Marriageable.ring" or "variable.Marriageable.ring" will make more
symmetrical with the implementation? "dot protocol" can be used to all
"explicit call", ambiguous or not.

Em dom, 10 de jan de 2016 às 21:49, Andrew Bennett via swift-evolution < > swift-evolution@swift.org> escreveu:

Sorry if this is already mentioned, but I quite like C#'s "Explicit
Interface Implementation" approach:

Explicit Interface Implementation - C# Programming Guide - C# | Microsoft Learn

Basically:

var Marriageable.ring: String? { ... }
var CallReceivable.ring: String? { ... }

to call it you could do self.ring if it was unambiguous, otherwise:

(self as Marriageable).ring
(self as CallReceivable).ring

On Mon, Jan 11, 2016 at 9:51 AM, Brent Royal-Gordon via swift-evolution < >> swift-evolution@swift.org> wrote:

> By giving warning simply for same name, it will be quite annoying when
the project run into this situation without any wrong. For example:
>
> protocol ForwardIndexType : _Incrementable {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> }
>
> extension ForwardIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> @warn_unused_result
> public func distanceTo(end: Self) -> Self.Distance
> }
>
> protocol BidirectionalIndexType : ForwardIndexType
> extension BidirectionalIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> }

Firstly, for methods and subscriptors the "name" would actually
encompass the entire signature, so `advancedBy(_:)` and
`advancedBy(_:limit:)` would not conflict because they have different
signatures.

Secondly, `ForwardIndexType` and `BidirectionalIndexType` are *not*
unrelated protocols—one of them conforms to the other. Thus, we can assume
that `BidirectionalIndexType` knows about `ForwardIndexType`'s `advancedBy`
methods and intends for its versions to have compatible semantics.

If instead `BidirectionalIndexType` did *not* conform to
`ForwardIndexType`, and `RandomAccessIndexType` tried to conform to both
`ForwardIndexType` and `BidirectionalIndexType`, *then* we would get an
error, because two independent protocols would have declared `advancedBy(_:
Self.Distance) -> Self` methods and it's possible they meant for them to
have different semantics.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--

Owen Zhao

I agree with zhaoxin, self.Marriageable.ring doesn't seem to provide the
same interface as the protocol. It is similar to how it would be done with
existing swift code, and doesn't risk sharing a name with static properties
and typealiases on the type.

Something we still need to handle though is associated and Self type
requirements.
* Should you be able to apply similar namespaces to an associated type?
* How do you cast to a protocol with an associated type?

For example:

protocol P1 {
    typealias Element

    func get() -> Element

}
protocol P2 {
    typealias Element
    func get() -> Element
}
struct X: P1, P2 {
    typealias P1.Element = Int
    typealias P2.Element = Float
    func get() -> P1.Element { fatalError() }
    func get() -> P2.Element { fatalError() }
}
struct Y: P1, P2 {
    typealias Element = Int
    func P1.get() -> Element { fatalError() }
    func P2.get() -> Element { fatalError() }
}

let e1 = (x as P1).get()
let e2 = (x as P2).get()

There will be errors on e1 and e2 like this:

*protocol 'P1' can only be used as a generic constraint because it has Self
or associated type requirements*

You may be able to get around it with a generic function, but the ugliness
of that solution should be considered in our choices for this proposal.

It would be nice (in general) to be able to do this:

let e3 = (x as P1<Int>).get()
let e4 = (x as P2<Float>).get()

However it's unclear what type e3 and e4 should be, there's currently no
support for automatic synthesis of type-erased structures, and there's no
meta-type I know of that can stand-in.

Perhaps associated and Self type requirements are out-of-scope of this
proposal, but I thought it was worth mentioning.

···

On Mon, Jan 11, 2016 at 4:26 PM, 肇鑫 <owenzx@gmail.com> wrote:

I don't think self.Marriageable.ring is a good idea. As self.Type
conforms Marriageable protocol, and a protocol is not a property of a
class/struct/enum.

Let's go back with the definition of the Protocol.

A protocol defines a blueprint of methods, properties, and other

requirements that suit a particular task or piece of functionality. The
protocol can then be adopted by a class, structure, or enumeration to
provide an actual implementation of those requirements. Any type that
satisfies the requirements of a protocol is said to conform to that
protocol.

In addition to specifying requirements that conforming types must
implement, you can extend a protocol to implement some of these
requirements or to implement additional functionality that conforming types
can take advantage of.

So if there are some independent protocols accidentally sharing the same
vars or functions, this does not violate the definition of the protocol.
Should the compiler warns the programmer? Maybe, as we can not sure there
must be conflicts. So I think the alert level is between warning and
silence.

Let's also see the solutions. The solutions in other languages inspire us
what to do. One is to change the properties names, the other is to cast the
type to the protocol.

I think the prior method is much easier to go with. As currently, you
can't name a store properties in extension, and you can't have two
properties with the same name in extension. However, there is also a
problem after you rename the property. As it is no longer the name that
defined in the protocol, you can use it with (self as Protocol).name. So
maybe we can use lable to solve the problem.

Basically:
           type A: Marriageable, CallReceivable {

     var Marriageable.ring ring: String? { ... }
     var CallReceivable.ring ringtone: String? { ... }
}

to call it :

(self as Marriageable).ring or self.ring
(self as CallReceivable).ring or self.ringtone

zhaoxin

On Mon, Jan 11, 2016 at 8:17 AM, Wallacy via swift-evolution < > swift-evolution@swift.org> wrote:

I Like this C#'s "Explicit Interface Implementation".
The cast to call is understandable, it's good, but just think about. If
we use "self.Marriageable.ring" or "variable.Marriageable.ring" will make
more symmetrical with the implementation? "dot protocol" can be used to all
"explicit call", ambiguous or not.

Em dom, 10 de jan de 2016 às 21:49, Andrew Bennett via swift-evolution < >> swift-evolution@swift.org> escreveu:

Sorry if this is already mentioned, but I quite like C#'s "Explicit
Interface Implementation" approach:

Explicit Interface Implementation - C# Programming Guide - C# | Microsoft Learn

Basically:

var Marriageable.ring: String? { ... }
var CallReceivable.ring: String? { ... }

to call it you could do self.ring if it was unambiguous, otherwise:

(self as Marriageable).ring
(self as CallReceivable).ring

On Mon, Jan 11, 2016 at 9:51 AM, Brent Royal-Gordon via swift-evolution >>> <swift-evolution@swift.org> wrote:

> By giving warning simply for same name, it will be quite annoying
when the project run into this situation without any wrong. For example:
>
> protocol ForwardIndexType : _Incrementable {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> }
>
> extension ForwardIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> @warn_unused_result
> public func distanceTo(end: Self) -> Self.Distance
> }
>
> protocol BidirectionalIndexType : ForwardIndexType
> extension BidirectionalIndexType {
> @warn_unused_result
> public func advancedBy(n: Self.Distance) -> Self
> @warn_unused_result
> public func advancedBy(n: Self.Distance, limit: Self) -> Self
> }

Firstly, for methods and subscriptors the "name" would actually
encompass the entire signature, so `advancedBy(_:)` and
`advancedBy(_:limit:)` would not conflict because they have different
signatures.

Secondly, `ForwardIndexType` and `BidirectionalIndexType` are *not*
unrelated protocols—one of them conforms to the other. Thus, we can assume
that `BidirectionalIndexType` knows about `ForwardIndexType`'s `advancedBy`
methods and intends for its versions to have compatible semantics.

If instead `BidirectionalIndexType` did *not* conform to
`ForwardIndexType`, and `RandomAccessIndexType` tried to conform to both
`ForwardIndexType` and `BidirectionalIndexType`, *then* we would get an
error, because two independent protocols would have declared `advancedBy(_:
Self.Distance) -> Self` methods and it's possible they meant for them to
have different semantics.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--

Owen Zhao

1 Like

Are you sure you can write implementation code in protocol declaration as your first solution displayed? And are you sure you can write stored properties in extension as your second solution displayed? yeah, you really handle problem in an easy way, not because the problem is such easy thing. :slight_smile:

Have you ever resolved this problem?

In my opinion, this is just like the classic problem "diamond inheritance". Since the protocol support multiple inheritance, the problem will arise sooner or later. :slight_smile:

What I like about internet conversations, is that I can carry on after 4 years as if nothing had happened :slight_smile:

I did solve the problem 'instantly' by refactoring/renaming the protocols.
However my point was that it was hard to find out on a spot what caused the issue.
As you identified correctly, it was the classic problem of Deadly Diamond of Death.

Uh-huh! It seems like, if the two protocols have properties with the same name, and the property really meaning different thing, we have to change one protocol property with another name. Awesome! But what if we cannot change the protocol, for example, the protocol definition comes from third party modules. I know the situation is rare, but I still remains. :joy:

Whatever we do, we need to source/binary compatibility to consider now.

Could we say that a protocol’s requirements can optionally be defined with the protocol’s name as a prefix if you don’t just want to use your property/func/etc with the same name?

protocol A {
    var foo: Int {get}
}

protocol B {
    var foo: Int {get}
}

struct C {
    var foo: Int
    var bar: Int
}

To conform C to A and B, you could write the same thing you’d write now:

extension C: A, B {}

or you could do something different if you and the author of A disagree on the semantics of foo

extension C: A, B {
    var A.foo: Int { bar }
    // no need to specify `B.foo`
}

You could also specify them directly in the type of you want the protocol’s properties to have different storage than your own properties of the same name:

struct D: A, B {
    var foo: Int // same as `A.foo`
    var B.foo: Int // different storage from `self.foo`
}

In generic code, you’d get the version associated with the protocol constraints:

func baz<T: A>(_ v: T) -> Int {
    v.foo // returns `v.A.foo`
}
func buz<T: B>(_ v: T) -> Int {
    v.foo // returns `v.B.foo`
}

It’d need some disambiguation in generic code in which the type is constrained to both protocols:

func faz<T: A&B>(_ v: T) -> Int {
    v.foo // does it return `v.A.foo‘ or `v.B.foo`?
}

I would say that if there isn’t a “custom” implementation for either protocol conformance, then the compiler knows that both v.A.foo‘ and v.B.foo` are the same thing and it doesn’t matter. As soon as one of the protocol conformances gets “customized”, the compiler can’t know which one you mean and you’d have to specify:

func faz<T: A&B>(_ v: T) -> Int {
    v.A.foo + v.B.foo // `v.foo` would be an error.
}

In generic code where the type is constrained to be a subclass of a class which contains a property with the same name as a protocol property and it has a “custom” conformance, the non-prefixed property refers to the original storage:

class E: A {
    var foo: Int
    var A.foo: Int { foo - 10 }
}

func fez<T: E>(_ v: T) -> (Int, Int) {
    (v.A.foo, v.foo)
}

let e = E(foo: 20)
fez(e) // returns (10, 20)

Dunno, it’s oh wow, 2:45am here. Apologies if I missed something obvious. I might prefer @Joe_Groff‘s @implements(A.foo) var foobaz: Int syntax... not sure... it’s longer, but it’s maybe clearer. Plus it seems like having it be an attribute might maybe align with some hypothetical future code generation or macro system where maybe you want to, um, log something every time you call a function that’s the same name as a protocol requirement but isn’t the same implementation? I’m not sure why you’d want to do that, but whatever, it’s 2:52am now and my think has stopped braining.

1 Like

I have good news! This syntax was implemented at some point in the last 5 years!

protocol A {
    var foo: Int {get}
}
protocol B {
    var foo: Int {get}
}
struct C: A, B {
    @_implements(A, foo)
    var fooForA: Int
    @_implements(B, foo)
    var fooForB: Int
}
let c = C(fooForA: 1, fooForB: 2)
let ca: A = c
let cb: B = c
// print(c.foo) // error: ambiguous use of 'fooForB'
print(ca.foo) // 1
print(cb.foo) // 2

Unfortunately it is underscored, which means it's not officially supported. There may be some unsupported edge cases, it can go away after we have something official, your warranty is void.

It is a good starting point for proposing something official

8 Likes

Oh, cool! BRB, gotta go try some stuff

Just a heads up—this is a several-years-old thread which was migrated from the former Swift mailing lists. The etiquette on this forum in such cases is generally to start a new thread (linking to the old one, if desired/necessary for context) to avoid notifying all former participants (who may or may not wish to be receiving emails still).

2 Likes