[pitch] Eliminate the "T1 -> T2" syntax, require "(T1) -> T2"

It would not affect the closure syntax. To paraphrase Chris:
No. Swift’s syntactic shortcuts in its closure parameter lists benefit simple functional algorithms. Few people would choose to write fully specified long-form declarations when, for example, reverse-sorting an array of integers:

y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
Compare the long form with this simpler form:

y = x.sorted { lhs, rhs in rhs < lhs }
You can use the even shorter form of { $1 < $0 }.

Closures offer a structurally distinct class of syntactic sugar:

You may elide parentheses (even with multiple arguments)
You may omit the return type
You may omit types
You may omit the parameter list in its entirety
Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

···

On Apr 19, 2016, at 9:44 AM, David Rönnqvist <david.ronnqvist@gmail.com> wrote:

Would this also affect the syntax for naming closure arguments? For example, would this (taken from "The Swift Programming Language (Swift 2.2)”):
  reversed = names.sort( { s1, s2 in return s1 > s2 } )
have to be written like this:
  reversed = names.sort( { (s1, s2) in return s1 > s2 } )
or is that a different syntax?

As a developer focused on _writing_ and _reading_ code like this, I don’t see the real benefits of this change. It only feels natural to me that I would be able to omit the parentheses when there is only one type, but that I would need them to group multiple arguments or to label arguments.

That said, I don’t feel strongly about it and the work of transitioning our code would be minimal. If this change provides other engineering benefits that aren’t noticeable on the surface, then I’m positive to the change for those reasons.

- David

I have a different read of the proposal I guess... I actually find that this:

(Int, Int) -> (Int, Int)

Naturally reads take a single pair (e.g. tuple) of (Int, Int) and return a single pair of (Int, Int)

This actually looks and feels like the right implementation to me:

let tx: (Int, Int) -> (Int, Int) = { ($0.1, $0.0) }

And I am saying that this would be explicitly wrong:

let tx: (Int, Int) -> (Int, Int) = { ($1, $0) }

If I wanted a type signature of that took two Int params, I would expect to write this:

let tx: Int, Int -> (Int, Int) = { ($1, $0) }

That reads, a type signature that takes two Int values and returns a single (Int, Int) pair.

The problem, to me at least, is that the function declaration overloads the meaning of () as "start of parameter list for function declaration".

func tx((Int, Int)) -> (Int, Int) { return ($0.1, $0.0) }

So now you have this mess to say the same thing: take a single pair of Int and return a pair of Int.

Similarly, if the () are going to required for parameter lists in function declarations, I really struggle to see why these two forms should mean the same thing:

y = x.sorted { (lhs, rhs) in rhs < lhs }
y = x.sorted { lhs, rhs in rhs < lhs }

Dropping the type signatures because that can be inferred is one thing, but changing the structure of the parameter list seems like an orthogonal optimization.

y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
y = x.sorted { (lhs, rhs) in rhs < lhs }
{ $1 < $0 }

This process keeps the structural elements while dropping all of the type pieces and maintains the consistency that in function types (regardless of how they are defined), the () denotes a parameter list. If you actually want a tuple within the parameter list, you need to do `((lhs, rhs))`.

-David

···

On Apr 19, 2016, at 9:24 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 19, 2016, at 9:44 AM, David Rönnqvist <david.ronnqvist@gmail.com <mailto:david.ronnqvist@gmail.com>> wrote:

Would this also affect the syntax for naming closure arguments? For example, would this (taken from "The Swift Programming Language (Swift 2.2)”):
  reversed = names.sort( { s1, s2 in return s1 > s2 } )
have to be written like this:
  reversed = names.sort( { (s1, s2) in return s1 > s2 } )
or is that a different syntax?

As a developer focused on _writing_ and _reading_ code like this, I don’t see the real benefits of this change. It only feels natural to me that I would be able to omit the parentheses when there is only one type, but that I would need them to group multiple arguments or to label arguments.

That said, I don’t feel strongly about it and the work of transitioning our code would be minimal. If this change provides other engineering benefits that aren’t noticeable on the surface, then I’m positive to the change for those reasons.

- David

It would not affect the closure syntax. To paraphrase Chris:
No. Swift’s syntactic shortcuts in its closure parameter lists benefit simple functional algorithms. Few people would choose to write fully specified long-form declarations when, for example, reverse-sorting an array of integers:

y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
Compare the long form with this simpler form:

y = x.sorted { lhs, rhs in rhs < lhs }
You can use the even shorter form of { $1 < $0 }.

Closures offer a structurally distinct class of syntactic sugar:

You may elide parentheses (even with multiple arguments)
You may omit the return type
You may omit types
You may omit the parameter list in its entirety
Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

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

I have a different read of the proposal I guess... I actually find that this:

(Int, Int) -> (Int, Int)

Naturally reads take a single pair (e.g. tuple) of (Int, Int) and return a single pair of (Int, Int)

This actually looks and feels like the right implementation to me:

let tx: (Int, Int) -> (Int, Int) = { ($0.1, $0.0) }

And I am saying that this would be explicitly wrong:

let tx: (Int, Int) -> (Int, Int) = { ($1, $0) }

If I wanted a type signature of that took two Int params, I would expect to write this:

let tx: Int, Int -> (Int, Int) = { ($1, $0) }

That reads, a type signature that takes two Int values and returns a single (Int, Int) pair.

The problem, to me at least, is that the function declaration overloads the meaning of () as "start of parameter list for function declaration".

func tx((Int, Int)) -> (Int, Int) { return ($0.1, $0.0) }

So now you have this mess to say the same thing: take a single pair of Int and return a pair of Int.

Similarly, if the () are going to required for parameter lists in function declarations, I really struggle to see why these two forms should mean the same thing:

y = x.sorted { (lhs, rhs) in rhs < lhs }
y = x.sorted { lhs, rhs in rhs < lhs }

Dropping the type signatures because that can be inferred is one thing, but changing the structure of the parameter list seems like an orthogonal optimization.

y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
y = x.sorted { (lhs, rhs) in rhs < lhs }
{ $1 < $0 }

This process keeps the structural elements while dropping all of the type pieces and maintains the consistency that in function types (regardless of how they are defined), the () denotes a parameter list. If you actually want a tuple within the parameter list, you need to do `((lhs, rhs))`.

David, you make some great points about potential for confusion around tuples vs parentheses around the parameter list. This is a great argument for only using them when we explicitly intend to specify a tuple rather than allowing them for the argument list.

···

Sent from my iPad

On Apr 19, 2016, at 1:24 PM, David Owens II via swift-evolution <swift-evolution@swift.org> wrote:

-David

On Apr 19, 2016, at 9:24 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 19, 2016, at 9:44 AM, David Rönnqvist <david.ronnqvist@gmail.com> wrote:

Would this also affect the syntax for naming closure arguments? For example, would this (taken from "The Swift Programming Language (Swift 2.2)”):
  reversed = names.sort( { s1, s2 in return s1 > s2 } )
have to be written like this:
  reversed = names.sort( { (s1, s2) in return s1 > s2 } )
or is that a different syntax?

As a developer focused on _writing_ and _reading_ code like this, I don’t see the real benefits of this change. It only feels natural to me that I would be able to omit the parentheses when there is only one type, but that I would need them to group multiple arguments or to label arguments.

That said, I don’t feel strongly about it and the work of transitioning our code would be minimal. If this change provides other engineering benefits that aren’t noticeable on the surface, then I’m positive to the change for those reasons.

- David

It would not affect the closure syntax. To paraphrase Chris:
No. Swift’s syntactic shortcuts in its closure parameter lists benefit simple functional algorithms. Few people would choose to write fully specified long-form declarations when, for example, reverse-sorting an array of integers:

y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
Compare the long form with this simpler form:

y = x.sorted { lhs, rhs in rhs < lhs }
You can use the even shorter form of { $1 < $0 }.

Closures offer a structurally distinct class of syntactic sugar:

You may elide parentheses (even with multiple arguments)
You may omit the return type
You may omit types
You may omit the parameter list in its entirety
Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

_______________________________________________
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

A massive -1 to this proposal. Eliminating parenthesis increases readability and mental parse-ability.

As my own datapoint, I have 276 instances of “[^) ] *->” across 93 source files in one of the apps I maintain. I love that I can do “T1 -> T2” as my function definitions, and the ability to leave out extraneous parentheses is an impetus for me to structure my code differently so that my closures take a single parameter. It means that I use typealiases for complex parameter types.

Making this change would be a massive regression in Swift’s readability, IMO.

Dave

···

On Apr 20, 2016, at 9:13 AM, Alan Skipp via swift-evolution <swift-evolution@swift.org> wrote:

A quick grep showed about 50 cases of using T1 -> T2 in my current project. Not a huge amount, but not tiny either. It wouldn’t be a completely dreadful change to make, just irritating having to add superfluous syntactical appendages to pacify the computer.

On 20 Apr 2016, at 00:28, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Tue, Apr 19, 2016 at 3:29 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

on Tue Apr 19 2016, Alan Skipp <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> I’d place the ability to omit parenthesises for single args in the same category
> as being able to omit `self` when accessing properties. They are small
> conveniences that make a large difference to an entire codebase.

Care to back that up with some measurements? Take one of your projects
and find the instances of “[^) ] *->”

How many places in the code are affected, and how much worse is the result?

Here's one data point: including a small handful of false positives, I found 74 results in a project with 33 Swift files.

"How much worse" is hard to answer. It certainly wouldn't be the worst thing in the world to add parens, but I'm not particularly compelled by the pro-() arguments in this thread.

Jacob

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

Great points! We definitely have some mess with tuples&parameters list in current Swift. Especially after we removed tuple splat feature.

But right now can't see what we can do about this.

(T1,T2) -> (T3,T4) really reads as "tuple (T1,T2) to tuple (T3,T4)" and this is partially true as result is really tuple (T3,T4).
Correct form looks like T1,T2 -> (T3,T4), but this just can't be expressed in current syntax of Swift - we need parens for param names, their modifiers like inout/@noescape etc., don't think Swift can parse without parens.

Even more. This is ok in current Swift 3:

let f : ((Int, Int)) -> (Int, Int) = { return ($0, $1) }
let f2 : ((Int, Int)) -> (Int, Int) = { return ($0.0, $0.1) }

I.e. absolutely the same functions, declared in different way. Why it allows to point a tuple to $0 & $1, instead of just $0.0 $0.1 ?

···

On 19.04.2016 21:24, David Owens II via swift-evolution wrote:

I have a different read of the proposal I guess... I actually find that this:

    (Int, Int) -> (Int, Int)

Naturally reads take a single pair (e.g. tuple) of (Int, Int) and return a
single pair of (Int, Int)

This actually looks and feels like the right implementation to me:

    let tx: (Int, Int) -> (Int, Int) = { ($0.1, $0.0) }

And I am saying that this would be explicitly wrong:

    let tx: (Int, Int) -> (Int, Int) = { ($1, $0) }

If I wanted a type signature of that took two Int params, I would expect to
write this:

    let tx: Int, Int -> (Int, Int) = { ($1, $0) }

That reads, a type signature that takes two Int values and returns a single
(Int, Int) pair.

The problem, to me at least, is that the function declaration overloads the
meaning of () as "start of parameter list for function declaration".

    func tx((Int, Int)) -> (Int, Int) { return ($0.1, $0.0) }

So now you have this mess to say the same thing: take a single pair of Int
and return a pair of Int.

Similarly, if the () are going to required for parameter lists in function
declarations, I really struggle to see why these two forms should mean the
same thing:

    y = x.sorted { (lhs, rhs) in rhs < lhs }
    y = x.sorted { lhs, rhs in rhs < lhs }

Dropping the type signatures because that can be inferred is one thing, but
changing the structure of the parameter list seems like an orthogonal
optimization.

    y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }
    y = x.sorted { (lhs, rhs) in rhs < lhs }
    { $1 < $0 }

This process keeps the structural elements while dropping all of the type
pieces and maintains the consistency that in function types (regardless of
how they are defined), the () denotes a parameter list. If you actually
want a tuple within the parameter list, you need to do `((lhs, rhs))`.

-David

On Apr 19, 2016, at 9:24 AM, Erica Sadun via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 19, 2016, at 9:44 AM, David Rönnqvist <david.ronnqvist@gmail.com >>> <mailto:david.ronnqvist@gmail.com>> wrote:

Would this also affect the syntax for naming closure arguments? For
example, would this (taken from "The Swift Programming Language (Swift
2.2)”):

1. |reversed = names.sort( { s1, s2 in return s1 > s2 } )|

have to be written like this:

1. |reversed = names.sort( { (s1, s2) in return s1 > s2 } )|

or is that a different syntax?

As a developer focused on _writing_ and _reading_ code like this, I
don’t see the real benefits of this change. It only feels natural to me
that I would be able to omit the parentheses when there is only one
type, but that I would need them to group multiple arguments or to label
arguments.

That said, I don’t feel strongly about it and the work of transitioning
our code would be minimal. If this change provides other engineering
benefits that aren’t noticeable on the surface, then I’m positive to the
change for those reasons.

- David

It would not affect the closure syntax. To paraphrase Chris:

No. Swift’s syntactic shortcuts in its closure parameter lists benefit
simple functional algorithms. Few people would choose to write fully
specified long-form declarations when, for example, reverse-sorting an
array of integers:

>y = x.sorted { (lhs : Int, rhs : Int) -> Bool in rhs < lhs }|

Compare the long form with this simpler form:

>y = x.sorted { lhs, rhs in rhs < lhs }|

You can use the even shorter form of |{ $1 < $0 }|.

Closures offer a structurally distinct class of syntactic sugar:

  * You may elide parentheses (even with multiple arguments)
  * You may omit the return type
  * You may omit types
  * You may omit the parameter list in its entirety

Short of a complete rethink of closure syntax, requiring parentheses
there would not improve the language in any measurable way.

_______________________________________________
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

Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

Does requiring the parentheses in (T1) -> T2 improve the language in any measurable way?

···

On 19 Apr 2016, at 17:24, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

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

To me, the unparenthesized style suggests that the input and output are peers, which feels more natural for the sort of value-to-value transform/predicate where this most commonly occurs. Parenthesizing the input feels fussier, which contributes to a sense that the argument is just one component to producing the result.
The parentheses are grammatically unnecessary in most cases (by frequency of use in higher-use programming, not by feature count).

I agree with your point that many simple higher order programming examples (e.g. map, filter, etc) take a single argument. That said, I don’t agree that this means that we should syntactically privilege this special case.

"Special case" is a loaded phrase. Why is it a special case as a parameter if it isn't a special case as a result?

Because, as I tried to explain in my original post, parameters *are* a special case. The result type of a function is just a type. The parameter list allows things that types do not: default arguments and variadics.

Default arguments are not allowed in the type grammar. Nor are different internal vs. external labels.

As a concrete example, surely you aren’t arguing that we should support:

  let x : Int… -> Int

are you?

No, but that's because the ... is a reference to the rest of the tuple and doesn't read correctly outside of one.

I guess the flip side is that call and declaration syntax both require parentheses (unless the only argument is a trailing closure), but again, we had strong justifications for that: declarations would always be ambiguous without parens, and calls would have serious problems (and the style-wars factor would be much larger, especially now with mandatory keyword arguments by default).

Right, but regardless of *why* we always require parens on Decls and ApplyExprs, we really do (and that isn’t going to change). Being consistent between func decls and function types is quite important IMO.

So we should require function argument labels in function types?

Uhm, yes, we already do. In:

  let x : (a : Int) -> Float
  let y : (Int) -> Float
  let z : Int -> Float

x and y have different (but compatible) types. y and z have identical types (sugared differently).

When I said "function type", I was referring to this production in the type grammar, not the type signature component of function declarations. I'm not sure how I could've been clearer on that without actually using the names of grammatical productions.

My point was that allowing a function type to be written as "(Int) -> Float" is already inconsistent with function declarations, because that is not legal function declaration syntax; you would have to write "(_ : Int) -> Float".

The current language composes naturally here, and your proposal feels like an odd extra rule.

I feel like the current language no longer represents our reality, though (or at least, our current ideal vision for reality). We've pretty thoroughly broken the "functions have one argument" model.

I don't see this syntax as an offshoot of the "functions always have one argument" model. I agree that that model is dead.

However, I don't think users require its death to be underlined and written in bold; it only ever surfaced to them in bugs anyway. But many functions do, nonetheless, have only one argument; and because of another change to the model, where argument labels are becoming part of the function's name and not its type, that argument can be written as just a type.

So to me, this question is whether we add a weird special-case rule that mandates the use of parentheses because they're required in a bunch of more complex but less common situations.

Changing the type grammar to reflect this seems good to me. I would think of it as changing the function type grammar to:

  function-type ::= '(' (type (',' type)*)? ')' '->' type

which, since the argument list can containing 0, 1, or many individual arguments, makes the parens more grammatically necessary.

This is tautological.

John.

···

On Apr 15, 2016, at 10:41 AM, Joe Groff <jgroff@apple.com> wrote:

On Apr 15, 2016, at 8:29 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 14, 2016, at 10:50 PM, Chris Lattner <clattner@apple.com> wrote:
On Apr 14, 2016, at 10:40 PM, John McCall <rjmccall@apple.com> wrote:

I believe it does, as the parens are already required at call sites and in function declarations, and when using functions with more than one argument.

-- E

···

On Apr 20, 2016, at 5:09 AM, Jeremy Pereira <jeremy.j.pereira@googlemail.com> wrote:

On 19 Apr 2016, at 17:24, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

Does requiring the parentheses in (T1) -> T2 improve the language in any measurable way?

To me that reads as "function taking two parameters and returning a tuple”.

Full disclosure: until this thread started, I did not know that T1 -> T2 was legal. However, except for function application, I have always though of parentheses as optional things that are there only to disambiguate syntax or override operator precedence. To me the consistent rule would be to allow T1 -> T2 not to ban it.

···

On 20 Apr 2016, at 08:02, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:

Great points! We definitely have some mess with tuples&parameters list in current Swift. Especially after we removed tuple splat feature.

But right now can't see what we can do about this.

(T1,T2) -> (T3,T4) really reads as "tuple (T1,T2) to tuple (T3,T4)”

I’m sorry, but the one statement does not follow from the other. That parentheses are required at call sites and in function declarations (NB: but not in closure definitions) does not imply that the language would be measurably improved by mandating them in function type declarations of the form T1 -> T2 as well.

In fact, it seems to me, that, generally, the language designers took the opposite point of view, namely that the language is measurably improved by allowing coders to omit syntax where the omission does not result in ambiguity. For instance, we can omit the statement terminator for the last statement on a line, type annotations for let and var identifiers where the type can be inferred, pretty much everything in a closure’s parameter and return type declaration.

···

On 20 Apr 2016, at 15:40, Erica Sadun <erica@ericasadun.com> wrote:

On Apr 20, 2016, at 5:09 AM, Jeremy Pereira <jeremy.j.pereira@googlemail.com> wrote:

On 19 Apr 2016, at 17:24, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Short of a complete rethink of closure syntax, requiring parentheses there would not improve the language in any measurable way.

Does requiring the parentheses in (T1) -> T2 improve the language in any measurable way?

I believe it does, as the parens are already required at call sites and in function declarations, and when using functions with more than one argument.

I don't think it is just a tautology. Without encoding it in the grammar, there's an ambiguity between tuples and multiple arguments; () -> T could mean either "takes a single () argument" or "takes no arguments". We could "obviously" disambiguate in favor of the latter interpretation, but then you're introducing special cases in the other direction to keep the U -> T syntax working.

-Joe

···

On Apr 15, 2016, at 11:43 AM, John McCall <rjmccall@apple.com> wrote:

On Apr 15, 2016, at 10:41 AM, Joe Groff <jgroff@apple.com> wrote:

On Apr 15, 2016, at 8:29 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 14, 2016, at 10:50 PM, Chris Lattner <clattner@apple.com> wrote:
On Apr 14, 2016, at 10:40 PM, John McCall <rjmccall@apple.com> wrote:

To me, the unparenthesized style suggests that the input and output are peers, which feels more natural for the sort of value-to-value transform/predicate where this most commonly occurs. Parenthesizing the input feels fussier, which contributes to a sense that the argument is just one component to producing the result.
The parentheses are grammatically unnecessary in most cases (by frequency of use in higher-use programming, not by feature count).

I agree with your point that many simple higher order programming examples (e.g. map, filter, etc) take a single argument. That said, I don’t agree that this means that we should syntactically privilege this special case.

"Special case" is a loaded phrase. Why is it a special case as a parameter if it isn't a special case as a result?

Because, as I tried to explain in my original post, parameters *are* a special case. The result type of a function is just a type. The parameter list allows things that types do not: default arguments and variadics.

Default arguments are not allowed in the type grammar. Nor are different internal vs. external labels.

As a concrete example, surely you aren’t arguing that we should support:

  let x : Int… -> Int

are you?

No, but that's because the ... is a reference to the rest of the tuple and doesn't read correctly outside of one.

I guess the flip side is that call and declaration syntax both require parentheses (unless the only argument is a trailing closure), but again, we had strong justifications for that: declarations would always be ambiguous without parens, and calls would have serious problems (and the style-wars factor would be much larger, especially now with mandatory keyword arguments by default).

Right, but regardless of *why* we always require parens on Decls and ApplyExprs, we really do (and that isn’t going to change). Being consistent between func decls and function types is quite important IMO.

So we should require function argument labels in function types?

Uhm, yes, we already do. In:

  let x : (a : Int) -> Float
  let y : (Int) -> Float
  let z : Int -> Float

x and y have different (but compatible) types. y and z have identical types (sugared differently).

When I said "function type", I was referring to this production in the type grammar, not the type signature component of function declarations. I'm not sure how I could've been clearer on that without actually using the names of grammatical productions.

My point was that allowing a function type to be written as "(Int) -> Float" is already inconsistent with function declarations, because that is not legal function declaration syntax; you would have to write "(_ : Int) -> Float".

The current language composes naturally here, and your proposal feels like an odd extra rule.

I feel like the current language no longer represents our reality, though (or at least, our current ideal vision for reality). We've pretty thoroughly broken the "functions have one argument" model.

I don't see this syntax as an offshoot of the "functions always have one argument" model. I agree that that model is dead.

However, I don't think users require its death to be underlined and written in bold; it only ever surfaced to them in bugs anyway. But many functions do, nonetheless, have only one argument; and because of another change to the model, where argument labels are becoming part of the function's name and not its type, that argument can be written as just a type.

So to me, this question is whether we add a weird special-case rule that mandates the use of parentheses because they're required in a bunch of more complex but less common situations.

Changing the type grammar to reflect this seems good to me. I would think of it as changing the function type grammar to:

  function-type ::= '(' (type (',' type)*)? ')' '->' type

which, since the argument list can containing 0, 1, or many individual arguments, makes the parens more grammatically necessary.

This is tautological.

This is a fair point, and one which would come up with (Int,Int) -> Float as well — maybe the user really does mean to pass a single value that's an (Int,Int) pair. I'm not sure this really works out to being a special case at the user level, though, since in either model the user trying to write a function that takes a single tuple-typed parameter simply has to introduce the extra parens: (()) -> Float or ((Int, Int)) -> Float. (Assuming that the type system preserves that structure in any way, of course.)

John.

···

On Apr 15, 2016, at 2:47 PM, Joe Groff <jgroff@apple.com> wrote:

On Apr 15, 2016, at 11:43 AM, John McCall <rjmccall@apple.com> wrote:

On Apr 15, 2016, at 10:41 AM, Joe Groff <jgroff@apple.com> wrote:

On Apr 15, 2016, at 8:29 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 14, 2016, at 10:50 PM, Chris Lattner <clattner@apple.com> wrote:
On Apr 14, 2016, at 10:40 PM, John McCall <rjmccall@apple.com> wrote:

To me, the unparenthesized style suggests that the input and output are peers, which feels more natural for the sort of value-to-value transform/predicate where this most commonly occurs. Parenthesizing the input feels fussier, which contributes to a sense that the argument is just one component to producing the result.
The parentheses are grammatically unnecessary in most cases (by frequency of use in higher-use programming, not by feature count).

I agree with your point that many simple higher order programming examples (e.g. map, filter, etc) take a single argument. That said, I don’t agree that this means that we should syntactically privilege this special case.

