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
