Proposal: Always flatten the single element tuple

Also, I want to repeat what I said on the other thread. We should revert to Swift 3 behavior for this, and then take the time to design the behavior we really want (either for 4.1 or 5). Anything else will cause us to break people’s code twice…

Thanks,
Jon

···

On Jun 7, 2017, at 11:50 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

Le 7 juin 2017 à 20:33, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> a écrit :

For example, take those three functions:

  func f(_ closure:(Int, Int) -> ())
  func g(_ closure:((Int, Int)) -> ())
  func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

  f { (a, b) in ... }
  g { (a, b) in ... }
  c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key, value: Value) tuples (their Element type). Problem is that 'key' and 'value' are identifiers that only matter for dictionaries, not for dictionary users. It's very important for dictionary users to forget about tuples, and the `key` and `value` words:

  // No pollution
  dictionary.map { (name, score) in ... }

It looks like some people in this mailing list are horrified by this "request" (not a feature request, but a request that Swift 3 behavior is restored, actually).

What could be the reasons for such a bad reaction?

1: measurable runtime overhead (slower programs in some cases, without any obvious way for the developper to notice where is the extra cost)
2: measurable compiler overhead (slower compilation)
3: implementation complexity (slower swift progress, technical debt, etc.)
4: other?

I understand 1. We are all fascinated by C++ and Rust "zero-overhead". If this is the main concern of the community, then we may focus the discussion of that very precise topic.

I can live with 2 (just a personal subjective preference)

About 3: I can not tell because I lack the necessary skills.

4: enlighten us!

Gwendal

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

SE-0110 may be an obvious extension of the proposed goal, but it is clear

that it has been implemented in Swift 4, and that the consequences are
derived of those changesets.

Those "unwanted" consequences can be reverted by temporarily reverting
SE-0110, without touching any other previous proposal.

In no place I see Gwendal asking for full reversal of 5 proposals. He is
just voicing something that a lot of app developers and library maintainers
are not going to understand in September.

For example, you can see all the changes needed in RXSwift so that it
compiles to Swift 4:

https://github.com/ReactiveX/RxSwift/pull/1282/commits/915e0
0fa6d1e59d58cd8c38dd6dc83765fc67fe4

I would not want to migrate to Swift 4 an app using such framework.

I asked you in my first reply to you, is your view that the distinction
itself between (Int, Int) -> Int and ((Int, Int)) -> Int is problematic? If
so, this is a very different discussion from seeking solutions to mitigate
particular ergonomic issues that arise from the change. However, you have
not answered the question.

As far as I can see, the bigger usability regressions are caused when
using generics, specially in collections and functional utilities. When you
specify that type with a tuple, as in Dictionary, then all the related
methods start receiving closures receiving tuples.

Notice that allowing some syntactic sugar on the call site of those
closures may not be enough, since you may have stored or passed a closure
for customization purposes.

Can you illustrate this with an example? I’m not sure I understand what
you’re getting at here.

Sure. Let's say you want to filter some elements from a dictionary, and you
have this valid Swift 3 code:

let conversationsById: [String: Conversation]
let unreadConversations = conversationsById.filter { $1.isUnread }

Or:

let unreadConversations = conversationsById.filter { id, c in
    return !id.isEmpty && c.isUnread
}

Neither of these expressions are permitted in Swift 4, but some syntactic
sugar can be added to allowing it again without the need of full tuple
splatting.

However, if for some reason you want to pass around a filter closure, you
will still have mismatch types:

func first(passing filter: (String, Conversation) -> Bool) -> Conversation?
{
    return conversationsById.filter(filter).first?.value
}

This example is simple and useless and contrived, but it gets even worse
when you take generics into account to catch any type of closure.

This behavior is so hurtful to functional style programming, that I think

either SE-0110 should be reverted, or alternatives like this Susan proposal
should be implemented.

To be clear, what you and Gwendal are objecting to is the loss of tuple
splatting, is it not? Again, SE-0110 is only the final piece; SE-0029 is
what removed implicit tuple splatting. As that proposal said, a properly
designed explicit tuple splatting can be considered if there’s demand, and
it was understood that removing this functionality would be a regression
and nonetheless the decision for removal was still deliberately undertaken.
Re-reading that proposal, the rationale was that it never worked correctly
to begin with, and the barrier for bringing it back is that it requires
considerable effort to design and implement the feature correctly.