"Special case" is a loaded phrase. Why is it a special case as a parameter if it isn't a special case as a result?

Because, as I tried to explain in my original post, parameters *are* a special case. The result type of a function is just a type. The parameter list allows things that types do not: default arguments and variadics.

Default arguments are not allowed in the type grammar. Nor are different internal vs. external labels.

As a concrete example, surely you aren’t arguing that we should support:

  let x : Int… -> Int

are you?

No, but that's because the ... is a reference to the rest of the tuple and doesn't read correctly outside of one.

I guess the flip side is that call and declaration syntax both require parentheses (unless the only argument is a trailing closure), but again, we had strong justifications for that: declarations would always be ambiguous without parens, and calls would have serious problems (and the style-wars factor would be much larger, especially now with mandatory keyword arguments by default).

Right, but regardless of *why* we always require parens on Decls and ApplyExprs, we really do (and that isn’t going to change). Being consistent between func decls and function types is quite important IMO.

So we should require function argument labels in function types?

Uhm, yes, we already do. In:

  let x : (a : Int) -> Float
  let y : (Int) -> Float
  let z : Int -> Float

x and y have different (but compatible) types. y and z have identical types (sugared differently).

When I said "function type", I was referring to this production in the type grammar, not the type signature component of function declarations. I'm not sure how I could've been clearer on that without actually using the names of grammatical productions.

My point was that allowing a function type to be written as "(Int) -> Float" is already inconsistent with function declarations, because that is not legal function declaration syntax; you would have to write "(_ : Int) -> Float".

The current language composes naturally here, and your proposal feels like an odd extra rule.

I feel like the current language no longer represents our reality, though (or at least, our current ideal vision for reality). We've pretty thoroughly broken the "functions have one argument" model.

I don't see this syntax as an offshoot of the "functions always have one argument" model. I agree that that model is dead.

However, I don't think users require its death to be underlined and written in bold; it only ever surfaced to them in bugs anyway. But many functions do, nonetheless, have only one argument; and because of another change to the model, where argument labels are becoming part of the function's name and not its type, that argument can be written as just a type.

So to me, this question is whether we add a weird special-case rule that mandates the use of parentheses because they're required in a bunch of more complex but less common situations.

Changing the type grammar to reflect this seems good to me. I would think of it as changing the function type grammar to:

  function-type ::= '(' (type (',' type)*)? ')' '->' type

which, since the argument list can containing 0, 1, or many individual arguments, makes the parens more grammatically necessary.

This is tautological.

I don't think it is just a tautology. Without encoding it in the grammar, there's an ambiguity between tuples and multiple arguments; () -> T could mean either "takes a single () argument" or "takes no arguments". We could "obviously" disambiguate in favor of the latter interpretation, but then you're introducing special cases in the other direction to keep the U -> T syntax working.

Doesn't really seem worth the trouble to me. It is fine as it is. Other
languages like Java and Scala allow this short hand and Swift allows it in
closures. Not that I am that fussed since the brackets are no big deal.

···

On Saturday, 16 April 2016, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

