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(_:)                 // error: ambiguous function reference. 
> Could be 'Int -> Int' or '(Int, Int) -> Int'
>     let f6 = foo(_:) as Int -> Int   // #13 Function reference to #3. Has 
> type 'Int -> Int'.
>     let f7 = foo(_:) 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(_:)     // '[Int] -> Int -> ArraySlice<Int>'
> 
>     let g1 = [Int].dropFirst(_:) // '[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

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to