I'm sorry if there has been any disrespect for my part, I appreciate what
you all do here. But sometimes it's a bit frustrating having to explain why
something that seems obvious to us is not obvious to others.

I think that you have a bigger picture from the Swift Evolution point, but
the thing is that Swift users like me doesn't have that context, and we are
baffled when we encounter that some pretty simple code turns into a weird
thing in a migration. I have linked to several real examples out there, and
it seems to be a popular opinion. Nevertheless, it was Tony Parker's mail
the one that kindled this discussion with a really simple question.

It may be the last piece, but it is the piece that impacted simple code the
most, or at least the one that had the most unexpected consequences.
Certainly that proposal does not contain nearly enough code examples or
goes deeply enough. And this is subjective, but for me this isn't true:
"Minor changes to user code may be required if this proposal is accepted."

The fact that it affects even the standard library shows how hurtful
removing this feature is. It's not a nice-to-have feature or a
somewhat-useful feature, it's that right now it's very difficult to model
something as essential as Dictionary with a nice-to-use API. So lots of
other custom types will suffer too.

If we all agree that this is a loss of ergonomics, then the Core Team have
several options for Swift 4:

1. Do nothing.
2. Revert SE-0110 related changes.
3. Add a complex tuple-splatting feature.
4. Add some syntactic sugar keeping the same syntax.
5. Add some syntactic sugar but changing the syntax.

For me:

1. I want to avoid this, and I think Gwendal and others too.
2. I know nothing about how it was implemented, but for the time being it
would be the best option for me, as it would avoid painful migrations where
you are forced to make your code worse. But if it's too difficult to do, I
understand.
3. No time for this.
4. Probably no time for this, and I don't think it would not cover all the
regressions (like the example of passing a closure).
5. It will also hurts a bit the ergonomics, but more importantly it will
hurt the migrations. And this will probably be replaced in next versions of
Swift, so it's very suboptimal, I think.

Anyway, I may disagree with the result, but if it's what the Core Team
wants or needs then ¯\_(ツ)_/¯

···

On 7 Jun 2017, at 21:29, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

--
Víctor Pimentel

SE-0110 may be an obvious extension of the proposed goal, but it is clear

that it has been implemented in Swift 4, and that the consequences are
derived of those changesets.

Those "unwanted" consequences can be reverted by temporarily reverting
SE-0110, without touching any other previous proposal.

In no place I see Gwendal asking for full reversal of 5 proposals. He is
just voicing something that a lot of app developers and library maintainers
are not going to understand in September.

For example, you can see all the changes needed in RXSwift so that it
compiles to Swift 4:

Compile under Swift 4 by volodg · Pull Request #1282 · ReactiveX/RxSwift · GitHub
915e00fa6d1e59d58cd8c38dd6dc83765fc67fe4

I would not want to migrate to Swift 4 an app using such framework.

I asked you in my first reply to you, is your view that the distinction
itself between (Int, Int) -> Int and ((Int, Int)) -> Int is problematic? If
so, this is a very different discussion from seeking solutions to mitigate
particular ergonomic issues that arise from the change. However, you have
not answered the question.

As far as I can see, the bigger usability regressions are caused when
using generics, specially in collections and functional utilities. When you
specify that type with a tuple, as in Dictionary, then all the related
methods start receiving closures receiving tuples.

Notice that allowing some syntactic sugar on the call site of those
closures may not be enough, since you may have stored or passed a closure
for customization purposes.

Can you illustrate this with an example? I’m not sure I understand what
you’re getting at here.

Sure. Let's say you want to filter some elements from a dictionary, and
you have this valid Swift 3 code:

let conversationsById: [String: Conversation]
let unreadConversations = conversationsById.filter { $1.isUnread }

Or:

let unreadConversations = conversationsById.filter { id, c in
    return !id.isEmpty && c.isUnread
}

Neither of these expressions are permitted in Swift 4, but some syntactic
sugar can be added to allowing it again without the need of full tuple
splatting.

Right.

However, if for some reason you want to pass around a filter closure, you

will still have mismatch types:

func first(passing filter: (String, Conversation) -> Bool) ->
Conversation? {
    return conversationsById.filter(filter).first?.value
}

This example is simple and useless and contrived, but it gets even worse
when you take generics into account to catch any type of closure.

I’m not going to raise any issues over how realistic the scenario might be.
It's for answering a question, after all.

However, what I don’t get is why you’d have a mismatch here. Your method
`first` accepts a closure, and surely the type it expects should be
`((String, Conversation)) -> Bool` instead, since that is the type that the
`filter` method expects in the body of your implementation?

Certainly, there are scenarios not possible (or at least, very difficult)
without tuple splatting, but that’s straight-up an argument for tuple
splatting. That's, IMO, an unnecessary argument, since the usefulness of
the feature has never been in question, only the resources to design and
implement.

This behavior is so hurtful to functional style programming, that I think

either SE-0110 should be reverted, or alternatives like this Susan proposal
should be implemented.

To be clear, what you and Gwendal are objecting to is the loss of tuple
splatting, is it not? Again, SE-0110 is only the final piece; SE-0029 is
what removed implicit tuple splatting. As that proposal said, a properly
designed explicit tuple splatting can be considered if there’s demand, and
it was understood that removing this functionality would be a regression
and nonetheless the decision for removal was still deliberately undertaken.
Re-reading that proposal, the rationale was that it never worked correctly
to begin with, and the barrier for bringing it back is that it requires
considerable effort to design and implement the feature correctly.

I'm sorry if there has been any disrespect for my part, I appreciate what
you all do here. But sometimes it's a bit frustrating having to explain why
something that seems obvious to us is not obvious to others.

I think that you have a bigger picture from the Swift Evolution point, but
the thing is that Swift users like me doesn't have that context, and we are
baffled when we encounter that some pretty simple code turns into a weird
thing in a migration. I have linked to several real examples out there, and
it seems to be a popular opinion. Nevertheless, it was Tony Parker's mail
the one that kindled this discussion with a really simple question.

It may be the last piece, but it is the piece that impacted simple code
the most, or at least the one that had the most unexpected consequences.
Certainly that proposal does not contain nearly enough code examples or
goes deeply enough. And this is subjective, but for me this isn't true:
"Minor changes to user code may be required if this proposal is accepted."

SE-0110 is short not because there wasn’t thought devoted to the change but
because that thought was largely already undertaken elsewhere (for example,
in SE-0029). As far as I can tell, the only part of SE-0110 that actually
required review as a proposal was the double-parenthesis syntax for a
single argument of tuple type; beyond that, I cannot understand how SE-0029
could be considered implemented without implementing SE-0110.

The fact that it affects even the standard library shows how hurtful

removing this feature is. It's not a nice-to-have feature or a
somewhat-useful feature, it's that right now it's very difficult to model
something as essential as Dictionary with a nice-to-use API. So lots of
other custom types will suffer too.

I think “very difficult” is a stretch; suffice it to say that it’s a step
back in ergonomics that may disproportionately affect certain programming
styles.

If we all agree that this is a loss of ergonomics, then the Core Team have

several options for Swift 4:

1. Do nothing.
2. Revert SE-0110 related changes.
3. Add a complex tuple-splatting feature.
4. Add some syntactic sugar keeping the same syntax.
5. Add some syntactic sugar but changing the syntax.

For me:

1. I want to avoid this, and I think Gwendal and others too.
2. I know nothing about how it was implemented, but for the time being it
would be the best option for me, as it would avoid painful migrations where
you are forced to make your code worse. But if it's too difficult to do, I
understand.
3. No time for this.
4. Probably no time for this, and I don't think it would not cover all the
regressions (like the example of passing a closure).

My point is that some (many?) of the regressions are intentional (i.e. loss
of implicit tuple splatting generally); Gwendal and others may not like it,
but that's not grounds for disputing an approved proposal. The aim of the
conversation should be focusing on the regressions that are
_unintentional_, since unintentional consequences do represent grounds for
revisiting a proposal. Pervasively littering `$0.0` inside closures was not
foreseen (or at least discussed, as far as I recall), and that can be
mitigated by allowing tuple destructuring in the closure parameter
shorthand, whether with new syntax or not. A complete implementation of
this feature has never actually existed in Swift, but it would certainly be
useful.

There is no need to speculate as to whether it would be replaced in future
version of Swift; if it is useful enough to be included now, it should be
included for all time; otherwise, it should not be included at all.

5. It will also hurts a bit the ergonomics, but more importantly it will

···

On Wed, Jun 7, 2017 at 19:15 Víctor Pimentel Rodríguez <vpimentel@tuenti.com> wrote:

On 7 Jun 2017, at 21:29, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
hurt the migrations. And this will probably be replaced in next versions of
Swift, so it's very suboptimal, I think.

Anyway, I may disagree with the result, but if it's what the Core Team
wants or needs then ¯\_(ツ)_/¯

--
Víctor Pimentel

>
> However, what I don’t get is why you’d have a mismatch here. Your method
`first` accepts a closure, and surely the type it expects should be
`((String, Conversation)) -> Bool` instead, since that is the type that the
`filter` method expects in the body of your implementation?

I must continue to ask for more justification as to why it's _so_ valuable
to consider "(Int,Int)->()" and "((Int,Int))->()" to be different in _most_
cases. Even in the contrived-described example above, the function may be
reusable and the engineer may pass "String" and "Conversation" objects
directly in some cases, and this distinction only makes things more
difficult and requires cumbersome workarounds.

I know you feel that the ship has sailed, and I understand the ambiguities
these proposals intended to clarify, but for the most common cases,
treating a function that takes a single tuple the same way we treat a
function that takes the same number of values that same tuple contains
is...really nice, so maybe there's a better way to disambiguate.

Again, an argument list was once a tuple: this solution was investigated
and "completely abandoned" early in the development of Swift "for a large
number of reasons (including inout, default arguments, variadic arguments,
labels, etc)". Removal of implicit tuple splatting was outlined in SE-0029.
Namely, for a function `foo(_:b:)` called using a tuple `x`:

A call to foo(x) looks like a call to an overloaded version of foo, both

to the compiler and to the human who maintains the code. This is extremely
confusing if you don't know the feature exists.

There are real ambiguities in the syntax, e.g. involving Any arguments and

situations where you want to pass a tuple value as a single parameter.

The current implementation has a ton of implementation bugs - it doesn't

work reliably.

The current implementation adds complexity to the type checker, slowing it

down and adding maintenance burden.

The current implementation doesn't work the way we would want a tuple

splat operation to work.

...

The root problem here is that we use exactly the same syntax for both

forms of function application. If the two forms were differentiated (an
option considered in “alternatives considered” below) then some of these
problems would be defined away.

However,

[A]ctually designing this feature would be a non-trivial effort...
We don't have an obvious sigil to use. "prefix-star" should be left unused

for now in case we want to use it to refer to memory-related operations in
the future.

Making the tuple splat operation great requires more than just fixing the

syntactic ambiguities we have, it would require re-evaluating the semantics
of the operation (e.g. in light of parameter labels, varargs and other
features).

The removal of tuple splatting was approved during Swift *2* Evolution; it
so happens that one particular segment of the feature was not excised until
SE-0110, but the writing was on the wall, so to speak, for 18 months.

I'll work with whatever final solution the Swift team comes up with, but in

···

On Wed, Jun 7, 2017 at 10:12 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

> On Jun 7, 2017, at 10:33 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
the meantime I'm not going to shy away from reacting to regressions.

I've referred to these regressions as "ergonomic" in the past, but it's
more about expressiveness. Swift 3 (and earlier) syntax allowed for
functional, point-free expression that could amount to very succinct,
readable code. The code Swift 4 requires is harder to read and harder to
maintain.

Stephen

I must continue to ask for more justification as to why it's _so_ valuable to consider "(Int,Int)->()" and "((Int,Int))->()" to be different in _most_ cases. Even in the contrived-described example above, the function may be reusable and the engineer may pass "String" and "Conversation" objects directly in some cases, and this distinction only makes things more difficult and requires cumbersome workarounds.

I know you feel that the ship has sailed, and I understand the ambiguities these proposals intended to clarify, but for the most common cases, treating a function that takes a single tuple the same way we treat a function that takes the same number of values that same tuple contains is...really nice, so maybe there's a better way to disambiguate.

I'll work with whatever final solution the Swift team comes up with, but in the meantime I'm not going to shy away from reacting to regressions.

I've referred to these regressions as "ergonomic" in the past, but it's more about expressiveness. Swift 3 (and earlier) syntax allowed for functional, point-free expression that could amount to very succinct, readable code. The code Swift 4 requires is harder to read and harder to maintain.

Stephen

···

On Jun 7, 2017, at 10:33 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

However, what I don’t get is why you’d have a mismatch here. Your method `first` accepts a closure, and surely the type it expects should be `((String, Conversation)) -> Bool` instead, since that is the type that the `filter` method expects in the body of your implementation?

Xiaodi, I'm interested in your opinion, don't you think that *SE-0066* was the main proposal that separated (Int,Int)->() and ((Int,Int))->() function *types*?
(https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md\)

'Proposed solution' section of this proposal clearly separates function *type* of one tuple argument and of a list of arguments, no?

It seems like this proposal was NOT fully implemented in Swift 3 or was implemented with bugs, or full implementation was delayed, and it just should be fully implemented in Swift 4, independent of SE-0110 proposal.

This is why I can't understand, how revisiting of SE-0110 *can help at all* ?

OK, let's say we revisited/rejected SE-0110, let's forgot about it for a moment. What's next? How the problem with two *separated* types for func/closure should be solved when one type is expected and other type is provided? Definitely we need new formal proposal for this to discuss in details.

Again, even *without* SE-0110 type of (Int,Int)->() should be in Swift 4 not the same as ((Int,Int))->(). This means that you can't "just sent" instance of one type if other type is required.

Vladimir.

···

On 08.06.2017 6:34, Xiaodi Wu via swift-evolution wrote:

On Wed, Jun 7, 2017 at 10:12 PM, Stephen Celis <stephen.celis@gmail.com > <mailto:stephen.celis@gmail.com>> wrote:

    > On Jun 7, 2017, at 10:33 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
    >
    > However, what I don’t get is why you’d have a mismatch here. Your method `first` accepts a closure, and surely the type it expects should be `((String, Conversation)) -> Bool` instead, since that is the type that the `filter` method expects in the body of your implementation?

    I must continue to ask for more justification as to why it's _so_ valuable to
    consider "(Int,Int)->()" and "((Int,Int))->()" to be different in _most_ cases.
    Even in the contrived-described example above, the function may be reusable and
    the engineer may pass "String" and "Conversation" objects directly in some cases,
    and this distinction only makes things more difficult and requires cumbersome
    workarounds.

    I know you feel that the ship has sailed, and I understand the ambiguities these
    proposals intended to clarify, but for the most common cases, treating a function
    that takes a single tuple the same way we treat a function that takes the same
    number of values that same tuple contains is...really nice, so maybe there's a
    better way to disambiguate.

Again, an argument list was once a tuple: this solution was investigated and "completely abandoned" early in the development of Swift "for a large number of reasons (including inout, default arguments, variadic arguments, labels, etc)". Removal of implicit tuple splatting was outlined in SE-0029. Namely, for a function `foo(_:b:)` called using a tuple `x`:

>A call to foo(x) looks like a call to an overloaded version of foo, both to the compiler and to the human who maintains the code. This is extremely confusing if you don't know the feature exists.
>There are real ambiguities in the syntax, e.g. involving Any arguments and situations where you want to pass a tuple value as a single parameter.
>The current implementation has a ton of implementation bugs - it doesn't work reliably.
>The current implementation adds complexity to the type checker, slowing it down and adding maintenance burden.
>The current implementation doesn't work the way we would want a tuple splat operation to work.

...

>The root problem here is that we use exactly the same syntax for both forms of function application. If the two forms were differentiated (an option considered in “alternatives considered” below) then some of these problems would be defined away.

However,

>[A]ctually designing this feature would be a non-trivial effort...
>We don't have an obvious sigil to use. "prefix-star" should be left unused for now in case we want to use it to refer to memory-related operations in the future.
>Making the tuple splat operation great requires more than just fixing the syntactic ambiguities we have, it would require re-evaluating the semantics of the operation (e.g. in light of parameter labels, varargs and other features).

