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
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.
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))
}
}
}
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.
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.
<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
^
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