[Pitch] Richer function identifiers, simpler function types


(Pyry Jahkola) #1

This is another reaction to SE-0066 <https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md> to which I'm mildly against.

I'd like to propose the following language changes to simplify function types and clarify what a function's name is. What gets removed is already ambiguous. And what is added makes higher-level programming with functions considerably simpler than currently. Furthermore, the proposed change considerably limits what constitutes the overload set of a function, which probably also speeds up the compilation process.

Let's consider the following declarations:

    func foo() // #1 Function named 'foo(_)' with type '() -> ()'.
    func foo(x: Int) -> Int // #2 Function named 'foo(x:)' with type 'Int -> Int' (not an overload).
    func foo(_ x: Int) -> Int // #3 Function named 'foo(_:)' with type 'Int -> Int'
    func foo(_ x: (Int, Int)) -> Int // #4 Function named 'foo(_:)' with type '(Int, Int) -> Int' (overload of #3).
    func foo(x: Int, y: Int) -> Int // #5 Function named 'foo(x:y:)' with type '(Int, Int) -> Int'.
    func foo(x: Int, y: Int) -> Bool // #6 Function named 'foo(x:y:)' with type '(Int, Int) -> Bool' (overload of #5).
    let foo: Int // error: invalid redeclaration of 'foo' (previously declared as a function)
    let baz: (Int, Int) -> Int // #7 Variable named 'baz' with type '(Int, Int) -> Int'.
    class Bar {
        func baz() // #8 Method named 'Bar.baz(_)' with type 'Bar -> () -> ()'.
        func baz(x y: Int) // #9 Method named 'Bar.baz(x:)' with type 'Bar -> Int -> ()'.
        static func baz(x: Int = 0) // #10 Static method named 'Bar.Self.baz(x:)' with type 'Int -> ()'.
    }
    let f1 = foo // error: not a function reference, did you mean 'foo(_)'?
    let f2 = foo as () -> () // error: not a function reference, did you mean 'foo(_)'?
    let f3 = foo(_) // #11 Function reference to #1. Has type '() -> ()'.
    let f4 = foo(x:) // #12 Function reference to #2. Has type 'Int -> Int'.
    let f5 = foo(_:slight_smile: // error: ambiguous function reference. Could be 'Int -> Int' or '(Int, Int) -> Int'
    let f6 = foo(_:slight_smile: as Int -> Int // #13 Function reference to #3. Has type 'Int -> Int'.
    let f7 = foo(_:slight_smile: as (Int, Int) -> Int // #14 Function reference to #4. Has type '(Int, Int) -> Int'.
    let x1: Int = foo(x:y:)(1, 2) // #15 Function application of #5. Picks the right overload by explicit return type.
    let x2: Bool = foo(x:y:)((1, 2)) // #16 Function application of #6. Allowing a tuple here causes no ambiguity.
    let f9 = baz // #17 Function reference synonymous to #7. Has type '(Int, Int) -> Int'.
    let bar = Bar()
    let f10 = bar.baz // error: not a function reference, did you mean 'bar.baz(_)' or 'bar.baz(x:)'?
    let f11 = bar.baz(_) // #18 Function synonymous to the closure '{ bar.baz() }' with type '() -> ()'.
    let f12 = bar.baz(x:) // #19 Function synonymous to the closure '{ bar.baz(x: $0) }' with type 'Int -> ()'.
    let f13 = Bar.Self.baz(x:) // #20 Function synonymous to the closure '{ Bar.baz(x: $0) }' with type 'Int -> ()'.
    let f14 = Bar.Self.baz(_) // #21 Function synonymous to the closure '{ Bar.baz() }' with type '() -> ()'.

The following list of proposed changes sum up what's new above.

C1: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by adding the underscore-in-parentheses syntax `foo(_)` to refer to the zero-argument function #1.

C2: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by removing the ambiguity between instance and type members. From now on, `Bar.baz(_)`

C3: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by banning the use of base name only to refer to a function, i.e. neither `foo` nor `Bar.baz` can be used to refer to refer to any of #1#6 or #8#10.

C4: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> to allow the selective omission of defaulted arguments, e.g. `let f = print(_:separator:)` creates the function variable `f: (Any, String) -> ()` equivalent to `{ print($0, separator: $1) }`.

C5: Clarify the language specification by stating that functions with different labels (e.g. `foo(_:)` vs. `foo(x:)`) are not overloads of each other. Instead, two functions are considered overloads of each other if only if they have matching base names (e.g. `foo`) and matching argument labels (e.g. `(x:y:)`) but differing argument or return types (e.g. #3 and #4, or #5 and #6).

C6: Clarify that by using the base name `foo` for a function, the same scope cannot define a variable with the name `foo`. And, vice versa, in a scope that defines a variable named `foo`, there can be no function `foo(...)` defined at the same scope level.

The implications are:

I1: The use of a function's base name to refer to a function will cease to work. It has, however, been buggy up to this date. Consider the following:

    let f = [Int].prefix // '[Int] -> Int -> ArraySlice<Int>'

    let g1 = [Int].dropFirst // Inexplicably chooses the '[Int] -> Int -> ArraySlice<Int>' overload!
    let g2 = [Int].dropFirst as [Int] -> () -> ArraySlice<Int> // Disambiguate by type.

    let h1 = [Int].sorted // Chooses the '[Int] -> () -> [Int]' overload, unlike 'dropFirst' above.
    let h2 = [Int].sorted as [Int] -> ((Int, Int) -> Bool) -> [Int] // Disambiguate by type.

With the proposed changes, the above becomes:

    let f = [Int].prefix(_:slight_smile: // '[Int] -> Int -> ArraySlice<Int>'

    let g1 = [Int].dropFirst(_:slight_smile: // '[Int] -> Int -> ArraySlice<Int>'
    let g2 = [Int].dropFirst(_) // '[Int] -> () -> ArraySlice<Int>'

    let h1 = [Int].sorted(_) // '[Int] -> () -> [Int]'
    let h2 = [Int].sorted(isOrderedBefore:) // '[Int] -> ((Int, Int) -> Bool) -> [Int]'

I2: When referring to functions the argument labels disappear in the returned function. That's a good thing because there's no generic way to call functions with arguments, and that's why closures too always come without argument labels. We don't, however, lose any clarity at the point where the function reference is passed as an argument because function references always contain the labels in the new notation. (Also, see the future directions for an idea how argument labels can be restored in the function variable.)

I3: Function argument lists are no longer that special and there's no need to notate single-n-tuple argument lists separately from n-argument functions, i.e. SE-0066 <https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md> is not really needed anymore. The new intuition here is that it's the function's name that defines how a function can be called, not its type.

I4: Because function variables cannot be overloaded, we can without ambiguity allow all of the following "tuple splatting":

    let tuple1 = (1, 2)
    let tuple2 = (x: 1, y: 2)
    let tuple3 = (a: 1, b: 2)
    let y1 = foo(tuple1) // Not a "tuple splat", calls #4 as normal.
    let y2 = foo(tuple2) // Not a "tuple splat", calls #4 as normal.
    let y3 = foo(tuple3) // Not a "tuple splat", calls #4 as normal.
    let y4 = foo(_:)(1, 2) // Not a "tuple splat", calls the reference to #4 as normal.
    let y5 = foo(_:)((1, 2)) // "Tuple splat", calls #4.
    let y6 = foo(_:)(((1, 2))) // "Tuple splat", calls #4. Nothing special here, just an unnecessary pair of parens.
    let y7 = foo(_:)(tuple1) // "Tuple splat", calls #4.
    let y8 = foo(_:)(tuple2) // "Tuple splat", calls #4. The labelled tuple type is compatible with '(Int, Int)'.
    let z1 = foo(x:y:)(tuple1) as Int // "Tuple splat", calls #5 because the return type is explicitly 'Int'.
    let z2 = foo(x:y:)(tuple2) as Int // "Tuple splat", calls #5. The labels don't really matter here.
    let z3 = foo(x:y:)(tuple3) as Int // "Tuple splat", calls #5. Like above, any tuple labels are compatible in the call.
    let z4 = (foo(x:y:) as (Int, Int) -> Bool)(tuple3) // Here's another way to explicitly pick up the overload.

Future directions

F1: In this proposal, I made a difference between function identifier names and variable identifier names. However, it could be well allowed to use function-like names for function variables when clarity is needed. Then calling a block would require the argument labels just like functions:

    let block(value:) = {value in ...}
    block(value: 1)

    func foo(_ isOrderedBefore(lhs:rhs:): (Int, Int) -> Bool) {
        let x = isOrderedBefore(lhs: 1, rhs: 2)
    }

F2: The following idea probably divides opinions, but because function variables are unambiguous, we could even allow calling them without parentheses like in Haskell. That would open up many doors to currying and building highly composable functional libraries:

    let f = {x in ...}
    let y = f 1 // calls 'f' with an 'Int'
    let z = f (1, 2) // calls 'f' with an '(Int, Int)'

F3: And if that was allowed, maybe it could be possible to define functions with variable-like names and currying too, but now I'm getting too far. I think the proposal is worth considering even if we never go to the direction of Haskell in this way.

— Pyry


(Tino) #2

Although SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> has a good goal, I don't like the details due to the use of parenthesis:
I'd prefer to keep them limited to actual function calls.
But most of the time, it would be possible to avoid ambiguity and keep the simpler syntax; this would be eliminated with you proposal, so I'm against it (SE-0021 would only be needed in situations that can't be solved with the old form).

C1: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by adding the underscore-in-parentheses syntax `foo(_)` to refer to the zero-argument function #1.

I don't think that's consistent with the other use cases of the underscore: Afair, it never means "nothing", but always "there is something, but ignore it"

Additionally, I think it would be nice to have a clean way to avoid all ambiguities when referring to a function be specifying all involved types, so unless I didn't miss any downsides of using square brackets, my favorite would look like

    func foo() // #1 Function named 'foo(_)' with type '() -> ()'.

let f = foo

    func foo(x: Int) -> Int // #2 Function named 'foo(x:)' with type 'Int -> Int' (not an overload).

let f = foo[x: Int]

    func foo(_ x: Int) -> Int // #3 Function named 'foo(_:)' with type 'Int -> Int'

let f = foo[Int]

    func foo(_ x: (Int, Int)) -> Int // #4 Function named 'foo(_:)' with type '(Int, Int) -> Int' (overload of #3).

let f = foo[(Int, Int)]

    func foo(x: Int, y: Int) -> Int // #5 Function named 'foo(x:y:)' with type '(Int, Int) -> Int'.

let f = foo[x: Int, y: Int]

    func foo(x: Int, y: Int) -> Bool // #6 Function named 'foo(x:y:)' with type '(Int, Int) -> Bool' (overload of #5).

let f = foo[x: Int, y: Int]

Ideally, it should be possible to specify the return type as well… but there are so many syntax options for this, and I don't see a natural choice...
let f = foo:Bool[x: Int, y: Int]
let f = foo->Bool[x: Int, y: Int]
let f = foo[x: Int, y: Int; Bool]
let f = foo[x: Int, y: Int]->Bool

Tino


(Pyry Jahkola) #3

Corrections, additions below…

Let's consider the following declarations:

    class Bar {
        func baz() // #8 Method named 'Bar.baz(_)' with type 'Bar -> () -> ()'.
        func baz(x y: Int) // #9 Method named 'Bar.baz(x:)' with type 'Bar -> Int -> ()'.
        static func baz(x: Int = 0) // #10 Static method named 'Bar.Self.baz(x:)' with type 'Int -> ()'.
    }
    // ...
    let bar = Bar()
    let f10 = bar.baz // error: not a function reference, did you mean 'bar.baz(_)' or 'bar.baz(x:)'?
    let f11 = bar.baz(_) // #18 Function synonymous to the closure '{ bar.baz() }' with type '() -> ()'.
    let f12 = bar.baz(x:) // #19 Function synonymous to the closure '{ bar.baz(x: $0) }' with type 'Int -> ()'.
    let f13 = Bar.Self.baz(x:) // #20 Function synonymous to the closure '{ Bar.baz(x: $0) }' with type 'Int -> ()'.
    let f14 = Bar.Self.baz(_) // #21 Function synonymous to the closure '{ Bar.baz() }' with type '() -> ()'.

(…)

C2: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by removing the ambiguity between instance and type members. From now on, `Bar.baz(_)`

That was meant to read:

"(…) From now on, `Bar.baz(_)` unambiguously refers to the instance method named `baz(_)`. To refer to the static or class method named `baz(_)`, put the keyword `.Self` between the type and the base name. This syntax should align with the outcome of SE-0068 <https://github.com/apple/swift-evolution/blob/master/proposals/0068-universal-self.md> such the keyword resembles the static method call `thing.Self.baz()` of a `thing` of type `Bar`."

(Alternatives considered: (1) the use of `Bar.self.baz(_)` with lower-case `self`, which is probably more confusing; and (2) inverting the choice and making the instance method require a keyword instead, which would harm readability as it's usually the instance method that you'd refer to and not the static one.)

C6: Clarify that by using the base name `foo` for a function, the same scope cannot define a variable with the name `foo`. And, vice versa, in a scope that defines a variable named `foo`, there can be no function `foo(...)` defined at the same scope level.

This doesn't prevent shadowing of course. E.g. An inner scope with a variable named `foo` would shadow all functions named `foo(...)` of an enclosing scope.

The implications are:

I1: The use of a function's base name to refer to a function will cease to work. It has, however, been buggy up to this date. Consider the following:

    let f = [Int].prefix // '[Int] -> Int -> ArraySlice<Int>'

    let g1 = [Int].dropFirst // Inexplicably chooses the '[Int] -> Int -> ArraySlice<Int>' overload!
    let g2 = [Int].dropFirst as [Int] -> () -> ArraySlice<Int> // Disambiguate by type.

    let h1 = [Int].sorted // Chooses the '[Int] -> () -> [Int]' overload, unlike 'dropFirst' above.
    let h2 = [Int].sorted as [Int] -> ((Int, Int) -> Bool) -> [Int] // Disambiguate by type.

By the way, the same also happens with the current Swift (both 2.2 and 3 master) when referring to the partially applied instance method like so:

    let xs = [1,3,2]
    let g = xs.dropFirst // Chooses the 'Int -> ArraySlice<Int>' overload.
    let h = xs.sorted // Chooses the '() -> [Int]' overload, unlike 'dropFirst' above.

I3: Function argument lists are no longer that special and there's no need to notate single-n-tuple argument lists separately from n-argument functions, i.e. SE-0066 <https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md> is not really needed anymore. The new intuition here is that it's the function's name that defines how a function can be called, not its type.

In yet other words, we don't have to capture the arity of a function in the type system at all—it's enough to carry that information around in the function name.

~>

Finally, this was a fairly technical pitch but a pitch anyway. Any ideas pro or against?

— Pyry


(Alex Hoppen) #4

Sorry for answering this late, but I think this is a great proposal and would like to see especially the `foo(_)` syntax up for review, as it came up twice already while Doug Gregor and I discussed the implementation of getters and setters for #selector (here <https://bugs.swift.org/browse/SR-1239?focusedCommentId=13980&#comment-13980> and here <https://github.com/apple/swift-evolution/pull/280>) and the upcoming proposal for disallowing arbitrary expressions inside #selector (Link to proposal <https://github.com/ahoppen/swift-evolution/blob/arbitrary-expressions-in-selectors/proposals/0000-arbitrary-expressions-in-selectors.md>). I think not being able to reference an overloaded method without parameters without using `as` to disambiguate by type is a major hole in the type system.

Some comments inline.

This is another reaction to SE-0066 <https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md> to which I'm mildly against.

I'd like to propose the following language changes to simplify function types and clarify what a function's name is. What gets removed is already ambiguous. And what is added makes higher-level programming with functions considerably simpler than currently. Furthermore, the proposed change considerably limits what constitutes the overload set of a function, which probably also speeds up the compilation process.

Let's consider the following declarations:

    func foo() // #1 Function named 'foo(_)' with type '() -> ()'.
    func foo(x: Int) -> Int // #2 Function named 'foo(x:)' with type 'Int -> Int' (not an overload).
    func foo(_ x: Int) -> Int // #3 Function named 'foo(_:)' with type 'Int -> Int'
    func foo(_ x: (Int, Int)) -> Int // #4 Function named 'foo(_:)' with type '(Int, Int) -> Int' (overload of #3).
    func foo(x: Int, y: Int) -> Int // #5 Function named 'foo(x:y:)' with type '(Int, Int) -> Int'.
    func foo(x: Int, y: Int) -> Bool // #6 Function named 'foo(x:y:)' with type '(Int, Int) -> Bool' (overload of #5).
    let foo: Int // error: invalid redeclaration of 'foo' (previously declared as a function)
    let baz: (Int, Int) -> Int // #7 Variable named 'baz' with type '(Int, Int) -> Int'.
    class Bar {
        func baz() // #8 Method named 'Bar.baz(_)' with type 'Bar -> () -> ()'.
        func baz(x y: Int) // #9 Method named 'Bar.baz(x:)' with type 'Bar -> Int -> ()'.
        static func baz(x: Int = 0) // #10 Static method named 'Bar.Self.baz(x:)' with type 'Int -> ()'.
    }
    let f1 = foo // error: not a function reference, did you mean 'foo(_)'?
    let f2 = foo as () -> () // error: not a function reference, did you mean 'foo(_)'?
    let f3 = foo(_) // #11 Function reference to #1. Has type '() -> ()'.
    let f4 = foo(x:) // #12 Function reference to #2. Has type 'Int -> Int'.
    let f5 = foo(_:slight_smile: // error: ambiguous function reference. Could be 'Int -> Int' or '(Int, Int) -> Int'
    let f6 = foo(_:slight_smile: as Int -> Int // #13 Function reference to #3. Has type 'Int -> Int'.
    let f7 = foo(_:slight_smile: as (Int, Int) -> Int // #14 Function reference to #4. Has type '(Int, Int) -> Int'.
    let x1: Int = foo(x:y:)(1, 2) // #15 Function application of #5. Picks the right overload by explicit return type.
    let x2: Bool = foo(x:y:)((1, 2)) // #16 Function application of #6. Allowing a tuple here causes no ambiguity.
    let f9 = baz // #17 Function reference synonymous to #7. Has type '(Int, Int) -> Int'.
    let bar = Bar()
    let f10 = bar.baz // error: not a function reference, did you mean 'bar.baz(_)' or 'bar.baz(x:)'?
    let f11 = bar.baz(_) // #18 Function synonymous to the closure '{ bar.baz() }' with type '() -> ()'.
    let f12 = bar.baz(x:) // #19 Function synonymous to the closure '{ bar.baz(x: $0) }' with type 'Int -> ()'.
    let f13 = Bar.Self.baz(x:) // #20 Function synonymous to the closure '{ Bar.baz(x: $0) }' with type 'Int -> ()'.
    let f14 = Bar.Self.baz(_) // #21 Function synonymous to the closure '{ Bar.baz() }' with type '() -> ()'.

The following list of proposed changes sum up what's new above.

C1: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by adding the underscore-in-parentheses syntax `foo(_)` to refer to the zero-argument function #1.

A huge +10 on this one as it stands. I think in the context of functions the underscore already has a meaning of "there is nothing" as in the parameter names. The only possible issue I see is whether we may end up in a conflict should we ever decide for functions to have out-only parameters that you may ignore by passing "_" as argument. But I don't see this coming. Only opinions from the core team?

C2: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by removing the ambiguity between instance and type members. From now on, `Bar.baz(_)`

I’m slightly opposed to this one. I, for my part, would expect `Bar.baz(_)` to refer to the static function instead of `Bar`, since nothing in this name suggests a instance methods. The fact that you can access instance methods on a type as `(Type) -> (Args) -> ReturnValue` has always seem more like magic to me.

C3: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by banning the use of base name only to refer to a function, i.e. neither `foo` nor `Bar.baz` can be used to refer to refer to any of #1#6 or #8#10.

I think this is largely impacted by the future direction of Swift on whether argument names are counted as part of a function’s name or not. I think they currently aren’t but if I recall correctly there are thought to change this. If this is the case, removing the option to use `foo` to refer to `foo(x:)` or `foo(_)` would only make sense from my point of view and should be done in the Swift 3 timeframe as a source breaking change. Otherwise I see no point in removing the option to reference a method by its base name, because technically speaking, it’s simply its name.
Could maybe someone of the core team comment on the future direction, Swift should take?

C4: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> to allow the selective omission of defaulted arguments, e.g. `let f = print(_:separator:)` creates the function variable `f: (Any, String) -> ()` equivalent to `{ print($0, separator: $1) }`.

Sounds very reasonable to me. Especially, I think the idea of creating a new function that simply forwards to the original function solves the problem of handling unspecified default parameters very elegantly.
At first I was worried about the additional indirection and its potential performance implications, but functions with default parameters already dispatch another function call for each argument anyway, so this shouldn’t be a problem.

C5: Clarify the language specification by stating that functions with different labels (e.g. `foo(_:)` vs. `foo(x:)`) are not overloads of each other. Instead, two functions are considered overloads of each other if only if they have matching base names (e.g. `foo`) and matching argument labels (e.g. `(x:y:)`) but differing argument or return types (e.g. #3 and #4, or #5 and #6).

AFAIK, currently they are. See my comment on C3 for this.

C6: Clarify that by using the base name `foo` for a function, the same scope cannot define a variable with the name `foo`. And, vice versa, in a scope that defines a variable named `foo`, there can be no function `foo(...)` defined at the same scope level.

Again implied by the decision on whether arguments (and their names) are counted as part of the function’s signature or not.

The implications are:

I1: The use of a function's base name to refer to a function will cease to work. It has, however, been buggy up to this date. Consider the following:

    let f = [Int].prefix // '[Int] -> Int -> ArraySlice<Int>'

    let g1 = [Int].dropFirst // Inexplicably chooses the '[Int] -> Int -> ArraySlice<Int>' overload!
    let g2 = [Int].dropFirst as [Int] -> () -> ArraySlice<Int> // Disambiguate by type.

    let h1 = [Int].sorted // Chooses the '[Int] -> () -> [Int]' overload, unlike 'dropFirst' above.
    let h2 = [Int].sorted as [Int] -> ((Int, Int) -> Bool) -> [Int] // Disambiguate by type.

With the proposed changes, the above becomes:

    let f = [Int].prefix(_:slight_smile: // '[Int] -> Int -> ArraySlice<Int>'

    let g1 = [Int].dropFirst(_:slight_smile: // '[Int] -> Int -> ArraySlice<Int>'
    let g2 = [Int].dropFirst(_) // '[Int] -> () -> ArraySlice<Int>'

    let h1 = [Int].sorted(_) // '[Int] -> () -> [Int]'
    let h2 = [Int].sorted(isOrderedBefore:) // '[Int] -> ((Int, Int) -> Bool) -> [Int]'

I2: When referring to functions the argument labels disappear in the returned function. That's a good thing because there's no generic way to call functions with arguments, and that's why closures too always come without argument labels. We don't, however, lose any clarity at the point where the function reference is passed as an argument because function references always contain the labels in the new notation. (Also, see the future directions for an idea how argument labels can be restored in the function variable.)

I can’t really see where this implication comes from and I’m strongly against it. I wouldn’t expect value of a variable to change just because I assign it to a new variable. Neither would I want my function’s signature to change just because I assign the function to another variable.
I rather think that it’s a missing feature that closures cannot have named arguments, but that’s orthogonal to this proposal.

I3: Function argument lists are no longer that special and there's no need to notate single-n-tuple argument lists separately from n-argument functions, i.e. SE-0066 <https://github.com/apple/swift-evolution/blob/master/proposals/0066-standardize-function-type-syntax.md> is not really needed anymore. The new intuition here is that it's the function's name that defines how a function can be called, not its type.

I4: Because function variables cannot be overloaded, we can without ambiguity allow all of the following "tuple splatting":

    let tuple1 = (1, 2)
    let tuple2 = (x: 1, y: 2)
    let tuple3 = (a: 1, b: 2)
    let y1 = foo(tuple1) // Not a "tuple splat", calls #4 as normal.
    let y2 = foo(tuple2) // Not a "tuple splat", calls #4 as normal.
    let y3 = foo(tuple3) // Not a "tuple splat", calls #4 as normal.
    let y4 = foo(_:)(1, 2) // Not a "tuple splat", calls the reference to #4 as normal.
    let y5 = foo(_:)((1, 2)) // "Tuple splat", calls #4.
    let y6 = foo(_:)(((1, 2))) // "Tuple splat", calls #4. Nothing special here, just an unnecessary pair of parens.
    let y7 = foo(_:)(tuple1) // "Tuple splat", calls #4.
    let y8 = foo(_:)(tuple2) // "Tuple splat", calls #4. The labelled tuple type is compatible with '(Int, Int)'.
    let z1 = foo(x:y:)(tuple1) as Int // "Tuple splat", calls #5 because the return type is explicitly 'Int'.
    let z2 = foo(x:y:)(tuple2) as Int // "Tuple splat", calls #5. The labels don't really matter here.
    let z3 = foo(x:y:)(tuple3) as Int // "Tuple splat", calls #5. Like above, any tuple labels are compatible in the call.
    let z4 = (foo(x:y:) as (Int, Int) -> Bool)(tuple3) // Here's another way to explicitly pick up the overload.

All function arguments used to be one tuple, but it turned out that certain features (inout params and varargs, if i recall correctly) cannot be handled if a function is considered as only taking one tuple as an argument. Tuple splatting was removed because it didn’t fit into the language naturally anymore.

– Alex


(Pyry Jahkola) #5

C1: Extend SE-0021 <https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md> by adding the underscore-in-parentheses syntax `foo(_)` to refer to the zero-argument function #1.

I don't think that's consistent with the other use cases of the underscore: Afair, it never means "nothing", but always "there is something, but ignore it"

Right, that was another choice I forgot to justify:

We already refer to `func foo(_ x: Int, _ y: Int)` as `foo(_:_:)` even if the function call only contains a comma and no colons nor underscores. Yes, `func foo()` neither has an underscore in the definition. But we need something to distinguish its from the function call `foo()`, so `foo(_)` is an honest attempt to stick to the existing SE-0021 practice.

Personally, I'm fine with the syntax introduced by SE-0021 and I'd rather keep its criticism out of this thread if possible.

However, we could consider the alternative that you proposed where `foo` alone may only refer to the zero-argument function and for everything else, the argument list is always required.

(…) so unless I didn't miss any downsides of using square brackets, (…)

let f = foo
let f = foo[x: Int]
let f = foo[Int]
let f = foo[(Int, Int)]
let f = foo[x: Int, y: Int]
let f = foo[x: Int, y: Int]

The square brackets do avoid the ambiguity too but FWIW, they look foreign to me. It's also pretty noisy having to spell out all the argument types (unless you meant that it's optional doing so). In the majority of cases, there is no overloading by type, or if there is then the type can be inferred at the point of use.

— Pyry

···

On 28 Apr 2016, at 14:37, Tino Heth <2th@gmx.de> wrote: