History and future of Swift's parentheses


#1

At first I thought that task 1 looked innocuous enough; then I discovered this bizarre behavior. Not sure if this was the point of your email.

func compose<T, U, V>(_ g: @escaping (U)->V, _ f: @escaping (T)->U) -> (T)->V {
return { x in g(f(x)) }
}

func strung(_ tuple: (Int, Int)) -> (String, String) {
return ("\(tuple.0)", "\(tuple.1)")
}

func concat(_ tuple: (String, String)) -> String {
return tuple.0 + tuple.1
}

print(String(reflecting: compose(concat, strung)(3, 4))) // "34"; but shouldn't we need a tuple?
print(String(reflecting: compose(concat, strung)((3, 4)))) // "34"; this makes sense

let f = compose(concat, strung)
print(String(reflecting: f(3, 4))) // ERROR: extra argument in call; but if the first line works, shouldn't this too?
print(String(reflecting: f((3, 4)))) // "34"; also makes sense

···

On Jun 07, 2017, at 03:03 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

Swift uses parentheses for a lot of different things (tuples, parameters, calls, grouping, pattern matching, etc), this has led to some confusion, for both language designers and users.

Here's a practical introduction that is possibly worth more than a thousand words (or perhaps even swift-evo posts):

Exercise 1
Write a (generic) function composition operator in Swift 4
(working as expected for all function types).

Exercise 2
Write a generic type WrappedFunction that can wrap any function in Swift 4
(with type parameters for Input and Output).

When you have completed (or given up) the exercises, please note that once in the history of Swift, these were simple tasks. You could come up with working solutions very quickly without any boilerplate or special casing (although perhaps not entirely without some of the parentheses-related inconsistencies of that time).

I've been reporting a lot of parentheses-related bugs since the first Swift beta, and I'm only getting more and more worried that the current incremental approach to fixing these is not working very well, resulting in a language that is more complex and less expressive than it could be.

Discussions often seem to end up being a bit too focused on particular use cases and details, and less on how everything fit together as a system.

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

PS

My perhaps unpopular thoughts:

I wish the parentheses-related parts of Swift could be carefully evaluated and redesigned from scratch, as a whole, in order to arrive at a solution that is as simple and expressive as possible.

But perhaps this has already happened and we are now just taking some steps back that are necessary for reimplementing some of the good stuff (that has been - at least IMHO - sort of hinted in earlier versions of Swift). But it feels like there is not enough time ...

Swift, please break my code all you want before it's too late, as long as it results in increased (rather than decreased) consistency, simplicity, expressiveness, optimizability and safety. Otherwise I could have used Objective C++.

/Jens

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


(Gor Gyolchanyan) #2

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.


(David Sweeris) #3

That’s what we used to do. It caused problems with other language features (“inout" and “variadic" parameters were the two big ones, IIRC).

- Dave Sweeris

···

On Jun 9, 2017, at 8:12 AM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.


#4

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

···

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.


(David Waite) #5

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.

A function would need to support a nonassignable, superset tuple in some cases such as:
- non-escaping functions
- inout parameters

- All functions return exactly one parameter, which is a tuple.

Is or could be? Are you defining inout parameters as syntactically hidden return values?

-DW

···

On Jun 9, 2017, at 9:12 AM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Gor Gyolchanyan) #6

Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.

···

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal


(Gor Gyolchanyan) #7

My answer to `inout` is to promote it to a full-fledged "storage class" (in C terminology) and allow normal variables to be `inout`.
This would immediately solve the problems with `inout` being a magical thing in functions, as well as a convenient way of storing "references" (in C++ terminology) to potentially huge inout expressions, not to mention returning an inout from a function, effectively spreading the getter-setter awesomeness to everything else besides properties and subscripts.

As for variadic parameters: currently they are what I call a "dead end feature", meaning that there is no way to propagate them further and abstract them away, because you can't "unpack" a sequence into a variadic parameter (the way you can in Python with the prefix `*`), so my answer to that would also be to promote it to its own Sequence protocol conforming compiler-magic structure, which would also become part of the type system and alleviate the problem.

···

On Jun 9, 2017, at 11:07 PM, David Sweeris <davesweeris@mac.com> wrote:

