Extend trailing closure rule


(Matt Neuburg) #1

Stop me if you've heard this one; I've only just joined the list, in order to raise it.

Here's a common thing to say:

    UIView.animate(withDuration:0.4, animations: {
        self.v.backgroundColor = UIColor.red()
    })

That's ugly. I'd rather write:

    UIView.animate(withDuration:0.4) {
        self.v.backgroundColor = UIColor.red()
    }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

The idea is that this would work for _any_ function call where the function takes, as its last parameters, a series of function arguments that have default values. There can be only one trailing closure, so it should be assumed to occupy the first available slot, as it were.

Would this be viable? Would it make a decent proposal? m.


(Jordan Rose) #2

I'm one of those in favor of going the other way: if a function takes multiple closure arguments, you shouldn't be allowed to use a trailing closure at all, because it may not be obvious to readers of your code which one you are using. (This is especially true if the closures have the same signature.)

Jordan

···

On Jun 8, 2016, at 12:06, Matt Neuburg via swift-evolution <swift-evolution@swift.org> wrote:

Stop me if you've heard this one; I've only just joined the list, in order to raise it.

Here's a common thing to say:

   UIView.animate(withDuration:0.4, animations: {
       self.v.backgroundColor = UIColor.red()
   })

That's ugly. I'd rather write:

   UIView.animate(withDuration:0.4) {
       self.v.backgroundColor = UIColor.red()
   }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

The idea is that this would work for _any_ function call where the function takes, as its last parameters, a series of function arguments that have default values. There can be only one trailing closure, so it should be assumed to occupy the first available slot, as it were.

Would this be viable? Would it make a decent proposal? m.


(Rimantas Liubertas) #3

That's ugly. I'd rather write:

UIView.animate(withDuration:0.4) {
self.v.backgroundColor = UIColor.red()
}

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is.

Actually you can. UIView has three signatures ‘animateWithduration’:

