Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift protocol and return types on global functions

Tags:

swift

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?

like image 364
aeubanks Avatar asked Sep 05 '14 23:09

aeubanks


People also ask

What is swift protocols?

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.

Can you return a function from another function in Swift?

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.

What is an in-out parameter in Swift?

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.

How do you assign a function to a variable in Swift?

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.


1 Answers

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.

like image 169
Rob Napier Avatar answered Oct 23 '22 08:10

Rob Napier