ireneu
(Ireneu Pla)
1
Hello!
I'm extending Array for a custom Element type to add a mutating function. However, I find myself unable to use the function, even on vars.
I get the following error:
Cannot use mutating member on immutable value: 'values' is immutable
Example code:
protocol Value {}
struct StringValue: Value {
let value: String
}
struct IntValue: Value {
let value: Int
}
extension Array where Element: Value {
mutating func chain(_ newValue: Element) {
if let last = self.last as? StringValue, let newValue = newValue as? StringValue {
self = Array(self.dropLast()) // small edit
self.append(StringValue(value: last.value + newValue.value))
} else {
self.append(newValue)
}
}
}
var values = [Value]()
values.chain(StringValue(value: "whatever")) // compiler error on this line
I feel like I'm missing something, but can't tell what. All feedback is welcome 
2 Likes
You can't use Value as a concrete type for values. In this case you could use Any, an enum wrapper or an erased type that conforms to Value.
What is it you're trying to accomplish exactly?
ireneu
(Ireneu Pla)
3
Hi Dennis, I'm not sure what you mean. I have encountered no issue until this point setting the type of values as [Value].
For example, the following code runs without problems:
values.append(StringValue(value: "example"))
values.append(IntValue(value: 5))
for value in values {
switch value {
case let value as StringValue:
print(value.value)
case let value as IntValue:
print(value.value)
default:
fatalError()
}
}
About what I'm trying to do.
I have an heterogenous array that contains items of different types, all inheriting from the same protocol. Some of these types can be combined if they are next to each other (similar to my initial example).
To make the code cleaner, I wanted to define the function in an Array extension, but it doesn't work as expected and I would love to find out why.
ireneu
(Ireneu Pla)
4
The code works when defining the extension this way
extension Array where Element == Value
instead of like this
extension Array where Element: Value
I can't tell why this is the case. Ideas wanted!
2 Likes
I see. The issue is not the mutation (at least it's not here on my end); it's the usage of Value as a concrete type when you use it as a protocol. To tackle heterogeneous Arrays in a type safe way using enums:
public enum Value {
case int (Int)
case string (String)
}
extension Array where Element == Value {
@inlinable public mutating func chain (_ newValue: Element) {
switch newValue {
case .string (let newString):
if let oldValue = self.last, case .string(let oldString) = oldValue {
self[self.endIndex - 1] = .string(oldString + newString)
}
case .int (let newInt):
self.append(.int(newInt))
}
}
}
Using it:
var values: [Value] = [.int(0), .string("str")]
values.chain(.string("ing"))
print(values)
// [Value.int(0), Value.string("string")]
Another way using concrete types:
public protocol Value {}
extension String: Value {}
extension Int: Value {}
extension Array where Element == Value {
@inlinable public mutating func chain (_ newValue: Element) {
if let oldValue = self.last as? String, let newValue = newValue as? String {
self[self.endIndex - 1] = oldValue + newValue
} else {
self.append(newValue)
}
}
}
Using it:
var values: [Value] = [0, "str"]
values.chain("ing")
print(values)
// [0, "string"]
The second solution is extendable. The first one isn't.
ireneu
(Ireneu Pla)
6
Thanks for your suggestions. It looks like we are not stumbling upon the same compiler messages 
I am able to run the initial code I submitted after replacing the Element: Value by Element == Value without any issues (I'm running Swift 4.1.2).
Both the enum solution and extending the built-in types you propose don't fit my needs, however. The actual types I'm using are much more complex, and trying to fit them in enums or in extensions to built-in types would be unwieldy.
Thankfully I can run my intended code, although I still can't figure out why it works when defining Element in one way, but not in the other. 
Nevin
7
When you write,
extension Array where Element: Value
that applies to arrays where all the elements are the same type, and that type conforms to the Value protocol.
On the other hand, when you write,
extension Array where Element == Value
that applies to arrays whose elements are protocol existentials, meaning each element is a sort of “box” that can hold an instance of any type which conforms to the Value protocol.
Now, the important thing to note is that protocols do not conform to themselves. In other words, the “box” type does not conform to the Value protocol.
An instance of type Value *cannot* be used in a place that expects an instance of a type that conforms to the Value protocol, because the existential type Value does not conform to the protocol Value.
This might change in a future version of Swift, but for now that is how things work.
6 Likes
ireneu
(Ireneu Pla)
8
Thanks for your answer, this really clarifies the matter!
Pity that the compiler error was so misleading.
1 Like
jrose
(Jordan Rose)
9
It's fixed in Swift 4.2!
<stdin>:23:8: error: using 'Value' as a concrete type conforming to protocol 'Value' is not supported
values.chain(StringValue(value: "whatever")) // compiler error on this line
^
5 Likes
mrbodich
(Mrbodich)
10
Thank you, finally I found you! Works now for me.
Mike
11
What will be the best practice for that code. i.e should the extension of Array with specific element be at the same class file or as with all the extensiosn for the array