Over the weekend, Brent has a question about Swift protocols. This shows, hopefully, just a point-in-time problem with Swift's type system. Unfortunately, there isn't a great workaround for this problem.
The crux of the issue is this: protocols with a Self
requirement enforce your code to essentially be made up of homogenous types in order to do anything useful. This kinda stinks, and in Brent's case, is not what he wants.
If you ever do this:
protocol Value : Equatable() {}
Then boom, you're stuck. The Equatable
protocol has a Self
requirement, which trickles downstream to all of your protocols and types that apply a conformance to it.
Now, I don't know Brent's situation, but there is a way out of this in your situation allows for it: don't conform your protocol to Equatable
, instead, conform your types to it.
protocol Value {}
struct MyType : Value, Equatable {}
When you do this, you can now write the signature that Brent wanted.
protocol Smashable {
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value
}
The problem here, is if you want your types to be equatable to one another, you'll need to provide a heterogenous equality function:
func ==(lhs: Value, rhs: Value) -> Bool {
// lhs == rhs?
}
This means that your base Value
protocol needs to define all of the members for equality, which again, might be OK for your scenario.
A full sample looks like this:
protocol Value {
var identifier: String { get }
}
protocol Smashable {
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value
}
struct Foo : Value, Smashable, Equatable {
let identifier: String
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value {
return Bar(identifier: "smashed by Foo")
}
}
func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.identifier == rhs.identifier
}
struct Bar : Value, Smashable, Equatable {
let identifier: String
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value {
return Foo(identifier: "smashed by Bar")
}
}
func ==(lhs: Bar, rhs: Bar) -> Bool {
return lhs.identifier == rhs.identifier
}
func ==(lhs: Value, rhs: Value) -> Bool {
return lhs.identifier == rhs.identifier
}
let f = Foo(identifier: "foo")
let b = Bar(identifier: "bar")
let fsmash = f.valueBySmashing‚ÄãOtherValue‚Äã(b)
let bsmash = b.valueBySmashing‚ÄãOtherValue‚Äã(f)
if f == b { print("f == b") }
else { print("f != b") }
if fsmash == bsmash { print("fsmash == bsmash") }
else { print("fsmash != bsmash") }
UPDATE June 22, 2015
I probably should have mentioned the isEqualTo
guidance from The Protocols talk from WWDC (also in the Crustacean sample code. We can clean up the sample code a bit to not require all of our conforming types to implement an ==
operator nor an isEqualTo
function:
protocol Value {
var identifier: String { get }
func isEqualTo(other: Value) -> Bool
}
extension Value {
func isEqualTo(other: Value) -> Bool {
return self.identifier == other.identifier
}
}
protocol Smashable {
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value
}
struct Foo : Value, Smashable, Equatable {
let identifier: String
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value {
return Bar(identifier: "smashed by Foo")
}
}
struct Bar : Value, Smashable, Equatable {
let identifier: String
func valueBySmashing‚ÄãOtherValue‚Äã(value: Value) -> Value {
return Foo(identifier: "smashed by Bar")
}
}
func == <T : Value>(lhs: T, rhs: T) -> Bool {
return lhs.isEqualTo(rhs)
}
func ==(lhs: Value, rhs: Value) -> Bool {
return lhs.isEqualTo(rhs)
}
let f = Foo(identifier: "foo")
let b = Bar(identifier: "bar")
let fsmash = f.valueBySmashing‚ÄãOtherValue‚Äã(b)
let bsmash = b.valueBySmashing‚ÄãOtherValue‚Äã(f)
if f == b { print("f == b") }
else { print("f != b") }
if fsmash == bsmash { print("fsmash == bsmash") }
else { print("fsmash != bsmash") }