Proposal: Make $0 always refer to a closure’s first argument


(Paul Cantrell) #1

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

This is certainly confusing. I’m posting to the list after receiving two Siesta user questions in the same day that both boil down to this issue.

Even if you do understand the behavior, it’s a real nuisance: it prevents concise implementation of a multi-arg closure which wants to ignore all but its first argument. Instead, such a closure has to drop back to the more verbose syntax:

    bar { a, _ in foo(a) } // sigh

…or use this legibility-proof workaround:

    bar { foo($0.0) } // yuck! wat?!

(Note that this problem exists only for the first argument; a closure that wants to ignore all but the second has no such problem.)

This behavior contradicts the Swift documentation, which clearly says that $0 refers to the first argument:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

And:

A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on.

I can’t find anything in the docs that mentions this “all args tuple” behavior, so perhaps it’s a bug? Let me know if it is, and I’ll just file a bug report for it.

However the “whole tuple” behavior does seem to be intentional, and preserving that while fixing the problem above appears to require a language change. Thus…

Proposal

The implicit closure variable $0 should always refer to the closure’s first argument, and a different implicit name — perhaps $* or $_ or $... — should refer to the all-args tuple.

Thoughts?

Cheers,

Paul


(John McCall) #2

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

John.

···

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

This is certainly confusing. I’m posting to the list after receiving two Siesta user questions in the same day that both boil down to this issue.

Even if you do understand the behavior, it’s a real nuisance: it prevents concise implementation of a multi-arg closure which wants to ignore all but its first argument. Instead, such a closure has to drop back to the more verbose syntax:

    bar { a, _ in foo(a) } // sigh

…or use this legibility-proof workaround:

    bar { foo($0.0) } // yuck! wat?!

(Note that this problem exists only for the first argument; a closure that wants to ignore all but the second has no such problem.)

This behavior contradicts the Swift documentation, which clearly says that $0 refers to the first argument:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

And:

A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on.

I can’t find anything in the docs that mentions this “all args tuple” behavior, so perhaps it’s a bug? Let me know if it is, and I’ll just file a bug report for it.

However the “whole tuple” behavior does seem to be intentional, and preserving that while fixing the problem above appears to require a language change. Thus…

Proposal

The implicit closure variable $0 should always refer to the closure’s first argument, and a different implicit name — perhaps $* or $_ or $... — should refer to the all-args tuple.

Thoughts?

Cheers,

Paul

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


(Paul Cantrell) #3

Roger that. Filed the bug: https://bugs.swift.org/browse/SR-586

Thanks!

P

···

On Jan 19, 2016, at 10:35 PM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.


(Jordan Rose) #4

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

Jordan

···

On Jan 19, 2016, at 20:35 , John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.


(Erica Sadun) #5

I like the idea of distinguishing full argument tuple from its members.

I dislike $_ because the meaning of _ is more "wildcard ignore" than "wildcard match and represent"
I also dislike $* (product) and $... (because it's just horrible)

I don't mind $$ or $#, and could see adding $$.0 or $#.1 aliases for $0 and $1.

-- E

···

On Jan 19, 2016, at 8:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

This is certainly confusing. I’m posting to the list after receiving two Siesta user questions in the same day that both boil down to this issue.

Even if you do understand the behavior, it’s a real nuisance: it prevents concise implementation of a multi-arg closure which wants to ignore all but its first argument. Instead, such a closure has to drop back to the more verbose syntax:

    bar { a, _ in foo(a) } // sigh

…or use this legibility-proof workaround:

    bar { foo($0.0) } // yuck! wat?!

(Note that this problem exists only for the first argument; a closure that wants to ignore all but the second has no such problem.)

This behavior contradicts the Swift documentation, which clearly says that $0 refers to the first argument:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

And:

A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on.

I can’t find anything in the docs that mentions this “all args tuple” behavior, so perhaps it’s a bug? Let me know if it is, and I’ll just file a bug report for it.

However the “whole tuple” behavior does seem to be intentional, and preserving that while fixing the problem above appears to require a language change. Thus…

Proposal

The implicit closure variable $0 should always refer to the closure’s first argument, and a different implicit name — perhaps $* or $_ or $... — should refer to the all-args tuple.

Thoughts?

Cheers,

Paul

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


(Joe Groff) #6

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

-Joe

···

On Jan 20, 2016, at 8:57 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 20:35 , John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.


(John McCall) #7

Yeah, that makes sense to me. $_, clearly. :slight_smile:

John.

···

On Jan 20, 2016, at 9:49 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 20, 2016, at 8:57 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 19, 2016, at 20:35 , John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.


(David Waite) #8

Since $<identifier> is reserved for the repl/debugger, $<operator character> is troublesome for parsing, and $<reserved character> could result in odd special cases or limitations in language evolution - how about just $ ?

e.g., $0 is semantically a shortcut for $.0

-DW

···

On Jan 20, 2016, at 10:49 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 8:57 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 19, 2016, at 20:35 , John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

-Joe

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


(Chris Lattner) #9

Likewise, on the caller side, we should have a “splat” sigil to splat a tuple into the argument list of a call. We need a similar thing for varargs to enable vararg forwarding as well.

-Chris

···

On Jan 20, 2016, at 9:49 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.


(Thorsten Seitz) #10

+1 :slight_smile:

-Thorsten

···

Am 20.01.2016 um 18:49 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

-Joe

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


(TJ Usiyan) #11

`$$` Doesn't take another sigil away and shouldn't be ambiguous since it
isn't used in current syntax.

···

On Wed, Jan 20, 2016 at 2:39 PM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 9:49 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 20, 2016, at 8:57 AM, Jordan Rose via swift-evolution < > swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 20:35 , John McCall via swift-evolution < > swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution < > swift-evolution@swift.org> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to
the tuple of all arguments, or to just the first argument. The presence of
another placeholder variable ($1 in the third example) seems to trigger
the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a
tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the
closure. Thus, a two-argument closure falls down if you ignore the second
argument. It’s dumb, and we’ve known about it for a long time; and yet
it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

I wouldn't go as far as to say it's a bug. It's known and occasionally
useful for forwarding arguments. (For a while I had it as a fix-it for
doing function representation conversions.)

I agree that having it always be the first argument is less surprising and
probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish
arguments from tuples, we probably ought to introduce a separate '$*'-like
sigil to bind "all arguments" distinct from $0.

Yeah, that makes sense to me. $_, clearly. :slight_smile:

John.

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


(Thorsten Seitz) #12

That’s a good idea!

-Thorsten

PS: we might also use $0 for the tuple and $1, … for the args, like regex syntax does ($0 = whole match, $1 = first group etc.). This would not work so well with the general zero-based index behavior of Swift.

···

Am 20.01.2016 um 20:05 schrieb David Waite via swift-evolution <swift-evolution@swift.org>:

Since $<identifier> is reserved for the repl/debugger, $<operator character> is troublesome for parsing, and $<reserved character> could result in odd special cases or limitations in language evolution - how about just $ ?

e.g., $0 is semantically a shortcut for $.0

-DW


(Paul Cantrell) #13

Where does this leave the discussion, then? I’ve filed it as a bug, though there’s disagreement about whether it is.

Can we split this into two questions? My wish and dream would to get $0 fixed to match the docs (i.e. only match the first param) in 2.2. That’s an ongoing pain point.

The $* (or similar) feature would be handy, but seems like it should be accompanied by a larger vararg splatting discussion, and should probably fall to Swift 4+ given the release goals for Swift 3 — which is fine with me, but I’d really hate to have to wait that long to see $0 fixed.

Cheers,

Paul

···

On Jan 20, 2016, at 4:55 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 9:49 AM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

Likewise, on the caller side, we should have a “splat” sigil to splat a tuple into the argument list of a call. We need a similar thing for varargs to enable vararg forwarding as well.


(Chris Lattner) #14

I’m sorry, I’ve lost context on what the proposal here is. Can you please restate it? To me, it seems most natural that $0 always refer to the first parameter of a closure.

-Chris

···

On Jan 21, 2016, at 1:05 PM, Paul Cantrell <cantrell@pobox.com> wrote:

On Jan 20, 2016, at 4:55 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 20, 2016, at 9:49 AM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

Likewise, on the caller side, we should have a “splat” sigil to splat a tuple into the argument list of a call. We need a similar thing for varargs to enable vararg forwarding as well.

Where does this leave the discussion, then? I’ve filed it as a bug, though there’s disagreement about whether it is.

Can we split this into two questions? My wish and dream would to get $0 fixed to match the docs (i.e. only match the first param) in 2.2. That’s an ongoing pain point.

The $* (or similar) feature would be handy, but seems like it should be accompanied by a larger vararg splatting discussion, and should probably fall to Swift 4+ given the release goals for Swift 3 — which is fine with me, but I’d really hate to have to wait that long to see $0 fixed.


(Paul Cantrell) #15

Bumping this. I don’t much care about the $* question, but am eager to see the $0 overloading fixed.

Can the core team make a call on whether I should write a proposal to make that happen, or whether filing the bug is sufficient?

Cheers,

Paul

···

On Jan 21, 2016, at 3:05 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 4:55 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 9:49 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 10:35 PM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 7:20 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

It’s dumber than that. The type-checker assumes that the closure has a tuple of arguments ($0, $1, …, $N), where $N is the largest N seen in the closure. Thus, a two-argument closure falls down if you ignore the second argument. It’s dumb, and we’ve known about it for a long time; and yet it’s been remarkably annoying to fix, and so we haven’t yet.

Anyway, it’s a bug and doesn’t need to go through evolution.

I wouldn't go as far as to say it's a bug. It's known and occasionally useful for forwarding arguments. (For a while I had it as a fix-it for doing function representation conversions.)

I agree that having it always be the first argument is less surprising and probably more generally useful, though.

In the spirit of the Great Argument Simplification to distinguish arguments from tuples, we probably ought to introduce a separate '$*'-like sigil to bind "all arguments" distinct from $0.

Likewise, on the caller side, we should have a “splat” sigil to splat a tuple into the argument list of a call. We need a similar thing for varargs to enable vararg forwarding as well.

Where does this leave the discussion, then? I’ve filed it as a bug, though there’s disagreement about whether it is.

Can we split this into two questions? My wish and dream would to get $0 fixed to match the docs (i.e. only match the first param) in 2.2. That’s an ongoing pain point.

The $* (or similar) feature would be handy, but seems like it should be accompanied by a larger vararg splatting discussion, and should probably fall to Swift 4+ given the release goals for Swift 3 — which is fine with me, but I’d really hate to have to wait that long to see $0 fixed.


(David Sweeris) #16

I’m sorry, I’ve lost context on what the proposal here is. Can you please restate it? To me, it seems most natural that $0 always refer to the first parameter of a closure.

-Chris

Paul Cantrell’s original post documented behavior which causes $0 to sometimes refer to a tuple of all the arguments instead of the just first argument. John McCall said it was bug, but then Jordan Rose replied and said, ”I wouldn’t go as far as to say it’s a bug. It’s known and occasionally useful for forwarding arguments". My understanding is that everyone agrees on two things: the bug should eventually be fixed, and the bug’s functionality is actually kinda cool (when it’s not biting you) and there should still be a way to invoke it. The debate was over whether to do it now and make $*, $_, $$, $…, or $# (I think that’s all of them) be the “all args” tuple, or whether we should wait fix it until Swift gets “a more complete revision of the varargs system” (which was assumed to be at least Swift 4).

At least that’s how I understand it. Here’s Paul’s original post:

- Dave Sweeris

···

On Jan 27, 2016, at 10:09, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 19, 2016, at 19:20, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

This is certainly confusing. I’m posting to the list after receiving two Siesta user questions in the same day that both boil down to this issue.

Even if you do understand the behavior, it’s a real nuisance: it prevents concise implementation of a multi-arg closure which wants to ignore all but its first argument. Instead, such a closure has to drop back to the more verbose syntax:

    bar { a, _ in foo(a) } // sigh

…or use this legibility-proof workaround:

    bar { foo($0.0) } // yuck! wat?!

(Note that this problem exists only for the first argument; a closure that wants to ignore all but the second has no such problem.)

This behavior contradicts the Swift documentation, which clearly says that $0 refers to the first argument:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

And:

A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on.

I can’t find anything in the docs that mentions this “all args tuple” behavior, so perhaps it’s a bug? Let me know if it is, and I’ll just file a bug report for it.

However the “whole tuple” behavior does seem to be intentional, and preserving that while fixing the problem above appears to require a language change. Thus…

Proposal

The implicit closure variable $0 should always refer to the closure’s first argument, and a different implicit name — perhaps $* or $_ or $... — should refer to the all-args tuple.

Thoughts?

Cheers,

Paul

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


(Paul Cantrell) #17

I’m sorry, I’ve lost context on what the proposal here is. Can you please restate it? To me, it seems most natural that $0 always refer to the first parameter of a closure.

-Chris

Paul Cantrell’s original post documented behavior which causes $0 to sometimes refer to a tuple of all the arguments instead of the just first argument. John McCall said it was bug, but then Jordan Rose replied and said, ”I wouldn’t go as far as to say it’s a bug. It’s known and occasionally useful for forwarding arguments". My understanding is that everyone agrees on two things: the bug should eventually be fixed, and the bug’s functionality is actually kinda cool (when it’s not biting you) and there should still be a way to invoke it. The debate was over whether to do it now and make $*, $_, $$, $…, or $# (I think that’s all of them) be the “all args” tuple, or whether we should wait fix it until Swift gets “a more complete revision of the varargs system” (which was assumed to be at least Swift 4).

An excellent summary, to which I’d just add: it’s unclear to me whether we need to wait for $* (or whatever the new language feature is) in order to make the “$0 is always the first arg” fix. So there is a third option, which I’d prefer:

1. Declare current “$0 is sometimes a tuple” behavior a bug.
2. Fix it soon so that $0 is always the first arg, maybe even in 2.x.
3. Implement either $* and/or more comprehensive advanced varargs later, probably in 4+.

The case for this approach is:

• the current behavior of $0 is somewhat nonsensical,
• confusion caused by this behavior is common, and
• use of $0 as an all-args tuple is rare and (mostly) easy to work around.

However, this approach hinges on deciding whether the current behavior is a bug or a feature. That's a call I think the core team has to make.

Cheers, P

···

On Jan 27, 2016, at 2:20 PM, Dave via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 27, 2016, at 10:09, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

At least that’s how I understand it. Here’s Paul’s original post:

- Dave Sweeris

On Jan 19, 2016, at 19:20, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Surprisingly, this code does not compile:

    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) } // compiler error
    bar { foo($1) } // just dandy
    bar { foo($0 + $1) } // also works