On Jun 9, 2017, at 8:12 AM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.

That’s what we used to do. It caused problems with other language features (“inout" and “variadic" parameters were the two big ones, IIRC).

- Dave Sweeris


#8

Exactly. Been trying to communicate this for awhile. The types are almost always completely isomorphic and so the distinction barely exists. Function-only modifiers (like inout) make things slightly more complicated but I don’t think is an insurmountable problem.

Really miss the expressiveness we lost :confused:

···

On Jun 9, 2017, at 1:17 PM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.


(John McCall) #9

C++ implements this idea by being utterly unsafe; Rust implements it by introducing entire new dimensions of language complexity. Are you proposing one of these in particular, or do you have your own concept?

John.

···

On Jun 9, 2017, at 2:42 PM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

My answer to `inout` is to promote it to a full-fledged "storage class" (in C terminology) and allow normal variables to be `inout`.
This would immediately solve the problems with `inout` being a magical thing in functions, as well as a convenient way of storing "references" (in C++ terminology) to potentially huge inout expressions, not to mention returning an inout from a function, effectively spreading the getter-setter awesomeness to everything else besides properties and subscripts.


(Jens Persson) #10

The point of exercise 1 is to show that it is impossible (in Swift 4) to
write a generic function composition operator (or function) which works as
expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for
functions with exactly one parameter. You'd have to special-case it for
every combination of parameter counts of f and g that it should be able to
handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) ->
(T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but
ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) ->
(T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the
compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every
combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) ->
V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically
usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions like
that."

