Deprecating Trailing Closures


(Haravikk) #1

When I started using Swift I was initially very enthusiastic about trailing closures, but I’ve actually kind of gone off them somewhat and I’d like to discuss why.

Firstly, here are two ways to write a common example using the .map() method:

  let foo = myArray.map { $0 + 1 }
  let foo = myArray.map({ $0 + 1 })

It’s tough to say that the first form is any neater than the second, other than the second having more brackets. However, the first form is somewhat ambiguous, as .map in this case looks like a property rather than a method, it also visually looks like a statement, followed by a closure rather than the two things logically being related. Of course it’s quick to learn that these are related, but for consistency I’m starting to now prefer the use of parenthesis in almost all cases.

The other advantage of trailing closures is the omission of the label, but trailing closures aren’t strictly necessary for this, as we can already omit external labels for parameters if we want to, and the example above shows that a trailing closure isn’t necessary for this. The only real difference is that the trailing closure form makes a label optional, because you can either provide the closure with label in parenthesis (if the label is required) or omit it by trailing, like so:

  something.someMethod(foo: 1, predicate: { $0 < $1})
  something.someMethod(foo: 1) { $0 < $1}

However this kind of arbitrarily makes the following impossible:

  something.someMethod(foo: 1, { $0 < $1 })

With this in mind it seems to me that we might be better served by the ability to make external labels optional, as this would allow us to be just as succinct, while being completely clear about what is being passed into this method.

The only real remaining advantage that I see to trailing closures is the ability to define pseudo language constructs, for example:

  func repeatUntilEmpty<C:CollectionType>(collection:C, @noescape _ body:() throws -> Void) rethrows { while !collection.isEmpty { body() } }
  repeatUntilEmpty(myArray) {
    /* Do something over and over until myArray is empty */
  }

Which I think is a pretty uncommon type of structure, but could be useful in some specialised situations. To support this though we could easily use a new @trailing attribute instead to indicate that the closure can be used in this way. My example isn’t very good as I can’t think of a case that really, really needs this, but I think they’re probably out there.

To summarise, having come down off my initial enthusiasm for trailing closures I’m not sure that they really add that much syntactically, especially in the most common cases, while actually being a little ambiguous looking and adding inconsistency to the language. I think they should remain for the less common cases that can really benefit from them, but as a feature that is opted into, so that we can go for consistency by default.

I’m interested to hear other people’s thoughts.


(Kurt Werle) #2

Coming from ruby, I'm quite fond of trailing closures. I couldn't really
give you a concrete reason why - putting them in the ()'s really isn't that
big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the
more complex the closure (describing context variables and return types,
throws, etc) the uglier it seem to me to put it inside parens.

···

On Thu, Mar 24, 2016 at 6:57 AM, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

When I started using Swift I was initially very enthusiastic about
trailing closures, but I’ve actually kind of gone off them somewhat and I’d
like to discuss why.

Firstly, here are two ways to write a common example using the .map()
method:

let foo = myArray.map { $0 + 1 }
let foo = myArray.map({ $0 + 1 })

It’s tough to say that the first form is any neater than the second, other
than the second having more brackets. However, the first form is somewhat
ambiguous, as .map in this case looks like a property rather than a method,
it also visually looks like a statement, followed by a closure rather than
the two things logically being related. Of course it’s quick to learn that
these are related, but for consistency I’m starting to now prefer the use
of parenthesis in almost all cases.

The other advantage of trailing closures is the omission of the label, but
trailing closures aren’t strictly necessary for this, as we can already
omit external labels for parameters if we want to, and the example above
shows that a trailing closure isn’t necessary for this. The only real
difference is that the trailing closure form makes a label optional,
because you can either provide the closure with label in parenthesis (if
the label is required) or omit it by trailing, like so:

something.someMethod(foo: 1, predicate: { $0 < $1})
something.someMethod(foo: 1) { $0 < $1}

However this kind of arbitrarily makes the following impossible:

something.someMethod(foo: 1, { $0 < $1 })

With this in mind it seems to me that we might be better served by the
ability to make external labels optional, as this would allow us to be just
as succinct, while being completely clear about what is being passed into
this method.

The only real remaining advantage that I see to trailing closures is the
ability to define pseudo language constructs, for example:

func repeatUntilEmpty<C:CollectionType>(collection:C, @noescape _ body:()
throws -> Void) rethrows { while !collection.isEmpty { body() } }
repeatUntilEmpty(myArray) {
/* Do something over and over until myArray is empty */
}

Which I think is a pretty uncommon type of structure, but could be useful
in some specialised situations. To support this though we could easily use
a new @trailing attribute instead to indicate that the closure can be used
in this way. My example isn’t very good as I can’t think of a case that
really, really needs this, but I think they’re probably out there.

To summarise, having come down off my initial enthusiasm for trailing
closures I’m not sure that they really add that much syntactically,
especially in the most common cases, while actually being a little
ambiguous looking and adding inconsistency to the language. I think they
should remain for the less common cases that can really benefit from them,
but as a feature that is opted into, so that we can go for consistency by
default.

I’m interested to hear other people’s thoughts.

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

--
kurt@CircleW.org
http://www.CircleW.org/kurt/


(William Dillon) #3

Agree.

Another thing I like about trailing closures is that it allows me to make custom constructs that feel more like a part of the language. For example, I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at the end. On this one I’d definitely say that if you don’t like it, don’t use it. I don’t *think* that you’re forced to use it anywhere. It’s a hard sell to take it away from everyone.

- Will

···

On Mar 24, 2016, at 7:18 AM, Kurt Werle via swift-evolution <swift-evolution@swift.org> wrote:

Coming from ruby, I'm quite fond of trailing closures. I couldn't really give you a concrete reason why - putting them in the ()'s really isn't that big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the more complex the closure (describing context variables and return types, throws, etc) the uglier it seem to me to put it inside parens.


(Haravikk) #4

Perhaps, but if you’re getting that onto a single line then it starts to look ugly regardless IMO, whereas splitting it across multiple lines isn’t really any different with or without being in the parenthesis, except that the closing parenthesis at least clarifies that it was a method you’re closing, for example:

  var modifier = true
  let foo = myArray.map({
    var result = $0
    if modifier { result = -result }

    modifier = !modifier
    return result
  })

I dunno, I guess I’m just struggling to find cases in my own code where they really offer much of an advantage to justify having a distinctly different way of doing things, with the ambiguity and inconsistency that represents, which is why I’ve brought it up to discuss; i.e- do we use them just because they exist? Trivial aesthetic difference? Personal preference only?

···

On 24 Mar 2016, at 14:18, Kurt Werle <kurt@circlew.org> wrote:

Coming from ruby, I'm quite fond of trailing closures. I couldn't really give you a concrete reason why - putting them in the ()'s really isn't that big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the more complex the closure (describing context variables and return types, throws, etc) the uglier it seem to me to put it inside parens.


(James Campbell) #5

I think the feature should stay but we need community guidelines on when
and when not to use them.

···

*___________________________________*

*James⎥Head Of CEO*

*james@supmenow.com <james@supmenow.com>⎥supmenow.com <http://supmenow.com>*

*Sup*

*Runway East *

*10 Finsbury Square*

*London*

* EC2A 1AF *

On Thu, Mar 24, 2016 at 4:13 PM, William Dillon via swift-evolution < swift-evolution@swift.org> wrote:

On Mar 24, 2016, at 7:18 AM, Kurt Werle via swift-evolution < > swift-evolution@swift.org> wrote:

Coming from ruby, I'm quite fond of trailing closures. I couldn't really
give you a concrete reason why - putting them in the ()'s really isn't that
big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the
more complex the closure (describing context variables and return types,
throws, etc) the uglier it seem to me to put it inside parens.

Agree.

Another thing I like about trailing closures is that it allows me to make
custom constructs that feel more like a part of the language. For example,
I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at
the end. On this one I’d definitely say that if you don’t like it, don’t
use it. I don’t *think* that you’re forced to use it anywhere. It’s a
hard sell to take it away from everyone.

- Will

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


(Thorsten Seitz) #6

