type may not reference itself as a requirement (Swift Xcode 7 beta 5)

I've always been able to say this:


protocol Flier {
    typealias Other : Flier
    func flockTogetherWith(f:Other)
}
struct Bird : Flier {
    func flockTogetherWith(f:Insect) {}
}
struct Insect : Flier {
    func flockTogetherWith(f:Insect) {}
}


Now suddenly in Xcode 7 beta 5, it's illegal: "type may not reference itself as a requirement". Why not? I see how to work around it, but why do I have to? What I'm trying to do is require that only Fliers can flock together; that's a wrong thing to do?

Accepted Reply

Having this kind of constraint isn't inherently unreasonable, but in some cases it would lead to compiler crashes. We decided to lock down on this for now. Your workaround of using a second protocol is a reasonable one.

Replies

What's the significance of "Other"? Why not:


protocol Flier {
    func flockTogetherWith(f:Flier)
}

Maybe he wants to restrict the set of Fliers each Flier can flock with? Bird can flock with Insect, but not with Bird. I don't know why you'd want it that way around. but whatever.

Hi, Quincey! — That would require adopters of Flier to declare the parameter of `flockTogetherWith:` as a Flier. That isn't what I want to say. I want to require adopters of Flier to declare the parameter of `flockTogetherWith:` as an adopter of Flier (thus also resolving the generic).


The joke is that this new restriction is so easy to work around. Simply declare another protocol for no other purpose than to have Flier adopt it, and use that as the type constraint on Other. To me, that shows how meaningless this new restriction is: it accomplishes nothing that I can see. What I'm hoping is that someone from Apple will weigh in and prove otherwise (or else encourage me to file this as a bug). Just waiting to see which way the cat jumps...

By the way, you can see the original context here, in my book.

I notice also that the compiler is fine with


protocol Flier { 
    typealias Other = Flier 
    func flockTogetherWith(f: Other) 
}


as opposed to


typealias Other: Flier


I understand that these mean different things (and that there has been some debate about exactly what "typealias =" means in a protocol). Even so, it seems a bit weird that the compiler would allow you to specify a default typealias value that is in fact impermissible. (Flier has associated-type requirements, so it can never be used as a stand-alone type in this way.) Probably a different issue than the OP is reporting, though.

it seems a bit weird that the compiler would allow you to specify a default typealias value that is in fact impermissible


That's not a typealias value - it's just a typealias. In other words, what you're doing is pure name substitution: Other simply means Flier. What I'm doing is defining a generic.

Having this kind of constraint isn't inherently unreasonable, but in some cases it would lead to compiler crashes. We decided to lock down on this for now. Your workaround of using a second protocol is a reasonable one.

Thanks, @SevenTenEleven. Is it worth my filing a bug report? I guess this would be a request to revisit this in the future if possible.

Maybe I'm missing something, but if the adopters of Flier declare the function with something other than Flier, then your protocol function is not really usable. That is, this should produce an error:


     let flier: Flier = …
     let other: Flier = …
     flier.flockTogetherWith (other)


because there's no guarantee that 'other' is an acceptable type to 'flier'.


So, what your protocol actually achieved was "simply" a requirement that Fliers have a method of that name. That might make you feel good, but it's actually unnecessary — since you can only call the method on a variable of a specific conforming type, if it didn't implement the method you'd get a compilation error anyway, without the protocol.


In those circumstances (that is, if I've got this right, in the circumstance that the protocol merely documents a method name), it could be argued that the two-protocol approach is superior: there are Flockers (which can be flocked with) and Fliers (which flock with Flockers and happen to be Flockers too). Or something like that.

Well, you're defining an associated type along with a constraint on that type, not a generic, but I get your meaning.


I believe you're mistaken regarding "typealias =" in a protocol, though. The "=" form includes an associated-type requirement into the protocol and also gives it a default (but overridable) value. So it's more than just a transient name substitution. And take a look at this variant:


protocol Flier {
    typealias Other = NSArray
    func flockTogetherWith(f: Flier) // Error: Protocol 'Flier' can only be used as a generic constraint because...
}


Here, the form is identical to "typealias Other = Flier", but now the compiler recognizes the impossibility. So clearly there's something more going on than just substituting Flier for Other.

The example is a test of how generic syntax works (and of the compiler). It isn't about what the useful content of

flockTogetherWith:
might be; it's about what satisfies the generic. Given this declaration (before beta 4):


protocol Flier {  
    typealias Other : Flier  
    func flockTogetherWith(f:Other)  
}  

...these are legal adopters, because the declared type of the parameter is a Flier adopter:

struct Bird : Flier {  
    func flockTogetherWith(f:Insect) {}  
}  
struct Insect : Flier {  
    func flockTogetherWith(f:Insect) {}  
}  

This would not be a legal adopter, and the compiler would slam the door:

struct Airplane : Flier {  
    func flockTogetherWith(f:String) {}  
}  

I think we have one already, but filing a duplicate might help you know if/when it gets implemented.

I guess I'm asking what you're trying to express. If the answer is "to ensure that compliant types implement a method of that name" then I'd say there's no reason to. If the answer is "to ensure that compliant types have common behavior", then I'd say you haven't done that.


Perhaps an analogy here would be something like Array.append. Some other, arbitrary class may also need a method called "append", which may indeed append something to something, but that's no reason why you'd need an Appender protocol to which both conform.

Can you let me know if this is currently being worked on? I can't upgrade my existing Swift 1.x code-base as a result which has been written to rely on this - a hard fought working approach since the broken reflection support in Swift 1.x forced.


This is a subset of what I'm trying to achieve, it fails with "Type may not reference itself as a requirement" in Swift 2.0 and I'm not able to use the second protocol hack described above to resolve it:


public protocol HasMetadata {

typealias T : HasMetadata //fails with: Type may not reference itself as a requirement

static func metadata() -> Metadata<T>

}

public class Metadata<T : HasMetadata> {

public var properties:[String:(T) -> Int]

init(properties:[String:(T) -> Int]){

self.properties = properties

}

}


public class Test

{

required public init(id:Int) {

self.id = id

}

public var id:Int = 0

}

extension Test : HasMetadata {

public static func metadata() -> Metadata<Test> {

return Metadata<Test>(properties: ["id": { $0.id }])

}

}


So it can be used like:


print(Test.metadata().properties["id"](Test(1)))


Does anyone know how this can be rewritten in Swift 2.0?

The same super-protocol trick does work:


public protocol SuperHasMetadata {
   
}
public protocol HasMetadata : SuperHasMetadata {
    typealias T : SuperHasMetadata
    static func metadata() -> Metadata<T>
}
public class Metadata<T : SuperHasMetadata> {
    public var properties:[String:(T) -> Int]
    init(properties:[String:(T) -> Int]){
        self.properties = properties
    }
}
public class Test
{
    required public init(id:Int) {
        self.id = id
    }
    public var id:Int = 0
}
extension Test : HasMetadata {
    public static func metadata() -> Metadata<Test> {
        return Metadata<Test>(properties: ["id": { $0.id }])
    }
}