My answer is that this is just a simple but telling example. The issue (as
I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful
abstractions.
It's no fun when the language forces you to write lots of specific variants
of your generic code.

I would feel less worried about the parentheses situation if the language
was going in a direction where you could see how this simple exercise would
be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for the
first parameter, only much worse.

/Jens

···

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> wrote:

Yes, except why would you need to define `((A, B)) -> C`?, If you need to
pass a 2-element tuple into a function that takes two parameters - you can!
If you want to pass two values into a function that *looks* like it takes
a single 2-element tuple - you can! Seems to me that the difference between
`((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in
mind that this only works for bare tuples (the ones that can't have
labels). Non-closure functions DO have labels, which is part of their
signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution < > swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's
parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all
purposes that involve parentheses, as well as dividing tuples into two
categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter,
which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare
tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten
would work extremely well with this idea, by making all these changes
completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such
thing as a one-element tuple in the language, then (A,B) -> C and ((A, B))
-> C could not be distinguished, for the simple reason that ((A, B)) -> C
could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly
one parameter, which is a tuple (your idea), whose single element is a
tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal


(TJ Usiyan) #11

I vote language complexity in the form of hygienic macros.

/me slinks away

···

On Sat, Jun 10, 2017 at 1:25 AM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

> On Jun 9, 2017, at 2:42 PM, Gor Gyolchanyan via swift-evolution < > swift-evolution@swift.org> wrote:
>
> My answer to `inout` is to promote it to a full-fledged "storage class"
(in C terminology) and allow normal variables to be `inout`.
> This would immediately solve the problems with `inout` being a magical
thing in functions, as well as a convenient way of storing "references" (in
C++ terminology) to potentially huge inout expressions, not to mention
returning an inout from a function, effectively spreading the getter-setter
awesomeness to everything else besides properties and subscripts.

C++ implements this idea by being utterly unsafe; Rust implements it by
introducing entire new dimensions of language complexity. Are you
proposing one of these in particular, or do you have your own concept?

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


(Gor Gyolchanyan) #12

You can kinda do this already:

func foo(_ parameter: inout ParameterType) {
  var shortcut: TheType {
    get {
      return parameter.very.very.long.and.tedious.inout.member
    }
    set {
      parameter.very.very.long.and.tedious.inout.member = newValue
    }
  }
  // shortcut is of type `inout TypeType`
}

This is exactly what I imagine the compiler generating when I write this in a hypothetical Swift:

func foo(_ parameter: inout ParameterType) {
  var shortcut: inout TheType = parameter.very.very.long.and.tedious.inout.member
}

Or, using type inference:

func foo(_ parameter: inout ParameterType) {
  var shortcut: inout = parameter.very.very.long.and.tedious.inout.member
}

···

On Jun 10, 2017, at 8:25 AM, John McCall <rjmccall@apple.com> wrote:

On Jun 9, 2017, at 2:42 PM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

My answer to `inout` is to promote it to a full-fledged "storage class" (in C terminology) and allow normal variables to be `inout`.
This would immediately solve the problems with `inout` being a magical thing in functions, as well as a convenient way of storing "references" (in C++ terminology) to potentially huge inout expressions, not to mention returning an inout from a function, effectively spreading the getter-setter awesomeness to everything else besides properties and subscripts.

C++ implements this idea by being utterly unsafe; Rust implements it by introducing entire new dimensions of language complexity. Are you proposing one of these in particular, or do you have your own concept?

John.


(Michael Ilseman) #13

The point of exercise 1 is to show that it is impossible (in Swift 4) to write a generic function composition operator (or function) which works as expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for functions with exactly one parameter. You'd have to special-case it for every combination of parameter counts of f and g that it should be able to handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) -> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions like that."

My answer is that this is just a simple but telling example. The issue (as I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful abstractions.
It's no fun when the language forces you to write lots of specific variants of your generic code.

I would feel less worried about the parentheses situation if the language was going in a direction where you could see how this simple exercise would be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for the first parameter, only much worse.

Out of curiosity, how do you think this would impact ABI? What are your concrete concerns here?

I don't think the analogy of first parameter label is relevant, as that needn't be ABI.

···

On Jun 9, 2017, at 2:10 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> wrote:
Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

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


#14

I see. My ideal solution to this would be an explicit tuple packing and unpacking API, as opposed to implicitly flexible functions. Something akin to (borrowing Python’s asterisk syntax):

func compose<T, U, V>(_ g: (*U)->*V, _ f: (*T)->*U) -> (*T)->*V {...}

The asterisks indicate that the arguments to the function will be packed into a tuple (or not, in the case of a single-argument function) whose type is denoted by T, U, V, or conversely that the functions take/return argument lists that look like *T, *U, *V where T, U, V are tuple types. Then the function definition would look like

{
    return { x: *T in g(f(x)) }
}

This would in essence create a new non nominal type: function argument lists. These would be distinct from tuples, although the two may freely be converted into one another using the asterisk. This type would be structurally distinct from tuples by virtue of the fact that single-element argument lists exist while single-element tuples do not. (An asterisk will convert a single-element argument list into a single (non-tuple) element and vice versa.)

To users, argument lists would be accessed solely through tuples with the asterisk syntax. The identity **T == T would hold whenever T is a tuple or argument list. Their usefulness would lie in the ability to call f(*x) when f takes multiple arguments and x is a tuple.

I acknowledge that this is probably not going to happen for Swift 4 but I would sure like it if it did.

···

On Jun 9, 2017, at 5:10 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to write a generic function composition operator (or function) which works as expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for functions with exactly one parameter. You'd have to special-case it for every combination of parameter counts of f and g that it should be able to handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) -> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions like that."

My answer is that this is just a simple but telling example. The issue (as I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful abstractions.
It's no fun when the language forces you to write lots of specific variants of your generic code.

I would feel less worried about the parentheses situation if the language was going in a direction where you could see how this simple exercise would be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for the first parameter, only much worse.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> wrote:
Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

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


(Jens Persson) #15

The analogy of the special first parameter label was relevant (for me) in
that it was a special rule that was invented to resolve a problem/situation
with no clear best solution. Probably most people agree now that the
current simpler and more uniform rule set for parameter labels are
obviously better. It was possible to escape the ugly special case of the
first parameter, after all.

Similarly, I wonder if the parentheses-related mess actually can be simpler
and more uniform, after all. I remember a lot of discussions in which
people argued that Swift couldn't and/or shouldn't get rid of the special
first parameter label rules.

I'm not a compiler hacker and I have no idea exactly what aspects of the
language is easy/hard/impossible to change once Swift is binary stable.

My concrete concerns are that the messy parentheses-related parts of the
language will continue to be messy for ever.
I have no idea if there is any actual reason to worry about that, but ABI
stability was originally intended for Swift 3, and then it was postponed
because some stuff needed to be done before ABI stability. Now I'm just
worried that solving the parentheses situation is something that needs to
be done before ABI stability. Please correct/enlighten me!

/Jens

···

On Sat, Jun 10, 2017 at 12:50 AM, Michael Ilseman <milseman@apple.com> wrote:

On Jun 9, 2017, at 2:10 PM, Jens Persson via swift-evolution < > swift-evolution@swift.org> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to
write a generic function composition operator (or function) which works as
expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for
functions with exactly one parameter. You'd have to special-case it for
every combination of parameter counts of f and g that it should be able to
handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) ->
(T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but
ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) ->
(T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the
compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every
combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) ->
V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically
usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions
like that."

My answer is that this is just a simple but telling example. The issue (as
I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful
abstractions.
It's no fun when the language forces you to write lots of specific
variants of your generic code.

I would feel less worried about the parentheses situation if the language
was going in a direction where you could see how this simple exercise would
be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for
the first parameter, only much worse.

Out of curiosity, how do you think this would impact ABI? What are your
concrete concerns here?

I don't think the analogy of first parameter label is relevant, as that
needn't be ABI.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> > wrote:

Yes, except why would you need to define `((A, B)) -> C`?, If you need to
pass a 2-element tuple into a function that takes two parameters - you can!
If you want to pass two values into a function that *looks* like it takes
a single 2-element tuple - you can! Seems to me that the difference between
`((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in
mind that this only works for bare tuples (the ones that can't have
labels). Non-closure functions DO have labels, which is part of their
signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution < >> swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's
parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all
purposes that involve parentheses, as well as dividing tuples into two
categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter,
which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare
tuple pattern.

The currently ongoing proposal to make a single-element tuple
auto-flatten would work extremely well with this idea, by making all these
changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such
thing as a one-element tuple in the language, then (A,B) -> C and ((A, B))
-> C could not be distinguished, for the simple reason that ((A, B)) -> C
could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly
one parameter, which is a tuple (your idea), whose single element is a
tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

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


#16

It may make things more terrible. The solution introduces two separated type system and one is Swift 3 mode for asterisk syntax.

···

Robert Bennett via swift-evolution <swift-evolution@swift.org> 於 2017年6月10日 上午6:02 寫道:

I see. My ideal solution to this would be an explicit tuple packing and unpacking API, as opposed to implicitly flexible functions. Something akin to (borrowing Python’s asterisk syntax):

func compose<T, U, V>(_ g: (*U)->*V, _ f: (*T)->*U) -> (*T)->*V {...}

The asterisks indicate that the arguments to the function will be packed into a tuple (or not, in the case of a single-argument function) whose type is denoted by T, U, V, or conversely that the functions take/return argument lists that look like *T, *U, *V where T, U, V are tuple types. Then the function definition would look like

{
    return { x: *T in g(f(x)) }
}

This would in essence create a new non nominal type: function argument lists. These would be distinct from tuples, although the two may freely be converted into one another using the asterisk. This type would be structurally distinct from tuples by virtue of the fact that single-element argument lists exist while single-element tuples do not. (An asterisk will convert a single-element argument list into a single (non-tuple) element and vice versa.)

To users, argument lists would be accessed solely through tuples with the asterisk syntax. The identity **T == T would hold whenever T is a tuple or argument list. Their usefulness would lie in the ability to call f(*x) when f takes multiple arguments and x is a tuple.

I acknowledge that this is probably not going to happen for Swift 4 but I would sure like it if it did.

On Jun 9, 2017, at 5:10 PM, Jens Persson via swift-evolution <swift-evolution@swift.org> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to write a generic function composition operator (or function) which works as expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for functions with exactly one parameter. You'd have to special-case it for every combination of parameter counts of f and g that it should be able to handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) -> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions like that."

My answer is that this is just a simple but telling example. The issue (as I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful abstractions.
It's no fun when the language forces you to write lots of specific variants of your generic code.

I would feel less worried about the parentheses situation if the language was going in a direction where you could see how this simple exercise would be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for the first parameter, only much worse.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> wrote:
Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

_______________________________________________
swift-evolution mailing list
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


(Xiaodi Wu) #17

I'm confused as to the topic of this thread:

On the one hand, you're starting off with a statement about parentheses
being used for multiple different things, suggesting that what you want to
discuss is related to spelling using parentheses.

OTOH, the exercises that you put forward show that you're concerned about
the loss of implicit tuple splatting, a well-acknowledged regression, for
which the way forward is to introduce explicit tuple splatting at some
point in the future (at least, according to SE-0029, that's the intended
direction for Swift).

What, in the end, are you claiming to be "messy," and what kinds of
cleaning up are you seeking to discuss?

···

On Fri, Jun 9, 2017 at 7:34 PM, Jens Persson via swift-evolution < swift-evolution@swift.org> wrote:

The analogy of the special first parameter label was relevant (for me) in
that it was a special rule that was invented to resolve a problem/situation
with no clear best solution. Probably most people agree now that the
current simpler and more uniform rule set for parameter labels are
obviously better. It was possible to escape the ugly special case of the
first parameter, after all.

Similarly, I wonder if the parentheses-related mess actually can be
simpler and more uniform, after all. I remember a lot of discussions in
which people argued that Swift couldn't and/or shouldn't get rid of the
special first parameter label rules.

I'm not a compiler hacker and I have no idea exactly what aspects of the
language is easy/hard/impossible to change once Swift is binary stable.

My concrete concerns are that the messy parentheses-related parts of the
language will continue to be messy for ever.
I have no idea if there is any actual reason to worry about that, but ABI
stability was originally intended for Swift 3, and then it was postponed
because some stuff needed to be done before ABI stability. Now I'm just
worried that solving the parentheses situation is something that needs to
be done before ABI stability. Please correct/enlighten me!

/Jens

On Sat, Jun 10, 2017 at 12:50 AM, Michael Ilseman <milseman@apple.com> > wrote:

On Jun 9, 2017, at 2:10 PM, Jens Persson via swift-evolution < >> swift-evolution@swift.org> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to
write a generic function composition operator (or function) which works as
expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for
functions with exactly one parameter. You'd have to special-case it for
every combination of parameter counts of f and g that it should be able to
handle.

The following program demonstrates how it can be done in Swift 3.1 and
3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U)
-> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here
but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U)
-> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the
compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every
combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U)
-> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically
usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions
like that."

My answer is that this is just a simple but telling example. The issue
(as I see it) exists in all situations involving generics and function
types.

I'm a regular programmer and I like to be able to write basic, useful
abstractions.
It's no fun when the language forces you to write lots of specific
variants of your generic code.

I would feel less worried about the parentheses situation if the language
was going in a direction where you could see how this simple exercise would
be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for
the first parameter, only much worse.

Out of curiosity, how do you think this would impact ABI? What are your
concrete concerns here?

I don't think the analogy of first parameter label is relevant, as that
needn't be ABI.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> >> wrote:

Yes, except why would you need to define `((A, B)) -> C`?, If you need
to pass a 2-element tuple into a function that takes two parameters - you
can! If you want to pass two values into a function that *looks* like it
takes a single 2-element tuple - you can! Seems to me that the difference
between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But
keep in mind that this only works for bare tuples (the ones that can't have
labels). Non-closure functions DO have labels, which is part of their
signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution < >>> swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's
parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all
purposes that involve parentheses, as well as dividing tuples into two
categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter,
which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare
tuple pattern.

The currently ongoing proposal to make a single-element tuple
auto-flatten would work extremely well with this idea, by making all these
changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such
thing as a one-element tuple in the language, then (A,B) -> C and ((A, B))
-> C could not be distinguished, for the simple reason that ((A, B)) -> C
could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly
one parameter, which is a tuple (your idea), whose single element is a
tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

_______________________________________________
swift-evolution mailing list
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


(Jens Persson) #18

The topic of this thread:
It is simply the history and future of parentheses in Swift. My initial
post is just a starting point, it could have been something else. But I'm
interested to hear what people have to say about the history and future of
Swift's parentheses.

What is messy:
As I said, many things parentheses-related in Swift are (at least
currently) messy: tuples, func calling, -definitions, -signatures, -types,
discussions about them, etc.

It's hard to discuss (and probably design) a system in which parentheses
are used for so many different but intertweaved concepts (is there a
complete list of all the uses for parentheses in Swift?).

There are many concrete examples of this parentheses-related "mess",
namely bugs and/or inconsistencies. See for example the four bugs I was
just recently asked to file in this thread:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170605/037049.html

Similar inconsistencies and bugs have been in Swift since the first beta.

Why are they still there? Why does it take so long to get rid of them?

Again, I think part of the problem is that the multipurpose parentheses
confuses people and also makes it necessary to add special rules to handle
clashes in the syntax which would perhaps not have been there if
parentheses hadn't been used for so many different things.

Don't know if that clarified anything or if I'm just repeating myself.

I'm not seeking to discuss any particular kinds of cleaning up, just
interested to hear anything people think about related to parentheses in
Swift (tuples, paramter lists, argument lists, pattern matching, ...).

/Jens

···

On Sat, Jun 10, 2017 at 2:00 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

I'm confused as to the topic of this thread:

On the one hand, you're starting off with a statement about parentheses
being used for multiple different things, suggesting that what you want to
discuss is related to spelling using parentheses.

OTOH, the exercises that you put forward show that you're concerned about
the loss of implicit tuple splatting, a well-acknowledged regression, for
which the way forward is to introduce explicit tuple splatting at some
point in the future (at least, according to SE-0029, that's the intended
direction for Swift).

What, in the end, are you claiming to be "messy," and what kinds of
cleaning up are you seeking to discuss?

On Fri, Jun 9, 2017 at 7:34 PM, Jens Persson via swift-evolution < > swift-evolution@swift.org> wrote:

The analogy of the special first parameter label was relevant (for me) in
that it was a special rule that was invented to resolve a problem/situation
with no clear best solution. Probably most people agree now that the
current simpler and more uniform rule set for parameter labels are
obviously better. It was possible to escape the ugly special case of the
first parameter, after all.

Similarly, I wonder if the parentheses-related mess actually can be
simpler and more uniform, after all. I remember a lot of discussions in
which people argued that Swift couldn't and/or shouldn't get rid of the
special first parameter label rules.

I'm not a compiler hacker and I have no idea exactly what aspects of the
language is easy/hard/impossible to change once Swift is binary stable.

My concrete concerns are that the messy parentheses-related parts of the
language will continue to be messy for ever.
I have no idea if there is any actual reason to worry about that, but ABI
stability was originally intended for Swift 3, and then it was postponed
because some stuff needed to be done before ABI stability. Now I'm just
worried that solving the parentheses situation is something that needs to
be done before ABI stability. Please correct/enlighten me!

/Jens

On Sat, Jun 10, 2017 at 12:50 AM, Michael Ilseman <milseman@apple.com> >> wrote:

On Jun 9, 2017, at 2:10 PM, Jens Persson via swift-evolution < >>> swift-evolution@swift.org> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to
write a generic function composition operator (or function) which works as
expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for
functions with exactly one parameter. You'd have to special-case it for
every combination of parameter counts of f and g that it should be able to
handle.

The following program demonstrates how it can be done in Swift 3.1 and
3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U)
-> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here
but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U)
-> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the
compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every
combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U)
-> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically
usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions
like that."

My answer is that this is just a simple but telling example. The issue
(as I see it) exists in all situations involving generics and function
types.

I'm a regular programmer and I like to be able to write basic, useful
abstractions.
It's no fun when the language forces you to write lots of specific
variants of your generic code.

I would feel less worried about the parentheses situation if the
language was going in a direction where you could see how this simple
exercise would be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for
the first parameter, only much worse.

Out of curiosity, how do you think this would impact ABI? What are your
concrete concerns here?

I don't think the analogy of first parameter label is relevant, as that
needn't be ABI.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com> >>> wrote:

Yes, except why would you need to define `((A, B)) -> C`?, If you need
to pass a 2-element tuple into a function that takes two parameters - you
can! If you want to pass two values into a function that *looks* like it
takes a single 2-element tuple - you can! Seems to me that the difference
between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But
keep in mind that this only works for bare tuples (the ones that can't have
labels). Non-closure functions DO have labels, which is part of their
signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com> >>>> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution < >>>> swift-evolution@swift.org> a écrit :

So I wonder if any of you have had any thoughts about what Swift's
parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all
purposes that involve parentheses, as well as dividing tuples into two
categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter,
which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare
tuple pattern.

The currently ongoing proposal to make a single-element tuple
auto-flatten would work extremely well with this idea, by making all these
changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such
thing as a one-element tuple in the language, then (A,B) -> C and ((A, B))
-> C could not be distinguished, for the simple reason that ((A, B)) -> C
could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes
exactly one parameter, which is a tuple (your idea), whose single element
is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

_______________________________________________
swift-evolution mailing list
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


(Michael Ilseman) #19

The analogy of the special first parameter label was relevant (for me) in that it was a special rule that was invented to resolve a problem/situation with no clear best solution. Probably most people agree now that the current simpler and more uniform rule set for parameter labels are obviously better. It was possible to escape the ugly special case of the first parameter, after all.

Similarly, I wonder if the parentheses-related mess actually can be simpler and more uniform, after all. I remember a lot of discussions in which people argued that Swift couldn't and/or shouldn't get rid of the special first parameter label rules.

I'm not a compiler hacker and I have no idea exactly what aspects of the language is easy/hard/impossible to change once Swift is binary stable.

My concrete concerns are that the messy parentheses-related parts of the language will continue to be messy for ever.
I have no idea if there is any actual reason to worry about that, but ABI stability was originally intended for Swift 3, and then it was postponed because some stuff needed to be done before ABI stability. Now I'm just worried that solving the parentheses situation is something that needs to be done before ABI stability. Please correct/enlighten me!

Ah, rest assured that ABI stability (likely*) has little to do with your concerns. In theory, some post-ABI-stability Swift version could rename every single keyword and replace curly braces with “begin/end” and parens with emoji without affecting the ABI. Similarly, syntactic sugar and destructuring tuples shouldn’t affect ABI*. Source stability is what you’re likely confusing this with, which can be a little nebulous.