Coming from Smalltalk where there are no native control structures at all, i.e. all control structures are built using closures, I'm very used to creating control structure-like methods or mini DSLs. For these trailing closures are indispensable.
That is also very common in other languages like Ruby or Scala.

(Smalltalk was even better because it has no parenthesis around argument lists which allowed for multiple closure arguments looking as nice as trailing closures.)

-Thorsten

···

Am 24.03.2016 um 14:57 schrieb Haravikk via swift-evolution <swift-evolution@swift.org>:

When I started using Swift I was initially very enthusiastic about trailing closures, but I’ve actually kind of gone off them somewhat and I’d like to discuss why.

Firstly, here are two ways to write a common example using the .map() method:

  let foo = myArray.map { $0 + 1 }
  let foo = myArray.map({ $0 + 1 })

It’s tough to say that the first form is any neater than the second, other than the second having more brackets. However, the first form is somewhat ambiguous, as .map in this case looks like a property rather than a method, it also visually looks like a statement, followed by a closure rather than the two things logically being related. Of course it’s quick to learn that these are related, but for consistency I’m starting to now prefer the use of parenthesis in almost all cases.

The other advantage of trailing closures is the omission of the label, but trailing closures aren’t strictly necessary for this, as we can already omit external labels for parameters if we want to, and the example above shows that a trailing closure isn’t necessary for this. The only real difference is that the trailing closure form makes a label optional, because you can either provide the closure with label in parenthesis (if the label is required) or omit it by trailing, like so:

  something.someMethod(foo: 1, predicate: { $0 < $1})
  something.someMethod(foo: 1) { $0 < $1}

However this kind of arbitrarily makes the following impossible:

  something.someMethod(foo: 1, { $0 < $1 })

With this in mind it seems to me that we might be better served by the ability to make external labels optional, as this would allow us to be just as succinct, while being completely clear about what is being passed into this method.

The only real remaining advantage that I see to trailing closures is the ability to define pseudo language constructs, for example:

  func repeatUntilEmpty<C:CollectionType>(collection:C, @noescape _ body:() throws -> Void) rethrows { while !collection.isEmpty { body() } }
  repeatUntilEmpty(myArray) {
    /* Do something over and over until myArray is empty */
  }

Which I think is a pretty uncommon type of structure, but could be useful in some specialised situations. To support this though we could easily use a new @trailing attribute instead to indicate that the closure can be used in this way. My example isn’t very good as I can’t think of a case that really, really needs this, but I think they’re probably out there.

To summarise, having come down off my initial enthusiasm for trailing closures I’m not sure that they really add that much syntactically, especially in the most common cases, while actually being a little ambiguous looking and adding inconsistency to the language. I think they should remain for the less common cases that can really benefit from them, but as a feature that is opted into, so that we can go for consistency by default.

I’m interested to hear other people’s thoughts.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #7

I’m not proposing to remove them entirely, in fact your lock example is a perfect example of when a trailing closure makes the most sense, as a form of customised language feature. But I’m wondering if perhaps cases like these should be created using an attribute that specifically enables it? e.g- your definition could become:

  func protect(action: @trailing (Void) -> Void) { … }

It’s other cases like common usages of .map() and similar methods where I’ve found myself using the closure in its trailing form less and less, and am not as sure if it’s really needed, or may actually be more of a detriment to the language than a benefit.

···

On 24 Mar 2016, at 16:13, William Dillon <william@housedillon.com> wrote:

On Mar 24, 2016, at 7:18 AM, Kurt Werle via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Coming from ruby, I'm quite fond of trailing closures. I couldn't really give you a concrete reason why - putting them in the ()'s really isn't that big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the more complex the closure (describing context variables and return types, throws, etc) the uglier it seem to me to put it inside parens.

Agree.

Another thing I like about trailing closures is that it allows me to make custom constructs that feel more like a part of the language. For example, I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at the end. On this one I’d definitely say that if you don’t like it, don’t use it. I don’t *think* that you’re forced to use it anywhere. It’s a hard sell to take it away from everyone.

- Will


(Howard Lovatt) #8