The removal of tuple splatting was approved during Swift *2* Evolution; it so happens that one particular segment of the feature was not excised until SE-0110, but the writing was on the wall, so to speak, for 18 months.

    I'll work with whatever final solution the Swift team comes up with, but in the
    meantime I'm not going to shy away from reacting to regressions.

    I've referred to these regressions as "ergonomic" in the past, but it's more
    about expressiveness. Swift 3 (and earlier) syntax allowed for functional,
    point-free expression that could amount to very succinct, readable code. The code
    Swift 4 requires is harder to read and harder to maintain.

    Stephen

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

Also, I want to repeat what I said on the other thread. We should revert to Swift 3 behavior for this, and then take the time to design the behavior we really want (either for 4.1 or 5). Anything else will cause us to break people’s code twice…

Yes, *just* ignore/revert a number of connected, actively discussed and accepted, implemented(partially for some) proposals, freeze the current broken state of function types for very long period without any ability in near feature to fix it.

Instead of improvement of the syntax of the problematic parts of code even after release (if not possible to suggest a suitable solution before the release). I don't think that *improvements* in syntax can break people's code.

Huh..

···

On 08.06.2017 9:43, Jonathan Hull wrote:

Thanks,
Jon

On Jun 7, 2017, at 11:50 AM, Gwendal Roué via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Le 7 juin 2017 à 20:33, Gwendal Roué <gwendal.roue@gmail.com >>> <mailto:gwendal.roue@gmail.com>> a écrit :

For example, take those three functions:

func f(_ closure:(Int, Int) -> ())
func g(_ closure:((Int, Int)) -> ())
func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

f { (a, b) in ... }
g { (a, b) in ... }
c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key, value: Value) tuples (their Element type). Problem is that 'key' and 'value' are identifiers that only matter for dictionaries, not for dictionary users. It's very important for dictionary users to forget about tuples, and the `key` and `value` words:

// No pollution
dictionary.map { (name, score) in ... }

It looks like some people in this mailing list are horrified by this "request" (not a feature request, but a request that Swift 3 behavior is restored, actually).

What could be the reasons for such a bad reaction?

1: measurable runtime overhead (slower programs in some cases, without any obvious way for the developper to notice where is the extra cost)
2: measurable compiler overhead (slower compilation)
3: implementation complexity (slower swift progress, technical debt, etc.)
4: other?

I understand 1. We are all fascinated by C++ and Rust "zero-overhead". If this is the main concern of the community, then we may focus the discussion of that very precise topic.

I can live with 2 (just a personal subjective preference)

About 3: I can not tell because I lack the necessary skills.

4: enlighten us!

Gwendal

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

Imagine if “(Int, Int)” was hidden behind a type-alias. You expect you’d be entering a single argument. But, surprise, you unintentionally activated the secret tuple-exploding mode and you’re actually passing in two arguments!

[I didn’t follow the original discussion, so apologies if using a type-alias doesn’t case tuple-explosion. Of course, that would be a different code smell….]

···

On Jun 7, 2017, at 11:12 PM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 7, 2017, at 10:33 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

However, what I don’t get is why you’d have a mismatch here. Your method `first` accepts a closure, and surely the type it expects should be `((String, Conversation)) -> Bool` instead, since that is the type that the `filter` method expects in the body of your implementation?

I must continue to ask for more justification as to why it's _so_ valuable to consider "(Int,Int)->()" and "((Int,Int))->()" to be different in _most_ cases. Even in the contrived-described example above, the function may be reusable and the engineer may pass "String" and "Conversation" objects directly in some cases, and this distinction only makes things more difficult and requires cumbersome workarounds.


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com

Xiaodi, I'm interested in your opinion, don't you think that *SE-0066* was
the main proposal that separated (Int,Int)->() and ((Int,Int))->() function
*types*?
(https://github.com/apple/swift-evolution/blob/master/propos
als/0066-standardize-function-type-syntax.md)

As far as I can tell, SE-0110 clarified the spelling for a single tuple
argument, SE-0066 standardized the syntax for an argument list, and SE-0029
clarified the behavior of function applications. However, the notion that
(Int, Int) -> () and ((Int, Int)) -> () should be different types,
independent of whether we knew how the two would be spelled, is the
inevitable consequence of distinguishing argument lists from tuples.
According to SE-0029, that change apparently occurred *before Swift 1*, and
so unless I'm mistaken the remainder of this issue has really been
technical debt since that time.

'Proposed solution' section of this proposal clearly separates function

···

On Thu, Jun 8, 2017 at 7:53 AM, Vladimir.S <svabox@gmail.com> wrote:

*type* of one tuple argument and of a list of arguments, no?

It seems like this proposal was NOT fully implemented in Swift 3 or was
implemented with bugs, or full implementation was delayed, and it just
should be fully implemented in Swift 4, independent of SE-0110 proposal.

This is why I can't understand, how revisiting of SE-0110 *can help at
all* ?

OK, let's say we revisited/rejected SE-0110, let's forgot about it for a
moment. What's next? How the problem with two *separated* types for
func/closure should be solved when one type is expected and other type is
provided? Definitely we need new formal proposal for this to discuss in
details.

Again, even *without* SE-0110 type of (Int,Int)->() should be in Swift 4
not the same as ((Int,Int))->(). This means that you can't "just sent"
instance of one type if other type is required.

Vladimir.

On 08.06.2017 6:34, Xiaodi Wu via swift-evolution wrote:

On Wed, Jun 7, 2017 at 10:12 PM, Stephen Celis <stephen.celis@gmail.com >> <mailto:stephen.celis@gmail.com>> wrote:

    > On Jun 7, 2017, at 10:33 PM, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
    >
    > However, what I don’t get is why you’d have a mismatch here. Your
method `first` accepts a closure, and surely the type it expects should be
`((String, Conversation)) -> Bool` instead, since that is the type that the
`filter` method expects in the body of your implementation?

    I must continue to ask for more justification as to why it's _so_
valuable to
    consider "(Int,Int)->()" and "((Int,Int))->()" to be different in
_most_ cases.
    Even in the contrived-described example above, the function may be
reusable and
    the engineer may pass "String" and "Conversation" objects directly in
some cases,
    and this distinction only makes things more difficult and requires
cumbersome
    workarounds.

    I know you feel that the ship has sailed, and I understand the
ambiguities these
    proposals intended to clarify, but for the most common cases,
treating a function
    that takes a single tuple the same way we treat a function that takes
the same
    number of values that same tuple contains is...really nice, so maybe
there's a
    better way to disambiguate.

Again, an argument list was once a tuple: this solution was investigated
and "completely abandoned" early in the development of Swift "for a large
number of reasons (including inout, default arguments, variadic arguments,
labels, etc)". Removal of implicit tuple splatting was outlined in SE-0029.
Namely, for a function `foo(_:b:)` called using a tuple `x`:

>A call to foo(x) looks like a call to an overloaded version of foo,
both to the compiler and to the human who maintains the code. This is
extremely confusing if you don't know the feature exists.
>There are real ambiguities in the syntax, e.g. involving Any arguments
and situations where you want to pass a tuple value as a single parameter.
>The current implementation has a ton of implementation bugs - it
doesn't work reliably.
>The current implementation adds complexity to the type checker, slowing
it down and adding maintenance burden.
>The current implementation doesn't work the way we would want a tuple
splat operation to work.

...

>The root problem here is that we use exactly the same syntax for both
forms of function application. If the two forms were differentiated (an
option considered in “alternatives considered” below) then some of these
problems would be defined away.

However,

>[A]ctually designing this feature would be a non-trivial effort...
>We don't have an obvious sigil to use. "prefix-star" should be left
unused for now in case we want to use it to refer to memory-related
operations in the future.
>Making the tuple splat operation great requires more than just fixing
the syntactic ambiguities we have, it would require re-evaluating the
semantics of the operation (e.g. in light of parameter labels, varargs and
other features).

The removal of tuple splatting was approved during Swift *2* Evolution;
it so happens that one particular segment of the feature was not excised
until SE-0110, but the writing was on the wall, so to speak, for 18 months.

    I'll work with whatever final solution the Swift team comes up with,
but in the
    meantime I'm not going to shy away from reacting to regressions.

    I've referred to these regressions as "ergonomic" in the past, but
it's more
    about expressiveness. Swift 3 (and earlier) syntax allowed for
