>> - A plain `with` whose closure parameter is not mutable and which is marked 
>> `@discardableResult`.
> 
> I would like to see this version restricted to AnyObject.  It has extremely 
> limited utility with value types.  It would usually be a mistake to call it 
> with a value type.

I would not. It gives you a way to give a value type a short, scoped, immutable 
alias:

        with(RareMagicalDeviceOwner.shared.spimsterWickets[randomIndex]) {
                print($0.turns)
                print($0.turnSpeed)
        }

And in this form, there is no danger of mistakenly mutating the value type, 
because mutating methods would not be allowed:

        with(RareMagicalDeviceOwner.shared.spimsterWickets[randomIndex]) {
                $0.turnRepeatedly(times: 3)     // Error: can't call mutating 
method on immutable parameter
        }

To be clear, I'm not convinced there's a need to make any change from the 
proposed version at all. I'm spitballing alternate designs here, trying to see 
if there might be something a little better out there. But so far, I think the 
proposal balances the feature size against strictness pretty well, whereas 
these stricter designs I'm discussing increase the surface of the feature more 
than they improve it. This is a small (but significant!) convenience, and I 
feel pretty strongly that it should have a small implementation.

> That said, I am not convinced these non-copying functions would be worth 
> having after method cascades are introduced.  Are there any use cases left 
> for them in that future?

Yes, absolutely. Method cascades have a narrow use case: methods on `self`. Not 
everything in Swift is a method, and not all methods are on `self`.

        with(tableView.cellForRow(at: indexPath).myLabel) { label in
                print("Constraining label: \(label)")
                
                NSLayoutConstraint.activate(
                        NSLayoutConstraint.withVisualFormat("|-[label]-|", 
options: [], metrics: [:], views: ["label": label]) +
                        NSLayoutConstraint.withVisualFormat("V:|[label]|", 
options: [], metrics: [:], views: ["label": label])
                )
                
                constrainedLabels.append(label)
        }

None of the calls in that `with` block would benefit from method cascades, but 
they all benefit from `with`.

>> - A `withVar` whose parameter *is* mutable and which is *not* marked 
>> `@discardableResult`. (This would help with the fact that our use of 
>> `@discardableResult` is a little dangerous, in that people might expect 
>> mutations to affect the original variable even if it's a value type.)
>> 
>> `withVar` does, I think, make it pretty clear that you're working with a 
>> copy of the variable.
> 
> One thing to consider in choosing a name here is the cases where this 
> function would still be useful in a future that includes method cascades.  
> The one thing this function does that method cascades don’t is make a copy of 
> the value before operating on it and returning it.  
> 
> With that in mind, I think it is worthwhile to consider the name `withCopy` 
> and make the closure argument optional.

I specifically considered and rejected `withCopy` because it only creates a 
copy of a value type, not a reference type. (Of course, it does create a copy 
of the reference itself, but that's a very subtle distinction.) I chose 
`withVar` to make it very clear that you're getting the same semantics as you 
would for a `var` temporary.

> public func withCopy<T>(_ item: T, update: (@noescape (inout T) throws -> 
> Void)?) rethrows -> T {
>    var this = item
>    try update?(&this)
>    return this
> }
> 
> This function would be more clear and useful in conjunction with method 
> cascades:
> 
> let bar = withCopy(foo)
>    ..cascaded = “value"
>    ..operations()
>    ..onFoo()

Honestly, I'm not sure there's a coherent way to make method cascades work with 
your `withCopy` (or the `copy` function you mentioned upthread) at all.

Here's the problem. Suppose you have a property like this:

        var array: [Int]

And then you write this:

        array = [1, 2, 3]
        return array
                        ..remove(at: 1)
                        ..remove(at: 0)

I assume you think this should not only *return* `[3]`, but also *set* `array` 
to `[3]`. That's kind of implied by the fact that you think we need a 
`withCopy(array)` call to protect `array` from being affected by these calls.

But that means that in this version:

        array = [1, 2, 3]
        return withCopy(array)
                        ..remove(at: 1)
                        ..remove(at: 0)

You are trying to call `mutating` methods on an *immutable* value, the return 
value of `withCopy`. Normally, the compiler would reject that.

Perhaps you could say that method cascades operate on a copy if the receiver is 
immutable, but that makes code vague and its behavior subtle and easily changed 
by accident. For instance, if a property is `internal private(set)`, then 
moving a method cascade from code which can't see the setter to code which can 
would silently change the code from immutable to mutable. Similarly, adding the 
`private(set)` would not cause the code which previously modified it to produce 
an error; it would instead silently change to no longer mutate where it used to 
before. That's not acceptable behavior from a language feature.

About the only solution to this I can come up with is to make `withCopy` have 
an `inout` return. But this at best forms an attractive nuisance: If you use 
normal `mutating` method calls instead of method cascading, your changes are 
going to disappear into the ether. And depending on how `inout` returns are 
actually implemented, it could lead to worse misbehavior.

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to