* Unless you’re proposing a change to the semantics of the language that could affect e.g. name mangling or the type metadata hierarchy, then that would be ABI-affecting. For example, proposing that all functions must only take a single tuple rather than multiple arguments could affect the runtime representation of function types. But even then, there are approaches to mitigate this, so such a proposal would likely present an ABI migration strategy.

···

On Jun 9, 2017, at 4:34 PM, Jens Persson <jens@bitcycle.com> wrote:

/Jens

On Sat, Jun 10, 2017 at 12:50 AM, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

On Jun 9, 2017, at 2:10 PM, Jens Persson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The point of exercise 1 is to show that it is impossible (in Swift 4) to write a generic function composition operator (or function) which works as expected for any reasonable functions.
This was possible in Swift 3, but in Swift 4 it will only work for functions with exactly one parameter. You'd have to special-case it for every combination of parameter counts of f and g that it should be able to handle.

The following program demonstrates how it can be done in Swift 3.1 and 3.2:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
let squaredSum = compose(square, sum)
let result = squaredSum((3, 4)) // A bit unexepected with a tuple here but ok ...
print(result) // 49
// Well, it worked, not flawlessly but we did manage to write
// a function composition function and we composed sum
// and square, and we could call it and get a correct result.

And this program demonstrates what happens if you try it in Swift 4:

