[swift-evolution] Proposal: Add replace(_:with:) function to the stdlib

Kevin Ballard kevin at sb.org
Wed Jan 13 15:30:18 CST 2016


FWIW, in the code I've written over the past few days in a new project, I've already used my local replace(_:with:) 6 times, and I just tried to use it a 7th from within the unit tests and got an error because the function is private >_<

It does turn out that nearly all of my particular uses for this function
end up replacing the value with `nil`, but there certainly are cases
where you'd want to replace a value with some other non-nil value (such
as the example I listed in the proposal about a spot where it might be
used in the stdlib).

On Mon, Jan 11, 2016, at 01:22 PM, Jordan Rose wrote:
> -1 from me as well, for the same reason as Dave. In the Dictionary
> case, doing a lookup and then a replacement isn't just more verbose;
> it's actually less efficient. And I *still* wouldn't embed an
> updateValue(_:forKey:) in a larger expression, for clarity's sake.

I'm not recommending that replace(_:with:) be used as a replacement for
Dictionary.updateValue(_:forKey:). I just found it interesting that it
has the exact same semantics (but, as you said, it's less performant).

> Now, admittedly, if the assigned-to value were some complicated
> expression (e.g. "view.frame.size.height"), the same performance
> argument applies. But I still think I'd rather people just use a
> temporary variable.

What do you mean? Using `replace(&view.frame.size.height, with:
newHeight)` is no less performant than doing things manually. The
Dictionary.updateValue(_:forKey:) case only differs because
accessing the subscript getter + setter has to look up the key
twice, but `&view.frame.size.height` doesn't have to look up
anything, it just has to read the property and then set it again,
which is exactly what you have to do if you want to read the old
value and assign a new one regardless of whether you're using a
function like replace(_:with:) to do it.

>> self.task?.cancel() self.task = nil
>>
>> let oldValue = self.prop self.prop = newValue if let oldValue =
>> oldValue {  // multi-line cleanup }
>
> I'll admit the latter is not as nice, but it's definitely easier for
> me to read.

Maybe for you. I find it much easier to read

if let oldValue = replace(&self.prop, with: nil) {


// ...


}



I recognize that many people will prefer the "manual" version, but I'm
sure many people will be like me and prefer the replace(_:with:)
version. I don't find the argument "I think it's easier to read <some
other way>" to be very compelling when it comes to stdlib or language
features, because we already have plenty of precedent for being able to
write the same thing in multiple ways. There's also a middle ground
here, if you just don't like embedding replace(_:with:) in a larger
construct, that still uses a temporary variable but allows you to avoid
duplicating complicated accessor expressions, e.g.

let oldValue =
replace(&self.sections[indexPath.section].rows[indexPath.row].data,
with: newValue)

if let oldValue = oldValue {

// ...

}


I've also found a lot of use for replace(_:with:) in `guard let`
expressions. There especially the "manual" version sucks because it
leaves a second variable lying around that you don't want anymore. In
the library I'm working on right now, I have a bunch of uses of
replace(_:with:) that look like

guard var request = replace(&self.request, with; nil) else {

writeResponse(Response(status: .InternalServerError, text: "Internal
server error: couldn't find active request"))

return

}


(yes, this is actually for an HTTP server written in Swift, which I'm
using to test networking code)

What's your opinion on the alternative <- operator (which IIRC Dave
suggested)? I much prefer replace(_:with:), and I don't think it
conflicts with stdlib conventions (it's no different than a mutating
method that returns a value, it just happens to be a function, but
there's not much of a difference between mutating methods and functions
that take an inout first parameter), but I'd rather have this
functionality as an <- operator than not have it at all. As an example,
the previous two code samples, when written with <-, would look like

let oldValue =
(self.sections[indexPath.section].rows[indexPath.row].data <- newValue)

if let oldValue = oldValue {

// ...

}



and

guard var request = (self.request <- nil) else {

writeResponse(Response(status: .InternalServerError, text: "Internal
server error: couldn't find active request"))

return

}


-Kevin Ballard

> Jordan
>
>
>> On Jan 9, 2016, at 16:48, Kevin Ballard via swift-evolution <swift-
>> evolution at swift.org> wrote:
>>
>> Proposal PR submitted as
>> https://github.com/apple/swift-evolution/pull/93
>>
>> -Kevin Ballard
>>
>> On Sun, Dec 13, 2015, at 02:21 PM, Kevin Ballard wrote:
>>> A function I find myself defining in a lot of my projects looks like
>>> the following:
>>>
>>> /// Replace the value of `a` with `b` and return the old value.
>>> public func replace<T>(inout a: T, with b: T) -> T {    var value =
>>> b    swap(&a, &value)    return value }
>>>
>>> This is a pretty simple function, and useful in a wide variety of
>>> circumstances, so I'd love to get it into the standard library. It
>>> doesn't actually enable any behavior that wasn't previously
>>> possible, but it does shrink some common code patterns, and I find
>>> the shorter code easier to read.
>>>
>>> An example of a place where I use it often is in replacing an
>>> optional property with a new value (or with nil) and cleaning up the
>>> previous value. Assuming a property like
>>>
>>> var task: NSURLSessionTask?
>>>
>>> This replaces
>>>
>>> if let task = self.task {    task.cancel() } task = nil
>>>
>>> with
>>>
>>> replace(&task, with: nil)?.cancel()
>>>
>>> Or sometimes I use it like
>>>
>>> if let value = replace(&prop, with: newValue) {    // multi-line
>>> cleanup }
>>>
>>> This is particularly nice if it's a COW value that I want to mutate,
>>> as it means I don't have to worry about getting unwanted copies due
>>> to the property still holding the old value while I muck with it.
>>>
>>> Question: For trivial backwards-compatible API changes like this,
>>> does a proposal PR need to be submitted to the swift-evolution repo,
>>> or is discussion on this ML sufficient before submitting a patch?
>>>
>>> -Kevin Ballard
>>
>>
>> _______________________________________________
>> swift-evolution mailing list swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160113/dcbbc366/attachment.html>


More information about the swift-evolution mailing list