I use trailing closures all the time because I find the brackets too noisy,
like ; at the end of a line is too noisy. The sort of code I use is:

    let foo = myArray
        .filter { $0 & 1 == 1 }
        .map { $0 + 1 }
        .reduce(0) { $0 + $1 }

···

On Friday, 25 March 2016, Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

When I started using Swift I was initially very enthusiastic about
trailing closures, but I’ve actually kind of gone off them somewhat and I’d
like to discuss why.

Firstly, here are two ways to write a common example using the .map()
method:

let foo = myArray.map { $0 + 1 }
let foo = myArray.map({ $0 + 1 })

It’s tough to say that the first form is any neater than the second, other
than the second having more brackets. However, the first form is somewhat
ambiguous, as .map in this case looks like a property rather than a method,
it also visually looks like a statement, followed by a closure rather than
the two things logically being related. Of course it’s quick to learn that
these are related, but for consistency I’m starting to now prefer the use
of parenthesis in almost all cases.

The other advantage of trailing closures is the omission of the label, but
trailing closures aren’t strictly necessary for this, as we can already
omit external labels for parameters if we want to, and the example above
shows that a trailing closure isn’t necessary for this. The only real
difference is that the trailing closure form makes a label optional,
because you can either provide the closure with label in parenthesis (if
the label is required) or omit it by trailing, like so:

something.someMethod(foo: 1, predicate: { $0 < $1})
something.someMethod(foo: 1) { $0 < $1}

However this kind of arbitrarily makes the following impossible:

something.someMethod(foo: 1, { $0 < $1 })

With this in mind it seems to me that we might be better served by the
ability to make external labels optional, as this would allow us to be just
as succinct, while being completely clear about what is being passed into
this method.

The only real remaining advantage that I see to trailing closures is the
ability to define pseudo language constructs, for example:

func repeatUntilEmpty<C:CollectionType>(collection:C, @noescape _ body:()
throws -> Void) rethrows { while !collection.isEmpty { body() } }
repeatUntilEmpty(myArray) {
/* Do something over and over until myArray is empty */
}

Which I think is a pretty uncommon type of structure, but could be useful
in some specialised situations. To support this though we could easily use
a new @trailing attribute instead to indicate that the closure can be used
in this way. My example isn’t very good as I can’t think of a case that
really, really needs this, but I think they’re probably out there.

To summarise, having come down off my initial enthusiasm for trailing
closures I’m not sure that they really add that much syntactically,
especially in the most common cases, while actually being a little
ambiguous looking and adding inconsistency to the language. I think they
should remain for the less common cases that can really benefit from them,
but as a feature that is opted into, so that we can go for consistency by
default.

I’m interested to hear other people’s thoughts.

--
-- Howard.


(Radek Pietruszewski) #9

I have to admit I’m quite fond of the trailing closure syntax. This is partly because of my earlier experiences with Ruby. But partly I just really found it more readable to see a separation between a regular function call, with just normal parameters passed in, and higher order functions, like the `map` example you’ve made. Having two sets of parentheses/braces like foo({ … }) really makes them blend in and I don’t notice the curly braces as much. Having a stylistic separation between:

  foo(argument)
  foo { code }

helps me catch the difference between the two.

But it is a stylistic choice. Like I mentioned in a different reply, if we’re going to have trailing closures at all, I don’t see a point in enforcing this other than with a linter.

* * *

The only way I see this working is if the trailing closure syntax has *other* semantics as well. For example, there’s been a discussion about allowing specially marked closures to return/break/continue in the scope of the caller function, not the closure’s scope (like Ruby’s blocks do). Now, that would make a bit more sense. You’d have a special attribute (can’t think of a name, but let’s stick with your @trailing), that would imply:

- required trailing closure syntax
- @noescape, I think
- and return/break/continue affects outside functions

And you could use that to make your own pseudo-language-constructs. This would enforce trailing closure syntax, and nothing else could use trailing closure syntax.

Now, I wouldn’t be super happy about not being able to write `map { foo }` myself :grin: — but _that_ proposal would make sense to me. It wouldn’t just be enforcement of a stylistic choice, but the syntax would actually convey some important semantics.

What do you think?

— Radek

···

On 24 Mar 2016, at 14:57, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

When I started using Swift I was initially very enthusiastic about trailing closures, but I’ve actually kind of gone off them somewhat and I’d like to discuss why.

Firstly, here are two ways to write a common example using the .map() method:

  let foo = myArray.map { $0 + 1 }
  let foo = myArray.map({ $0 + 1 })

It’s tough to say that the first form is any neater than the second, other than the second having more brackets. However, the first form is somewhat ambiguous, as .map in this case looks like a property rather than a method, it also visually looks like a statement, followed by a closure rather than the two things logically being related. Of course it’s quick to learn that these are related, but for consistency I’m starting to now prefer the use of parenthesis in almost all cases.

The other advantage of trailing closures is the omission of the label, but trailing closures aren’t strictly necessary for this, as we can already omit external labels for parameters if we want to, and the example above shows that a trailing closure isn’t necessary for this. The only real difference is that the trailing closure form makes a label optional, because you can either provide the closure with label in parenthesis (if the label is required) or omit it by trailing, like so:

  something.someMethod(foo: 1, predicate: { $0 < $1})
  something.someMethod(foo: 1) { $0 < $1}

However this kind of arbitrarily makes the following impossible:

  something.someMethod(foo: 1, { $0 < $1 })

With this in mind it seems to me that we might be better served by the ability to make external labels optional, as this would allow us to be just as succinct, while being completely clear about what is being passed into this method.

The only real remaining advantage that I see to trailing closures is the ability to define pseudo language constructs, for example:

  func repeatUntilEmpty<C:CollectionType>(collection:C, @noescape _ body:() throws -> Void) rethrows { while !collection.isEmpty { body() } }
  repeatUntilEmpty(myArray) {
    /* Do something over and over until myArray is empty */
  }

Which I think is a pretty uncommon type of structure, but could be useful in some specialised situations. To support this though we could easily use a new @trailing attribute instead to indicate that the closure can be used in this way. My example isn’t very good as I can’t think of a case that really, really needs this, but I think they’re probably out there.

To summarise, having come down off my initial enthusiasm for trailing closures I’m not sure that they really add that much syntactically, especially in the most common cases, while actually being a little ambiguous looking and adding inconsistency to the language. I think they should remain for the less common cases that can really benefit from them, but as a feature that is opted into, so that we can go for consistency by default.

I’m interested to hear other people’s thoughts.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #10

I follow the "Rule of Kevin", which is not language enforced. Parens around functional
closures (->T), not around procedural (->Void) ones. This promotes "language construct"-like
Void calls, avoids compiler parsing issues when chaining (or using "guard", "if", etc). It lets
me know instantly how the closure is used.

While I was originally reluctant to adopt it, its advantages have become self-evident over time.
This ends up being slightly wordier, especially in the few cases you need to use argument labels.

I think it's worth it.

-- E

···

On Mar 24, 2016, at 10:41 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 24 Mar 2016, at 16:13, William Dillon <william@housedillon.com <mailto:william@housedillon.com>> wrote:
Another thing I like about trailing closures is that it allows me to make custom constructs that feel more like a part of the language. For example, I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at the end. On this one I’d definitely say that if you don’t like it, don’t use it. I don’t *think* that you’re forced to use it anywhere. It’s a hard sell to take it away from everyone.

- Will

I’m not proposing to remove them entirely, in fact your lock example is a perfect example of when a trailing closure makes the most sense, as a form of customised language feature. But I’m wondering if perhaps cases like these should be created using an attribute that specifically enables it? e.g- your definition could become:

  func protect(action: @trailing (Void) -> Void) { … }

It’s other cases like common usages of .map() and similar methods where I’ve found myself using the closure in its trailing form less and less, and am not as sure if it’s really needed, or may actually be more of a detriment to the language than a benefit.


(Rainer Brockerhoff) #11

I find trailing closures extremely valuable for APIs like GCD and
NSURLSession and derivates. No more dangling )s.

It's probably my own (style) fault, but in ObjC I sometimes see lines like
  })]}));
making my own code become read-only :slight_smile:

···

On 3/24/16 13:59, Erica Sadun via swift-evolution wrote:

On Mar 24, 2016, at 10:41 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 24 Mar 2016, at 16:13, William Dillon <william@housedillon.com <mailto:william@housedillon.com>> wrote:
Another thing I like about trailing closures is that it allows me to make custom constructs that feel more like a part of the language. For example, I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at the end. On this one I’d definitely say that if you don’t like it, don’t use it. I don’t *think* that you’re forced to use it anywhere. It’s a hard sell to take it away from everyone.

- Will

I’m not proposing to remove them entirely, in fact your lock example is a perfect example of when a trailing closure makes the most sense, as a form of customised language feature. But I’m wondering if perhaps cases like these should be created using an attribute that specifically enables it? e.g- your definition could become:

  func protect(action: @trailing (Void) -> Void) { … }

It’s other cases like common usages of .map() and similar methods where I’ve found myself using the closure in its trailing form less and less, and am not as sure if it’s really needed, or may actually be more of a detriment to the language than a benefit.

I follow the "Rule of Kevin", which is not language enforced. Parens around functional
closures (->T), not around procedural (->Void) ones. This promotes "language construct"-like
Void calls, avoids compiler parsing issues when chaining (or using "guard", "if", etc). It lets
me know instantly how the closure is used.

While I was originally reluctant to adopt it, its advantages have become self-evident over time.
This ends up being slightly wordier, especially in the few cases you need to use argument labels.

--
Rainer Brockerhoff <rainer@brockerhoff.net>
Belo Horizonte, Brazil
"In the affairs of others even fools are wise
In their own business even sages err."
http://brockerhoff.net/blog/


(Juan Ignacio Laube) #12

I think the feature should stay but we need community guidelines on when and when not to use them.

I agree with James here.

···

On Mar 24, 2016, at 1:15 PM, James Campbell via swift-evolution <swift-evolution@swift.org> wrote:

I think the feature should stay but we need community guidelines on when and when not to use them.

___________________________________

James⎥Head Of CEO

james@supmenow.com <mailto:james@supmenow.com>⎥supmenow.com <http://supmenow.com/>
Sup

Runway East >

10 Finsbury Square

London

> EC2A 1AF

On Thu, Mar 24, 2016 at 4:13 PM, William Dillon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 24, 2016, at 7:18 AM, Kurt Werle via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Coming from ruby, I'm quite fond of trailing closures. I couldn't really give you a concrete reason why - putting them in the ()'s really isn't that big a deal. But I'll say that I move them outside every single time...

I will say that your examples are the most trivial possible and that the more complex the closure (describing context variables and return types, throws, etc) the uglier it seem to me to put it inside parens.

Agree.

Another thing I like about trailing closures is that it allows me to make custom constructs that feel more like a part of the language. For example, I really love this extension for NSLock that I have:

extension NSLock {
    func protect(action: (Void) -> Void) {
        self.lock()
        action()
        self.unlock()
    }
}

Now, whenever I need to use my lock, I can just do:

peersLock.protect {
    outputString += "\(self.peers.count) peers:\n"
    for (_, peer) in self.peers {
        outputString += "\(peer)\n"
    }
}

To me, it looks cleaner to me to not have this paren dangling around at the end. On this one I’d definitely say that if you don’t like it, don’t use it. I don’t *think* that you’re forced to use it anywhere. It’s a hard sell to take it away from everyone.

- Will

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

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


(Haravikk) #13

That’s a pretty good rule, and I think it’s what I’m now doing myself as well. Any thoughts on whether it should be enforced, though?
There’s a thread currently about allowing trailing closures within guard etc., but personally I think that that’s a bad idea, and that it may actually be better to head in the opposite direction (use them less), but currently it’s an automatic feature which I’m not sure is a good thing.

Given your “Rule of Kevin” we could have the a rule for closures as a last parameter that if they have a non-Void return type, they must add a new @trailing attribute, otherwise trailing is implied.

I realise there’s an argument to be made that it should just be up to linters or personal preference, but I’m concerned that many developers like myself will use trailing closures everywhere they’re permitted because it seems like the right thing to do, and only later start to consider whether it’s a good idea to actually use them in specific cases. But for consistency’s sake I’d say it’s not good to use them except for language construct type calls (like .forEach actually, which I always forget about too), which is where the main advantage lies.

···

On 24 Mar 2016, at 16:59, Erica Sadun <erica@ericasadun.com> wrote:

I follow the "Rule of Kevin", which is not language enforced. Parens around functional
closures (->T), not around procedural (->Void) ones. This promotes "language construct"-like
Void calls, avoids compiler parsing issues when chaining (or using "guard", "if", etc). It lets
me know instantly how the closure is used.

While I was originally reluctant to adopt it, its advantages have become self-evident over time.
This ends up being slightly wordier, especially in the few cases you need to use argument labels.

I think it's worth it.


(Andrey Tarantsov) #14

I use trailing closures all the time because I find the brackets too noisy, like ; at the end of a line is too noisy. The sort of code I use is:

    let foo = myArray
        .filter { $0 & 1 == 1 }
        .map { $0 + 1 }
        .reduce(0) { $0 + $1 }

+1 to this. Please don't remove them, they're great.

A.


(Haravikk) #15

This doesn’t really seem much neater or more readable to me than:

  let foo = myArray
    .filter({ $0 & 1 == 1 })
    .map({ $0 + 1 })
    .reduce(0, { $0 + $1 })

While they may add a small amount of noise, in the latter case the parenthesis clarifies that each is a method, and what all of its arguments are, and don’t use any custom syntax. Of course this assumes that .reduce() would have an optional label on its closure argument.

···

On 24 Mar 2016, at 21:08, Howard Lovatt <howard.lovatt@gmail.com> wrote:

I use trailing closures all the time because I find the brackets too noisy, like ; at the end of a line is too noisy. The sort of code I use is:

    let foo = myArray
        .filter { $0 & 1 == 1 }
        .map { $0 + 1 }
        .reduce(0) { $0 + $1 }


(Brent Royal-Gordon) #16

The only way I see this working is if the trailing closure syntax has *other* semantics as well. For example, there’s been a discussion about allowing specially marked closures to return/break/continue in the scope of the caller function, not the closure’s scope (like Ruby’s blocks do). Now, that would make a bit more sense. You’d have a special attribute (can’t think of a name, but let’s stick with your @trailing), that would imply:

- required trailing closure syntax
- @noescape, I think
- and return/break/continue affects outside functions

And you could use that to make your own pseudo-language-constructs. This would enforce trailing closure syntax, and nothing else could use trailing closure syntax.

What do you think?

First of all, you're right that a block that could return/break/continue/etc. would need to be @noescape. Otherwise, what would those keywords do if the function had already returned?

I think requring trailing closures to be @noescape would take away some very compelling use cases for them. In particular, you would lose the ability to use trailing closures for asynchronous code, like completion functions. I, for one, would be sad to lose those—they are often some of the largest, most block-like pieces of code that you put into a closure.

At the same time, many of the things which people seem to think shouldn't be trailing closures, like `map` blocks, seem like good candidates for early returns or other control flow.

Basically, if you imagine a Venn diagram of "closures it would be useful to break out of early" and "closures you might want to use trailing closure syntax with", I don't think there's a particularly large amount of overlap between the two circles.

···

--
Brent Royal-Gordon
Architechies


(Radek Pietruszewski) #17

I’m not proposing to remove them entirely, in fact your lock example is a perfect example of when a trailing closure makes the most sense, as a form of customised language feature. But I’m wondering if perhaps cases like these should be created using an attribute that specifically enables it? e.g- your definition could become:

  func protect(action: @trailing (Void) -> Void) { … }

It’s other cases like common usages of .map() and similar methods where I’ve found myself using the closure in its trailing form less and less, and am not as sure if it’s really needed, or may actually be more of a detriment to the language than a benefit.

I follow the "Rule of Kevin", which is not language enforced. Parens around functional
closures (->T), not around procedural (->Void) ones. This promotes "language construct"-like
Void calls, avoids compiler parsing issues when chaining (or using "guard", "if", etc). It lets
me know instantly how the closure is used.

While I was originally reluctant to adopt it, its advantages have become self-evident over time.
This ends up being slightly wordier, especially in the few cases you need to use argument labels.

I think it's worth it.

I don’t follow the Rule of Kevin myself, although I’m not against it either, and I see why some people would like it.

But, if we’re going to have trailing closures at all (which seems desirable for creating things that look like language features), enforcement based on syntactic preference doesn’t make sense to me.

This really reminds me of a “remove implicit self” discussion. Some people (me included) were against, because they disliked the noisiness of “self” everywhere. Others argued that the implicitness is somewhat unsafe. A valid position to take. But it’s a kind of thing teams can use a linter for, if they want to enforce it as a rule.

Trailing closures seem like a similar thing. We could remove it altogether (although I think that would be a shame), or let’s just leave it up to preference (and developing guidelines) on where to use them.

Best,
— Radek


(Radek Pietruszewski) #18

I think requring trailing closures to be @noescape would take away some very compelling use cases for them. In particular, you would lose the ability to use trailing closures for asynchronous code, like completion functions. I, for one, would be sad to lose those—they are often some of the largest, most block-like pieces of code that you put into a closure.

I, too, would be sad about that — but aside from a syntactic/stylistic choice, there aren’t any important-to-the-compiler semantics conveyed by the trailing closure, are there?

At the same time, many of the things which people seem to think shouldn't be trailing closures, like `map` blocks, seem like good candidates for early returns or other control flow.

I *don’t* think `map` blocks should be able to control the outside scope. Map should map, that is transform. `forEach` should probably be able to do outside function control flow, and the use of trailing closure would be consistent with the Rule of Kevin.

Basically, if you imagine a Venn diagram of "closures it would be useful to break out of early" and "closures you might want to use trailing closure syntax with", I don't think there's a particularly large amount of overlap between the two circles.

Aside from `map`, do you have other examples where there is no overlap?

— Radek


(Thorsten Seitz) #19

+1 from me as well.

-Thorsten

···

Am 24.03.2016 um 22:31 schrieb Andrey Tarantsov via swift-evolution <swift-evolution@swift.org>:

I use trailing closures all the time because I find the brackets too noisy, like ; at the end of a line is too noisy. The sort of code I use is:

    let foo = myArray
        .filter { $0 & 1 == 1 }
        .map { $0 + 1 }
        .reduce(0) { $0 + $1 }

+1 to this. Please don't remove them, they're great.


(Erica Sadun) #20

I'm agnostic on enforcement. I'm unthrilled about adding `@trailing`.

-- E

···

On Mar 24, 2016, at 11:57 AM, Haravikk <swift-evolution@haravikk.me> wrote:

On 24 Mar 2016, at 16:59, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

I follow the "Rule of Kevin", which is not language enforced. Parens around functional
closures (->T), not around procedural (->Void) ones. This promotes "language construct"-like
Void calls, avoids compiler parsing issues when chaining (or using "guard", "if", etc). It lets
me know instantly how the closure is used.

While I was originally reluctant to adopt it, its advantages have become self-evident over time.
This ends up being slightly wordier, especially in the few cases you need to use argument labels.

I think it's worth it.

That’s a pretty good rule, and I think it’s what I’m now doing myself as well. Any thoughts on whether it should be enforced, though?
There’s a thread currently about allowing trailing closures within guard etc., but personally I think that that’s a bad idea, and that it may actually be better to head in the opposite direction (use them less), but currently it’s an automatic feature which I’m not sure is a good thing.

Given your “Rule of Kevin” we could have the a rule for closures as a last parameter that if they have a non-Void return type, they must add a new @trailing attribute, otherwise trailing is implied.

I realise there’s an argument to be made that it should just be up to linters or personal preference, but I’m concerned that many developers like myself will use trailing closures everywhere they’re permitted because it seems like the right thing to do, and only later start to consider whether it’s a good idea to actually use them in specific cases. But for consistency’s sake I’d say it’s not good to use them except for language construct type calls (like .forEach actually, which I always forget about too), which is where the main advantage lies.