In Swift I am trying to implement a method "tap" similar to the method which exists in Ruby.
I've come up with the following example code:
private protocol Tap {
mutating func tap(_ block: (inout Self) -> Void) -> Self
}
private extension Tap {
mutating func tap(_ block: (inout Self) -> Void) -> Self {
block(&self)
return self
}
}
extension Array: Tap {}
var a = Array(repeating: "Hello", count: 5)
a.tap {
$0.append("5")
}.tap {
$0.append("7")
}
print(a) // (Expected) => ["Hello", "Hello", "Hello", "Hello", "Hello", "5", "7"]
I'm not super familiar with mutating functions, inout parameters, or Swift in general, but the code above looks like it should work to me. tap
works as expected when it's not being included in a method chain. When I include it as part of a method chain, like in the above example, the Swift compiler complains:
Cannot use mutating member on immutable value: function call returns immutable value
Can anyone explain to me why this doesn't work? Can anyone provide a working solution and explain why that solution works?
Another example usage would be:
let user = User(fromId: someId).tap {
$0.firstName = someFirstName
$0.lastName = someLastName
}
tap
is a convenience thing that comes from Ruby. I'm mainly interested in understanding why the types in my function aren't working out right.
The return self
returns a copy of the original array, not the original array itself. Until this copy is stored as a var
, it cannot be mutated. So, this would work:
var b = a.tap {
$0.append("5")
}
b.tap {
$0.append("7")
}
But not without storing b
as a var
first. Of course, you wouldn't create a b
in the first place, you would just use a
repeatedly as you already pointed out.
So, the issue is that you can accomplish tap
once, but cannot chain tap
s. This is because the return of self
is implicitly immutable, and you cannot call a mutating function on an immutable value. Changing tap
to a non-mutating function could get you what you want:
private extension Tap {
func tap(_ block: (inout Self) -> Void) -> Self {
let copy = self
block(©)
return copy
}
}
var a = Array(repeating: "Hello", count: 5)
a = a.tap({$0.append("5")}).tap({$0.append("7")})
Because each invocation of tap(
returns a copy of the original modified by the given block, you can call it on immutable types. That means that you can chain.
The only downside is that new a =
in the beginning.
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