This is a followup to the question: Protocol func returning Self. The protocol is as follows:
protocol Copyable {
init(copy: Self)
func copy() ->
This question has the same form as the copy one, and the same solution. Make mutation an initializer rather than a method.
protocol Copyable {
init(copy: Self)
}
protocol Mutatable : Copyable {
init(byMutating: Self)
}
class C : Mutatable {
var a = 0
required init(_ a: Int) {
self.a = a
}
required init(copy: C) {
a = copy.a
}
required convenience init(byMutating: C) {
self.init(copy: byMutating)
self.a++
}
}
// These are purely for convenience
func copy<T : Copyable>(x: T) -> T {
return x.dynamicType(copy: x)
}
func mutated<T: Mutatable>(x: T) -> T {
return x.dynamicType(byMutating: x)
}
But to reiterate Mattt's point in the linked article, you can have a C(copy: x)
syntax fairly conveniently, and you can have a copy(x)
syntax pretty conveniently, and there is always x.dynamicType(copy: x)
. But you can't have a x.copy()
syntax without some annoying work. You either have to duplicate func copy() -> Self { return copy(self) }
in every class, or you have to create some concrete class that implements this method and C
ultimately inherits from. This is currently a basic limitation of Swift. I agree with Mattt's diagnosis of possible solutions, and suspect that some kind of trait system, possibly along the lines of Scala's, will probably be added in the future.
It's worth focusing on Mattt's comment that "all of this highlights a significant tension between methods and functions in Swift." This is another way of saying that there are tensions between the object-oriented paradigm and the functional paradigm, and moving between them can create some disconnects. Languages try to paper-over that issue with various features, but there are important differences between objects with messages and properties, vs functions with data and combinators, and "getting the best of both worlds" can sometimes create some rough edges.
It's easy to forget, when comparing Swift to other languages, that there is a big difference between v0.9 and v2.11. Many things we take for granted in our favorite languages did not exist in their v1 either.
To your comment, you may be thinking that mutated
is of type Self
. But it's of type C
, as your autocomplete indicates. As before, C
is not the same as Self
unless you can promise that there are no subclasses (C
being either final
or a struct). Swift types are resolved at compile-time, not runtime, unless you use dynamicType
.
To be a little more specific, Swift looks at this line:
let mutated = copy(self)
It notes that copy
is generic on the type of its parameter, and it must construct a version of copy
at compile-time to call. There is no type Self
. It's just a placeholder, and must be resolved at compile-time. The type of self
in this lexical scope is C
. So it constructs copy<C>
. But if you subclassed C
, this could be the wrong function (and in this case, would be). This is very closely related to: https://stackoverflow.com/a/25549841/97337.
The fact that type autocomplete says (C)
rather than C
is a minor side-effect of how Swift functions and tuples work, and comes up pretty regularly, but I've yet to encounter a case where it really mattered. A Swift function like func f(x: Int, y:Int)
does not actually have two parameters. It has one 2-tuple parameter of type (Int, Int)
. This fact is important to how the currying syntax works (see the Swift Programming Language for more on currying in Swift). So when you specialize copy
, you specialized it with a 1-tuple of type (C)
. (Or possibly, the compiler is just trying to do that as one of various attempts, and that's just the one it reports on.) In Swift any value can be trivially exchanged for a 1-tuple of the same type. So the return of copy
is actually the 1-tuple of C
, written (C)
. I suspect that the Swift compiler will improve its messages over time to remove the extraneous parentheses, but that's why they show up sometimes.