2

I keep seeing different syntax for how to extend an array. Here are the two that I'm seeing. Can someone explain what the difference is?

extension Array where Element == StringProtocol {
    func foo(){}
}

extension Array where Element:StringProtocol {
    func foo(){}
}

So what's the difference?

Bonus:

I'm trying to write an extension that works with [String] and [Substring] and it was suggested that I base it on StringProtocol, hence the above. However, if I try doing something like the following...

func foo(separator:Character)

    for line in self{

        let components = line.split(separator: separator, maxSplits: 1, omittingEmptySubsequences: false)

        let prefix = components[0].replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression) // Only trim the end of the prefix
        let suffix = components.count > 1
            ? components[1].trimmingCharacters(in: .whitespaces) // Perform full trim on the suffix
            : nil
        ...
    }

}

I get this...

Member 'split' cannot be used on value of protocol type 'StringProtocol'; use a generic constraint instead

So how do you say Element is a T where T conforms to StringProtocol?

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • Can you provide a concrete example of what you want to achieve? An example of a `foo()` method that fails to compile as an extension method? – Martin R Mar 10 '18 at 07:11
  • Done. See my edit. – Mark A. Donohoe Mar 10 '18 at 07:13
  • And also, can you clarify the difference in the first part of the question? – Mark A. Donohoe Mar 10 '18 at 07:15
  • It should be `where Element: StringProtocol` , and then your code compiles in my Xcode 9.2. – Martin R Mar 10 '18 at 07:16
  • Then I get a different error. Added more code to show that one. Starting to think the solution is just to convert it to a `[String]` as the first part of the code. That way it doesn't matter how I enter, but I'll always be working with an array of Strings internally. – Mark A. Donohoe Mar 10 '18 at 07:18
  • 1
    `components` is an array of `Substring` and that has no `replacingOccurrences` method. So you have to convert it to a `String` array : `let components = line.split(...).map(String.init)` as in https://stackoverflow.com/a/49206208/1187415 – Martin R Mar 10 '18 at 07:21

1 Answers1

5

Short answer: In this particular use-case,

extension Array where Element: StringProtocol {}

is what you want. It defines an extension for arrays of T where T conforms to StringProtocol. On the other hand,

extension Array where Element == StringProtocol {}

defines an extension for arrays of type [StringProtocol] which are – as far as I can tell – impossible to create because that protocol has associated type requirements.


Long answer: Let's do this for a simple protocol which has no associated types:

protocol MyProtocol {
    var id: Int { get }
}

extension Array where Element: MyProtocol {
    func foo() {
        for elem in self { print(elem.id) }
    }
}

extension Array where Element == MyProtocol {
    func bar() {
        for elem in self { print(elem.id) }
    }
}

and two types conforming to the protocol

struct A: MyProtocol {
    let id: Int
}
struct B: MyProtocol {
    let id: Int
}

Then I can call foo() on arrays of type [A] and of type [B] because both A and B conform to MyProtocol:

let aArray = [A(id: 1), A(id: 2), A(id: 3)]
let bArray = [B(id: 4), B(id: 5), B(id: 6)]

aArray.foo()
bArray.foo()

On the other hand, bar() can be called on an array of type [MyProtocol], which is an array of “boxes” where each box can hold any value of a type conforming to that protocol:

let pArray: [MyProtocol] = [A(id: 10), B(id: 11)]
pArray.bar()

(See Type conversion when using protocol in Swift for more information about that.) But this does not compile

aArray.bar() // '[A]' is not convertible to 'Array<MyProtocol>'

unless the array is converted explicitly to an array of type [MyProtocol]:

(aArray as [MyProtocol]).bar()

which creates a new array and is a O(N) operation, as explained in above-mentioned answer. And vice versa,

pArray.foo() // Using 'MyProtocol' as a concrete type conforming to protocol 'MyProtocol' is not supported

does not compile because a protocol does not conform to itself.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382