The compiler error is:

    Cannot convert value of type (Int, Int) to expected argument type Int

It appears that the meaning of $0 is overloaded: it can refer either to the tuple of all arguments, or to just the first argument. The presence of another placeholder variable ($1 in the third example) seems to trigger the latter behavior.

This is certainly confusing. I’m posting to the list after receiving two Siesta user questions in the same day that both boil down to this issue.

Even if you do understand the behavior, it’s a real nuisance: it prevents concise implementation of a multi-arg closure which wants to ignore all but its first argument. Instead, such a closure has to drop back to the more verbose syntax:

    bar { a, _ in foo(a) } // sigh

…or use this legibility-proof workaround:

    bar { foo($0.0) } // yuck! wat?!

(Note that this problem exists only for the first argument; a closure that wants to ignore all but the second has no such problem.)

This behavior contradicts the Swift documentation, which clearly says that $0 refers to the first argument:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

And:

A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on.

I can’t find anything in the docs that mentions this “all args tuple” behavior, so perhaps it’s a bug? Let me know if it is, and I’ll just file a bug report for it.

However the “whole tuple” behavior does seem to be intentional, and preserving that while fixing the problem above appears to require a language change. Thus…

Proposal

The implicit closure variable $0 should always refer to the closure’s first argument, and a different implicit name — perhaps $* or $_ or $... — should refer to the all-args tuple.

Thoughts?

Cheers,

Paul

_______________________________________________
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