> On Apr 15, 2016, at 2:47 PM, Joe Groff <jgroff@apple.com <javascript:;>> > wrote:
>> On Apr 15, 2016, at 11:43 AM, John McCall <rjmccall@apple.com > <javascript:;>> wrote:
>>> On Apr 15, 2016, at 10:41 AM, Joe Groff <jgroff@apple.com > <javascript:;>> wrote:
>>>> On Apr 15, 2016, at 8:29 AM, John McCall via swift-evolution < > swift-evolution@swift.org <javascript:;>> wrote:
>>>>
>>>>> On Apr 14, 2016, at 10:50 PM, Chris Lattner <clattner@apple.com > <javascript:;>> wrote:
>>>>> On Apr 14, 2016, at 10:40 PM, John McCall <rjmccall@apple.com > <javascript:;>> wrote:
>>>>>>>> To me, the unparenthesized style suggests that the input and
output are peers, which feels more natural for the sort of value-to-value
transform/predicate where this most commonly occurs. Parenthesizing the
input feels fussier, which contributes to a sense that the argument is just
one component to producing the result.
>>>>>>>> The parentheses are grammatically unnecessary in most cases (by
frequency of use in higher-use programming, not by feature count).
>>>>>>>
>>>>>>> I agree with your point that many simple higher order programming
examples (e.g. map, filter, etc) take a single argument. That said, I don’t
agree that this means that we should syntactically privilege this special
case.
>>>>>>
>>>>>> "Special case" is a loaded phrase. Why is it a special case as a
parameter if it isn't a special case as a result?
>>>>>
>>>>> Because, as I tried to explain in my original post, parameters *are*
a special case. The result type of a function is just a type. The
parameter list allows things that types do not: default arguments and
variadics.
>>>>
>>>> Default arguments are not allowed in the type grammar. Nor are
different internal vs. external labels.
>>>>
>>>>> As a concrete example, surely you aren’t arguing that we should
support:
>>>>>
>>>>> let x : Int… -> Int
>>>>>
>>>>> are you?
>>>>
>>>> No, but that's because the ... is a reference to the rest of the
tuple and doesn't read correctly outside of one.
>>>>
>>>>>>>> I guess the flip side is that call and declaration syntax both
require parentheses (unless the only argument is a trailing closure), but
again, we had strong justifications for that: declarations would always be
ambiguous without parens, and calls would have serious problems (and the
style-wars factor would be much larger, especially now with mandatory
keyword arguments by default).
>>>>>>>
>>>>>>> Right, but regardless of *why* we always require parens on Decls
and ApplyExprs, we really do (and that isn’t going to change). Being
consistent between func decls and function types is quite important IMO.
>>>>>>
>>>>>> So we should require function argument labels in function types?
>>>>>
>>>>> Uhm, yes, we already do. In:
>>>>>
>>>>> let x : (a : Int) -> Float
>>>>> let y : (Int) -> Float
>>>>> let z : Int -> Float
>>>>>
>>>>> x and y have different (but compatible) types. y and z have
identical types (sugared differently).
>>>>
>>>> When I said "function type", I was referring to this production in
the type grammar, not the type signature component of function
declarations. I'm not sure how I could've been clearer on that without
actually using the names of grammatical productions.
>>>>
>>>> My point was that allowing a function type to be written as "(Int) ->
Float" is already inconsistent with function declarations, because that is
not legal function declaration syntax; you would have to write "(_ : Int)
-> Float".
>>>>
>>>> The current language composes naturally here, and your proposal feels
like an odd extra rule.
>>>
>>> I feel like the current language no longer represents our reality,
though (or at least, our current ideal vision for reality). We've pretty
thoroughly broken the "functions have one argument" model.
>>
>> I don't see this syntax as an offshoot of the "functions always have
one argument" model. I agree that that model is dead.
>>
>> However, I don't think users require its death to be underlined and
written in bold; it only ever surfaced to them in bugs anyway. But many
functions do, nonetheless, have only one argument; and because of another
change to the model, where argument labels are becoming part of the
function's name and not its type, that argument can be written as just a
type.
>>
>> So to me, this question is whether we add a weird special-case rule
that mandates the use of parentheses because they're required in a bunch of
more complex but less common situations.
>>
>>> Changing the type grammar to reflect this seems good to me. I would
think of it as changing the function type grammar to:
>>>
>>> function-type ::= '(' (type (',' type)*)? ')' '->' type
>>>
>>> which, since the argument list can containing 0, 1, or many individual
arguments, makes the parens more grammatically necessary.
>>
>> This is tautological.
>
> I don't think it is just a tautology. Without encoding it in the
grammar, there's an ambiguity between tuples and multiple arguments; () ->
T could mean either "takes a single () argument" or "takes no arguments".
We could "obviously" disambiguate in favor of the latter interpretation,
but then you're introducing special cases in the other direction to keep
the U -> T syntax working.

This is a fair point, and one which would come up with (Int,Int) -> Float
as well — maybe the user really does mean to pass a single value that's an
(Int,Int) pair. I'm not sure this really works out to being a special case
at the user level, though, since in either model the user trying to write a
function that takes a single tuple-typed parameter simply has to introduce
the extra parens: (()) -> Float or ((Int, Int)) -> Float. (Assuming that
the type system preserves that structure in any way, of course.)

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

--
-- Howard.