[swift-evolution] [Pitch] Circling back to `with`

Alejandro Martinez alexito4 at gmail.com
Fri May 27 17:48:53 CDT 2016


Hi,
I really like the proposal as it's in the last draft. And the examples are really good, specially the one that is similar to the Ruby tap.
I've been using the Then extension for a while and makes the initialization much nicer. I also use a set of extensions that allow me to print intermediate objects in a chain of methods. Initially I was using it for FRP style but I ended up using it while developing on chain of functional methods on arrays. Is basically a specific implementation of what this proposal will generalize, which is cool.

My only concern is with the method cascading proposal. If that gets implement in the future how useful will this proposal be? I'm not that familiar with method cascading to have a strong opinion on this, but I would like to see other members to speak about it.

Btw, thanks for adding the proposals directly in the email. Makes keeping up to date withthe list   much easier 👏🏻

Sent from my iPad

> On 27 May 2016, at 20:42, Erica Sadun via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> On May 25, 2016, at 5:34 PM, Dany St-Amant <dsa.mls at icloud.com> wrote:
>>> Le 25 mai 2016 à 14:28, Erica Sadun via swift-evolution <swift-evolution at swift.org> a écrit :
>>> 
>>> Over the past couple of days, the Twitters have discovered some work I'd done on closure-based setup. 
>>> It's clear that a demand is out there and strong for this kind of behavior, even without implicit `self` as 
>>> part of the mix or cascading. In that light, I've put together the following:
>>> 
>>> https://gist.github.com/erica/96d9c5bb4eaa3ed3b2ff82dc35aa8dae
>>> 
>>> If the community demand is this high, I think we should re-consider pushing it before 3. 
>>> Feedback as always welcome, including criticism.
>> 
>> Recently there was another possible 'with' variant:
>> - instead of being a: with 'instance' create new 'instance' using 'closure'
>> - it was a: with(/on) 'instance/s' perform 'closure' (same idea as the closure-based initialization, described in the motivation)
>> 
>> The thread "What about a VBA style with Statement?": http://thread.gmane.org/gmane.comp.lang.swift.evolution/14384
>> 
>> In either case the 'with' can be misleading/confusing; with, on its own doesn't suggest creation of a new entity, nor does it strongly suggest possible mutation.
>> 
>> Just mentioning it as to end up... with the proper name for this new function.
>> 
>> Dany
> 
> Naming can always be bikeshedded. 
> Brent's submitted a pull request: https://github.com/apple/swift-evolution/pull/346
> Here's a gist: https://gist.github.com/brentdax/ce3272e3d35f5ccac56483666f86b8fb
> The current state of the proposal follows 
> -- E
> 
> Introducing with to the Standard Library
> Proposal: TBD
> Author: Erica Sadun, Brent Royal-Gordon
> Status: TBD
> Review manager: TBD
> Introduction
> 
> This proposal introduces a with function to the standard library. This function simplifies the initialization of objects and modification of value types.
> 
> Swift-evolution thread: What about a VBA style with Statement?
> 
> Motivation
> 
> When setting up or modifying an instance, developers sometimes use an immediately-called closure to introduce a short alias for the instance and group the modification code together. For example, they may initialize and customize a Cocoa object:
> 
> let questionLabel: UILabel = {
>     $0.textAlignment = .Center
>     $0.font = UIFont(name: "DnealianManuscript", size: 72)
>     $0.text = questionText
>     $0.numberOfLines = 0
>     mainView.addSubview($0)
>     return $0
> }(UILabel())
> Or they may duplicate and modify a constant value-typed instance:
> 
> let john = Person(name: "John", favoriteColor: .blueColor())
> let jane: Person = { (var copy) in
>     copy.name = "Jane"
>     return copy
> }(john)
> This technique has many drawbacks:
> 
> The compiler cannot infer the return type.
> You must explicitly return the modified instance.
> The instance being used comes after, not before, the code using it.
> Nevertheless, developers have created many variations on this theme, because they are drawn to its benefits:
> 
> The short, temporary name reduces noise compared to repeating a variable name like questionLabel.
> The block groups together the initialization code.
> The scope of mutability is limited.
> SE-0003, which removes var parameters, will make this situation even worse by requiring a second line of boilerplate for value types. And yet developers will probably keep using these sorts of tricks.
> 
> Fundamentally, this is a very simple and common pattern: creating a temporary mutable variable confined to a short scope, whose value will later be used immutably in a wider scope. Moreover, this pattern shortens the scopes of mutable variables, so it is something we should encourage. We believe it's worth codifying in the standard library.
> 
> Proposed Solution
> 
> We propose introducing a function with the following simplified signature:
> 
> func with<T>(_: T, update: (inout T -> Void)) -> T
> with assigns the value to a new variable, passes that variable as a parameter to the closure, and then returns the potentially modified variable. That means:
> 
> When used with value types, the closure can modify a copy of the original value.
> When used with reference types, the closure can substitute a different instance for the original, perhaps by calling copy() or some non-Cocoa equivalent.
> The closure does not actually have to modify the parameter; it can merely use it, or (for a reference type) modify the object without changing the reference.
> 
> Examples
> 
> Initializing a Cocoa Object
> 
> Before:
> 
> let questionLabel: UILabel = {
>     $0.textAlignment = .Center
>     $0.font = UIFont(name: "DnealianManuscript", size: 72)
>     $0.text = questionText
>     $0.numberOfLines = 0
>     mainView.addSubview($0)
>     return $0
> }(UILabel())
> After:
> 
> let questionLabel = with(UILabel()) {
>     $0.textAlignment = .Center
>     $0.font = UIFont(name: "DnealianManuscript", size: 72)
>     $0.text = questionText
>     $0.numberOfLines = 0
>     mainView.addSubview($0)
> }
> Using with here moves the UILabel() initialization to the top, allows the type of questionLabel to be inferred, and removes the return statement.
> 
> Copying and Modifying a Constant
> 
> Before (without var parameter):
> 
> let john = Person(name: "John", favoriteColor: .blueColor())
> let jane: Person = {
>     var copy = $0
>     copy.name = "Jane"
>     return copy
> }(john)
> After:
> 
> let john = Person(name: "John", favoriteColor: .blueColor())
> let jane = with(john) {
>     $0.name = "Jane"
> }
> In addition to the aforementioned benefits, with removes the var copy line.
> 
> Treating a Mutable Method As a Copy-and-Return Method
> 
> You would like to write this:
> 
> let fewerFoos = foos.removing(at: i)
> But there is only a remove(at:) mutating method. Using with, you can write:
> 
> let fewerFoos = with(foos) { $0.remove(at: i) }
> Avoiding Mutable Shadowing
> 
> The standard library includes an operator for concatenating two RangeReplaceableCollections with this implementation:
> 
> var lhs = lhs
> // FIXME: what if lhs is a reference type?  This will mutate it.
> lhs.reserveCapacity(lhs.count + numericCast(rhs.count))
> lhs.append(contentsOf: rhs)
> return lhs
> Using with, you can eliminate the shadowing of lhs:
> 
> // FIXME: what if lhs is a reference type?  This will mutate it.
> return with(lhs) {
>   $0.reserveCapacity($0.count + numericCast(rhs.count))
>   $0.append(contentsOf: rhs)
> }
> It's important to note that with does not resolve the "FIXME" comment. Like the var lhs = lhs in the original code, with only copies value types, not reference types. If RangeReplaceableCollection included a Foundation-like copy()method that was guaranteed to return a copy even if it was a reference type, with would work nicely with that solution:
> 
> return with(lhs.copy()) {
>   $0.reserveCapacity($0.count + numericCast(rhs.count))
>   $0.append(contentsOf: rhs)
> }
> Inspecting an Intermediate Value
> 
> Suppose you want to inspect a value in the middle of a long method chain. For instance, you're not sure this is retrieving the type of cell you expect:
> 
> let view = tableView.cellForRow(at: indexPath)?.contentView.withTag(42)
> Currently, you would need to either split the statement in two so you could capture the return value of cellForRow(at:) in a constant, or insert a very clunky immediate-closure call in the middle of the statement. Using with, you can stay close to the original expression:
> 
> let view = with(tableView.cellForRow(at: indexPath)) { print($0) }?.contentView.withTag(42)
> Because the closure doesn't alter $0, the cell passes through the with call unaltered, so it can be used by the rest of the method chain.
> 
> Detailed Design
> 
> We propose adding the following free function to the standard library:
> 
> /// Returns `item` after calling `update` to inspect and possibly 
> /// modify it.
> /// 
> /// If `T` is a value type, `update` uses an independent copy 
> /// of `item`. If `T` is a reference type, `update` uses the 
> /// same instance passed in, but it can substitute a different 
> /// instance by setting its parameter to a new value.
> @discardableResult
> public func with<T>(_ item: T, update: @noescape (inout T) throws -> Void) rethrows -> T {
>   var this = item
>   try update(&this)
>   return this
> }
> @discardableResult permits the use of with(_:update:) to create a scoped temporary copy of the value with a shorter name.
> 
> Impact on Existing Code
> 
> This proposal is purely additive and has no impact on existing code.
> 
> Alternatives Considered
> 
> Doing nothing: with is a mere convenience; any code using it could be written another way. If rejected, users could continue to write code using the longhand form, the various closure-based techniques, or homegrown versions of with.
> 
> Using method syntax: Some list members preferred a syntax that looked more like a method call with a trailing closure:
> 
> let questionLabel = UILabel().with {
>     $0.textAlignment = .Center
>     $0.font = UIFont(name: "DnealianManuscript", size: 72)
>     $0.numberOfLines = 0
>     addSubview($0)
> }
> This would require a more drastic solution as it's not possible to add methods to all Swift types. Nor does it match the existing design of functions like withExtendedLifetime(_:_:), withUnsafePointer(_:_:), and reflect(_:).
> 
> Adding self rebinding: Some list members wanted a way to bind self to the passed argument, so that they can use implicit self to eliminate $0.:
> 
> let supView = self
> let questionLabel = with(UILabel()) { 
>     self in
>     textAlignment = .Center
>     font = UIFont(name: "DnealianManuscript", size: 72)
>     numberOfLines = 0
>     supView.addSubview(self)
> }
> We do not believe this is practical to propose in the Swift 3 timeframe, and we believe with would work well with this feature if it were added later.
> 
> Adding method cascades: A competing proposal was to introduce a way to use several methods or properties on the same instance; Dart and Smalltalk have features of this kind.
> 
> let questionLabel = UILabel()
>     ..textAlignment = .Center
>     ..font = UIFont(name: "DnealianManuscript", size: 72)
>     ..numberOfLines = 0
> addSubview(questionLabel)
> Like rebinding self, we do not believe method cascades are practical for the Swift 3 timeframe. We also believe that many of with's use cases would not be subsumed by method cascades even if they were added.
> 
> 
> _______________________________________________
> 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/20160527/53541636/attachment.html>


More information about the swift-evolution mailing list