class func animateWithDuration(_ duration: NSTimeInterval (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_DataTypes/index.html#//apple_ref/swift/tdef/c:@T@NSTimeInterval),
                    animations animations: () -> Void (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_StandardLibrary_TypeAliases/index.html#//apple_ref/swift/tdef/s:s4Void))

class func animateWithDuration(_ duration: NSTimeInterval (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_DataTypes/index.html#//apple_ref/swift/tdef/c:@T@NSTimeInterval),
                    animations animations: () -> Void (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_StandardLibrary_TypeAliases/index.html#//apple_ref/swift/tdef/s:s4Void),
                    completion completion: ((Bool (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_Bool_Structure/index.html#//apple_ref/swift/struct/s:Sb)) -> Void (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_StandardLibrary_TypeAliases/index.html#//apple_ref/swift/tdef/s:s4Void))?)

class func animateWithDuration(_ duration: NSTimeInterval (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_DataTypes/index.html#//apple_ref/swift/tdef/c:@T@NSTimeInterval),
                         delay delay: NSTimeInterval (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_DataTypes/index.html#//apple_ref/swift/tdef/c:@T@NSTimeInterval),
                       options options: UIViewAnimationOptions (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/UIKit/Reference/UIView_Class/index.html#//apple_ref/swift/struct/c:@E@UIViewAnimationOptions),
                    animations animations: () -> Void (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_StandardLibrary_TypeAliases/index.html#//apple_ref/swift/tdef/s:s4Void),
                    completion completion: ((Bool (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_Bool_Structure/index.html#//apple_ref/swift/struct/s:Sb)) -> Void (file:///Users/rimliu/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_StandardLibrary_TypeAliases/index.html#//apple_ref/swift/tdef/s:s4Void))?)

so your version is valid.

Best regards,
Rimantas


(Brent Royal-Gordon) #4

Here's a common thing to say:

   UIView.animate(withDuration:0.4, animations: {
       self.v.backgroundColor = UIColor.red()
   })

That's ugly. I'd rather write:

   UIView.animate(withDuration:0.4) {
       self.v.backgroundColor = UIColor.red()
   }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

If we may take leave of practical considerations for a moment, I'd like to note that the ideal syntax for a call like this would probably look like:

  UIView.animate(withDuration: 0.4) {
    // animations
  }
  completion { finished in
    // completion
  }

And of course, since `completion` has a default value, this would naturally degrade to:

  UIView.animate(withDuration: 0.4) {
    // animations
  }

I'm guessing this isn't possible because the `completion` could instead be a call to a separate function with a trailing closure. But is there some way we could get something similar? That could significantly improve our handling of multi-block APIs and give trailing closures the ability to emulate more kinds of syntax.

···

--
Brent Royal-Gordon
Architechies


(Matt Neuburg) #5

Well, I guess I didn't pick a strong enough case. Try this one:

        UIView.animate(withDuration:0.4, delay: 0, options: [.autoreverse]) {
            self.view.backgroundColor = UIColor.red()
        }

That doesn't compile. I'm suggesting that it would be cool if it did. m.

···

On Jun 8, 2016, at 12:29 PM, Rimantas Liubertas <rimantas@gmail.com> wrote:

That's ugly. I'd rather write:

UIView.animate(withDuration:0.4) {
self.v.backgroundColor = UIColor.red()
}

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is.

Actually you can. UIView has three signatures ‘animateWithduration’:

class func animateWithDuration(_ duration: NSTimeInterval,
                    animations animations: () -> Void)

class func animateWithDuration(_ duration: NSTimeInterval,
                    animations animations: () -> Void,
                    completion completion: ((Bool) -> Void)?)

class func animateWithDuration(_ duration: NSTimeInterval,
                         delay delay: NSTimeInterval,
                       options options: UIViewAnimationOptions,
                    animations animations: () -> Void,
                    completion completion: ((Bool) -> Void)?)

so your version is valid.

Best regards,
Rimantas

--
matt neuburg, phd = http://www.apeth.net/matt/
pantes anthropoi tou eidenai oregontai phusei
Programming iOS 9! http://shop.oreilly.com/product/0636920044352.do
iOS 9 Fundamentals! http://shop.oreilly.com/product/0636920044345.do
RubyFrontier! http://www.apeth.com/RubyFrontierDocs/default.html


(Paul Cantrell) #6

I’m not in favor of that. Good argument labeling can make it perfectly clear to readers.

Siesta even leans on this feature a bit in one of its API methods:

    configure(whenURLMatches: { $0 != authentication.url }) {
        $0.config.headers["authentication-token"] = self.accessToken
    }

…with this local refactoring if the first closure grows unwieldy:

    let specialFancyResources = {
        // Special fancy matching goes here
    }
    
    configure(whenURLMatches: specialFancyResources) {
        $0.config.headers["authentication-token"] = self.accessToken
    }

Both of those forms seem readable to me. I’d hate to rule them out.

Cheers, P

···

On Jun 8, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 8, 2016, at 12:06, Matt Neuburg via swift-evolution <swift-evolution@swift.org> wrote:

Stop me if you've heard this one; I've only just joined the list, in order to raise it.

Here's a common thing to say:

  UIView.animate(withDuration:0.4, animations: {
      self.v.backgroundColor = UIColor.red()
  })

That's ugly. I'd rather write:

  UIView.animate(withDuration:0.4) {
      self.v.backgroundColor = UIColor.red()
  }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

The idea is that this would work for _any_ function call where the function takes, as its last parameters, a series of function arguments that have default values. There can be only one trailing closure, so it should be assumed to occupy the first available slot, as it were.

Would this be viable? Would it make a decent proposal? m.

I'm one of those in favor of going the other way: if a function takes multiple closure arguments, you shouldn't be allowed to use a trailing closure at all, because it may not be obvious to readers of your code which one you are using. (This is especially true if the closures have the same signature.)


(Erica Sadun) #7

Mid-call closure can mean one of several things:

1. Bad design
2. Multiple closures
3. Design before Swift / ObjC focused

To which:

1. Well, not sure that should be "fixed"
2. I think multiple closures should all be treated the same without trailing
3's a different kind of thing. I vaguely endorse having Cocoa request how it should be imported beyond the SE-0005 rules.

We were kicking around some ideas on "how should defaults embetter" that this kind of relates to: https://gist.github.com/erica/3987ec54b8f4a580ae5fc18f4e9e7ca5 In this example, I can see a rule of "if there's only one closure named animation, completion, etc, promote it to the last argument". Kind of.

-- E, dithery

···

On Jun 8, 2016, at 4:11 PM, Matt Neuburg via swift-evolution <swift-evolution@swift.org> wrote:

Well, I guess I didn't pick a strong enough case. Try this one:

       UIView.animate(withDuration:0.4, delay: 0, options: [.autoreverse]) {
           self.view.backgroundColor = UIColor.red()
       }

That doesn't compile. I'm suggesting that it would be cool if it did. m.


(L Mihalkovic) #8

Stop me if you've heard this one; I've only just joined the list, in order to raise it.

Here's a common thing to say:

  UIView.animate(withDuration:0.4, animations: {
      self.v.backgroundColor = UIColor.red()
  })

That's ugly. I'd rather write:

  UIView.animate(withDuration:0.4) {
      self.v.backgroundColor = UIColor.red()
  }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

The idea is that this would work for _any_ function call where the function takes, as its last parameters, a series of function arguments that have default values. There can be only one trailing closure, so it should be assumed to occupy the first available slot, as it were.

Would this be viable? Would it make a decent proposal? m.

I'm one of those in favor of going the other way: if a function takes multiple closure arguments, you shouldn't be allowed to use a trailing closure at all, because it may not be obvious to readers of your code which one you are using. (This is especially true if the closures have the same signature.)

+1

···

On Jun 8, 2016, at 10:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 8, 2016, at 12:06, Matt Neuburg via swift-evolution <swift-evolution@swift.org> wrote:

Jordan

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Saagar Jha) #9

How about using a colon to separate it, to make `completion` look like an
argument and to separate it from being a function? Something like this:

UIView.animate(withDuration: 0.4) {
// animations
}
completion: { finished in
// completion
}

···

On Thu, Jun 9, 2016 at 1:06 AM Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

> Here's a common thing to say:
>
> UIView.animate(withDuration:0.4, animations: {
> self.v.backgroundColor = UIColor.red()
> })
>
> That's ugly. I'd rather write:
>
> UIView.animate(withDuration:0.4) {
> self.v.backgroundColor = UIColor.red()
> }
>
> What stops me is that `animations:` is not eligible for trailing closure
syntax, because it isn't the last parameter — `completion:` is. But
`completion:` has a default value, namely `nil` — that's why I'm allowed to
omit it. So why can't the compiler work its way backwards through the
parameters, and say to itself: "Well, I see a trailing closure, and I don't
see any `animations:` label or any `completion:` label, so this trailing
closure must be the `animations:` argument and the `completions:` argument
must be `nil`."

If we may take leave of practical considerations for a moment, I'd like to
note that the ideal syntax for a call like this would probably look like:

        UIView.animate(withDuration: 0.4) {
                // animations
        }
        completion { finished in
                // completion
        }

And of course, since `completion` has a default value, this would
naturally degrade to:

        UIView.animate(withDuration: 0.4) {
                // animations
        }

I'm guessing this isn't possible because the `completion` could instead be
a call to a separate function with a trailing closure. But is there some
way we could get something similar? That could significantly improve our
handling of multi-block APIs and give trailing closures the ability to
emulate more kinds of syntax.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Saagar Jha


(Thorsten Seitz) #10

Stop me if you've heard this one; I've only just joined the list, in order to raise it.

Here's a common thing to say:

  UIView.animate(withDuration:0.4, animations: {
      self.v.backgroundColor = UIColor.red()
  })

That's ugly. I'd rather write:

  UIView.animate(withDuration:0.4) {
      self.v.backgroundColor = UIColor.red()
  }

What stops me is that `animations:` is not eligible for trailing closure syntax, because it isn't the last parameter — `completion:` is. But `completion:` has a default value, namely `nil` — that's why I'm allowed to omit it. So why can't the compiler work its way backwards through the parameters, and say to itself: "Well, I see a trailing closure, and I don't see any `animations:` label or any `completion:` label, so this trailing closure must be the `animations:` argument and the `completions:` argument must be `nil`."

The idea is that this would work for _any_ function call where the function takes, as its last parameters, a series of function arguments that have default values. There can be only one trailing closure, so it should be assumed to occupy the first available slot, as it were.

Would this be viable? Would it make a decent proposal? m.

I'm one of those in favor of going the other way: if a function takes multiple closure arguments, you shouldn't be allowed to use a trailing closure at all, because it may not be obvious to readers of your code which one you are using. (This is especially true if the closures have the same signature.)

I’m not in favor of that. Good argument labeling can make it perfectly clear to readers.

Siesta even leans on this feature a bit in one of its API methods:

    configure(whenURLMatches: { $0 != authentication.url }) {
        $0.config.headers["authentication-token"] = self.accessToken
    }

…with this local refactoring if the first closure grows unwieldy:

    let specialFancyResources = {
        // Special fancy matching goes here
    }
    
    configure(whenURLMatches: specialFancyResources) {
        $0.config.headers["authentication-token"] = self.accessToken
    }

Both of those forms seem readable to me. I’d hate to rule them out.

+1

-Thorsten

···

Am 08.06.2016 um 22:59 schrieb Paul Cantrell via swift-evolution <swift-evolution@swift.org>:

On Jun 8, 2016, at 3:46 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 12:06, Matt Neuburg via swift-evolution <swift-evolution@swift.org> wrote:

Cheers, P

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution