This is a followup to the question: Protocol func returning Self. The protocol is as follows:
protocol Copyable {
init(copy: Self)
func copy() -> Self
}
The following works fine but the copy()
function is exactly the same for every implementation, namely
func copy() -> Self {
return self.dynamicType(copy: self)
}
In accordance to this http://nshipster.com/swift-default-protocol-implementations/ I tried a global func
func copy<T : Copyable>(makeCopy: T) -> T {
return makeCopy.dynamicType(copy: makeCopy)
}
However, when it's called in a class implementing a the below protocol
protocol Mutatable : Copyable {
func mutated() -> Self
}
class C : Mutatable {
var a = 0
required init(_ a: Int) {
self.a = a
}
required init(copy: C) {
a = copy.a
}
func mutated() -> Self {
let mutated = copy(self)
mutated.a++
return mutated // error: 'C' is not convertible to 'Self'
}
}
I get the error as noted. When I type mutated
autocomplete shows mutated
as (C)
and I have no idea what that means. I've also tried adding required
to func mutated()
but apparently required
is only allowed for inits
. Any way to get this to work?
Swift - Protocols. It is just described as a methods or properties skeleton instead of implementation. Methods and properties implementation can further be done by defining classes, functions and enumerations. Conformance of a protocol is defined as the methods or properties satisfying the requirements of the protocol.
You can use this type like any other type in Swift, which makes it easy to pass functions as parameters to other functions, and to return functions from functions. Functions can also be written within other functions to encapsulate useful functionality within a nested function scope.
Parameters can provide default values to simplify function calls and can be passed as in-out parameters, which modify a passed variable once the function has completed its execution. Every function in Swift has a type, consisting of the function’s parameter types and return type.
As with any other type, you can leave it to Swift to infer the function type when you assign a function to a constant or variable: You can use a function type such as (Int, Int) -> Int as a parameter type for another function.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With