func compose<T, U, V>(_ g: @escaping (U) -> V, _ f: @escaping (T) -> U) -> (T) -> V {
    return { x in g(f(x)) }
}
func sum(_ a: Int, _ b: Int) -> Int { return a + b }
func square(_ a: Int) -> Int { return a * a }
// let squaredSum = compose(square, sum) // Error! (without the compose-variant below)

// The error message is:
// Cannot convert value of type `(Int, Int) -> Int` to
// expected argument type `(_) -> _`

// That's it, it is simply not possible!

// You'd have to write special variants of the compose func for every combination
// of parameter counts! For example, in order to get this sum and square
// example working, this specific variant must be written:
func compose<T, U, V, W>(_ g: @escaping (V) -> W, _ f: @escaping (T, U) -> V) -> (T, U) -> W {
    return { (x, y) in g(f(x, y)) }
}
// Now it will work:
let squaredSum = compose(square, sum)
// But only thanks to that awfully specific compose func variant ...
// We would have to write a lot more variants for it to be practically usable on pretty much any common function.

I'm sure some will say:
"no regular developers use function composition anyway so why ..."
or
"It's not very swifty to use free functions and higher order functions like that."

My answer is that this is just a simple but telling example. The issue (as I see it) exists in all situations involving generics and function types.