functional,
    point-free expression that could amount to very succinct, readable
code. The code
    Swift 4 requires is harder to read and harder to maintain.

    Stephen

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

I just want to say, as a "regular developer", that I'm with Vladimir S here.

Swift 3 (and current Swift 4) both still have a lot of inconsistencies
and/or bugs related to tuple- and function types which will need to be
fixed before ABI stability, and they are not fixed by going back to a state
with even more inconsistencies.

FWIW I can't see how SE-0110 can be considered fully implemented, and this
is simply because the most recent compiler still cannot properly
"Distinguish between single-tuple and multiple-argument function types"
(that's the title of SE-0110), this is evident in lots of different ways,
here are some of them:

Single-tuple and multiple-argument function types should not be considered
equal

Don't allow swapping a single-tuple function with a multiple-argument
function

Don't treat func signatures as being the same when they are in fact
different

Single-tuple and multiple-argument function types should be treated as
different types

I think of the "regression"/decreased ergonomics as a price that is worth
paying for, and a necessary step towards, getting the language in a state
in which it is possible to get rid of Swift's current parentheses-related
inconsistencies and possibly reimplement the hopefully just temporarily
lost ergonomics and/or expressiveness.

Another related "regression" that I think is worth noting has to do with
generics, namely the inability to have type parameters for function types
in this form: Parameter -> Result. This is no longer possible in Swift 4
and it will instead require special casing for each number of parameters in
the function types. This means that it is also impossible to make a type
generic over just function types (wihtout special casing for parameter
count). These things were (sort of) possible to do in earlier versions, but
they couldn't make it all the way without other parts of the language
falling apart. They certainly hinted about something nice, and maybe if the
language is allowed to be simplified and made more consistent, they can be
reimplemented in a proper way.

/Jens

···

On Thu, Jun 8, 2017 at 3:26 PM, Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:

On 08.06.2017 9:43, Jonathan Hull wrote:

Also, I want to repeat what I said on the other thread. We should revert
to Swift 3 behavior for this, and then take the time to design the behavior
we really want (either for 4.1 or 5). Anything else will cause us to break
people’s code twice…

Yes, *just* ignore/revert a number of connected, actively discussed and
accepted, implemented(partially for some) proposals, freeze the current
broken state of function types for very long period without any ability in
near feature to fix it.

Instead of improvement of the syntax of the problematic parts of code even
after release (if not possible to suggest a suitable solution before the
release). I don't think that *improvements* in syntax can break people's
code.

Huh..

Thanks,
Jon

On Jun 7, 2017, at 11:50 AM, Gwendal Roué via swift-evolution < >>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Le 7 juin 2017 à 20:33, Gwendal Roué <gwendal.roue@gmail.com <mailto: >>>> gwendal.roue@gmail.com>> a écrit :

For example, take those three functions:

func f(_ closure:(Int, Int) -> ())
func g(_ closure:((Int, Int)) -> ())
func h(_ closure:((a: Int, b: Int)) -> ())

If one can always write (as in Swift 3):

f { (a, b) in ... }
g { (a, b) in ... }
c { (a, b) in ... }

Then one can easily deal with a badly fit closure signature.

This is most examplified by dictionaries. They always expose (key: Key,
value: Value) tuples (their Element type). Problem is that 'key' and
'value' are identifiers that only matter for dictionaries, not for
dictionary users. It's very important for dictionary users to forget about
tuples, and the `key` and `value` words:

// No pollution
dictionary.map { (name, score) in ... }

It looks like some people in this mailing list are horrified by this
"request" (not a feature request, but a request that Swift 3 behavior is
restored, actually).

What could be the reasons for such a bad reaction?

1: measurable runtime overhead (slower programs in some cases, without
any obvious way for the developper to notice where is the extra cost)
2: measurable compiler overhead (slower compilation)
3: implementation complexity (slower swift progress, technical debt,
etc.)
4: other?

I understand 1. We are all fascinated by C++ and Rust "zero-overhead".
If this is the main concern of the community, then we may focus the
discussion of that very precise topic.

I can live with 2 (just a personal subjective preference)

About 3: I can not tell because I lack the necessary skills.

4: enlighten us!

Gwendal

_______________________________________________
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