I'm a regular programmer and I like to be able to write basic, useful abstractions.
It's no fun when the language forces you to write lots of specific variants of your generic code.

I would feel less worried about the parentheses situation if the language was going in a direction where you could see how this simple exercise would be a no brainer.

Can Swift's parentheses-situation be sorted out before ABI stability?
Otherwise it would be a bit like if Swift had kept the special rule for the first parameter, only much worse.

Out of curiosity, how do you think this would impact ABI? What are your concrete concerns here?

I don't think the analogy of first parameter label is relevant, as that needn't be ABI.

/Jens

On Fri, Jun 9, 2017 at 7:17 PM, Gor Gyolchanyan <gor@gyolchanyan.com <mailto:gor@gyolchanyan.com>> wrote:
Yes, except why would you need to define `((A, B)) -> C`?, If you need to pass a 2-element tuple into a function that takes two parameters - you can! If you want to pass two values into a function that *looks* like it takes a single 2-element tuple - you can! Seems to me that the difference between `((A, B)) -> C` and `(A, B) -> C` is virtually non-existent. But keep in mind that this only works for bare tuples (the ones that can't have labels). Non-closure functions DO have labels, which is part of their signature, so this is a different story.

On Jun 9, 2017, at 6:18 PM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:

Le 9 juin 2017 à 17:12, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

So I wonder if any of you have had any thoughts about what Swift's parentheses-related future (or evolutionary baggage) will be?

I really wish swift used the concept of tuples **exclusively** for all purposes that involve parentheses, as well as dividing tuples into two categories:
- Bare tuples, which do not have labels.
- Rich tuples, which do.
As a consequence, here's a list of statements that would become true:
- All functions take exactly one parameter, which is a tuple.
- All closures (a.k.a. function pointers) take exactly one parameter, which is a bare tuple.
- All functions return exactly one parameter, which is a tuple.
- Pattern matching is done on a single bare tuple using a single bare tuple pattern.

The currently ongoing proposal to make a single-element tuple auto-flatten would work extremely well with this idea, by making all these changes completely backward-compatible.

If I have well understood, Swift has evolved away from this.

If what you describe were true, added to the fact that there is no such thing as a one-element tuple in the language, then (A,B) -> C and ((A, B)) -> C could not be distinguished, for the simple reason that ((A, B)) -> C could not be defined.

For ((A, B)) -> C to be defined, we'd need a function that takes exactly one parameter, which is a tuple (your idea), whose single element is a tuple (oops, there is no single-valued tuples).

No opinion here, just they way I have understood recent Swift history.
Gwendal

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


(Jens Persson) #20

I think I understand (and understood), in very basic terms, the difference
between source stability and binary stability, and I was thinking something
like this:

What if there is a chance that the "uniform tuple concept" could be
redesigned and reimplemented after all, handling inout, variadic, etc in
some way, allowing named single element tuples, allowing A -> B to
represent (possibly only "pure") functions with _any_number_ of args, and
not just one, as in Swift 4, and so on.

I'm still not entirely sure if this is ABI-affecting or not. But anyway,
thank you for your calming words!

(I will try to not worry that (almost) everything parentheses-related in
Swift will forever be stuck in a local optimum, because its current state
and the history that lead to it is so confusing that it will stop all
attempts at a substantially better solution and only allow minor
changes/polishing.)

/Jens

···

On Mon, Jun 12, 2017 at 7:13 PM, Michael Ilseman <milseman@apple.com> wrote:

* Unless you’re proposing a change to the semantics of the language that
could affect e.g. name mangling or the type metadata hierarchy, then that
would be ABI-affecting. For example, proposing that all functions must only
take a single tuple rather than multiple arguments could affect the runtime
representation of function types. But even then, there are approaches to
mitigate this, so such a proposal would likely present an ABI migration
strategy.